1 -- statusd for MPD (Music Player Daemon) using a single persistent connection
3 -- bugs/requests/comments: Leandro Lucarella <llucax@gmail.com>
5 -- Based on statusd_exec.lua from Antti Vähäkotamäki (the async code) and
6 -- statusd_mpd.lua from delirium@hackish.org (the mpd glue code).
8 -- Usage is self documented in the default settings section. It provides the
9 -- same functionality from statusd_mpd.luam from delirium@hackish.org but it
10 -- uses a single persistent connection to MPD and does better usage from hints
11 -- (the status is "hinted important" when mpd status or volume change (and
12 -- optionally, up to important_time seconds from the begining of each song).
14 -- This is free software; you can redistribute it and/or modify it under
15 -- the terms of the GNU Lesser General Public License as published by
16 -- the Free Software Foundation; either version 2.1 of the License, or
17 -- (at your option) any later version.
23 -- 500 or less makes seconds increment relatively smoothly while playing
24 update_interval = 1000,
26 -- mpd server info (localhost:6600 are mpd defaults)
27 address = "localhost",
30 -- mpd password (if any)
33 -- seconds to consider the mpd in 'important' hint (0 never, -1 always)
34 important_time = 10, -- seconds
38 -- can use the following:
39 -- track metadata: %artist, %title, %num, %album, %year, %len
40 -- current track position: %pos
42 -- escape for the percent character: %%
45 template = "%artist - %num - %title (%pos / %len)",
47 -- output debug filename (false or nil for no debug output)
51 local settings = table.join(statusd.get_config("mpd"), defaults)
58 -- Function for starting and restarting execution of a process
60 local function start_execution()
62 -- Set up a function for receiving the data
63 -- and informing the statusbar
65 local function process_input(data)
67 -- At first, we are not initialized
70 -- When we get new data, output the last complete line
75 if settings.debug then
76 debug_file:write("ion-statusd [statusd_mpd]: DEBUG mpd data:\n"
83 if string.sub(data, 1, 6) ~= "OK MPD" then
84 statusd.inform("mpd_hint", "critical")
85 statusd.inform("mpd", "bad mpd response on init")
88 -- 'password' response (if necessary)
89 if settings.password ~= nil then
90 if string.sub(data, 1, 2) ~= "OK" then
91 statusd.inform("mpd_hint", "critical")
92 statusd.inform("mpd_hint", "bad mpd password")
96 -- everything ok, receive more data
98 data = coroutine.yield()
101 -- process only if we have 2 OK responses
102 local start, end_ = string.find(data, "\nOK")
103 if start and string.find(data, "\nOK", end_) then
106 local hint = "normal"
108 for attrib, val in string.gfind(data, "([^\n]-): ([^\n]*)") do
110 if attrib == "time" then
111 _, _, info.pos, info.len = string.find(val,
113 if settings.important_time == -1
114 or info.pos+0 <= settings.important_time
115 and settings.important_time ~= 0 then
118 info.pos = string.format("%d:%02d",
119 math.floor(info.pos / 60),
120 math.mod(info.pos, 60))
121 info.len = string.format("%d:%02d",
122 math.floor(info.len / 60),
123 math.mod(info.len, 60))
124 elseif attrib == "state" then
126 if info.state ~= last_state then
129 last_state = info.state
130 elseif attrib == "volume" then
131 info.volume = val .. "%"
132 if info.volume ~= last_volume then
135 last_volume = info.volume
136 elseif attrib == "Artist" then
138 elseif attrib == "Title" then
140 elseif attrib == "Album" then
142 elseif attrib == "Track" then
144 elseif attrib == "Date" then
149 -- done querying; now build the string
150 statusd.inform("mpd_hint", hint)
151 if info.state == "play" then
152 local mpd_st = settings.template
154 mpd_st = string.gsub(mpd_st, "%%([%w%_]+)",
155 function (x) return(info[x] or "") end)
156 mpd_st = string.gsub(mpd_st, "%%%%", "%%")
157 statusd.inform("mpd", mpd_st)
158 elseif info.state == "pause" then
159 statusd.inform("mpd", "Paused")
161 statusd.inform("mpd", "No song playing")
164 -- Wait for bgread to signal that more data
165 -- is available or the program has exited
167 data = coroutine.yield()
169 -- There was no enough data to interpret a complete status
170 data = data .. coroutine.yield()
174 -- Program has exited.
175 -- Start a timer for a new execution if set.
177 timer:set(settings.update_interval,
178 function() start_execution() end)
181 -- Set up a simple function for printing errors
183 local function process_error(data)
185 io.stderr:write("ion-statusd [statusd_mpd]: " .. data)
187 data = coroutine.yield()
191 -- Execute the program in the background and create
192 -- coroutine functions to handle input and error data
193 local sleep = settings.update_interval / 1000
194 sleep = string.gsub(sleep, '(%d+),(%d+)', '%1.%2')
195 local cmd = 'while sleep ' .. sleep .. '; do ' ..
196 'echo "status\ncurrentsong"; done | ' ..
197 settings.netcat .. ' ' .. settings.address .. ' ' ..
199 if settings.password ~= nil then
200 cmd = "echo 'password " .. settings.password .. "'; " .. cmd
202 statusd.popen_bgread(cmd, coroutine.wrap(process_input),
203 coroutine.wrap(process_error))
207 -- Now start execution of all defined processes
209 if settings.debug then debug_file = io.open("ion_statusd_mpd.log", "a+") end
210 timer = statusd.create_timer()
213 -- vim: set et sts=4 sw=4 :