1 -- Authors: Marc Hartstein <marc.hartstein@alum.vassar.edu>
3 -- Last Changed: Unknown
5 -- statusd for MPD (Music Player Daemon)
6 -- by Marc Hartstein <marc.hartstein@alum.vassar.edu>
10 -- Based on a script by delirium@hackish.org
12 -- Feel free to contact me with any bugs or suggestions for improvement.
15 -- http://www.tecgraf.puc-rio.br/~diego/professional/luasocket/home.html
19 -- statusd_mpd-socket.lua is intended as a drop-in replacement for
20 -- statusd_mpd.lua. It should work with your existing statusd_mpd.lua
21 -- configuration, if any.
23 -- To achieve this, statusd_mpd-socket.lua needs to be located by statusd as
26 -- The easiest way to accomplish this is to create a symbolic link in your
27 -- ~/.ion3 directory from statusd_mpd.lua -> wherever you have placed
28 -- statusd_mpd-socket.lua
30 -- Don't forget to include %mpd in a statusbar template in cfg_statusbar.lua
34 -- See the defaults table below for the configurable settings and their default
35 -- values. You should create a mpd table in cfg_statusbar.lua to customize
36 -- these settings as for any other statusd plugin.
39 -- 500 or less makes seconds increment relatively smoothly while playing
40 update_interval
= 500,
42 -- how long to go to sleep when we can't talk to mpd so we don't spam the
43 -- system with connection attempts every half-second
44 retry_interval
= 60*1000, -- 1m
46 -- mpd server info (localhost:6600 are mpd defaults)
47 address
= "localhost",
50 -- mpd password (if any)
55 -- can use the following:
56 -- track metadata: %artist, %title, %num, %album, %year, %len
57 -- conditional metadata: %artist_or_album
58 -- current track position: %pos
59 -- escape for the percent character: %%
61 -- %artist_or_album will display the artist if any, otherwise it will
62 -- display the album name. I find this useful for Broadway recordings.
65 template
= "%artist - %num - %title (%pos / %len)"
68 local settings
= table.join(statusd
.get_config("mpd"), defaults
)
74 local socket
= require("socket")
77 -- set up a try function which closes the socket if there's an error
78 local try
= socket
.newtry(function() mpd_socket
:close() end)
80 local open_socket
= socket
.protect(function()
81 -- connect to the server
82 mpd_socket
= socket
.try(socket
.connect(settings
.address
, settings
.port
))
84 mpd_socket
:settimeout(100)
86 local data
-- buffer for reads
88 data
= try(mpd_socket
:receive())
89 if data
== nil or string.sub(data
,1,6) ~= "OK MPD" then
91 return nil, "mpd not running"
94 -- send password (if necessary)
95 if settings
.password
~= nil then
96 try(mpd_socket
:send("password " .. settings
.password
.. "\n"))
99 data
= try(mpd_socket
:receive())
100 until data
== nil or string.sub(data
,1,2) == "OK" or string.sub(data
,1,3) == "ACK"
101 if data
== nil or string.sub(data
,1,2) ~= "OK" then
103 return nil, "bad mpd password"
111 local get_mpd_status
= socket
.protect(function()
113 local data
-- buffer for reads
114 local success
= false
118 -- %pos, %len, and current state (paused/stopped/playing)
119 try(mpd_socket
:send("status\n"))
121 data
= try(mpd_socket
:receive())
122 if data
== nil then break end
124 local _
,_
,attrib
,val
= string.find(data
, "(.-): (.*)")
125 if attrib
== "time" then
126 _
,_
,info
.pos
,info
.len
= string.find(val
, "(%d+):(%d+)")
127 info
.pos
= string.format("%d:%02d", math
.floor(info
.pos
/ 60), math
.mod(info
.pos
, 60))
128 info
.len
= string.format("%d:%02d", math
.floor(info
.len
/ 60), math
.mod(info
.len
, 60))
129 elseif attrib
== "state" then
132 until string.sub(data
,1,2) == "OK" or string.sub(data
,1,3) == "ACK"
133 if data
== nil or string.sub(data
,1,2) ~= "OK" then
135 return nil, "error querying mpd status"
140 try(mpd_socket
:send("currentsong\n"))
142 data
= try(mpd_socket
:receive())
143 if data
== nil then break end
145 local _
,_
,attrib
,val
= string.find(data
, "(.-): (.*)")
146 if attrib
== "Artist" then info
.artist
= val
147 elseif attrib
== "Title" then info
.title
= val
148 elseif attrib
== "Album" then info
.album
= val
149 elseif attrib
== "Track" then info
.num
= val
150 elseif attrib
== "Date" then info
.year
= val
152 until string.sub(data
,1,2) == "OK" or string.sub(data
,1,3) == "ACK"
153 if data
== nil or string.sub(data
,1,2) ~= "OK" then
155 return nil, "error querying current song"
158 if info
.artist
== nil then
159 info
.artist_or_album
= info
.album
161 info
.artist_or_album
= info
.artist
166 -- done querying; now build the string
167 if info
.state
== "play" then
168 local mpd_st
= settings
.template
170 mpd_st
= string.gsub(mpd_st
, "%%([%w%_]+)", function (x
) return(info
[x
] or "") end)
171 mpd_st
= string.gsub(mpd_st
, "%%%%", "%%")
172 return success
, mpd_st
173 elseif info
.state
== "pause" then
174 return success
, "Paused"
176 return success
, "No song playing"
180 local init_mpd
-- forward declaration
182 local function update_mpd()
183 -- update unless there's an error that's not yet twice in a row, to allow
184 -- for transient errors due to load spikes
185 local success
, mpd_st
= get_mpd_status()
186 if success
or not last_success
then
187 statusd
.inform("mpd", mpd_st
)
190 if not success
and not last_success
then
191 -- something's wrong, try to reopen the connection
194 mpd_timer
:set(settings
.update_interval
, update_mpd
)
197 last_success
= success
200 init_mpd
= function ()
202 --statusd.inform("mpd","Opening connection...")
203 success
,errstr
= open_socket()
205 statusd
.inform("mpd",errstr
)
206 -- go to sleep for a while, then try again
207 mpd_timer
:set(settings
.retry_interval
, init_mpd
)
215 -- Sending a template we don't honor just confuses the statusbar
218 -- Go to sleep immediately, so a slow connect doesn't break the whole statusbar
219 mpd_timer
=statusd
.create_timer()
220 mpd_timer
:set(2000,init_mpd
)
221 statusd
.inform("mpd", "Initializing")