summaryrefslogtreecommitdiff
path: root/contrib/headless_pianobar
blob: d520200b34817060a3ade05d7ac36232b6ad55aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
#!/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