#!/usr/bin/tclsh ####################################################################### # # BlueamaroK - Control basic functions of the Amarok player with a # Sony Ericsson mobile telephone through a Bluetooth connection. # # This script is released under the GPL. # # Pablo Chinea # ver. 0.51 - 13/01/2006 # N.Cat # version 0.5.2 - 05/10/2006 # ChangeLog: # * Fix parsing when KDE applications output on stderr # * Make configuration easier and more robust # * Allow disconnects and reconnect automatically # * Improve menu, now just one Play/Pause entry # * Show message and set status info on song change # * Mark active song in playlist # * Improve playlist item formatting # * Improve error handling # ####################################################################### # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # ####################################################################### global CONFIGFILE set CONFIGFILE "blueamarokrc" ################# # Configuration # ################# proc configurar {} { global CONFIGFILE global DISPOSITIVO if { ! [ info exists DISPOSITIVO ] || [ string equal "" $DISPOSITIVO ] } { set DISPOSITIVO "/dev/rfcomm0" } if { [ catch { exec kdialog --inputbox "Bluetooth (RFCOMM) serial port:" $DISPOSITIVO --caption "BlueamaroK Configuration" 2>/dev/null } DISPOSITIVO ] == 1 } { return 1 } if { [ catch { set outfile [ open $CONFIGFILE w ] } ] == 1 } { exec kdialog --sorry "Can't open config file \"$DISPOSITIVO\" for writing" --caption "BlueamaroK" 2>/dev/null exit 1 } puts $outfile $DISPOSITIVO close $outfile return 0 } ###################### # Load configuration # ###################### proc cargar {} { global CONFIGFILE global DISPOSITIVO if { [ file exists $CONFIGFILE ] } { set infile [ open $CONFIGFILE r ] set DISPOSITIVO [ gets $infile ] close $infile if { [ string equal "" $DISPOSITIVO ] } { configurar } } else { configurar } } ########################## # Connect with the phone # ########################## proc Conectar {} { global DISPOSITIVO global CANAL while { [ catch { set CANAL [ open $DISPOSITIVO r+ ] } ] == 1 } { exec kdialog --sorry "Can't open Bluetooth (RFCOMM) device \"$DISPOSITIVO\"" --caption "BlueamaroK" 2>/dev/null # Exit if user gives up if { [ configurar ] == 1 } { exit 0 } } fconfigure $CANAL -buffering line fileevent $CANAL readable "Leer" } ################################ # Reconnect if connection lost # ################################ proc Reconectar {} { global DISPOSITIVO global CANAL if { [ catch { set CANAL [ open $DISPOSITIVO r+ ] } ] == 1 } { after 10000 Reconectar } else { fconfigure $CANAL -buffering line fileevent $CANAL readable "Leer" IniCon } } ####################################### # Capture the input send by the phone # ####################################### proc Leer {} { global CANAL gets $CANAL entrada if { [ eof $CANAL ] } { # Disconnected? close $CANAL Reconectar return } puts stderr "<- $entrada" Parsear $entrada } ################################### # Send an AT command to the phone # ################################### proc atsend { atcmd } { global CANAL puts stderr "-> $atcmd" puts $CANAL $atcmd } ####################################### # Capture the commands sent by amaroK # ####################################### proc iface {} { #global status global EST gets stdin entrada if { [ string equal "configure" $entrada ] } { configurar } elseif { [ string equal "trackChange" $entrada ] } { set artista [ exec dcop amarok player artist 2>/dev/null ] set titulo [ exec dcop amarok player artist 2>/dev/null ] if { $EST == 0 } { atsend "AT*EAID=1,0,\"Now Playing:\\0A\\0A$artista - $titulo\",20" vwait OK vwait CONT } set nombre [ song $artista $titulo ] atsend "AT*EAST=\">$nombre\"" vwait OK } elseif { [ string match "engineStateChange: *" $entrada ] } { if { [ string match "engineStateChange: playing" $entrada ] } { #set status 2 set artista [ exec dcop amarok player artist 2>/dev/null ] set titulo [ exec dcop amarok player artist 2>/dev/null ] if { $EST == 0 } { atsend "AT*EAID=1,0,\"Now Playing:\\0A\\0A$artista - $titulo\",20" vwait OK vwait CONT } set nombre [ song $artista $titulo ] atsend "AT*EAST=\">$nombre\"" vwait OK } else { #set status 0 atsend "AT*EAST=\"BlueamaroK\"" vwait OK } } } ############################### # Execute an action in amarok # ############################### proc kaccion { cmd } { exec dcop amarok player $cmd 2>/dev/null } ################################################### # Execute an action in amarok (with 2 parameters) # ################################################### proc kaccion2 { cmd1 cmd2 } { exec dcop amarok player $cmd1 $cmd2 2>/dev/null } ####################### # Parsing phone input # ####################### proc Parsear { cadena } { global EST global CONT if { [ string match "OK" $cadena ] } { global OK set OK 1 } if { [ string match "ERROR" $cadena ] } { set EST 1 atsend "AT*EAID=1,0,\"BlueamaroK error!\\0APlease send debug output to khertz\\80gmail.com\",0" vwait OK vwait CONT } switch $EST { 0 { if { [ string match "*EAAI" $cadena ] } { set EST 1 # default menu to "Play" kmenu 2 } if { [ string match "*EAII: 0" $cadena ] } { set CONT 1 } } 1 { if { [ string match "*EAMI*" $cadena ] } { set val [ string range $cadena 7 end ] if { $val != 0 } { global MENU eval [ lindex $MENU [ expr $val * 2 - 1 ] ] kmenu $val } else { set EST 0 } set LASTVAL $val } if { [ string match "*EAII*" $cadena ] } { if { [ string match "*EAII: 1,1" $cadena ] } { kmenu 2 } set CONT 1 } } 2 { if { [ string match "*EAII*" $cadena ] } { if { [ string match "*EAII: 15*" $cadena ] } { set val [ string range $cadena 10 end ] kaccion2 setVolume $val } else { set CONT 1 set EST 1 } } } 3 { if { [ string match "*EAMI*" $cadena ] } { set val [ string range $cadena 7 end ] if { $val != 0 } { global lista global pag if { ($pag != 0) } { if { ($val == 1) } { showplaylist -1 0 } elseif { ($val == 7) } { showplaylist 1 0 } else { exec dcop amarok playlist playByIndex [ expr $pag * 5 + $val - 2 ] 2>/dev/null } } else { if { ( $val == 6 ) } { showplaylist 1 0 } else { exec dcop amarok playlist playByIndex [ expr $val - 1 ] 2>/dev/null } } set CONT 1 } set EST 1 set CONT 1 } } } } ####################### # Volume control menu # ####################### proc volume {} { global EST set EST 2 set vol [ expr [ exec dcop amarok player getVolume 2>/dev/null ] / 10 ] atsend "AT*EAID=4,2,\"amaroK Volume\",10,$vol" vwait CONT } ######################## # Create the main menu # ######################## proc kmenu { sel } { global MENU global status set action "Play" set status [ exec dcop amarok player status 2>/dev/null ] if { $status == 2 } { set action "Pause" } set MENU [list \ "Previous Track" "kaccion prev" \ $action "kaccion playPause" \ "Stop" "kaccion stop" \ "Next Track" "kaccion next" \ "Skip Backward" "kaccion2 seekRelative -5" \ "Skip Forward" "kaccion2 seekRelative 5" \ "Volume" volume \ "Mute" "kaccion mute" \ "Now Playing" Info \ "Playlist" playlist \ "About" Acerca \ ] set titulos "" foreach { elem cmd } $MENU { set titulos "$titulos,\"$elem\"" } set long [ expr [ llength $MENU ] / 2 ] atsend "AT*EASM=\"amaroK\",1,$sel,$long$titulos" vwait OK } ######################### # Generate the playlist # ######################### proc genlist { fich } { set infile [open $fich r] while { [ gets $infile cadena ] >= 0 } { if { [ string match "**" $cadena ] } { regexp "(.*)" [ string trim $cadena ] -> titulo } if { [ string match "**" $cadena ] } { regexp "(.*)" [ string trim $cadena ] -> artista } if { [ string match "* 15 } { set artista [ string range $artista 0 12 ] append artista "..." } if { [ string length $titulo ] > 24 } { set titulo [ string range $titulo 0 21 ] append titulo "..." } if { [ string length $artista ] > 0 } { set nombre "$artista - $titulo" } else { set nombre $titulo } return $nombre } #################### # Get the playlist # #################### proc playlist {} { global EST global lista if { [ exec dcop amarok playlist getTotalTrackCount 2>/dev/null ] == 0 } { atsend "AT*EAID=1,0,\"Playlist is empty.\",20" vwait OK vwait CONT } else { global pos global pag set EST 3 set lista [ genlist [ exec dcop amarok playlist saveCurrentPlaylist 2>/dev/null ] ] exec dcop amarok playlist shortStatusMessage "Default Playlist saved by BlueamaroK" set pos [ exec dcop amarok playlist getActiveIndex 2>/dev/null ] if { $pos == -1 } { set pag 0 set offset 0 } else { set pag [ expr $pos / 5 ] set offset [ expr $pos % 5 + 1 ] } showplaylist 0 $offset } } ########################## # Show the playlist menu # ########################## proc showplaylist { inc offset } { global lista global pag global pos set pag [ expr $pag + $inc ] set longit [ llength $lista ] set ini [ expr $pag * 5 ] if { [ expr ($longit - 1) / 5 ] == $pag } { set fin [ expr $longit - 1 ] } else { set fin [ expr $ini + 4 ] } set titulos "" set long [ expr $fin - $ini + 1 ] if { $offset } { set sel $offset } else { set sel 1 } # Does the page contain the active song? if { $pag == [ expr $pos / 5 ] } { set active [ expr $pos % 5 + 1 ] } else { set active 0 } if { $pag != 0 } { set titulos "$titulos,\" <<< \"" set long [ expr $long + 1 ] set sel [ expr $sel + 1 ] } foreach elem [ lrange $lista $ini $fin ] { # Add ">" prefix for active song set active [ expr $active - 1 ] if { $active == 0 } { set titulos "$titulos,\"> $elem\"" } else { set titulos "$titulos,\"$elem\"" } } if { [ expr ($longit - 1) / 5 ] != $pag } { set titulos "$titulos,\" >>> \"" set long [ expr $long + 1 ] } set npag [ expr $pag + 1 ] set tpag [ expr ($longit - 1) / 5 + 1 ] set head "Playlist ($npag/$tpag)" if { [ string length $head ] > 15 } { set head "# ($npag/$tpag)" } atsend "AT*EASM=\"$head\",1,$sel,$long$titulos" vwait CONT } ########################## # Show track information # ########################## proc Info {} { set nombre [ exec dcop amarok player nowPlaying 2>/dev/null ] if { [ string equal "" $nombre ] } { atsend "AT*EAID=1,0,\"amaroK is stopped.\",0" vwait OK vwait CONT return } set duracion [ exec dcop amarok player totalTime 2>/dev/null ] set info "$nombre\\0A($duracion)" set bitrate [ exec dcop amarok player bitrate 2>/dev/null ] set samplerate [ expr [ exec dcop amarok player sampleRate 2>/dev/null ] / 1000 ] set info "$info\\0A* $bitrate kbps - $samplerate kHz" set fichero [ exec dcop amarok player path 2>/dev/null ] if { [ file readable $fichero ] == 1 } { set tipo [ string trim [ exec file $fichero | cut -d: -f2 ] ] set info "$info\\0A* $tipo" } atsend "AT*EAID=14,2,\"Now Playing\",\"$info\"" vwait CONT } ####################### # Show "About" dialog # ####################### proc Acerca {} { set a_pro "BlueamaroK 0.5.2" set a_aut "* Pablo Chinea \\0A N.Cat " set a_web "* http://www.khertz.net/" set a_lic "* This script is released under the GPL." set acerca "$a_pro\\0A$a_aut\\0A$a_web\\0A$a_lic" atsend "AT*EAID=14,2,\"About\",\"$acerca\"" vwait CONT } ############################# # Initialize the connection # ############################# proc IniCon {} { atsend "ATZ" vwait OK atsend "ATE=0" vwait OK atsend "AT*EAM=\"BlueamaroK\"" vwait OK atsend "AT*EAID=1,0,\"Connected to BlueamaroK.\",20" vwait OK vwait CONT atsend "AT*EAST=\"BlueamaroK\"" vwait OK } ################# # Main function # ################# proc main {} { global EST set EST 0 cargar Conectar IniCon fileevent stdin readable "iface" vwait BUCLEINF } main