]> git.llucax.com Git - personal/ion3-config.git/blob - statusd_mpd.lua
e64bfe5c30cbe5eb191d57c9d72ee7c61f57eb95
[personal/ion3-config.git] / statusd_mpd.lua
1 -- statusd for MPD (Music Player Daemon) using a single persistent connection
2 --
3 -- bugs/requests/comments: Leandro Lucarella <llucax@gmail.com>
4 --
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).
7 --
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).
13 --
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.
18
19 local defaults={
20     -- netcat path
21     netcat = 'netcat',
22
23     -- 500 or less makes seconds increment relatively smoothly while playing
24     update_interval = 1000,
25
26     -- mpd server info (localhost:6600 are mpd defaults)
27     address = "localhost",
28     port = 6600,
29
30     -- mpd password (if any)
31     password = nil,
32
33     -- seconds to consider the mpd in 'important' hint (0 never, -1 always)
34     important_time = 10, -- seconds
35
36     -- display template
37     -- ---
38     -- can use the following:
39     --   track metadata: %artist, %title, %num, %album, %year, %len
40     --   current track position: %pos
41     --   volume: %volume
42     --   escape for the percent character: %%
43
44     -- a default template
45     template = "%artist - %num - %title (%pos / %len)"
46 }
47
48 local settings = table.join(statusd.get_config("mpd"), defaults)
49
50 local timer
51 local last_volume
52 local last_state
53
54 -- Function for starting and restarting execution of a process
55
56 local function start_execution()
57
58     -- Set up a function for receiving the data
59     -- and informing the statusbar
60
61     local function process_input(data)
62
63         -- At first, we are not initialized
64         local inited = false
65
66         -- When we get new data, output the last complete line
67
68         while data do
69
70             if not inited then
71                 -- welcome msg
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")
75                 end
76
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")
82                     end
83                 end
84
85                 -- everything ok, receive more data
86                 inited = true
87                 data = coroutine.yield()
88             end
89
90             local info = {}
91             local hint = "normal"
92
93             for attrib, val in string.gfind(data, "([^\n]-): ([^\n]*)") do
94
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
100                         hint = "important"
101                     end
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
107                     info.state = val
108                     if info.state ~= last_state then
109                         hint = "important"
110                     end
111                     last_state = info.state
112                 elseif attrib == "volume" then
113                     info.volume = val .. "%"
114                     if info.volume ~= last_volume then
115                         hint = "important"
116                     end
117                     last_volume = info.volume
118                 elseif attrib == "Artist" then
119                     info.artist = val
120                 elseif attrib == "Title" then
121                     info.title = val
122                 elseif attrib == "Album" then
123                     info.album = val
124                 elseif attrib == "Track" then
125                     info.num = val
126                 elseif attrib == "Date" then
127                     info.year = val
128                 end
129             end
130
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
135                 -- fill in %values
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")
142             else
143                 statusd.inform("mpd", "No song playing")
144             end
145
146             -- Wait for bgread to signal that more data
147             -- is available or the program has exited
148
149             data = coroutine.yield()
150         end
151
152         -- Program has exited.
153         -- Start a timer for a new execution if set.
154
155         timer:set(settings.update_interval,
156                   function() start_execution() end)
157     end
158
159     -- Set up a simple function for printing errors
160
161     local function process_error(data)
162         while data do
163             io.stderr:write("ion-statusd [statusd_mpd]: " .. data)
164             io.stderr:flush()
165             data = coroutine.yield()
166         end
167     end
168
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 .. ' ' ..
176                 settings.port
177     if settings.password ~= nil then
178         cmd = "echo 'password " .. settings.password .. "'; " .. cmd
179     end
180     statusd.popen_bgread(cmd, coroutine.wrap(process_input),
181                               coroutine.wrap(process_error))
182
183 end
184
185 -- Now start execution of all defined processes
186
187 timer = statusd.create_timer()
188 start_execution()
189
190 -- vim: set et sts=4 sw=4 :