KEXP radio playlist a System Tray icon

ZLM 2004-11-25:

One of the main events of 2004 for me was the discovery of KEXP, a Seatlle based non-profit radio that broadcasts live on the Internet at . I now listen to a lot of new music that I probably wouldn't know about otherwise.

I listen to it mainly at work, with my headphones to keep out the background noise of my open space. The station has a live playlist that I check whenever something nice is playing that I don't recognize.

I wrote this Windows Tcl script to avoid having to launch Opera and checking the web page so many times. At first I thought about displaying the song information on Winamp itself, but I took a look at the Winamp SDK and thought "pois.. talvez isto não seja grande idéia, Zé...". Anyway I never have Winamp visible, not even in the System Tray, so why bother?

So this simply uses Winico to display an icon in the System Tray (a nice yellow tea mug in my case) that shows the song information on the associated text whenever I pass the mouse pointer over it. Clicking the icon updates the song information, double-clicking toggles auto-updating on/off and right clicking exits. For the moment that's all the UI I need.

I am using some procs plundered here on the wiki: wsplit [L1 ] by Salvatore Sanfilippo, every [L2 ] by RS, and toggle [L3 ] by KPV. I use wsplit all the time, I wish something like it was part of the core Tcl.

wm withdraw . 
package require Winico
#I need to configure http to use a proxy...
package require http ; http::config -proxyhost -proxyport 8080
set target_page "" 
set autoupdate 0
set ico [winico createfrom c:/cup.ico]
winico taskbar add $ico -text "Bom dia!" -callback "actualizar %m"

proc actualizar {mensagem} {     
    if {$mensagem eq {WM_LBUTTONDBLCLK}} {  
        toggle ::autoupdate }    
    if {$mensagem eq {WM_LBUTTONDOWN}} {
        updateNow }
    if {$mensagem eq {WM_RBUTTONDOWN}} {
        exit }    

proc updateNow {} { winico taskbar modify $::ico -text [scrapText] }

proc scrapText {} { 
    if { [catch {set html [http::data [http::geturl $::target_page]]} errohttp] } {
        return "Erro! $errohttp"
    } else {
        set linha "[between "class=\"programtime\">" and "</tr>" in $html]"
        set songinfo [wsplit $linha "<td valign=\"top\">" ]        
        set artista [string trim [between "&nbsp;" and "</td>" in [lindex $songinfo 2]]]        
        #I know I should do something better about replacing html entities...
        set titulo  [string map { ")" ")" "(" "(" "'" "'" } [string trim [string map {"</td>" ""} [lindex $songinfo 4]]] ]
        set album   [string trim [string map {"</td>" ""} [lindex $songinfo 6]]]
        return "$artista - $titulo"

proc between { start "and" end "in" text} {
    set string_start  [string first $start $text]
    set string_end    [string first $end $text $string_start]
    set start_removed [string replace [string range $text $string_start $string_end] 0 [expr [string length $start ]-1] ]
    return [string replace $start_removed end end ]

proc wsplit {string sep} {
    set first [string first $sep $string]
    if {$first == -1} {
        return [list $string]
    } else {
        set l [string length $sep]
        set left [string range $string 0 [expr {$first-1}]]
        set right [string range $string [expr {$first+$l}] end]
        return [concat [list $left] [wsplit $right $sep]]

proc toggle varName {
   upvar 1 $varName var
   set var [expr {$var ? 0 : 1}]

proc every {ms body} {eval $body; after $ms [namespace code [info level 0]]}

every 120000  {if {$::autoupdate} { updateNow } }

RS: Note that the wsplit can be done simpler:

  1. map the separating string to a single char that cannot appear in the string
  2. split on that single char
proc wsplit {str sep} {
    split [string map [list $sep \0] $str] \0
% wsplit This<>is<>a<>test. <>
This is a test.