]> git.llucax.com Git - personal/ion3-config.git/blob - statusd_mpd.lua
Rewrite the MPD status monitor to use a persistent connection.
[personal/ion3-config.git] / statusd_mpd.lua
1 -- statusd for MPD (Music Player Daemon)
2 -- bugs/requests/comments: delirium@hackish.org
3
4 -- requires that netcat is available in the path
5
6 local defaults={
7     -- netcat path
8     netcat = 'netcat',
9
10     -- 500 or less makes seconds increment relatively smoothly while playing
11     update_interval = 1000,
12
13     -- mpd server info (localhost:6600 are mpd defaults)
14     address = "localhost",
15     port = 6600,
16
17     -- mpd password (if any)
18     password = nil,
19
20     -- seconds to consider the mpd in 'important' hint (0 never, -1 always)
21     important_time = 10, -- seconds
22
23     -- display template
24     -- ---
25     -- can use the following:
26     --   track metadata: %artist, %title, %num, %album, %year, %len
27     --   current track position: %pos
28     --   volume: %volume
29     --   escape for the percent character: %%
30
31     -- a default template
32     template = "%artist - %num - %title (%pos / %len)"
33 }
34
35 local settings = table.join(statusd.get_config("mpd"), defaults)
36
37 local timer
38 local last_volume
39 local last_state
40
41 -- Function for starting and restarting execution of a process
42
43 local function start_execution()
44
45     -- Set up a function for receiving the data
46     -- and informing the statusbar
47
48     local function process_input(data)
49
50         -- At first, we are not initialized
51         local inited = false
52
53         -- When we get new data, output the last complete line
54
55         while data do
56
57             if not inited then
58                 -- welcome msg
59                 if string.sub(data, 1, 6) ~= "OK MPD" then
60                     statusd.inform("mpd_hint", "critical")
61                     statusd.inform("mpd", "bad mpd response on init")
62                 end
63
64                 -- 'password' response (if necessary)
65                 if settings.password ~= nil then
66                     if string.sub(data, 1, 2) ~= "OK" then
67                         statusd.inform("mpd_hint", "critical")
68                         statusd.inform("mpd_hint", "bad mpd password")
69                     end
70                 end
71
72                 -- everything ok, receive more data
73                 inited = true
74                 data = coroutine.yield()
75             end
76
77             local info = {}
78             local hint = "normal"
79
80             for attrib, val in string.gfind(data, "([^\n]-): ([^\n]*)") do
81
82                 if attrib == "time" then
83                     _, _, info.pos, info.len = string.find(val, "(%d+):(%d+)")
84                     if settings.important_time == -1
85                                     or info.pos+0 <= settings.important_time
86                                         and settings.important_time ~= 0 then
87                         hint = "important"
88                     end
89                     info.pos = string.format("%d:%02d", math.floor(info.pos / 60),
90                             math.mod(info.pos, 60))
91                     info.len = string.format("%d:%02d", math.floor(info.len / 60),
92                             math.mod(info.len, 60))
93                 elseif attrib == "state" then
94                     info.state = val
95                     if info.state ~= last_state then
96                         hint = "important"
97                     end
98                     last_state = info.state
99                 elseif attrib == "volume" then
100                     info.volume = val .. "%"
101                     if info.volume ~= last_volume then
102                         hint = "important"
103                     end
104                     last_volume = info.volume
105                 elseif attrib == "Artist" then
106                     info.artist = val
107                 elseif attrib == "Title" then
108                     info.title = val
109                 elseif attrib == "Album" then
110                     info.album = val
111                 elseif attrib == "Track" then
112                     info.num = val
113                 elseif attrib == "Date" then
114                     info.year = val
115                 end
116             end
117
118             -- done querying; now build the string
119             statusd.inform("mpd_hint", hint)
120             if info.state == "play" then
121                 local mpd_st = settings.template
122                 -- fill in %values
123                 mpd_st = string.gsub(mpd_st, "%%([%w%_]+)",
124                         function (x) return(info[x]  or "") end)
125                 mpd_st = string.gsub(mpd_st, "%%%%", "%%")
126                 statusd.inform("mpd", mpd_st)
127             elseif info.state == "pause" then
128                 statusd.inform("mpd", "Paused")
129             else
130                 statusd.inform("mpd", "No song playing")
131             end
132
133             -- Wait for bgread to signal that more data
134             -- is available or the program has exited
135
136             data = coroutine.yield()
137         end
138
139         -- Program has exited.
140         -- Start a timer for a new execution if set.
141
142         timer:set(settings.update_interval,
143                   function() start_execution() end)
144     end
145
146     -- Set up a simple function for printing errors
147
148     local function process_error(data)
149         while data do
150             io.stderr:write("ion-statusd [statusd_mpd]: " .. data)
151             io.stderr:flush()
152             data = coroutine.yield()
153         end
154     end
155
156     -- Execute the program in the background and create
157     -- coroutine functions to handle input and error data
158     local sleep = settings.update_interval / 1000
159     sleep = string.gsub(sleep, '(%d+),(%d+)', '%1.%2')
160     local cmd = 'while sleep ' .. sleep .. '; do ' ..
161                 'echo "status\ncurrentsong"; done | ' ..
162                 settings.netcat .. ' ' .. settings.address .. ' ' ..
163                 settings.port
164     if settings.password ~= nil then
165         cmd = "echo 'password " .. settings.password .. "'; " .. cmd
166     end
167     statusd.popen_bgread(cmd, coroutine.wrap(process_input),
168                               coroutine.wrap(process_error))
169
170 end
171
172 -- Now start execution of all defined processes
173
174 timer = statusd.create_timer()
175 start_execution()
176
177 -- vim: set et sts=4 sw=4 :