#!/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 <khertz@gmail.com>
# ver. 0.51 - 13/01/2006
# N.Cat <trisk@quasarnet.org>
# 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 "*<item*" $cadena ] } {
set artista ""
}
if { [ string match "*<Title>*" $cadena ] } {
regexp "<Title>(.*)</Title>" [ string trim $cadena ] -> titulo
}
if { [ string match "*<Artist>*" $cadena ] } {
regexp "<Artist>(.*)</Artist>" [ string trim $cadena ] -> artista
}
if { [ string match "*</item*" $cadena ] } {
lappend lista [ song $artista $titulo ]
}
}
close $infile
return $lista
}
###############################
# Produce formatted song name #
###############################
proc song { artista titulo } {
# Filter strings
regsub -all {&} $artista {\&} artista
regsub -all {&} $titulo {\&} titulo
regsub -all {\[} $artista {(} artista
regsub -all {\[} $titulo {(} titulo
regsub -all {\]} $artista {)} artista
regsub -all {\]} $titulo {)} titulo
set artista [ string trim $artista ]
set titulo [ string trim $titulo ]
# Truncate strings
if { [ string length $artista ] > 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 <khertz\\80gmail.com>\\0A N.Cat <trisk\\80quasarnet.org>"
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