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 (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. 26# 27 28# 29# termclock - a simple analog clock for terminals 30# 31 32# Solaris needs /usr/xpg6/bin:/usr/xpg4/bin because the tools in /usr/bin are not POSIX-conformant 33export PATH=/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin 34 35# Make sure all math stuff runs in the "C" locale to avoid problems 36# with alternative # radix point representations (e.g. ',' instead of 37# '.' in de_DE.*-locales). This needs to be set _before_ any 38# floating-point constants are defined in this script). 39if [[ "${LC_ALL}" != "" ]] ; then 40 export \ 41 LC_MONETARY="${LC_ALL}" \ 42 LC_MESSAGES="${LC_ALL}" \ 43 LC_COLLATE="${LC_ALL}" \ 44 LC_CTYPE="${LC_ALL}" 45 unset LC_ALL 46fi 47export LC_NUMERIC=C 48 49function fatal_error 50{ 51 print -u2 "${progname}: $*" 52 exit 1 53} 54 55# cache tput values (to avoid |fork()|'ing a "tput" child every second) 56function tput_cup 57{ 58 # static variable as cache for "tput_cup" 59 typeset -S -A tput_cup_cache 60 61 integer y="$1" x="$2" 62 nameref c="tput_cup_cache[\"${y}_${x}\"]" 63 64 if [[ "$c" == "" ]] ; then 65 # fast path for known terminal types 66 if [[ ${TERM} == ~(Elr)(vt100|vt220|xterm|xterm-color|dtterm) ]] ; then 67 c="${ printf "\E[%d;%dH" y+1 x+1 ; }" 68 else 69 c="${ tput cup $y $x ; }" 70 fi 71 fi 72 73 print -r -n -- "$c" 74 return 0 75} 76 77# Get terminal size and put values into a compound variable with the integer 78# members "columns" and "lines" 79function get_term_size 80{ 81 nameref rect=$1 82 83 rect.columns=${ tput cols ; } || return 1 84 rect.lines=${ tput lines ; } || return 1 85 86 return 0 87} 88 89function draw_clock 90{ 91 float angle a 92 float x y 93 integer i=1 94 95 for(( angle=0.0 ; angle < 360. ; angle+=6 )) ; do 96 (( 97 a=angle/360.*(2*M_PI) , 98 99 x=clock.len_x*cos(a) , 100 y=clock.len_y*sin(a) 101 )) 102 103 tput_cup $(( y+clock.middle_y )) $(( x+clock.middle_x )) 104 105 # add "mark" every 30 degrees 106 if (( int(angle)%30 == 0 )) ; then 107 print -r -n "$(((++i)%12+1))" 108 else 109 print -r -n "x" 110 fi 111 done 112 return 0 113} 114 115function draw_hand 116{ 117 float angle="$1" a 118 typeset ch="$2" 119 float length="$3" 120 float x y 121 122 (( a=angle/360.*(2*M_PI) )) 123 124 for (( s=0.0 ; s < 10. ; s+=0.5 )) ; do 125 (( 126 x=(clock.len_x*(s/10.)*(length/100.))*cos(a) , 127 y=(clock.len_y*(s/10.)*(length/100.))*sin(a) 128 )) 129 130 tput_cup $(( y+clock.middle_y )) $(( x+clock.middle_x )) 131 print -r -n -- "${ch}" 132 done 133 return 0 134} 135 136function draw_clock_hand 137{ 138 nameref hand=$1 139 draw_hand $(( 360.*(hand.val/hand.scale)-90. )) "${hand.ch}" ${hand.length} 140 return 0 141} 142 143function clear_clock_hand 144{ 145 nameref hand=$1 146 draw_hand $(( 360.*(hand.val/hand.scale)-90. )) " " ${hand.length} 147 return 0 148} 149 150function main_loop 151{ 152 typeset c 153 154 # note: we can't use subshells when writing to the double-buffer file because this 155 # will render the tput value cache useless 156 while true ; do 157 if ${init_screen} ; then 158 init_screen="false" 159 160 get_term_size termsize || fatal_error $"Couldn't get terminal size." 161 162 (( 163 clock.middle_x=termsize.columns/2.-.5 , 164 clock.middle_y=termsize.lines/2.-.5 , 165 clock.len_x=termsize.columns/2-2 , 166 clock.len_y=termsize.lines/2-2 , 167 )) 168 169 { 170 clear 171 draw_clock 172 } >&6 173 fi 174 175 { 176 (( ${ date +"hours.val=%H , minutes.val=%M , seconds.val=%S" ; } )) 177 178 # small trick to get a smooth "analog" flair 179 (( 180 hours.val+=minutes.val/60. , 181 minutes.val+=seconds.val/60. 182 )) 183 184 draw_clock_hand seconds 185 draw_clock_hand minutes 186 draw_clock_hand hours 187 188 # move cursor to home position 189 tput_cup 0 0 190 } >&6 191 192 6<#((0)) 193 cat <&6 194 195 redirect 6<&- ; rm -f "${scratchfile}" ; redirect 6<> "${scratchfile}" 196 197 c="" ; read -r -t ${update_interval} -N 1 c 198 if [[ "$c" != "" ]] ; then 199 case "$c" in 200 ~(Fi)q | $'\E') return 0 ;; 201 esac 202 fi 203 204 { 205 clear_clock_hand hours 206 clear_clock_hand minutes 207 clear_clock_hand seconds 208 } >&6 209 done 210} 211 212function usage 213{ 214 OPTIND=0 215 getopts -a "${progname}" "${termclock_usage}" OPT '-?' 216 exit 2 217} 218 219# program start 220builtin basename 221builtin cat 222builtin date 223builtin mktemp 224builtin rm 225 226typeset progname="${ basename "${0}" ; }" 227 228float -r M_PI=3.14159265358979323846 229 230# terminal size rect 231compound termsize=( 232 integer columns=-1 233 integer lines=-1 234) 235 236typeset init_screen="true" 237 238compound clock=( 239 float middle_x 240 float middle_y 241 integer len_x 242 integer len_y 243) 244 245 246# set clock properties 247compound seconds=( 248 float val 249 typeset ch 250 float scale 251 integer length ) 252compound minutes=( 253 float val 254 typeset ch 255 float scale 256 integer length ) 257compound 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) 2009-12-02 \$\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." 293(( update_interval >= 0. && update_interval <= 7200. )) || fatal_error $"invalid update_interval value." 294 295# create temporary file for double-buffering and register an EXIT trap 296# to remove this file when the shell interpreter exits 297scratchfile="${ mktemp -t "termclock.ppid${PPID}_pid$$.XXXXXX" ; }" 298[[ "${scratchfile}" != "" ]] || fatal_error $"Could not create temporary file name." 299trap 'rm -f "${scratchfile}"' EXIT 300rm -f "${scratchfile}" ; redirect 6<> "${scratchfile}" || fatal_error $"Could not create temporary file." 301 302# register trap to handle window size changes 303trap 'init_screen="true"' WINCH 304 305main_loop 306 307# exiting - put cursor below clock 308tput_cup $((termsize.lines-2)) 0 309 310exit 0 311# EOF. 312