-- statusd for MPD (Music Player Daemon) using a single persistent connection -- -- bugs/requests/comments: Leandro Lucarella -- -- Based on statusd_exec.lua from Antti Vähäkotamäki (the async code) and -- statusd_mpd.lua from delirium@hackish.org (the mpd glue code). -- -- Usage is self documented in the default settings section. It provides the -- same functionality from statusd_mpd.luam from delirium@hackish.org but it -- uses a single persistent connection to MPD and does better usage from hints -- (the status is "hinted important" when mpd status or volume change (and -- optionally, up to important_time seconds from the begining of each song). -- -- This is free software; you can redistribute it and/or modify it under -- the terms of the GNU Lesser General Public License as published by -- the Free Software Foundation; either version 2.1 of the License, or -- (at your option) any later version. local defaults={ -- netcat path netcat = 'netcat', -- 500 or less makes seconds increment relatively smoothly while playing update_interval = 1000, -- mpd server info (localhost:6600 are mpd defaults) address = "localhost", port = 6600, -- mpd password (if any) password = nil, -- seconds to consider the mpd in 'important' hint (0 never, -1 always) important_time = 10, -- seconds -- display template -- --- -- can use the following: -- track metadata: %artist, %title, %num, %album, %year, %len -- current track position: %pos -- volume: %volume -- escape for the percent character: %% -- a default template template = "%artist - %num - %title (%pos / %len)" } local settings = table.join(statusd.get_config("mpd"), defaults) local timer local last_volume local last_state -- Function for starting and restarting execution of a process local function start_execution() -- Set up a function for receiving the data -- and informing the statusbar local function process_input(data) -- At first, we are not initialized local inited = false -- When we get new data, output the last complete line while data do if not inited then -- welcome msg if string.sub(data, 1, 6) ~= "OK MPD" then statusd.inform("mpd_hint", "critical") statusd.inform("mpd", "bad mpd response on init") end -- 'password' response (if necessary) if settings.password ~= nil then if string.sub(data, 1, 2) ~= "OK" then statusd.inform("mpd_hint", "critical") statusd.inform("mpd_hint", "bad mpd password") end end -- everything ok, receive more data inited = true data = coroutine.yield() end local info = {} local hint = "normal" for attrib, val in string.gfind(data, "([^\n]-): ([^\n]*)") do if attrib == "time" then _, _, info.pos, info.len = string.find(val, "(%d+):(%d+)") if settings.important_time == -1 or info.pos+0 <= settings.important_time and settings.important_time ~= 0 then hint = "important" end info.pos = string.format("%d:%02d", math.floor(info.pos / 60), math.mod(info.pos, 60)) info.len = string.format("%d:%02d", math.floor(info.len / 60), math.mod(info.len, 60)) elseif attrib == "state" then info.state = val if info.state ~= last_state then hint = "important" end last_state = info.state elseif attrib == "volume" then info.volume = val .. "%" if info.volume ~= last_volume then hint = "important" end last_volume = info.volume elseif attrib == "Artist" then info.artist = val elseif attrib == "Title" then info.title = val elseif attrib == "Album" then info.album = val elseif attrib == "Track" then info.num = val elseif attrib == "Date" then info.year = val end end -- done querying; now build the string statusd.inform("mpd_hint", hint) if info.state == "play" then local mpd_st = settings.template -- fill in %values mpd_st = string.gsub(mpd_st, "%%([%w%_]+)", function (x) return(info[x] or "") end) mpd_st = string.gsub(mpd_st, "%%%%", "%%") statusd.inform("mpd", mpd_st) elseif info.state == "pause" then statusd.inform("mpd", "Paused") else statusd.inform("mpd", "No song playing") end -- Wait for bgread to signal that more data -- is available or the program has exited data = coroutine.yield() end -- Program has exited. -- Start a timer for a new execution if set. timer:set(settings.update_interval, function() start_execution() end) end -- Set up a simple function for printing errors local function process_error(data) while data do io.stderr:write("ion-statusd [statusd_mpd]: " .. data) io.stderr:flush() data = coroutine.yield() end end -- Execute the program in the background and create -- coroutine functions to handle input and error data local sleep = settings.update_interval / 1000 sleep = string.gsub(sleep, '(%d+),(%d+)', '%1.%2') local cmd = 'while sleep ' .. sleep .. '; do ' .. 'echo "status\ncurrentsong"; done | ' .. settings.netcat .. ' ' .. settings.address .. ' ' .. settings.port if settings.password ~= nil then cmd = "echo 'password " .. settings.password .. "'; " .. cmd end statusd.popen_bgread(cmd, coroutine.wrap(process_input), coroutine.wrap(process_error)) end -- Now start execution of all defined processes timer = statusd.create_timer() start_execution() -- vim: set et sts=4 sw=4 :