1 -- Originally from https://awesomewm.org/recipes/mpc/
3 local lgi = require "lgi"
9 local function parse_password(host)
10 -- This function is based on mpd_parse_host_password() from libmpdclient
11 local position = string.find(host, "@")
15 return string.sub(host, position + 1), string.sub(host, 1, position - 1)
18 function mpc.new(host, port, password, error_handler, ...)
19 host = host or os.getenv("MPD_HOST") or "localhost"
20 port = port or os.getenv("MPD_PORT") or 6600
22 host, password = parse_password(host)
24 local self = setmetatable({
28 _error_handler = error_handler or function() end,
30 _try_reconnect = false,
31 _idle_commands = { ... }
37 function mpc:_error(err)
38 self._connected = false
39 self._error_handler(err)
40 self._try_reconnect = not self._try_reconnect
41 if self._try_reconnect then
46 function mpc:_connect()
47 if self._connected then return end
48 -- Reset all of our state
49 self._reply_handlers = {}
50 self._pending_reply = {}
51 self._idle_commands_pending = false
53 self._connected = true
55 -- Set up a new connection
57 if string.sub(self._host, 1, 1) == "/" then
59 address = Gio.UnixSocketAddress.new(self._host)
61 -- Do a TCP connection
62 address = Gio.NetworkAddress.new(self._host, self._port)
64 local client = Gio.SocketClient()
65 local conn, err = client:connect(address)
72 local input, output = conn:get_input_stream(), conn:get_output_stream()
73 self._conn, self._output, self._input = conn, output, Gio.DataInputStream.new(input)
75 -- Read the welcome message
76 self._input:read_line()
78 if self._password and self._password ~= "" then
79 self:_send("password " .. self._password)
82 -- Set up the reading loop. This will asynchronously read lines by
86 self._input:read_line_async(GLib.PRIORITY_DEFAULT, nil, function(obj, res)
87 local line, err = obj:read_line_finish(res)
88 -- Ugly API. On success we get string, length-of-string
89 -- and on error we get nil, error. Other versions of lgi
90 -- behave differently.
91 if line == nil or tostring(line) == "" then
92 err = "Connection closed"
94 if type(err) ~= "number" then
95 self._output, self._input = nil, nil
100 if line == "OK" or line:match("^ACK ") then
101 local success = line == "OK"
104 arg = self._pending_reply
108 local handler = self._reply_handlers[1]
109 table.remove(self._reply_handlers, 1)
110 self._pending_reply = {}
111 handler(success, arg)
113 local _, _, key, value = string.find(line, "([^:]+):%s(.+)")
115 self._pending_reply[string.lower(key)] = value
123 -- To synchronize the state on startup, send the idle commands now. As a
124 -- side effect, this will enable idle state.
125 self:_send_idle_commands(true)
130 function mpc:_send_idle_commands(skip_stop_idle)
131 -- We use a ping to unset this to make sure we never get into a busy
132 -- loop sending idle / unidle commands. Next call to
133 -- _send_idle_commands() might be ignored!
134 if self._idle_commands_pending then
137 if not skip_stop_idle then
141 self._idle_commands_pending = true
142 for i = 1, #self._idle_commands, 2 do
143 self:_send(self._idle_commands[i], self._idle_commands[i+1])
145 self:_send("ping", function()
146 self._idle_commands_pending = false
151 function mpc:_start_idle()
153 error("Still idle?!")
155 self:_send("idle", function(success, reply)
156 if reply.changed then
157 -- idle mode was disabled by mpd
158 self:_send_idle_commands()
164 function mpc:_stop_idle()
165 if not self._idle then
168 self._output:write("noidle\n")
172 function mpc:_send(command, callback)
174 error("Still idle in send()?!")
176 self._output:write(command .. "\n")
177 table.insert(self._reply_handlers, callback or function() end)
180 function mpc:send(...)
182 if not self._connected then
186 if not self._idle then
187 error("Something is messed up, we should be idle here...")
190 for i = 1, #args, 2 do
191 self:_send(args[i], args[i+1])
196 function mpc:toggle_play()
197 self:send("status", function(success, status)
198 if status.state == "stop" then
208 -- Example on how to use this (standalone)
210 local host, port, password = nil, nil, nil
211 local m = mpc.new(host, port, password, error,
212 "status", function(success, status) print("status is", status.state) end)
214 GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, function()
215 -- Test command submission
216 m:send("status", function(_, s) print(s.state) end,
217 "currentsong", function(_, s) print(s.title) end)
218 m:send("status", function(_, s) print(s.state) end)
220 GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, function()
225 GLib.MainLoop():run()