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)"
48 local settings = table.join(statusd.get_config("mpd"), defaults)
54 -- Function for starting and restarting execution of a process
56 local function start_execution()
58 -- Set up a function for receiving the data
59 -- and informing the statusbar
61 local function process_input(data)
63 -- At first, we are not initialized
66 -- When we get new data, output the last complete line
72 if string.sub(data, 1, 6) ~= "OK MPD" then
73 statusd.inform("mpd_hint", "critical")
74 statusd.inform("mpd", "bad mpd response on init")
77 -- 'password' response (if necessary)
78 if settings.password ~= nil then
79 if string.sub(data, 1, 2) ~= "OK" then
80 statusd.inform("mpd_hint", "critical")
81 statusd.inform("mpd_hint", "bad mpd password")
85 -- everything ok, receive more data
87 data = coroutine.yield()
93 for attrib, val in string.gfind(data, "([^\n]-): ([^\n]*)") do
95 if attrib == "time" then
96 _, _, info.pos, info.len = string.find(val, "(%d+):(%d+)")
97 if settings.important_time == -1
98 or info.pos+0 <= settings.important_time
99 and settings.important_time ~= 0 then
102 info.pos = string.format("%d:%02d", math.floor(info.pos / 60),
103 math.mod(info.pos, 60))
104 info.len = string.format("%d:%02d", math.floor(info.len / 60),
105 math.mod(info.len, 60))
106 elseif attrib == "state" then
108 if info.state ~= last_state then
111 last_state = info.state
112 elseif attrib == "volume" then
113 info.volume = val .. "%"
114 if info.volume ~= last_volume then
117 last_volume = info.volume
118 elseif attrib == "Artist" then
120 elseif attrib == "Title" then
122 elseif attrib == "Album" then
124 elseif attrib == "Track" then
126 elseif attrib == "Date" then
131 -- done querying; now build the string
132 statusd.inform("mpd_hint", hint)
133 if info.state == "play" then
134 local mpd_st = settings.template
136 mpd_st = string.gsub(mpd_st, "%%([%w%_]+)",
137 function (x) return(info[x] or "") end)
138 mpd_st = string.gsub(mpd_st, "%%%%", "%%")
139 statusd.inform("mpd", mpd_st)
140 elseif info.state == "pause" then
141 statusd.inform("mpd", "Paused")
143 statusd.inform("mpd", "No song playing")
146 -- Wait for bgread to signal that more data
147 -- is available or the program has exited
149 data = coroutine.yield()
152 -- Program has exited.
153 -- Start a timer for a new execution if set.
155 timer:set(settings.update_interval,
156 function() start_execution() end)
159 -- Set up a simple function for printing errors
161 local function process_error(data)
163 io.stderr:write("ion-statusd [statusd_mpd]: " .. data)
165 data = coroutine.yield()
169 -- Execute the program in the background and create
170 -- coroutine functions to handle input and error data
171 local sleep = settings.update_interval / 1000
172 sleep = string.gsub(sleep, '(%d+),(%d+)', '%1.%2')
173 local cmd = 'while sleep ' .. sleep .. '; do ' ..
174 'echo "status\ncurrentsong"; done | ' ..
175 settings.netcat .. ' ' .. settings.address .. ' ' ..
177 if settings.password ~= nil then
178 cmd = "echo 'password " .. settings.password .. "'; " .. cmd
180 statusd.popen_bgread(cmd, coroutine.wrap(process_input),
181 coroutine.wrap(process_error))
185 -- Now start execution of all defined processes
187 timer = statusd.create_timer()
190 -- vim: set et sts=4 sw=4 :