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 2009 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 mktemp 225builtin rm 226 227typeset progname="${ basename "${0}" ; }" 228 229float -r M_PI=3.14159265358979323846 230 231# terminal size rect 232compound termsize=( 233 integer columns=-1 234 integer lines=-1 235) 236 237typeset init_screen="true" 238 239compound clock=( 240 float middle_x 241 float middle_y 242 integer len_x 243 integer len_y 244) 245 246 247# set clock properties 248compound seconds=( 249 float val 250 typeset ch 251 float scale 252 integer length ) 253compound minutes=( 254 float val 255 typeset ch 256 float scale 257 integer length ) 258compound hours=( 259 float val 260 typeset ch 261 float scale 262 integer length ) 263 264seconds.length=90 seconds.scale=60 seconds.ch=$"s" 265minutes.length=75 minutes.scale=60 minutes.ch=$"m" 266hours.length=50 hours.scale=12 hours.ch=$"h" 267 268float update_interval=0.9 269 270typeset -r termclock_usage=$'+ 271[-?\n@(#)\$Id: termclock (Roland Mainz) 2009-05-09 \$\n] 272[-author?Roland Mainz <roland.mainz@nrubsig.org>] 273[-author?David Korn <dgk@research.att.com>] 274[+NAME?termclock - analog clock for terminals] 275[+DESCRIPTION?\btermclock\b is an analog clock for terminals. 276 The termclock program displays the time in analog or digital 277 form. The time is continuously updated at a frequency which 278 may be specified by the user.] 279[u:update?Update interval (defaults to 0.9 seconds).]:[interval] 280[+SEE ALSO?\bksh93\b(1), \bxclock\b(1)] 281' 282 283while getopts -a "${progname}" "${termclock_usage}" OPT ; do 284# printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|" 285 case ${OPT} in 286 u) update_interval=${OPTARG} ;; 287 *) usage ;; 288 esac 289done 290shift $((OPTIND-1)) 291 292# prechecks 293which tput >/dev/null || fatal_error $"tput 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