From 440d1d34387a5a4c92eaed6d1aa6e222fee78f33 Mon Sep 17 00:00:00 2001
From: Andrei 'Garoth' Thorp <garoth@gmail.com>
Date: Fri, 10 Jul 2009 23:48:03 -0400
Subject: [PATCH 4/4] basic_mpd: Imported

 * The format can be a string or function.
   * If string, that's used to format the output, respecting length
   * If function, that's just passed the song info to handle itself
 * Length is handled intelligently by shortening fields in the format
   string so that the longest gets shortened most
 * Unknown string for unknown metadata is available
 * Readme + documentation is done
 * Functions all documented
 * Uses Obvious timers
 * Imported kAworu's MPD library backend
---
 basic_mpd/init.lua |  151 +++++++++++++++++++++++++++++++++++++++++++++++++
 basic_mpd/readme   |   68 ++++++++++++++++++++++
 init.lua           |    1 +
 lib/init.lua       |    1 +
 lib/mpd/init.lua   |  160 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 381 insertions(+), 0 deletions(-)
 create mode 100644 basic_mpd/init.lua
 create mode 100644 basic_mpd/readme
 create mode 100644 lib/mpd/init.lua

diff --git a/basic_mpd/init.lua b/basic_mpd/init.lua
new file mode 100644
index 0000000..6df6727
--- /dev/null
+++ b/basic_mpd/init.lua
@@ -0,0 +1,151 @@
+------------------------------------------
+-- Author: Andrei "Garoth" Thorp        --
+-- Copyright 2009 Andrei "Garoth" Thorp --
+------------------------------------------
+
+local setmetatable = setmetatable
+local pairs = pairs
+local type = type
+local widget = widget
+local string = string
+local awful = require("awful")
+local naughty = require("naughty")
+local lib = {
+        mpd = require("obvious.lib.mpd"),
+        util = require("obvious.lib.util"),
+        hooks = require("obvious.lib.hooks"),
+}
+
+module("obvious.basic_mpd")
+
+local defaults = {
+        format = "$title - $album - $artist",
+        length = 75,
+        unknown = "(unknown)",
+}
+local settings = {}
+for key, value in pairs(defaults) do
+        settings[key] = value
+end
+
+local widget = widget({ type = "textbox",
+                        name = "mpd-playing",
+                        align = "left" })
+
+-- Utility function to handle the text for MPD
+-- @param songinfo: a table with fields "artist", "album", "title" in text
+-- @return formatted (settings.format) string to display on the widget. This
+-- respects settings.length and tries to make fields as close to the same
+-- lenghths as possible if shortening is required.
+local function format_metadata(songinfo)
+        format = settings.format or defaults.format
+
+        if (settings.length or defaults.length) <= 0 then
+                return ""
+        end
+
+        used_keys = {}
+        start = 1
+        stop = 1
+        while start do
+                start, stop = string.find(format, "%$%w+", stop)
+                key = string.match(format, "%$(%w+)", stop)
+                if key then
+                        if songinfo[key] then
+                                used_keys[key] = songinfo[key]
+                        else
+                                used_keys[key] = settings.unknown or
+                                                 defaults.unknown
+                        end
+                end
+        end
+
+        retval = ""
+        while true do
+                retval = string.gsub(format, "%$(%w+)", used_keys)
+                if #retval > (settings.length or defaults.length) then
+                        longest_key = nil
+                        longest_value = ""
+                        for key, value in pairs(used_keys) do
+                                if #value > #longest_value then
+                                        longest_key = key
+                                        longest_value = value
+                                end
+                        end
+                        if longest_key then
+                                -- shorten the longest by 1
+                                used_keys[longest_key] = string.sub( used_keys[longest_key],
+                                                   1,
+                                                   #longest_value - 1)
+                        else
+                                -- Seems like the format itself's too long
+                                err = "obvious.basic_mpd: impossible to fit " ..
+                                       "output into " .. (settings.length or
+                                       defaults.length) .. " characters.\n" ..
+                                       "Widget paused."
+                                naughty.notify({ text = err, timeout = 0 })
+                                lib.hooks.timer.stop(update)
+                                return ""
+                        end
+                else
+                        -- All good!
+                        break
+                end
+        end
+        return awful.util.escape(retval)
+end
+
+-- Updates the widget's display
+function update()
+        local status = lib.mpd.send("status")
+        local now_playing, songstats
+
+        if not status.state then
+                now_playing = "Music Off"
+                now_playing = lib.util.colour("yellow", now_playing)
+        elseif status.state == "stop" then
+                now_playing = "Music Stopped"
+        else
+                songstats = lib.mpd.send("playlistid " .. status.songid)
+                format = settings.format or defaults.format
+                if type(format) == "string" then
+                        now_playing = format_metadata(songstats)
+                elseif type(format) == "function" then
+                        now_playing = format(songstats)
+                else
+                        naughty.notify({ text = "obvious.basic_mpd: Invalid " ..
+                                                "display format. Widget " ..
+                                                "paused." })
+                        lib.hooks.timer.stop(update)
+                end
+        end
+
+        widget.text = now_playing
+end
+update()
+lib.hooks.timer.register(1, 30, update, "basic_mpd widget refresh rate")
+
+-- SETTINGS
+-- Set the format string
+-- @param format The format string
+function set_format(format)
+        settings.format = format or defaults.format
+        update()
+end
+
+-- Set the widget's text max length
+-- @param format The max length (in characters) of the widget's text
+function set_length(length)
+        settings.length = length or defaults.length
+        update()
+end
+
+-- Set the string to use for unknown metadata
+-- @param format The string to use for unknown metadata
+function set_unknown(unknown)
+        settings.unknown = unknown or defaults.unknown
+        update()
+end
+
+setmetatable(_M, { __call = function () return widget end })
+-- vim: filetype=lua:expandtab:shiftwidth=8:tabstop=8:softtabstop=8:encoding=utf-8:textwidth=80
diff --git a/basic_mpd/readme b/basic_mpd/readme
new file mode 100644
index 0000000..20d2d29
--- /dev/null
+++ b/basic_mpd/readme
@@ -0,0 +1,68 @@
+Basic MPD
+=========
+
+This widget's purpose is to provide a simple interface to MPD, the Music Player
+Daemon. The widget will output track/album/artist information, formatted
+according to your wishes.
+
+Note, because Basic MPD runs on top of Obvious' MPD library, you do have access
+to features like pause/next/previous and everything else. However, this is done
+through the library directly rather than through Basic MPD.
+
+Settings available:
+-------------------
+ - set_format: Takes a string/function to use to format the Basic MPD display
+   output. If a function is given, then a table with keys "artist", "album", and
+   "title" will be given to this function, and whatever string it returns will
+   be displayed. If a string is given to set_format, then it will be displayed
+   with substitutions done on it. The format understands "$artist", "$album" and
+   "$title" to do the replacement. For example, you could have a format string
+   like: "$artist//$album//$title". The slashes are literal slashes, and the
+   special variables will be replaced. If you're using a format string, the
+   length is respected. If you're using a function, handling length is up to
+   you. See set_length documentation about how it handles length.
+ - set_length: Setting length only works for when using a format string for
+   set_format (this is the default if you never call set_format). It would be
+   somewhat silly to have the length just cut off the end of the string. This
+   could land you in a situation where you know the title and album but not the
+   artist, for example. Instead, what happens is that the formatter tries to
+   shorten the longest components of format first and even out the lengths of
+   the fields. So for example, imagine you have the output "Matisse The Cat -
+   Jesse Cook - Frontiers" in your widget, but want this to be cut down to 32
+   characters max. The system would reformat this as "Matisse T - Jesse Coo -
+   Frontiers".
+ - set_unknown: If some metadata is not available, this is the string that
+   should be displayed in that metadata's place. So for example, if you don't
+   know the album, it might be "Matisse The Cat - Jesse Cook - (Unknown)".
+
+To set one of these settings, simply do something like:
+    obvious.basic_mpd.set_format("MPD: $title")
+
+Implementation:
+---------------
+To use it, include it into your rc.lua by inserting this line (near the top
+preferably):
+    require("obvious.basic_mpd")
+If you're going to be using the Obvious MPD library as well, also do:
+    require("obvious.lib.mpd")
+
+Then add it to your wibox's widgets list:
+    obvious.basic_mpd()
+
+It's also possible to create binds to control MPD via Obvious' MPD library:
+    -- p key to pause/play.
+    awful.key({ modkey }, "p", obvious.lib.mpd.toggle_play),
+    -- Plus and minus to increase and decrease volume
+    awful.key({ modkey, "Shift" }, "=", function ()
+                                            obvious.lib.mpd.volume_up(5)
+                                        end),
+    awful.key({ modkey }, "-", function () obvious.lib.mpd.volume_down(5) end),
+    -- > and < to go to next and previous song
+    awful.key({ modkey, "Shift" }, ",", function ()
+                                            obvious.lib.mpd.previous()
+                                            obvious.basic_mpd.update()
+                                        end),
+    awful.key({ modkey, "Shift" }, ".", function ()
+                                            obvious.lib.mpd.next()
+                                            obvious.basic_mpd.update()
+                                        end),
diff --git a/init.lua b/init.lua
index 94c2c08..c5baa11 100644
--- a/init.lua
+++ b/init.lua
@@ -9,5 +9,6 @@ require("obvious.battery")
 require("obvious.volume_alsa")
 require("obvious.wlan")
 require("obvious.popup_run_prompt")
+require("obvious.basic_mpd")
 
 module("obvious")
diff --git a/lib/init.lua b/lib/init.lua
index fb9b70b..1a67666 100644
--- a/lib/init.lua
+++ b/lib/init.lua
@@ -5,6 +5,7 @@
 
 require("obvious.lib.hooks")
 require("obvious.lib.util")
+require("obvious.lib.mpd")
 
 module("obvious.lib")
 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:encoding=utf-8:textwidth=80
diff --git a/lib/mpd/init.lua b/lib/mpd/init.lua
new file mode 100644
index 0000000..d1f0e35
--- /dev/null
+++ b/lib/mpd/init.lua
@@ -0,0 +1,160 @@
+-- Small interface to MusicPD
+-- based on a netcat version from Steve Jothen <sjothen at gmail dot com>
+-- (see http://github.com/otkrove/ion3-config/tree/master/mpd.lua)
+--
+-- Copyright (c) 2008-2009, Alexandre Perrin <kaworu@kaworu.ch>
+-- All rights reserved.
+--
+-- Redistribution and use in source and binary forms, with or without
+-- modification, are permitted provided that the following conditions
+-- are met:
+--
+-- 1. Redistributions of source code must retain the above copyright
+--    notice, this list of conditions and the following disclaimer.
+-- 2. Redistributions in binary form must reproduce the above copyright
+--    notice, this list of conditions and the following disclaimer in the
+--    documentation and/or other materials provided with the distribution.
+-- 4. Neither the name of the author nor the names of its contributors
+--    may be used to endorse or promote products derived from this software
+--    without specific prior written permission.
+--
+-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+-- ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+-- SUCH DAMAGE.
+
+require("socket")
+
+-- Grab env
+local socket = socket
+local string = string
+local tonumber = tonumber
+
+-- Music Player Daemon Lua library.
+module("obvious.lib.mpd")
+
+-- default settings values
+settings =
+{
+  hostname = "localhost",
+  port = 6600,
+  password = nil,
+}
+
+-- our socket
+local sock = nil;
+
+-- override default settings values
+function setup(hostname, port, password)
+  settings.hostname = hostname
+  settings.port = port
+  settings.password = password
+  -- Unset this so that next operation knows to
+  -- get a different server
+  sock = nil
+end
+
+
+-- calls the action and returns the server's response.
+--      Example: if the server's response to "status" action is:
+--              volume: 20
+--              repeat: 0
+--              random: 0
+--              playlist: 599
+--              ...
+--      then the returned table is:
+--      { volume = 20, repeat = 0, random = 0, playlist = 599, ... }
+function send(action)
+  local command = string.format("%s\n", action)
+  local values = {}
+
+  -- connect to MPD server if not already done.
+  if not sock then
+    sock = socket.connect(settings.hostname, settings.port)
+    if sock and settings.password then
+      send(string.format("password %s", settings.password))
+    end
+  end
+
+  if sock then
+    sock:send(command)
+    local line = sock:receive("*l")
+
+    if not line then -- closed (mpd killed?): reset socket and retry
+      sock = nil
+      return send(action)
+    end
+
+    while not (line:match("^OK$") or line:match(string.format("unknow command \"%s\"", action))) do
+      local _, _, key, value = string.find(line, "(.+):%s(.+)")
+      if key then
+        values[string.lower(key)] = value
+      end
+      line = sock:receive("*l")
+    end
+  end
+
+  return values
+end
+
+function next()
+  send("next")
+end
+
+function previous()
+  send("previous")
+end
+
+function pause()
+  send("pause")
+end
+
+function stop()
+  send("stop")
+end
+
+-- no need to check the new value, mpd will set the volume in [0,100]
+function volume_up(delta)
+  local stats = send("status")
+  local new_volume = tonumber(stats.volume) + delta
+  send(string.format("setvol %d", new_volume))
+end
+
+function volume_down(delta)
+  volume_up(-delta)
+end
+
+function toggle_random()
+  local stats = send("status")
+  if tonumber(stats.random) == 0 then
+    send("random 1")
+  else
+    send("random 0")
+  end
+end
+
+function toggle_repeat()
+  local stats = send("status")
+  if tonumber(stats["repeat"]) == 0 then
+    send("repeat 1")
+  else
+    send("repeat 0")
+  end
+end
+
+function toggle_play()
+  if send("status").state == "stop" then
+    send("play")
+  else
+    send("pause")
+  end
+end
+
+-- vim:filetype=lua:tabstop=8:shiftwidth=2:fdm=marker:
-- 
1.6.3.3

