#!/bin/sh GTKDIALOG=gtkdialog # PlayMusic (c) Thunor 2011 # GNU GENERAL PUBLIC LICENSE Version 2, June 1991 # See http://www.gnu.org/licenses/gpl-2.0.txt ## Strip comments from the entire project using: ## for f in func* main; do sed -i -e '/## /d' -e '/ ##/d' $f; done ## ---------------------------------------------------------------------------- ## Constants ## ---------------------------------------------------------------------------- ## These are application defaults which may be updated from the rcfile. TARGET=playmusic TITLE=PlayMusic VERSION=0.1.8 LOCAL_DATA_DIR=$HOME/.$TARGET PACKAGE_DATA_DIR=`pwd` DEBUG_CONTENT=0 DEBUG_TRANSITS=0 COVER_SEARCH_PATTERN="*.[JjPpBbGg][PpNnMmIi][GgPpFf]" DIALOG_README_SHOW=1 ## These don't require exporting. AUTONEXT_INTERVAL=2000 COVER_SIZE=-1 COVER_THUMB_SIZE=96 WINDOW_WIDTH=224 ## ---------------------------------------------------------------------------- ## Includes ## ---------------------------------------------------------------------------- ## These files are sourced from the PATH. PATH=$PACKAGE_DATA_DIR:$PATH . funcCoverSet . funcDialogReadMeOpen . funcgtkrcCreate . funcPlaylistCreate . funcrcfileRead . funcrcfileWrite . funcStop . functmpGet . functmpSet . funcTrackInfoSet ## ---------------------------------------------------------------------------- ## Local Functions ## ---------------------------------------------------------------------------- ## Enable the embedding of comments within the XML. Comment() { :; } ## Create the XML for the Previous/Next and Play/Pause buttons. ## On entry: $1 = button id ## $2 = button id funcButtonsCreate() { local index=-1 local f for f in $@; do echo ' <button> <input file stock="gtk-media-'`echo $f | tr A-Z a-z`'"></input> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"btn$f: IN\"</action>" fi`' <action>disable:tmrAutoNext</action>' if [ $f = Previous -o $f = Next ]; then echo ' <action>. funcPreviousNext; funcPreviousNext '$index'</action> <action>refresh:muiCoverRefreshCheck</action> <action>refresh:muiTrackInfoRefreshCheck</action>' else echo ' <action>. func'$f'; func'$f'</action>' fi echo ' <action>refresh:nbkPlayPause</action> <action>refresh:muiAutoNextCheck</action> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"btn$f: OUT\"</action>" fi`' </button>' index=1 done } ## ---------------------------------------------------------------------------- ## Main ## ---------------------------------------------------------------------------- ## Create the local data directory if it doesn't already exist. if [ ! -d $LOCAL_DATA_DIR ]; then mkdir $LOCAL_DATA_DIR if [ $? -ne 0 ]; then echo "Couldn't create $LOCAL_DATA_DIR" exit 1 fi fi ## Create the rcfile if it doesn't already exist. if [ ! -f $LOCAL_DATA_DIR/${TARGET}rc ]; then funcrcfileWrite new fi ## Process the command-line argument(s). if [ "$1" = -h -o "$1" = --help ]; then echo "$TITLE $VERSION (C) 2011 Thunor Usage: $TARGET [FILE or FOLDER] A FILE creates a playlist of the folder non-recursively and plays the file. A FOLDER creates a playlist of the folder recursively and plays the first file. No arguments resumes playing the last playlist and the last file played. See $PACKAGE_DATA_DIR/README for more information. " exit 1 fi ## Create a temporary directory. TEMP_DIR=`mktemp -d -t ${TARGET}.XXXXXXXX` if [ $? -ne 0 ]; then echo "Couldn't create temporary directory." exit 1 fi ## Load the rcfile. funcrcfileRead ## Create a custom style. funcgtkrcCreate ## Create the initial files required. functmpSet trackinforefreshcheck false functmpSet coverrefreshcheck false functmpSet dialogcovercheck false functmpSet coverpresscheck false functmpSet folderopencheck false functmpSet autonextcheck false functmpSet folderopen "[empty]" functmpSet playerstate stopped functmpSet trackinfotype 0 functmpSet playpause 0 functmpSet playerpid funcPlaylistCreate "$1" funcCoverSet funcTrackInfoSet ## Create the XML for the cover dialog. DIALOG_COVER=' <window name="winDialogCover" title="'$TITLE' - Cover" icon-name="'$TARGET'" window-position="2" border-width="0" resizable="false"> <button name="btnDialogCover" relief="2"> <variable>btnDialogCover</variable> <width>'$COVER_SIZE'</width> <height>'$COVER_SIZE'</height> <input file>'$TEMP_DIR'/cover</input> <action>closewindow:DIALOG_COVER</action> </button> <variable>DIALOG_COVER</variable> </window> ' ## Create the XML for the main dialog. echo ' <window name="winMain" title="'$TITLE'" icon-name="'$TARGET'" window-position="2" width-request="'$WINDOW_WIDTH'" resizable="false"> <vbox> <entry name="entTrackInfo" editable="false" has-frame="false" can-focus="false" tooltip-text="Left-click to toggle between artist/album and track info"> <variable>entTrackInfo</variable> <input file>'$TEMP_DIR'/trackinfo</input> <action signal="button-press-event">" if [ $BUTTON -eq 1 ]; then . funcTrackInfoSet; funcTrackInfoSet 2 fi "</action> <action signal="button-press-event">refresh:entTrackInfo</action> </entry> <hbox> '`Comment ## ## Because space-expand and space-fill have been set to true on the command- ## line and therefore will be the default for all widgets, we need to disable ## them for this button widget to keep it minimised and contained. ## `' <button name="btnCover" space-expand="false" space-fill="false" width-request="'$(($COVER_THUMB_SIZE + 10))'" height-request="'$(($COVER_THUMB_SIZE + 10))'" tooltip-text="Left-click: Next album Middle-click: View cover Right-click: Previous album"> <variable>btnCover</variable> <width>'$COVER_THUMB_SIZE'</width> <height>'$COVER_THUMB_SIZE'</height> <input file>'$TEMP_DIR'/cover</input> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"btnCover-click: IN\"</action>" echo "<action signal=\"button-press-event\">echo \ \"btnCover-button-press-event: IN\"</action>" fi`' '`Comment ## ## Code to handle the "click" signal. ## `' <action>disable:tmrAutoNext</action> <action>" . funcFolderPreviousNext; funcFolderPreviousNext 1 . funcWidgetSchedule funcWidgetSchedule muiCoverPressCheck coverpresscheck "</action> <action>refresh:muiCoverPressCheck</action> '`Comment ## ## Code to handle the "button-press-event" signal. Clicking will also emit ## the same signal but it gets filtered out when checking the BUTTON. ## `' <action signal="button-press-event">disable:tmrAutoNext</action> <action signal="button-press-event">" if [ $BUTTON -eq 2 ]; then . functmpGet . funcWidgetSchedule funcWidgetSchedule muiDialogCoverCheck dialogcovercheck ## tmrAutoNext was disabled on entry to these actions ## to stop it interferring with folder navigation and ## so we need to restore its correct state. if [ `functmpGet playerstate` = playing ]; then funcWidgetSchedule muiAutoNextCheck autonextcheck fi funcWidgetSchedule muiCoverPressCheck coverpresscheck elif [ $BUTTON -eq 3 ]; then . funcFolderPreviousNext; funcFolderPreviousNext -1 . funcWidgetSchedule funcWidgetSchedule muiCoverPressCheck coverpresscheck fi "</action> <action signal="button-press-event">refresh:muiCoverPressCheck</action> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"btnCover-click: OUT\"</action>" echo "<action signal=\"button-press-event\">echo \ \"btnCover-button-press-event: OUT\"</action>" fi`' </button> <vbox> <hbox> '"`funcButtonsCreate Previous Next`"' </hbox> <hbox> <button> <input file stock="gtk-media-stop"></input> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"btnStop: IN\"</action>" fi`' <action>disable:tmrAutoNext</action> <action>. funcStop; funcStop</action> <action>refresh:nbkPlayPause</action> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"btnStop: OUT\"</action>" fi`' </button> '`Comment ## ## As changing stock icons at runtime is not possible, a notebook widget is ## used instead with one button on each page so we can change the buttons. ## `' <notebook show-tabs="false" show-border="false"> '"`funcButtonsCreate Play Pause`"' <variable>nbkPlayPause</variable> <input file>'$TEMP_DIR'/playpause</input> </notebook> </hbox> <hbox> <button> <input file stock="gtk-open"></input> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"btnFolderOpen: IN\"</action>" fi`' <action>fileselect:entFolderOpen</action> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"btnFolderOpen: OUT\"</action>" fi`' </button> <button> <input file stock="gtk-quit"></input> </button> </hbox> </vbox> </hbox> </vbox> <vbox visible="false"> <timer milliseconds="true" interval="'$AUTONEXT_INTERVAL'"> <variable>tmrAutoNext</variable> <sensitive>false</sensitive> <action>. funcAutoNext; funcAutoNext</action> <action>refresh:muiCoverRefreshCheck</action> <action>refresh:muiTrackInfoRefreshCheck</action> </timer> <menubar> <menu> '`Comment ## ## There are several checkbox menuitems here that are being used to execute ## action functions conditionally and they are all named *Check because they ## are checking a value before effecting the target widget. They sit in-between ## the source widget and the target widget and instead of refreshing the target ## widget you would call funcWidgetSchedule and then refresh the check widget. ## `' <menuitem checkbox="false"> <variable>muiAutoNextCheck</variable> <input file>'$TEMP_DIR'/autonextcheck</input> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"muiAutoNextCheck: IN\"</action>" fi`' <action>enable:tmrAutoNext</action> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"muiAutoNextCheck: OUT\"</action>" fi`' </menuitem> <menuitem checkbox="false"> <variable>muiCoverRefreshCheck</variable> <input file>'$TEMP_DIR'/coverrefreshcheck</input> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"muiCoverRefreshCheck: IN\"</action>" fi`' <action>. funcCoverSet; funcCoverSet</action> <action>refresh:btnCover</action> <action>refresh:btnDialogCover</action> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"muiCoverRefreshCheck: OUT\"</action>" fi`' </menuitem> <menuitem checkbox="false"> <variable>muiTrackInfoRefreshCheck</variable> <input file>'$TEMP_DIR'/trackinforefreshcheck</input> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"muiTrackInfoRefreshCheck: IN\"</action>" fi`' <action>. funcTrackInfoSet; funcTrackInfoSet</action> <action>refresh:entTrackInfo</action> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"muiTrackInfoRefreshCheck: OUT\"</action>" fi`' </menuitem> <menuitem checkbox="false"> <variable>muiDialogCoverCheck</variable> <input file>'$TEMP_DIR'/dialogcovercheck</input> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"muiDialogCoverCheck: IN\"</action>" fi`' '`Comment ## ## Something to be aware of: launching will terminate the processing of further ## actions both here (you won't see OUT printed) and from any triggering action. ## `' <action>launch:DIALOG_COVER</action> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"muiDialogCoverCheck: OUT\"</action>" fi`' </menuitem> <menuitem checkbox="false"> <variable>muiCoverPressCheck</variable> <input file>'$TEMP_DIR'/coverpresscheck</input> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"muiCoverPressCheck: IN\"</action>" fi`' <action>refresh:nbkPlayPause</action> <action>refresh:muiCoverRefreshCheck</action> <action>refresh:muiTrackInfoRefreshCheck</action> <action>refresh:muiAutoNextCheck</action> <action>refresh:muiDialogCoverCheck</action> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"muiCoverPressCheck: OUT\"</action>" fi`' </menuitem> <menuitem checkbox="false"> <variable>muiFolderOpenCheck</variable> <input file>'$TEMP_DIR'/folderopencheck</input> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"muiFolderOpenCheck: IN\"</action>" fi`' <action>disable:tmrAutoNext</action> '`Comment ## ## I cannot split this across multiple lines because it requires double-quotes ## and splitting it into separate actions results in multiple subshells. ## `' <action>. funcStop; funcStop; . funcPlaylistCreate; funcPlaylistCreate "$entFolderOpen"; . funcWidgetSchedule; funcWidgetSchedule muiCoverRefreshCheck coverrefreshcheck; funcWidgetSchedule muiTrackInfoRefreshCheck trackinforefreshcheck; . funcPlay; funcPlay</action> <action>refresh:nbkPlayPause</action> <action>refresh:muiAutoNextCheck</action> <action>refresh:muiCoverRefreshCheck</action> <action>refresh:muiTrackInfoRefreshCheck</action> <action>refresh:entFolderOpen</action> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"muiFolderOpenCheck: OUT\"</action>" fi`' </menuitem> </menu> </menubar> '`Comment ## ## If the user selects a folder using the folder open button then the path ## will be stored here which will trigger the default signal. This triggering ## will also occur when it is refreshed [to empty it] and so a check widget is ## used to enable the playlist to be created and auto-played conditionally. ## `' <entry editable="false" fs-action="folder" fs-title="Select a folder to play"> <variable>entFolderOpen</variable> <input file>'$TEMP_DIR'/folderopen</input> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"entFolderOpen: IN\"</action>" fi`' '`Comment ## ## I cannot split this across multiple lines because it requires double-quotes ## and splitting it into separate actions results in multiple subshells. ## `' <action>if [ "$entFolderOpen" != "[empty]" ]; then . funcWidgetSchedule; funcWidgetSchedule muiFolderOpenCheck folderopencheck; fi</action> <action>refresh:muiFolderOpenCheck</action> '`if [ $DEBUG_TRANSITS -ne 0 ]; then echo "<action>echo \"entFolderOpen: OUT\"</action>" fi`' </entry> </vbox> '`Comment ## ## The window "show" signal is used to start playing the initial track ## and is a good place to auto-start something. ## `' <action signal="show">. funcPlay; funcPlay</action> <action signal="show">refresh:nbkPlayPause</action> <action signal="show">refresh:muiAutoNextCheck</action> </window> ' > $TEMP_DIR/dialog_main ## Export everything necessary. export GTKDIALOG export TARGET export TITLE export VERSION export LOCAL_DATA_DIR export PACKAGE_DATA_DIR export DEBUG_CONTENT export DEBUG_TRANSITS export COVER_SEARCH_PATTERN export DIALOG_README_SHOW export PATH export TEMP_DIR export GTK2_RC_FILES export DIALOG_COVER ## Open the README dialog? funcDialogReadMeOpen ## And off we go... $GTKDIALOG --space-expand=true --space-fill=true --file=$TEMP_DIR/dialog_main ## Stop any playing track. funcStop ## Copy the non-empty playlist* files to the local data directory. if [ `functmpGet playlistcount` -gt 0 ]; then cp $TEMP_DIR/playlist* $LOCAL_DATA_DIR/ fi ## Remove the temporary files. rm -rf $TEMP_DIR