From: Samir Benmendil Date: Sun, 2 Aug 2020 19:41:17 +0000 (+0100) Subject: awesome: add mpc lua lib to communicate with mpd X-Git-Url: https://git.rmz.io/dotfiles.git/commitdiff_plain/8144bf204ceb16f19e1d5a2943acd940e80e3704 awesome: add mpc lua lib to communicate with mpd --- diff --git a/awesome/widgets/mpc.lua b/awesome/widgets/mpc.lua new file mode 100644 index 0000000..c8967c7 --- /dev/null +++ b/awesome/widgets/mpc.lua @@ -0,0 +1,228 @@ +-- Originally from https://awesomewm.org/recipes/mpc/ + +local lgi = require "lgi" +local GLib = lgi.GLib +local Gio = lgi.Gio + +local mpc = {} + +local function parse_password(host) + -- This function is based on mpd_parse_host_password() from libmpdclient + local position = string.find(host, "@") + if not position then + return host + end + return string.sub(host, position + 1), string.sub(host, 1, position - 1) +end + +function mpc.new(host, port, password, error_handler, ...) + host = host or os.getenv("MPD_HOST") or "localhost" + port = port or os.getenv("MPD_PORT") or 6600 + if not password then + host, password = parse_password(host) + end + local self = setmetatable({ + _host = host, + _port = port, + _password = password, + _error_handler = error_handler or function() end, + _connected = false, + _try_reconnect = false, + _idle_commands = { ... } + }, { __index = mpc }) + self:_connect() + return self +end + +function mpc:_error(err) + self._connected = false + self._error_handler(err) + self._try_reconnect = not self._try_reconnect + if self._try_reconnect then + self:_connect() + end +end + +function mpc:_connect() + if self._connected then return end + -- Reset all of our state + self._reply_handlers = {} + self._pending_reply = {} + self._idle_commands_pending = false + self._idle = false + self._connected = true + + -- Set up a new connection + local address + if string.sub(self._host, 1, 1) == "/" then + -- It's a unix socket + address = Gio.UnixSocketAddress.new(self._host) + else + -- Do a TCP connection + address = Gio.NetworkAddress.new(self._host, self._port) + end + local client = Gio.SocketClient() + local conn, err = client:connect(address) + + if not conn then + self:_error(err) + return false + end + + local input, output = conn:get_input_stream(), conn:get_output_stream() + self._conn, self._output, self._input = conn, output, Gio.DataInputStream.new(input) + + -- Read the welcome message + self._input:read_line() + + if self._password and self._password ~= "" then + self:_send("password " .. self._password) + end + + -- Set up the reading loop. This will asynchronously read lines by + -- calling itself. + local do_read + do_read = function() + self._input:read_line_async(GLib.PRIORITY_DEFAULT, nil, function(obj, res) + local line, err = obj:read_line_finish(res) + -- Ugly API. On success we get string, length-of-string + -- and on error we get nil, error. Other versions of lgi + -- behave differently. + if line == nil or tostring(line) == "" then + err = "Connection closed" + end + if type(err) ~= "number" then + self._output, self._input = nil, nil + self:_error(err) + else + do_read() + line = tostring(line) + if line == "OK" or line:match("^ACK ") then + local success = line == "OK" + local arg + if success then + arg = self._pending_reply + else + arg = { line } + end + local handler = self._reply_handlers[1] + table.remove(self._reply_handlers, 1) + self._pending_reply = {} + handler(success, arg) + else + local _, _, key, value = string.find(line, "([^:]+):%s(.+)") + if key then + self._pending_reply[string.lower(key)] = value + end + end + end + end) + end + do_read() + + -- To synchronize the state on startup, send the idle commands now. As a + -- side effect, this will enable idle state. + self:_send_idle_commands(true) + + return self +end + +function mpc:_send_idle_commands(skip_stop_idle) + -- We use a ping to unset this to make sure we never get into a busy + -- loop sending idle / unidle commands. Next call to + -- _send_idle_commands() might be ignored! + if self._idle_commands_pending then + return + end + if not skip_stop_idle then + self:_stop_idle() + end + + self._idle_commands_pending = true + for i = 1, #self._idle_commands, 2 do + self:_send(self._idle_commands[i], self._idle_commands[i+1]) + end + self:_send("ping", function() + self._idle_commands_pending = false + end) + self:_start_idle() +end + +function mpc:_start_idle() + if self._idle then + error("Still idle?!") + end + self:_send("idle", function(success, reply) + if reply.changed then + -- idle mode was disabled by mpd + self:_send_idle_commands() + end + end) + self._idle = true +end + +function mpc:_stop_idle() + if not self._idle then + error("Not idle?!") + end + self._output:write("noidle\n") + self._idle = false +end + +function mpc:_send(command, callback) + if self._idle then + error("Still idle in send()?!") + end + self._output:write(command .. "\n") + table.insert(self._reply_handlers, callback or function() end) +end + +function mpc:send(...) + self:_connect() + if not self._connected then + return + end + local args = { ... } + if not self._idle then + error("Something is messed up, we should be idle here...") + end + self:_stop_idle() + for i = 1, #args, 2 do + self:_send(args[i], args[i+1]) + end + self:_start_idle() +end + +function mpc:toggle_play() + self:send("status", function(success, status) + if status.state == "stop" then + self:send("play") + else + self:send("pause") + end + end) +end + +--[[ + +-- Example on how to use this (standalone) + +local host, port, password = nil, nil, nil +local m = mpc.new(host, port, password, error, + "status", function(success, status) print("status is", status.state) end) + +GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, function() + -- Test command submission + m:send("status", function(_, s) print(s.state) end, + "currentsong", function(_, s) print(s.title) end) + m:send("status", function(_, s) print(s.state) end) + -- Force a reconnect + GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, function() + m._conn:close() + end) +end) + +GLib.MainLoop():run() +--]] + +return mpc