#!/usr/local/cdashtk # -*-Tcl-*- # # tkcda - a Tcl/Tk client for the cda (xmcd) daemon # (requires cdash) # Seth Golub # http://www.aigeek.com/geek/cdash/ # Nov 1996 # ##################### # Resource defaults # option add *optionPriority startupFile startupFile set option_priority [option get . optionPriority OptionPriority] option add .rcfile "$env(HOME)/.tkcdarc" $option_priority option add *update 1000 $option_priority option add *reacquireFreq 2000 $option_priority option add *background "#a6a6a6" $option_priority option add *foreground "#000000" $option_priority option add *highlightThickness 0 $option_priority option add *disp*background "#53868b" $option_priority option add *disp*foreground "#ffffff" $option_priority option add *disp.a*foreground "#00ffff" $option_priority option add *disp.a*background "#000000" $option_priority option add *disp.disctitle.borderWidth 0 $option_priority option add *disp.tracktitle.borderWidth 0 $option_priority option add *disp.num*foreground "#ffa500" $option_priority option add *disp.num*background "#000000" $option_priority option add *font -*-helvetica-medium-r-*--10-100-* $option_priority option add *disp.num.track.font -*-helvetica-medium-o-*--24-240-* $option_priority option add *disp.num.index.font -*-helvetica-bold-o-*--14-140-* $option_priority option add *disp.num.ttime.font -*-helvetica-medium-o-*--24-240-* $option_priority option add *disp.a*font -*-helvetica-medium-r-*--10-100-* $option_priority option add *toggles*font -*-helvetica-medium-r-*--10-100-* $option_priority option add *play_control.pp.playpause.text {play/pause} $option_priority option add *play_control.pp.play.text play $option_priority option add *play_control.pp.pause.text pause $option_priority option add *play_control.stop.text stop $option_priority option add *play_control.www.a.text W:Disc $option_priority option add *play_control.www.a.command {exec exe2www current-cd-url -g -a &} $option_priority option add *play_control.www.b.text W:Song $option_priority option add *play_control.www.b.command {exec exe2www current-cd-url -g -a -s &} $option_priority option add *play_control.track.prev.text {< track} $option_priority option add *play_control.track.next.text {track >} $option_priority option add *play_control.index.prev.text {< index} $option_priority option add *play_control.index.next.text {index >} $option_priority option add *toggles*relief ridge $option_priority option add *toggles*borderWidth 3 $option_priority option add *toggles.relief sunken $option_priority option add *toggles.borderWidth 0 $option_priority option add *toggles.lock.text lock $option_priority option add *toggles.shuffle.text shuffle $option_priority option add *toggles.repeat.text repeat $option_priority option add *toggles.daemon.text daemon $option_priority option add *eject.text eject $option_priority option add *quit.text quit $option_priority option add *volume.showValue false $option_priority option add *time_slider.showValue false $option_priority option add *trackslider.showValue false $option_priority option add *fixedWidth true $option_priority option add *unknownTitle "unknown disc title" $option_priority option add *playpause_button false $option_priority if {[option get . fixedWidth FixedWidth]} { option add *disp.disctitle.width 30 $option_priority option add *disp.tracktitle.width 30 $option_priority } #################### proc numerify {str} { set result $str regsub -all -- "--" $str "0" result return $result } proc play {} { global dirty_time if {$dirty_time} { return [impose_time] } return [cda_play] } proc play_or_pause {} { global running dirty_time if {$running == "Playing"} { return [cda_pause] } if {$dirty_time} { return [impose_time] } return [cda_play] } proc stop {} { cda_stop } # track/index, next/prev proc track_advance {next} { global running numtracks track index dirty_time ttime if {$running == "Playing"} { return [eval "cda_track $next"] } if {$running != "Stopped"} return set track [numerify $track] if {$next == "next"} { incr track if {$track > $numtracks} { set track 1 } } else { incr track -1 if {$track < 1} { set track $numtracks } } set dirty_time 1 # trim the time down, if necessary. set toc_index [expr [string trimleft $track 0] - 1] if {[mmss2sec $ttime] > [mmss2sec [tracktime $toc_index]]} { set ttime [tracktime $toc_index] } refresh } proc index_next {} { cda_index next } proc index_prev {} {cda_index prev} proc eject {} {cda_disc eject} proc set_toggle {cmd val} { eval "cda_$cmd [if $val {concat on} { concat off }]" } set toggle_daemon_reentry 0 proc toggle_daemon {new_setting} { global impose_state_code toggle_daemon_reentry if {$toggle_daemon_reentry} { return } set toggle_daemon_reentry 1 if {$new_setting} { start $impose_state_code } { stop_daemon } set toggle_daemon_reentry 0 } proc tracknumber {index} { global toc lindex [lindex $toc $index] 0 } proc tracktime {index} { global toc lindex [lindex $toc $index] 1 } proc tracktitle {index} { global toc lindex [lindex $toc $index] 2 } proc trackext {index} { global toc lindex [lindex $toc $index] 3 } proc get_disc_info {} { global disctitle numtracks toc has_disc set toc "" set disctitle "" if [expr !$has_disc] return cda_loaddb if [catch {set toc [cda_toc]} result] { set has_disc 0 return } set disctitle [cda_title] if {$disctitle == ""} { set disctitle [option get . unknownTitle UnknownTitle] } set numtracks [llength $toc] } proc get_status {} { global lock shuffle repeat program_on track index updateSlider \ volume balance route ttime toc tracktitle has_disc \ program timeInSec running dirty_time oldtrack \ updateTrackSlider sliderTrack numtracks # Make sure the daemon is still out there if {![cda_daemon_alive]} { daemon_died return 0 } set status_line [cda_status] set status [split $status_line] if {[llength $status] != 13} { error [format "Status line looks odd: %s" $status_line] } set old_has_disc $has_disc set oldrunning $running set running [lindex $status 0] if [string match "CD*" $running] { set has_disc 1 set program [cda_program] set running [string range $running 3 end] } else { set has_disc 0 set running "No Disc" } if {!($dirty_time && ([string match "Stopped" $running] || [string match "No Disc" $running]))} { set track [lindex $status 1] set index [lindex $status 2] set ttime [lindex $status 3] set dirty_time 0 } .disp.a.running configure -text $running set lock [lindex $status 4] set shuffle [lindex $status 5] set program_on [lindex $status 6] set repeat [lindex $status 7] set reptcount [lindex $status 8] set volume [lindex $status 9] set balance [lindex $status 10] set route [lindex $status 11] set discid [lindex $status 12] if {$has_disc != $old_has_disc} { get_disc_info .play_control.trackslider configure -to [string trimleft $numtracks 0] } set toc_index [expr [string trimleft $track 0] - 1] if $has_disc { set tracktitle [tracktitle $toc_index] } { set tracktitle "" } if {$oldtrack != $track} { if {$track == "--"} { .time_slider configure -to 100 } else { .time_slider configure -to \ [expr [mmss2sec [tracktime $toc_index]] - 1] } } if {$updateSlider} { set timeInSec [mmss2sec $ttime] } if {$updateTrackSlider} { set sliderTrack [if {$track == "--"} {expr 1} else { string trimleft $track 0 }] } set oldtrack $track return 1 } proc clear_status {} { global lock shuffle repeat program_on track index ttime toc \ tracktitle has_disc global disctitle ttimes set lock 0 set shuffle 0 set repeat 0 set program_on 0 set track "--" set index "--" set ttime "--:--" set tracktitle "" set has_disc 0 set toc "" set disctitle "" .disp.a.running configure -text "No Daemon" } proc mmss2sec {mmss} { scan [numerify $mmss] "%d:%d" minutes seconds return [expr $minutes * 60 + $seconds] } proc sec2mmss {seconds} { set minutes [expr $seconds / 60] set seconds [expr $seconds % 60] return [format "%02d:%02d" $minutes $seconds] } proc stopUpdatingTimeSlider {} { global updateSlider set updateSlider 0 } proc setTimeFromSlider {} { global updateSlider ttime dirty_time running set updateSlider 1 set ttime [sec2mmss [.time_slider get]] if {$running == "Playing"} { impose_time } else { set dirty_time 1 } } proc stopUpdatingTrackSlider {} { global updateTrackSlider set updateTrackSlider 0 } proc setTrackFromSlider {} { global updateTrackSlider dirty_time running track sliderTrack set updateTrackSlider 1 if {$running == "Playing"} { cda_play $sliderTrack } else { set track $sliderTrack set dirty_time 1 refresh } } ############### # Right column # frame .play_control frame .play_control.pp if {[option get . playpause_button Playpause_button]} { button .play_control.pp.playpause -command play_or_pause pack .play_control.pp.playpause -fill both -expand yes } else { button .play_control.pp.play -command play button .play_control.pp.pause -command cda_pause pack .play_control.pp.play .play_control.pp.pause -side left -fill both -expand yes } button .play_control.stop -command stop frame .play_control.www button .play_control.www.a button .play_control.www.b pack .play_control.www.a .play_control.www.b -in .play_control.www -side left -fill both -expand yes scale .play_control.trackslider -from 1 -to 100 -orient h -variable sliderTrack bind .play_control.trackslider setTrackFromSlider bind .play_control.trackslider +setTrackFromSlider bind .play_control.trackslider stopUpdatingTrackSlider bind .play_control.trackslider +stopUpdatingTrackSlider frame .play_control.track button .play_control.track.prev -command {track_advance prev} button .play_control.track.next -command {track_advance next} pack .play_control.track.prev .play_control.track.next -in .play_control.track -side left -fill both -expand yes frame .play_control.index button .play_control.index.prev -command index_prev button .play_control.index.next -command index_next pack .play_control.index.prev .play_control.index.next -in .play_control.index -side left -fill both -expand yes pack .play_control.pp .play_control.stop .play_control.trackslider .play_control.trackslider .play_control.track .play_control.index .play_control.www -in .play_control -fill both ############### # Left column # frame .toggles checkbutton .toggles.lock -variable lock -anchor w -command {set_toggle lock $lock} checkbutton .toggles.shuffle -variable shuffle -anchor w -command {set_toggle shuffle $shuffle} checkbutton .toggles.repeat -variable repeat -anchor w -command {set_toggle repeat $repeat} checkbutton .toggles.daemon -variable daemon_alive -anchor w -command {toggle_daemon $daemon_alive} button .eject -command eject button .quit -command exit pack .toggles.lock .toggles.shuffle .toggles.repeat .toggles.daemon .eject .quit -in .toggles -fill both ################ # Middle column # frame .middle scale .volume -from 0 -to 100 -orient h -variable volume -command cda_volume scale .time_slider -from 0 -to 100 -orient h -variable timeInSec bind .time_slider setTimeFromSlider bind .time_slider +setTimeFromSlider bind .time_slider stopUpdatingTimeSlider bind .time_slider +stopUpdatingTimeSlider frame .disp label .disp.disctitle -textvariable disctitle -anchor w label .disp.tracktitle -textvariable tracktitle -anchor w frame .disp.a label .disp.a.running -text "--" pack .disp.a.running -in .disp.a -fill x frame .disp.num label .disp.num.track -textvariable track label .disp.num.index -textvariable index label .disp.num.ttime -textvariable ttime pack .disp.num.track .disp.num.index .disp.num.ttime -in .disp.num -side left -fill both pack .disp.num -in .disp -fill both pack .disp.a .disp.disctitle .disp.tracktitle -in .disp -fill x pack .disp .volume .time_slider -in .middle -fill both ################ proc find_daemon {} { global daemon_alive if {![cda_daemon_alive]} { cda_on } set daemon_alive [cda_daemon_alive] cda_connect return $daemon_alive } proc stop_daemon {} { cda_off clear_status daemon_died } proc daemon_died {} { global daemon_alive reacquire_freq set daemon_alive 0 .disp.a.running configure -text "No Daemon" # It's unclear when we want to clear things, if ever. I think # this needs to be customizable. # clear_status after cancel refresh_loop after $reacquire_freq reacquire_loop } proc reacquire_loop {} { global reacquire_freq impose_state_code if {[cda_daemon_alive]} { start "" } else { after $reacquire_freq reacquire_loop } } proc refresh_loop {} { global refresh_freq if {[get_status]} { after $refresh_freq refresh_loop } } proc refresh {} { after cancel get_status refresh_loop } ### proc impose_time {} { global track ttime dirty_time set dirty_time 0 if {"--" == $track} { stop return } return [cda_play $track [numerify $ttime]] } proc impose_toggles {} { global lock shuffle repeat set_toggle lock $lock set_toggle shuffle $shuffle set_toggle repeat $repeat } proc impose_program {} { global program if {$program == ""} { cda_program clear } else { cda_program $program } } set state_name_list [list time toggles program] set state_jump_list [list impose_time impose_toggles impose_program] # takes one argument: a space separated list of attributes whose # saved state should be imposed on the daemon. proc create_state_assertion {state_string} { global state_name_list state_jump_list set state_list [split state_string] for { set i 0 set max [llength $lv] } {[expr $i <= $max]} {incr i} { set item [lindex $i $lv] set index [lsearch -exact $state_name_list $item] if {$index == -1} { error [format "%s is not a state name" $item] } eval [lindex $state_jump_list $i] } } ### pack .toggles -side left -fill y pack .middle -side left -fill both pack .play_control -side left -fill y #wm resizable . set has_disc 0 # start - Starts daemon and starts the poll cycle # # $cmd is for anything you want to do once we're connected but before # we read any settings. This might include asserting some settings we # didn't want to lose. # proc start {cmd} { find_daemon eval $cmd refresh_loop } set refresh_freq [option get . update Update] set reacquire_freq [option get . reacquireFreq ReacquireFreq] set rcfile [option get . rcfile Rcfile] set updateSlider 1 set updateTrackSlider 1 set dirty_time 0 set oldtrack -1 set running "" set impose_state_code "impose_time" if {[file exists $rcfile]} { source $rcfile } start ""