#!/bin/bash # # headless_pianobar # # Copyright (c) 2012 Daniel Thau # # Permission is hereby granted, free of charge, to any person obtaining a copy of # this software and associated documentation files (the "Software"), to deal in # the Software without restriction, including without limitation the rights to # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies # of the Software, and to permit persons to whom the Software is furnished to do # so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # ### Description/instructions: # # This script to allow pianobar to run headlessly as well as re-connect to # pianobar's UI. # # When this script is launched, if pianobar isn't running, it will launch # pianobar (in the background) and then immediately connect to pianobar's UI to # allow the user to do things such as log in. If pianobar is running, this # script will simply connect to pianobar's UI. # # If you close this script with ctrl-c or by closing the terminal, pianobar # should continue running in the background. # # To close pianobar, press the key mapped to close pianobar ('q' by default). # This script should detect that pianobar died and close as well. # ### Known issues/limitations: # # When the user is at a text input field - such as login or naming a station - # mappings to quit pianobar ('q' by default) will not quit pianobar. Moreover, # techniques such as ctrl-c or closing the terminal will not quit pianobar # either, as they only quit this script and leave pianobar running headlessly. # To quit pianobar from this situation, one must either complete the text entry # and get pianobar to a state where it will accept the quit mapping, or one # must kill pianobar from outside of this script through commands such as # `kill`. # # This script was initially intended to be bourne-shell compatible and # portable, however one major limitation was found: the bourne shell does not # seem to have a good way to read a single character at a time such as can be # done with bash's "-n" flag. Thus this script is dependent on less portable # techniques and currently requires bash. # Set default config options, as defined in the pianobar man page. Only set for variables actually used. # They can all be overridden by the main pianobar config file. # headless_pianobar options: # check_period: sets how frequently the script checks if pianobar is running # output_lines: sets how many lines of pianobar output to print when reconnecting CHECK_PERIOD=${CHECK_PERIOD:-"1"} OUTPUT_LINES=${OUTPUT_LINES:-"30"} ACT_SONGPLAY=P ACT_SONGPAUSETOGGLE=p ACT_SONGPAUSETOGGLE2=' ' # space XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config} FIFO=$XDG_CONFIG_HOME/pianobar/ctl OUT=$XDG_CONFIG_HOME/pianobar/out CONFIG=$XDG_CONFIG_HOME/pianobar/config # Load the pianobar config file as bash variables # also strip password info for security reasons # ex: act_upcoming becomes ACT_UPCOMING # ex: user becomes PIANOBAR_USER (in order to avoid conflicts with the user environment variable) load_config(){ if [[ -r $CONFIG ]] then source <(sed -n \ -e 's/password.*//' \ -e 's/user/pianobar_user/' \ -e 's/\([0-9A-Za-z_]*\) = \(.*\)/\U\1\E="\2"/p' < $CONFIG) else echo "Couldn't load config file $CONFIG, using defaults" false fi } is_running(){ ps -u $(id -u) -o comm | grep -q "^pianobar$" } # Launch pianobar if it is not already running. launch(){ if ! is_running then ensure_fifo true echo "Pianobar not running, launching Pianobar" nohup pianobar &>$OUT &disown sleep 1 else ensure_fifo fi } # Check on a regular basis that pianobar is still running. # If pianobar stops running, stop this script as well. running(){ while is_running do sleep $CHECK_PERIOD done echo "Pianobar died, quitting" quit } # Function to cleanly quit. Ensures that the two backgrounded processes (the # output, tail, and the check, running()) both exit along with the parent (this # script itself) quit(){ # tail and running() might both die when the parent dies as there was no # nohup, but double-check just to make sure. Don't want to leave a mess # behind. if [ ! -z $TAILPID ] then kill $TAILPID 2>/dev/null fi if [ ! -z $RUNNINGPID ] then kill $RUNNINGPID 2>/dev/null fi trap - HUP INT TERM kill $$ exit 0 } # Print pianobar's output. output(){ # Sanity check: ensure pianobar's output can be read. if [ ! -f $OUT ] then echo "pianobar does not seem to be outputting to $OUT, try killing it and starting $0 again" exit 2 fi tail -n$OUTPUT_LINES -f $OUT & TAILPID=$! } # Get input from user, character by character, and feed it to pianobar's ctl # fifo. Note that no newline character is given with `read`. Rather, one # simply gets an empty variable back. Detect this situation and pass a newline # along to pianobar. Otherwise, send the character read from input to # pianobar. If ^D or EOF is given, quit input(){ IFS="" while /bin/true do read -n1 -s INPUT if [[ $? == "1" || "$INPUT" == $'\004' ]] then quit elif [ "$INPUT" == "" ] then send_input "\n" else send_input "$INPUT" fi done } # send the first argument to the control fifo send_input(){ echo -ne "$1" > $FIFO } # Ensure the ctrl fifo exists. Arg1: create if it doesn't exist ensure_fifo(){ if [ ! -p $FIFO ] then if [[ $1 ]] then echo "Pianobar ctl fifo not present at $FIFO, creating" mkfifo $FIFO if [ ! -p $FIFO ] then echo "Failed to create fifo, aborting" exit 1 fi else echo "Pianobar ctl fifo not present at $FIFO, aborting" exit 1 fi fi } # Ensure quit() is called to clean up when exiting trap quit HUP INT TERM mkdir -p $XDG_CONFIG_HOME/pianobar # load the main pianobar config file # Don't use $XDG_CONFIG_HOME or $HOME after this because it may have changed if set in the config file. load_config # if no arguments, launch pianobar if not running, then connect to the pianobar ui if [[ $# == 0 ]] then launch # Run running() in the background to detect when pianobar closes. running & RUNNINGPID=$! output input else if ! is_running then # if there are arguments, and pianobar is not running, start pianobar if it makes sense to case $1 in $ACT_SONGPLAY | $ACT_SONGPAUSETOGGLE | $ACT_SONGPAUSETOGGLE2) launch shift ;; *) echo "Pianobar not running, nothing executed" exit 0 ;; esac fi # if there are arguments and pianobar is running, send arguments to pianobar, then exit ensure_fifo while [[ $# > 0 ]]; do send_input "$1" shift done fi