]> code.delx.au - gnu-emacs-elpa/blobdiff - ampc.el
* ampc.el (ampc-volume-step): New variable.
[gnu-emacs-elpa] / ampc.el
diff --git a/ampc.el b/ampc.el
index f4ddff87c2a885aec67fb629cc4327b2bd10f2d7..bd1cac9029d78ee3dfc7afc100662979a439eee0 100644 (file)
--- a/ampc.el
+++ b/ampc.el
@@ -4,7 +4,7 @@
 
 ;; Author: Christopher Schmidt <christopher@ch.ristopher.com>
 ;; Maintainer: Christopher Schmidt <christopher@ch.ristopher.com>
-;; Version: 0.1.2
+;; Version: 0.1.3
 ;; Created: 2011-12-06
 ;; Keywords: ampc, mpc, mpd
 ;; Compatibility: GNU Emacs: 24.x
 ;; your load-path or add the directory the file is in to it, e.g.:
 ;;
 ;; (add-to-list 'load-path "~/.emacs.d/ampc")
-;;
-;; Then add one autoload definition:
-;;
 ;; (autoload 'ampc "ampc" nil t)
 ;;
-;; Optionally bind a key to this function, e.g.:
-;;
-;; (global-set-key (kbd "<f9>") 'ampc)
-;;
-;; or
-;;
-;; (global-set-key (kbd "<f9>") (lambda () (interactive) (ampc "host" "port")))
-;;
 ;; Byte-compile ampc (M-x byte-compile-file RET /path/to/ampc.el RET) to improve
 ;; its performance!
 
 ;; interactively, `ampc' reads host address and port from the minibuffer.  If
 ;; called non-interactively, the first argument to `ampc' is the host, the
 ;; second is the port.  Both values default to nil, which will make ampc connect
-;; to localhost:6600.  Once ampc is connected to the daemon, it creates its
-;; window configuration in the selected window.  To make ampc use the full frame
-;; rather than the selected window, customise `ampc-use-full-frame'.
+;; to localhost:6600.  If the optional third argument is non-nil and ampc is
+;; connected to the daemon, it creates its window configuration in the selected
+;; window.  To make ampc use the full frame rather than the selected window,
+;; customise `ampc-use-full-frame'.  To check whether ampc is connected to the
+;; daemon, call `ampc-is-on-p' and `ampc-suspended-p'.
 ;;
 ;; ampc offers three independent views which expose different parts of the user
 ;; interface.  The current playlist view, the default view at startup, may be
 ;;
 ;; To mark an entry, move the point to the entry and press `m' (ampc-mark).  To
 ;; unmark an entry, press `u' (ampc-unmark).  To unmark all entries, press `U'
-;; (ampc-unmark-all).  To toggle marks, press `t' (ampc-toggle-marks).  To
-;; navigate to the next entry, press `n' (ampc-next-line).  Analogous, pressing
-;; `p' (ampc-previous-line) moves the point to the previous entry.
+;; (ampc-unmark-all).  To toggle marks, press `t' (ampc-toggle-marks).  Pressing
+;; `<down-mouse-1>' with the mouse mouse cursor on a list entry will move point
+;; to the entry and toggle the mark.  To navigate to the next entry, press `n'
+;; (ampc-next-line).  Analogous, pressing `p' (ampc-previous-line) moves the
+;; point to the previous entry.
 ;;
 ;; Window two shows the current playlist.  The song that is currently played by
 ;; the daemon, if any, is highlighted.  To delete the selected songs from the
-;; playlist, press `d' (ampc-delete).  To move the selected songs up, press
-;; `<up>' (ampc-up).  Analogous, press `<down>' (ampc-down) to move the selected
-;; songs down.
+;; playlist, press `d' (ampc-delete).  Pressing `<down-mouse-3>' will move the
+;; point to the entry under cursor and delete it from the playlist.  To move the
+;; selected songs up, press `<up>' (ampc-up).  Analogous, press `<down>'
+;; (ampc-down) to move the selected songs down.  Pressing `<return>'
+;; (ampc-play-this) or `<down-mouse-2>' will play the song at point/cursor.
 ;;
 ;; Windows three to five are tag browsers.  You use them to narrow the song
 ;; database to certain songs.  Think of tag browsers as filters, analogous to
 ;; songs that is filtered is displayed in the header line of the window.
 ;;
 ;; Window six shows the songs that match the filters defined by windows three to
-;; five.  To add the selected song to the playlist, press `a' (ampc-add).  This
-;; key binding works in tag browsers as well.  Calling `ampc-add' in a tag
-;; browser adds all songs filtered up to the selected browser to the playlist.
+;; five.  To add the selected song to the playlist, press `a' (ampc-add).
+;; Pressing `<down-mouse-3>' will move the point to the entry under the cursor
+;; and execute `ampc-add'.  These key bindings works in tag browsers as well.
+;; Calling `ampc-add' in a tag browser adds all songs filtered up to the
+;; selected browser to the playlist.
 ;;
 ;; The tag browsers of the (default) current playlist view (accessed via `J')
 ;; are `Genre' (window 3), `Artist' (window 4) and `Album' (window 5).  The key
 ;; stored playlists is the only view in ampc that may have only one marked
 ;; entry.
 ;;
+;; To queue a playlist, press `l' (ampc-load) or `<down-mouse-2>'.  To delete a
+;; playlist, press `d' (ampc-delete-playlist) or `<down-mouse-3>'.  The command
+;; `ampc-rename-playlist', bound to `r', can be used to rename a playlist.
+;;
 ;; Again, the key `<' may be used to setup a playlist view with a different
 ;; order of tag browsers.
 
 ;;; *** outputs view
 ;; The outputs view contains a single list which shows the configured outputs of
-;; mpd.  To toggle the enabled property of the selected outputs, press `a'
-;; (ampc-toggle-output-enabled).
+;; MPD.  To toggle the enabled property of the selected outputs, press `a'
+;; (ampc-toggle-output-enabled) or `<mouse-3>'.
 
 ;;; *** global keys
 ;; Aside from `J', `M', `K', `<' and `L', which may be used to select different
 ;; views, ampc defines the following global keys, which may be used in every
 ;; window associated with ampc:
 ;;
-;; `k' (ampc-toggle-play): Toggle play state.  If mpd does not play a song
+;; `k' (ampc-toggle-play): Toggle play state.  If MPD does not play a song
 ;; already, start playing the song at point if the current buffer is the
 ;; playlist buffer, otherwise start at the beginning of the playlist.  With
 ;; prefix argument 4, stop player rather than pause if applicable.
 ;;
 ;; `y' (ampc-increase-volume): Increase volume.
 ;; `M-y' (ampc-decrease-volume): Decrease volume.
+;; `C-M-y' (ampc-set-volume): Set volume.
 ;; `h' (ampc-increase-crossfade): Increase crossfade.
 ;; `M-h' (ampc-decrease-crossfade): Decrease crossfade.
 ;;
 ;;
 ;; `P' (ampc-goto-current-song): Select the current playlist window and move
 ;; point to the current song.
+;; `G' (ampc-mini): Select song to play via `completing-read'.
 ;;
 ;; `T' (ampc-trigger-update): Trigger a database update.
 ;; `Z' (ampc-suspend): Suspend ampc.
 ;;      (substitute-ampc-key (kbd "z") (kbd "Z"))
 ;;      (substitute-ampc-key (kbd "y") (kbd "z"))
 ;;      (substitute-ampc-key (kbd "M-y") (kbd "M-z"))
+;;      (substitute-ampc-key (kbd "C-M-y") (kbd "C-M-z"))
 ;;      (substitute-ampc-key (kbd "<") (kbd ";"))))
 ;;
 ;; If ampc is suspended, you can still use every interactive command that does
 ;; not directly operate on or with the user interace of ampc.  For example it is
 ;; perfectly fine to call `ampc-increase-volume' or `ampc-toggle-play' via M-x
-;; RET.  To display the information that is displayed by the status window of
-;; ampc, call `ampc-status'.
+;; RET.  Especially the commands `ampc-status' and `ampc-mini' are predestinated
+;; to be bound in the global keymap.  `ampc-status' messages the information
+;; that is displayed by the status window of ampc.  `ampc-mini' lets you select
+;; a song to play via `completing-read'.
+;;
+;; (global-set-key (kbd "<f7>")
+;;                 (lambda ()
+;;                   (interactive)
+;;                   (unless (ampc-on-p)
+;;                     (ampc nil nil t))
+;;                   (ampc-status)))
+;; (global-set-key (kbd "<f8>")
+;;                 (lambda ()
+;;                   (interactive)
+;;                   (unless (ampc-on-p)
+;;                     (ampc nil nil t))
+;;                   (ampc-mini)))
 
 ;;; Code:
 ;;; * code
 (require 'avl-tree)
 
 ;;; ** declarations
-;;; *** variables
 (defgroup ampc ()
   "Asynchronous client for the Music Player Daemon."
   :prefix "ampc-"
   "If non-nil, truncate lines in ampc buffers."
   :type 'boolean)
 
+(defcustom ampc-default-server '("localhost" . 6600)
+  "The MPD server to connect to if the arguments to `ampc' are nil.
+This variable is a cons cell, with the car specifying the
+hostname and the cdr specifiying the port.  Both values can be
+nil, which will make ampc query the user for values on each
+invocation."
+  :type '(cons (choice :tag "Hostname"
+                       (string)
+                       (const :tag "Ask" nil))
+               (choice :tag "Port"
+                       (string)
+                       (integer)
+                       (const :tag "Ask" nil))))
+
+(defcustom ampc-synchronous-commands '(t status currentsong)
+  "List of MPD commands that should be executed synchronously.
+Executing commands that print lots of output synchronously will
+result in massive performance improvements of ampc.  If the car
+of this list is `t', execute all commands synchronously other
+than the ones specified by the rest of the list."
+  :type '(repeat symbol))
+
 (defcustom ampc-status-tags nil
   "List of additional tags of the current song that are added to
 the internal status of ampc and thus are passed to the functions
 in `ampc-status-changed-hook'.  Each element may be a string that
 specifies a tag that is returned by MPD's `currentsong'
-command.")
+command."
+  :type '(list symbol))
+
+(defcustom ampc-volume-step 5
+  "Default step of `ampc-increase-volume' and
+`ampc-decrease-volume' to change the volume."
+  :type 'integer)
 
 ;;; **** hooks
 (defcustom ampc-before-startup-hook nil
@@ -279,9 +325,9 @@ all the time!"
 
 ;;; *** internal variables
 (defvar ampc-views
-  (let* ((songs '(1.0 song :properties (("Track" :title "#")
-                                        ("Title" :offset 6)
-                                        ("Time" :offset 26))))
+  (let* ((songs '(1.0 song :properties (("Track" :title "#" :width 4)
+                                        ("Title" :min 15 :max 40)
+                                        ("Time" :width 6))))
          (rs_a `(1.0 vertical
                      (0.7 horizontal
                           (0.33 tag :tag "Genre" :id 1)
@@ -294,23 +340,23 @@ all the time!"
                           (0.33 tag :tag "Album" :id 2)
                           (1.0 tag :tag "Artist" :id 3))
                      ,songs))
-         (pl-prop '(("Title")
-                    ("Artist" :offset 20)
-                    ("Album" :offset 40)
-                    ("Time" :offset 60))))
+         (pl-prop '(:properties (("Title" :min 15 :max 40)
+                                 ("Artist" :min 15 :max 40)
+                                 ("Album" :min 15 :max 40)
+                                 ("Time" :width 6)))))
     `(("Current playlist view (Genre|Artist|Album)"
        ,(kbd "J")
        horizontal
        (0.4 vertical
             (6 status)
-            (1.0 current-playlist :properties ,pl-prop))
+            (1.0 current-playlist ,@pl-prop))
        ,rs_a)
       ("Current playlist view (Genre|Album|Artist)"
        ,(kbd "M")
        horizontal
        (0.4 vertical
             (6 status)
-            (1.0 current-playlist :properties ,pl-prop))
+            (1.0 current-playlist ,@pl-prop))
        ,rs_b)
       ("Playlist view (Genre|Artist|Album)"
        ,(kbd "K")
@@ -318,7 +364,7 @@ all the time!"
        (0.4 vertical
             (6 status)
             (1.0 vertical
-                 (0.8 playlist :properties ,pl-prop)
+                 (0.8 playlist ,@pl-prop)
                  (1.0 playlists)))
        ,rs_a)
       ("Playlist view (Genre|Album|Artist)"
@@ -327,19 +373,20 @@ all the time!"
        (0.4 vertical
             (6 status)
             (1.0 vertical
-                 (0.8 playlist :properties ,pl-prop)
+                 (0.8 playlist ,@pl-prop)
                  (1.0 playlists)))
        ,rs_b)
       ("Outputs view"
        ,(kbd "L")
-       outputs :properties (("outputname" :title "Name")
-                            ("outputenabled" :title "Enabled" :offset 10))))))
+       outputs :properties (("outputname" :title "Name" :min 10 :max 30)
+                            ("outputenabled" :title "Enabled" :width 9))))))
 
 (defvar ampc-connection nil)
 (defvar ampc-host nil)
 (defvar ampc-port nil)
 (defvar ampc-outstanding-commands nil)
 
+(defvar ampc-no-implicit-next-dispatch nil)
 (defvar ampc-working-timer nil)
 (defvar ampc-yield nil)
 
@@ -347,6 +394,9 @@ all the time!"
 (defvar ampc-buffers-unordered nil)
 (defvar ampc-all-buffers nil)
 
+(defvar ampc-tab-offsets nil)
+(make-variable-buffer-local 'ampc-tab-offsets)
+
 (defvar ampc-type nil)
 (make-variable-buffer-local 'ampc-type)
 (defvar ampc-dirty nil)
@@ -370,12 +420,14 @@ all the time!"
     (define-key map (kbd "D") 'ampc-delete-playlist)
     (define-key map (kbd "y") 'ampc-increase-volume)
     (define-key map (kbd "M-y") 'ampc-decrease-volume)
+    (define-key map (kbd "C-M-y") 'ampc-set-volume)
     (define-key map (kbd "h") 'ampc-increase-crossfade)
     (define-key map (kbd "M-h") 'ampc-decrease-crossfade)
     (define-key map (kbd "e") 'ampc-toggle-repeat)
     (define-key map (kbd "r") 'ampc-toggle-random)
     (define-key map (kbd "f") 'ampc-toggle-consume)
     (define-key map (kbd "P") 'ampc-goto-current-song)
+    (define-key map (kbd "G") 'ampc-mini)
     (define-key map (kbd "q") 'ampc-quit)
     (define-key map (kbd "z") 'ampc-suspend)
     (define-key map (kbd "T") 'ampc-trigger-update)
@@ -396,12 +448,18 @@ all the time!"
     (define-key map (kbd "p") 'ampc-previous-line)
     (define-key map [remap next-line] 'ampc-next-line)
     (define-key map [remap previous-line] 'ampc-previous-line)
+    (define-key map (kbd "<down-mouse-1>") 'ampc-mouse-toggle-mark)
+    (define-key map (kbd "<mouse-1>") 'ampc-mouse-align-point)
     map))
 
 (defvar ampc-current-playlist-mode-map
   (let ((map (make-sparse-keymap)))
     (suppress-keymap map)
     (define-key map (kbd "<return>") 'ampc-play-this)
+    (define-key map (kbd "<down-mouse-2>") 'ampc-mouse-play-this)
+    (define-key map (kbd "<mouse-2>") 'ampc-mouse-align-point)
+    (define-key map (kbd "<down-mouse-3>") 'ampc-mouse-delete)
+    (define-key map (kbd "<mouse-3>") 'ampc-mouse-align-point)
     map))
 
 (defvar ampc-playlist-mode-map
@@ -411,6 +469,8 @@ all the time!"
     (define-key map (kbd "d") 'ampc-delete)
     (define-key map (kbd "<up>") 'ampc-up)
     (define-key map (kbd "<down>") 'ampc-down)
+    (define-key map (kbd "<down-mouse-3>") 'ampc-mouse-delete)
+    (define-key map (kbd "<mouse-3>") 'ampc-mouse-align-point)
     map))
 
 (defvar ampc-playlists-mode-map
@@ -419,6 +479,10 @@ all the time!"
     (define-key map (kbd "l") 'ampc-load)
     (define-key map (kbd "r") 'ampc-rename-playlist)
     (define-key map (kbd "d") 'ampc-delete-playlist)
+    (define-key map (kbd "<down-mouse-2>") 'ampc-mouse-load)
+    (define-key map (kbd "<mouse-2>") 'ampc-mouse-align-point)
+    (define-key map (kbd "<down-mouse-3>") 'ampc-mouse-delete-playlist)
+    (define-key map (kbd "<mouse-3>") 'ampc-mouse-align-point)
     map))
 
 (defvar ampc-tag-song-mode-map
@@ -426,6 +490,8 @@ all the time!"
     (suppress-keymap map)
     (define-key map (kbd "t") 'ampc-toggle-marks)
     (define-key map (kbd "a") 'ampc-add)
+    (define-key map (kbd "<down-mouse-3>") 'ampc-mouse-add)
+    (define-key map (kbd "<mouse-3>") 'ampc-mouse-align-point)
     map))
 
 (defvar ampc-outputs-mode-map
@@ -433,6 +499,8 @@ all the time!"
     (suppress-keymap map)
     (define-key map (kbd "t") 'ampc-toggle-marks)
     (define-key map (kbd "a") 'ampc-toggle-output-enabled)
+    (define-key map (kbd "<down-mouse-3>") 'ampc-mouse-toggle-output-enabled)
+    (define-key map (kbd "<mouse-3>") 'ampc-mouse-align-point)
     map))
 
 ;;; **** menu
@@ -450,6 +518,11 @@ all the time!"
     ["Pause" ampc-toggle-play
      :visible (and ampc-status
                    (equal (cdr (assq 'state ampc-status)) "play"))]
+    ["Stop" (lambda () (interactive) (ampc-toggle-play 4))
+     :visible (and ampc-status
+                   (equal (cdr (assq 'state ampc-status)) "play"))]
+    ["Next" ampc-next]
+    ["Previous" ampc-previous]
     "--"
     ["Clear playlist" ampc-clear]
     ["Shuffle playlist" ampc-shuffle]
@@ -460,6 +533,7 @@ all the time!"
     "--"
     ["Increase volume" ampc-increase-volume]
     ["Decrease volume" ampc-decrease-volume]
+    ["Set volume" ampc-set-volume]
     ["Increase crossfade" ampc-increase-crossfade]
     ["Decrease crossfade" ampc-decrease-crossfade]
     ["Toggle repeat" ampc-toggle-repeat
@@ -492,6 +566,31 @@ all the time!"
     ["Toggle marks" ampc-toggle-marks
      :visible (not (eq (car ampc-type) 'playlists))]))
 
+(defvar ampc-tool-bar-map
+  (let ((map (make-sparse-keymap)))
+    (tool-bar-local-item
+     "mpc/prev" 'ampc-previous 'previous map
+     :help "Previous")
+    (tool-bar-local-item
+     "mpc/play" 'ampc-toggle-play 'play map
+     :help "Play"
+     :visible '(and ampc-status
+                    (not (equal (cdr (assq 'state ampc-status)) "play"))))
+    (tool-bar-local-item
+     "mpc/pause" 'ampc-toggle-play 'pause map
+     :help "Pause"
+     :visible '(and ampc-status
+                    (equal (cdr (assq 'state ampc-status)) "play")))
+    (tool-bar-local-item
+     "mpc/stop" (lambda () (interactive) (ampc-toggle-play 4)) 'stop map
+     :help "Stop"
+     :visible '(and ampc-status
+                    (equal (cdr (assq 'state ampc-status)) "play")))
+    (tool-bar-local-item
+     "mpc/next" 'ampc-next 'next map
+     :help "Next")
+    map))
+
 ;;; ** code
 ;;; *** macros
 (defmacro ampc-with-buffer (type &rest body)
@@ -499,13 +598,12 @@ all the time!"
   `(let* ((type- ,type)
           (b (loop for b in ampc-buffers
                    when (with-current-buffer b
-                          (cond ((windowp type-)
-                                 (eq (window-buffer type-)
-                                     (current-buffer)))
-                                ((symbolp type-)
-                                 (eq (car ampc-type) type-))
-                                (t
-                                 (equal ampc-type type-))))
+                          (etypecase type-
+                            (window
+                             (eq (window-buffer type-)
+                                 (current-buffer)))
+                            (symbol
+                             (eq (car ampc-type) type-))))
                    return b
                    end)))
      (when b
@@ -535,7 +633,11 @@ all the time!"
                when (get-text-property (point) 'updated)
                do (delete-region (point) (1+ (line-end-position)))
                else
-               do (forward-line nil)
+               do (add-text-properties
+                   (+ (point) 2)
+                   (progn (forward-line nil)
+                          (1- (point)))
+                   '(mouse-face highlight))
                end)
          (goto-char point)
          (ampc-align-point))
@@ -557,7 +659,9 @@ all the time!"
                do (save-excursion
                     ,@body))
        (loop until (eobp)
-             for index from 0 to (1- (prefix-numeric-value arg-))
+             for index from 0 to (1- (if (numberp arg-)
+                                         arg-
+                                       (prefix-numeric-value arg-)))
              do (save-excursion
                   (goto-char (line-end-position))
                   ,@body)
@@ -582,11 +686,11 @@ all the time!"
 (define-derived-mode ampc-item-mode ampc-mode ""
   nil)
 
-(define-derived-mode ampc-mode fundamental-mode "ampc"
+(define-derived-mode ampc-mode special-mode "ampc"
   nil
   (buffer-disable-undo)
-  (setf buffer-read-only t
-        truncate-lines ampc-truncate-lines
+  (set (make-local-variable 'tool-bar-map) ampc-tool-bar-map)
+  (setf truncate-lines ampc-truncate-lines
         font-lock-defaults '((("^\\(\\*\\)\\(.*\\)$"
                                (1 'ampc-mark-face)
                                (2 'ampc-marked-face))
@@ -614,10 +718,6 @@ all the time!"
 (defun ampc-quote (string)
   (concat "\"" (replace-regexp-in-string "\"" "\\\"" string) "\""))
 
-(defun ampc-on-p ()
-  (and ampc-connection
-       (member (process-status ampc-connection) '(open run))))
-
 (defun ampc-in-ampc-p ()
   (when (ampc-on-p)
     ampc-type))
@@ -639,9 +739,16 @@ all the time!"
          (loop for d in (reverse data)
                do (ampc-add-impl (cdr (assoc "file" d)))))))
 
-(defun* ampc-skip (N &aux (song (cdr-safe (assq 'song ampc-status))))
-  (when song
-    (ampc-send-command 'play nil (max 0 (+ (string-to-number song) N)))))
+(defun* ampc-skip (N)
+  (ampc-send-command 'play
+                       nil
+                       (let ((N N))
+                         (lambda ()
+                           (let ((song (cdr-safe (assq 'song ampc-status))))
+                             (unless song
+                               (throw 'skip nil))
+                             (max 0 (+ (string-to-number song) N))))))
+  (ampc-send-command 'currentsong))
 
 (defun* ampc-find-current-song
     (limit &aux (point (point)) (song (cdr-safe (assq 'song ampc-status))))
@@ -655,18 +762,22 @@ all the time!"
       (narrow-to-region (max point (point)) (min limit (line-end-position)))
       (search-forward-regexp "\\(?1:\\(\\`\\*\\)?\\)\\(?2:.*\\)$"))))
 
-(defun ampc-set-volume (arg func)
-  (when (or arg ampc-status)
+(defun ampc-setvol (arg &optional func)
+  (when ampc-status
+    (when arg
+      (setf arg (prefix-numeric-value arg)))
     (ampc-send-command
      'setvol
      nil
-     (or (and arg (prefix-numeric-value arg))
-         (max (min (funcall func
+     (max (min (if func
+                   (funcall func
                             (string-to-number
                              (cdr (assq 'volume ampc-status)))
-                            5)
-                   100)
-              0)))))
+                            (or arg ampc-volume-step))
+                 arg)
+               100)
+          0))
+    (ampc-send-command 'status)))
 
 (defun ampc-set-crossfade (arg func)
   (when (or arg ampc-status)
@@ -759,9 +870,10 @@ all the time!"
            ((> (prefix-numeric-value arg) 0) 1)
            (t 0)))))
 
-(defun ampc-playlist ()
+(defun ampc-playlist (&optional at-point)
   (ampc-with-buffer 'playlists
-    (if (search-forward-regexp "^* \\(.*\\)$" nil t)
+    (if (and (not at-point)
+             (search-forward-regexp "^* \\(.*\\)$" nil t))
         (match-string 1)
       (unless (eobp)
         (buffer-substring-no-properties
@@ -806,8 +918,10 @@ all the time!"
     (move-beginning-of-line nil)
     (forward-char 2)))
 
-(defun ampc-pad (alist)
-  (loop for (offset . data) in alist
+(defun* ampc-pad (tabs &optional (sub 0))
+  (loop for tab in tabs
+        for offset in ampc-tab-offsets
+        do (setf offset (- offset sub))
         with first = t
         with current-offset = 0
         when (<= current-offset offset)
@@ -820,15 +934,16 @@ all the time!"
         concat " "
         and do (incf current-offset)
         end
-        concat data
-        do (setf current-offset (+ current-offset (length data))
-                 first nil)))
+        concat tab
+        do (incf current-offset (length tab))
+        (setf first nil)))
 
 (defun ampc-update-header ()
-  (if (eq (car ampc-type) 'status)
-      (setf header-line-format nil)
-    (setf header-line-format
+  (setf header-line-format
+        (unless (eq (car ampc-type) 'status)
           (concat
+           (when ampc-dirty
+             "  [ Updating... ]")
            (make-string (floor (fringe-columns 'left t)) ? )
            (ecase (car ampc-type)
              (tag
@@ -837,11 +952,8 @@ all the time!"
               "  Playlists")
              (t
               (ampc-pad (loop for p in (plist-get (cdr ampc-type) :properties)
-                              collect `(,(or (plist-get (cdr p) :offset) 2) .
-                                        ,(or (plist-get (cdr p) :title)
-                                             (car p)))))))
-           (when ampc-dirty
-             " [ Updating... ]")))))
+                              collect (or (plist-get (cdr p) :title)
+                                          (car p))))))))))
 
 (defun ampc-set-dirty (tag-or-dirty &optional dirty)
   (if (or (null tag-or-dirty) (eq tag-or-dirty t))
@@ -884,12 +996,12 @@ all the time!"
                            nil
                            (get-text-property (point) 'data))
       (ampc-with-buffer 'playlist
-        (delete-region (point-min) (point-max))
+        (erase-buffer)
         (ampc-set-dirty nil)))))
 
 (defun ampc-send-command-impl (command)
   (when ampc-debug
-    (message (concat "ampc: " command)))
+    (message "ampc: -> %s" command))
   (process-send-string ampc-connection (concat command "\n")))
 
 (defun ampc-send-command (command &optional unique &rest args)
@@ -909,19 +1021,40 @@ all the time!"
   (setf ampc-outstanding-commands
         (nconc (if unique
                    ampc-outstanding-commands
-                 (remove command ampc-outstanding-commands))
+                 (delete command ampc-outstanding-commands))
                `(,command))))
 
 (defun ampc-send-next-command ()
+  (loop while ampc-outstanding-commands
+        for command = (replace-regexp-in-string
+                       "^.*?-" ""
+                       (symbol-name (caar ampc-outstanding-commands)))
+        do
+        (loop until (catch 'skip
+                      (ampc-send-command-impl
+                       (concat command
+                               (loop for a in (cdar ampc-outstanding-commands)
+                                     concat " "
+                                     do (when (functionp a)
+                                          (setf a (funcall a)))
+                                     concat (typecase a
+                                              (integer (number-to-string a))
+                                              (t a)))))
+                      t)
+              do (pop ampc-outstanding-commands))
+        while (and ampc-outstanding-commands (not (eq (intern command) 'idle)))
+        while
+        (let ((member (member (intern command) ampc-synchronous-commands)))
+          (when (or (and (not (eq (car ampc-synchronous-commands) t)) member)
+                    (and (eq (car ampc-synchronous-commands) t) (not member)))
+            (loop with head = ampc-outstanding-commands
+                  with ampc-no-implicit-next-dispatch = t
+                  while (eq head ampc-outstanding-commands)
+                  do (accept-process-output ampc-connection 0 100))
+            t)))
   (unless ampc-outstanding-commands
-    (ampc-send-command 'idle))
-  (ampc-send-command-impl (concat (symbol-name (caar ampc-outstanding-commands))
-                                  (loop for a in
-                                        (cdar ampc-outstanding-commands)
-                                        concat " "
-                                        concat (cond ((integerp a)
-                                                      (number-to-string a))
-                                                     (t a))))))
+    (ampc-send-command 'idle)
+    (ampc-send-next-command)))
 
 (defun ampc-tree< (a b)
   (string< (car a) (car b)))
@@ -1010,20 +1143,22 @@ all the time!"
             do (ampc-insert
                 (ampc-pad
                  (loop for (p . v) in (plist-get (cdr ampc-type) :properties)
-                       collect `(,(- (or (plist-get v :offset) 2) 2)
-                                 . ,(or (cdr-safe (assoc p song)) ""))))
+                       collect (or (cdr-safe (assoc p song)) ""))
+                 2)
                 `((,song))))))
 
-(defun* ampc-narrow-entry (&optional (delimiter "file"))
-  (narrow-to-region (move-beginning-of-line nil)
-                    (or (progn (goto-char (line-end-position))
-                               (when (search-forward-regexp
-                                      (concat "^" (regexp-quote delimiter) ": ")
-                                      nil
-                                      t)
-                                 (move-beginning-of-line nil)
-                                 (1- (point))))
-                        (point-max))))
+(defun* ampc-narrow-entry (&optional (delimiter "file") &aux result)
+  (narrow-to-region
+   (move-beginning-of-line nil)
+   (or (progn (goto-char (line-end-position))
+              (when (setf result (search-forward-regexp
+                                  (concat "^" (regexp-quote delimiter) ": ")
+                                  nil
+                                  t))
+                (move-beginning-of-line nil)
+                (1- (point))))
+       (point-max)))
+  result)
 
 (defun ampc-get-window (type)
   (loop for w in (ampc-windows)
@@ -1037,21 +1172,17 @@ all the time!"
     (with-current-buffer data-buffer
       (loop
        for i from 0
-       while (search-forward-regexp "^file: " nil t)
+       with next
+       while (or (when next (goto-char next) t)
+                 (search-forward-regexp "^file: " nil t))
        do (save-restriction
-            (ampc-narrow-entry)
+            (setf next (ampc-narrow-entry))
             (let ((file (ampc-extract "file"))
-                  (text
-                   (ampc-pad
-                    (loop for (tag . tag-properties) in properties
-                          collect `(,(- (or (plist-get tag-properties
-                                                       :offset)
-                                            2)
-                                        2)
-                                    . ,(or (ampc-extract tag)
-                                           "[Not Specified]"))))))
+                  (pad-data (loop for (tag . tag-properties) in properties
+                                  collect (or (ampc-extract tag)
+                                              "[Not Specified]"))))
               (ampc-with-buffer 'playlist
-                (ampc-insert text
+                (ampc-insert (ampc-pad pad-data 2)
                              `(("file" . ,file)
                                (index . ,i))
                              (lambda (a b)
@@ -1070,54 +1201,81 @@ all the time!"
     (setf properties (plist-get (cdr ampc-type) :properties))
     (with-current-buffer data-buffer
       (loop
-       while (search-forward-regexp "^outputid: " nil t)
+       with next
+       while (or (when next (goto-char next) t)
+                 (search-forward-regexp "^outputid: " nil t))
        do (save-restriction
-            (ampc-narrow-entry "outputid")
+            (setf next (ampc-narrow-entry "outputid"))
             (let ((outputid (ampc-extract "outputid"))
-                  (outputenabled (ampc-extract "outputenabled"))
-                  (text
-                   (ampc-pad
-                    (loop for (tag . tag-properties) in properties
-                          collect `(,(- (or (plist-get tag-properties :offset)
-                                            2)
-                                        2)
-                                    . ,(ampc-extract tag))))))
+                  (outputenabled (ampc-extract "outputenabled")))
               (ampc-with-buffer 'outputs
-                (ampc-insert text `(("outputid" . ,outputid)
-                                    ("outputenabled" . ,outputenabled))))))))))
+                (ampc-insert (ampc-pad
+                              (loop for (tag . tag-properties) in properties
+                                    collect (with-current-buffer data-buffer
+                                              (ampc-extract tag)))
+                              2)
+                             `(("outputid" . ,outputid)
+                               ("outputenabled" . ,outputenabled))))))))))
+
+(defun* ampc-mini-impl (&aux songs)
+  (loop with next
+        while (or (when next (goto-char next) t)
+                  (search-forward-regexp "^file: " nil t))
+        for entry = (save-restriction
+                      (setf next (ampc-narrow-entry))
+                      `(,(concat (ampc-extract "Title") " - "
+                                 (ampc-extract "Artist"))
+                        . ,(string-to-number (ampc-extract "Pos"))))
+        do (loop with mentry = `(,(car entry) . ,(cdr entry))
+                 for index from 2
+                 while (assoc (car mentry) songs)
+                 do (setf (car mentry) (concat (car entry)
+                                               " (" (int-to-string index) ")"))
+                 finally do (push mentry songs)))
+  (unless songs
+    (message "No song in the playlist")
+    (return-from ampc-mini-impl))
+  (let ((song (assoc (let ((inhibit-quit t))
+                       (prog1
+                           (with-local-quit
+                             (completing-read "Song to play: " songs nil t))
+                         (setf quit-flag nil)))
+                     songs)))
+    (when song
+      (ampc-play-this (cdr song)))))
 
 (defun* ampc-fill-current-playlist (&aux properties)
   (ampc-fill-skeleton 'current-playlist
     (setf properties (plist-get (cdr ampc-type) :properties))
     (with-current-buffer data-buffer
       (loop
-       while (search-forward-regexp "^file: " nil t)
+       with next
+       while (or (when next (goto-char next) t)
+                 (search-forward-regexp "^file: " nil t))
        do (save-restriction
-            (ampc-narrow-entry)
+            (setf next (ampc-narrow-entry))
             (let ((file (ampc-extract "file"))
-                  (pos (ampc-extract "Pos"))
-                  (text
-                   (ampc-pad
-                    (loop for (tag . tag-properties) in properties
-                          collect `(,(- (or (plist-get tag-properties :offset)
-                                            2)
-                                        2)
-                                    . ,(or (ampc-extract tag)
-                                           "[Not Specified]"))))))
+                  (pos (ampc-extract "Pos")))
               (ampc-with-buffer 'current-playlist
-                (ampc-insert text
-                             `(("file" . ,file)
-                               ("Pos" . ,(string-to-number pos)))
-                             (lambda (a b)
-                               (let ((p1 (cdr (assoc "Pos" a)))
-                                     (p2 (cdr (assoc "Pos" b))))
-                                 (cond ((< p1 p2) 'insert)
-                                       ((eq p1 p2)
-                                        (if (equal (cdr (assoc "file" a))
-                                                   (cdr (assoc "file" b)))
-                                            'update
-                                          'insert))
-                                       (t (- p1 p2)))))))))))))
+                (ampc-insert
+                 (ampc-pad
+                  (loop for (tag . tag-properties) in properties
+                        collect (or (with-current-buffer data-buffer
+                                      (ampc-extract tag))
+                                    "[Not Specified]"))
+                  2)
+                 `(("file" . ,file)
+                   ("Pos" . ,(string-to-number pos)))
+                 (lambda (a b)
+                   (let ((p1 (cdr (assoc "Pos" a)))
+                         (p2 (cdr (assoc "Pos" b))))
+                     (cond ((< p1 p2) 'insert)
+                           ((eq p1 p2)
+                            (if (equal (cdr (assoc "file" a))
+                                       (cdr (assoc "file" b)))
+                                'update
+                              'insert))
+                           (t (- p1 p2)))))))))))))
 
 (defun ampc-fill-playlists ()
   (ampc-fill-skeleton 'playlists
@@ -1128,15 +1286,16 @@ all the time!"
                  (ampc-insert playlist playlist))))))
 
 (defun ampc-yield ()
-  (setf ampc-yield (1+ ampc-yield))
-  (ampc-fill-status))
+  (incf ampc-yield)
+  (ampc-fill-status)
+  (redisplay t))
 
 (defun ampc-fill-status ()
   (ampc-with-buffer 'status
-    (delete-region (point-min) (point-max))
+    (erase-buffer)
     (funcall (or (plist-get (cadr ampc-type) :filler)
                  (lambda (_)
-                   (insert (ampc-status) "\n")))
+                   (insert (ampc-status t) "\n")))
              ampc-status)
     (ampc-set-dirty nil)))
 
@@ -1301,6 +1460,10 @@ all the time!"
        (ampc-fill-playlists))
       (playlistinfo
        (ampc-fill-current-playlist))
+      (mini-playlistinfo
+       (ampc-mini-impl))
+      (mini-currentsong
+       (ampc-status))
       (listallinfo
        (ampc-fill-internal-db nil))
       (outputs
@@ -1313,21 +1476,22 @@ all the time!"
   (with-current-buffer (process-buffer ampc-connection)
     (when string
       (when ampc-debug
-        (message "ampc: -> %s" string))
+        (message "ampc: <- %s" string))
       (goto-char (process-mark ampc-connection))
       (insert string)
       (set-marker (process-mark ampc-connection) (point)))
     (save-excursion
       (goto-char (point-min))
       (let ((success))
-        (if (or (and (search-forward-regexp
-                      "^ACK \\[\\(.*\\)\\] {.*} \\(.*\\)\n\\'"
-                      nil
-                      t)
-                     (message "ampc command error: %s (%s)"
-                              (match-string 2)
-                              (match-string 1))
-                     t)
+        (if (or (progn
+                  (when (search-forward-regexp
+                         "^ACK \\[\\(.*\\)\\] {.*} \\(.*\\)\n\\'"
+                         nil
+                         t)
+                    (message "ampc command error: %s (%s)"
+                             (match-string 2)
+                             (match-string 1))
+                    t))
                 (and (search-forward-regexp "^OK\\(.*\\)\n\\'" nil t)
                      (setf success t)))
             (progn
@@ -1337,8 +1501,9 @@ all the time!"
                   (goto-char (point-min))
                   (ampc-handle-command (if success (match-string 1) 'error)))
                 (delete-region (point-min) match-end))
-              (ampc-send-next-command))
-          (ampc-handle-command 'running))))))
+              (unless ampc-no-implicit-next-dispatch
+                (ampc-send-next-command))))
+          (ampc-handle-command 'running)))))
 
 ;;; **** window management
 (defun ampc-windows (&optional unordered)
@@ -1354,6 +1519,30 @@ all the time!"
                                                           b)
                                                       w))))))
 
+(defun* ampc-set-tab-offsets
+    (&rest properties &aux (min 2) (optional-padding 0))
+  (loop for (title . props) in properties
+        for min- = (plist-get props :min)
+        do (incf min (or (plist-get props :width) min-))
+        when min-
+        do (incf optional-padding (- (plist-get props :max) min-))
+        end)
+  (setf ampc-tab-offsets nil)
+  (loop for (title . props) in properties
+        with offset = 2
+        do (add-to-list 'ampc-tab-offsets offset t)
+        (incf offset (or (plist-get props :width)
+                         (let ((min- (plist-get props :min))
+                               (max (plist-get props :max)))
+                           (if (>= min (window-width))
+                               min-
+                             (min max
+                                  (+ min-
+                                     (floor (* (/ (float (- max min-))
+                                                  optional-padding)
+                                               (- (window-width)
+                                                  min)))))))))))
+
 (defun* ampc-configure-frame-1 (split &aux (split-type (car split)))
   (if (member split-type '(vertical horizontal))
       (let* ((sizes))
@@ -1362,13 +1551,12 @@ all the time!"
                               (window-height))
               with rest = length
               with rest-car
-              for subsplit in (cdr split)
-              for s = (car subsplit)
-              if (equal s 1.0)
+              for (size . subsplit) in (cdr split)
+              if (equal size 1.0)
               do (push t sizes)
               and do (setf rest-car sizes)
               else
-              do (let ((l (if (integerp s) s (floor (* s length)))))
+              do (let ((l (if (integerp size) size (floor (* size length)))))
                    (setf rest (- rest l))
                    (push l sizes))
               finally do (setf (car rest-car) rest))
@@ -1414,12 +1602,16 @@ all the time!"
       (status
        (pop-to-buffer-same-window (get-buffer-create "*ampc Status*"))
        (ampc-mode)))
-    (destructuring-bind (&key (dedicated t) (mode-line t) &allow-other-keys)
+    (destructuring-bind
+        (&key (properties nil) (dedicated t) (mode-line t) &allow-other-keys)
         (cdr split)
-      (setf (window-dedicated-p (selected-window)) dedicated)
-      (unless mode-line
-        (setf mode-line-format nil)))
-    (setf ampc-type split)
+      (if properties
+          (apply 'ampc-set-tab-offsets properties)
+        (setf ampc-tab-offsets '(2)))
+      (setf ampc-type split
+            (window-dedicated-p (selected-window)) dedicated
+            mode-line-format (when mode-line
+                               (default-value 'mode-line-format))))
     (add-to-list 'ampc-all-buffers (current-buffer))
     (push `(,(or (plist-get (cdr split) :id)
                  (if (eq (car ampc-type) 'song) 9998 9999))
@@ -1433,21 +1625,79 @@ all the time!"
              (delete-other-windows))
     (loop with live-window = nil
           for w in (nreverse (ampc-windows t))
-          if (window-live-p w)
-          if (not live-window)
-          do (setf live-window w)
-          else
-          do (delete-window w)
-          end
-          end
+          do (when (window-live-p w)
+               (if (not live-window)
+                   (setf live-window w)
+                 (delete-window w)))
           finally do (if live-window (select-window live-window))))
   (setf ampc-buffers nil)
   (ampc-configure-frame-1 split)
   (setf ampc-buffers-unordered (mapcar 'cdr ampc-buffers)
         ampc-buffers (mapcar 'cdr (sort ampc-buffers
                                         (lambda (a b) (< (car a) (car b))))))
+  ;; fill the song, current-playlist and outputs buffers again as the tab
+  ;; offsets might have changed
+  (ampc-with-buffer 'song
+    (erase-buffer))
+  (ampc-with-buffer 'current-playlist
+    (erase-buffer))
+  (ampc-with-buffer 'outputs
+    (erase-buffer))
   (ampc-update))
 
+(defun ampc-mouse-play-this (event)
+  (interactive "e")
+  (select-window (posn-window (event-end event)))
+  (goto-char (posn-point (event-end event)))
+  (ampc-play-this))
+
+(defun ampc-mouse-delete (event)
+  (interactive "e")
+  (select-window (posn-window (event-end event)))
+  (goto-char (posn-point (event-end event)))
+  (ampc-delete 1))
+
+(defun ampc-mouse-add (event)
+  (interactive "e")
+  (select-window (posn-window (event-end event)))
+  (goto-char (posn-point (event-end event)))
+  (ampc-add-impl))
+
+(defun ampc-mouse-delete-playlist (event)
+  (interactive "e")
+  (select-window (posn-window (event-end event)))
+  (goto-char (posn-point (event-end event)))
+  (ampc-delete-playlist t))
+
+(defun ampc-mouse-load (event)
+  (interactive "e")
+  (select-window (posn-window (event-end event)))
+  (goto-char (posn-point (event-end event)))
+  (ampc-load t))
+
+(defun ampc-mouse-toggle-output-enabled (event)
+  (interactive "e")
+  (select-window (posn-window (event-end event)))
+  (goto-char (posn-point (event-end event)))
+  (ampc-toggle-output-enabled 1))
+
+(defun* ampc-mouse-toggle-mark (event &aux buffer-read-only)
+  (interactive "e")
+  (let ((window (posn-window (event-end event))))
+    (when (with-selected-window window
+            (goto-char (posn-point (event-end event)))
+            (unless (eobp)
+              (move-beginning-of-line nil)
+              (ampc-mark-impl (not (eq (char-after) ?*)) 1)
+              t))
+      (select-window window))))
+
+(defun ampc-mouse-align-point (event)
+  (interactive "e")
+  (select-window (posn-window (event-end event)))
+  (goto-char (posn-point (event-end event)))
+  (ampc-align-point))
+
 ;;; *** interactives
 (defun* ampc-unmark-all (&aux buffer-read-only)
   "Remove all marks."
@@ -1510,19 +1760,26 @@ ARG defaults to 1."
   (assert (ampc-in-ampc-p))
   (ampc-mark-impl nil arg))
 
+(defun ampc-set-volume (&optional arg)
+  "Set volume to ARG percent.
+If ARG is nil, read ARG from minibuffer."
+  (interactive "P")
+  (assert (ampc-on-p))
+  (ampc-setvol (or arg (read-number "Volume: "))))
+
 (defun ampc-increase-volume (&optional arg)
-  "Decrease volume.
-With prefix argument ARG, set volume to ARG percent."
+  "Increase volume by prefix argument ARG or, if ARG is nil,
+`ampc-volume-step'."
   (interactive "P")
   (assert (ampc-on-p))
-  (ampc-set-volume arg '+))
+  (ampc-setvol arg '+))
 
 (defun ampc-decrease-volume (&optional arg)
-  "Decrease volume.
-With prefix argument ARG, set volume to ARG percent."
+  "Decrease volume by prefix argument ARG or, if ARG is nil,
+`ampc-volume-step'."
   (interactive "P")
   (assert (ampc-on-p))
-  (ampc-set-volume arg '-))
+  (ampc-setvol arg '-))
 
 (defun ampc-increase-crossfade (&optional arg)
   "Increase crossfade.
@@ -1563,18 +1820,23 @@ otherwise disable it."
   (interactive "P")
   (ampc-toggle-state 'random arg))
 
-(defun ampc-play-this ()
-  "Play selected song."
-  (interactive)
-  (assert (ampc-in-ampc-p))
-  (unless (eobp)
-    (ampc-send-command 'play nil (1- (line-number-at-pos)))
+(defun ampc-play-this (&optional arg)
+  "Play selected song.
+With prefix argument ARG, play the ARG'th song located at the
+zero-indexed position of the current playlist."
+  (interactive "P")
+  (assert (and (ampc-on-p) (or arg (ampc-in-ampc-p))))
+  (if (not arg)
+      (unless (eobp)
+        (ampc-send-command 'play nil (1- (line-number-at-pos)))
+        (ampc-send-command 'pause nil 0))
+    (ampc-send-command 'play nil arg)
     (ampc-send-command 'pause nil 0)))
 
 (defun* ampc-toggle-play
     (&optional arg &aux (state (cdr-safe (assq 'state ampc-status))))
   "Toggle play state.
-If mpd does not play a song already, start playing the song at
+If MPD does not play a song already, start playing the song at
 point if the current buffer is the playlist buffer, otherwise
 start at the beginning of the playlist.
 
@@ -1625,13 +1887,17 @@ Interactively, read NEW-NAME from the minibuffer."
       (ampc-send-command 'rename nil (ampc-playlist) new-name)
     (error "No playlist selected")))
 
-(defun ampc-load ()
-  "Load selected playlist in the current playlist."
+(defun ampc-load (&optional at-point)
+  "Load selected playlist in the current playlist.
+If optional argument AT-POINT is non-nil (or if no playlist is
+selected), use playlist at point rather than the selected one."
   (interactive)
   (assert (ampc-in-ampc-p))
-  (if (ampc-playlist)
-      (ampc-send-command 'load nil (ampc-quote (ampc-playlist)))
-    (error "No playlist selected")))
+  (if (ampc-playlist at-point)
+      (ampc-send-command 'load nil (ampc-quote (ampc-playlist at-point)))
+    (if at-point
+        (error "No playlist at point")
+      (error "No playlist selected"))))
 
 (defun ampc-toggle-output-enabled (&optional arg)
   "Toggle the next ARG outputs.
@@ -1648,7 +1914,8 @@ If ARG is omitted, use the selected entries."
 
 (defun ampc-delete (&optional arg)
   "Delete the next ARG songs from the playlist.
-If ARG is omitted, use the selected entries."
+If ARG is omitted, use the selected entries.  If ARG is non-nil,
+all marks after point are removed nontheless."
   (interactive "P")
   (assert (ampc-in-ampc-p))
   (let ((point (point)))
@@ -1702,10 +1969,17 @@ If ARG is omitted, use the selected entries in the current buffer."
   (ampc-with-selection arg
     (ampc-add-impl)))
 
-(defun ampc-status ()
-  "Display the information that is displayed in the status window."
+(defun* ampc-status (&optional no-print)
+  "Display and return the information that is displayed in the status window.
+If optional argument NO-PRINT is non-nil, just return the text.
+If NO-PRINT is nil, the display may be delayed if ampc does not
+have enough information yet."
   (interactive)
   (assert (ampc-on-p))
+  (unless (or ampc-status no-print)
+    (ampc-send-command 'status t)
+    (ampc-send-command 'mini-currentsong t)
+    (return-from ampc-status))
   (let* ((flags (mapconcat
                  'identity
                  (loop for (f . n) in '((repeat . "Repeat")
@@ -1717,7 +1991,7 @@ If ARG is omitted, use the selected entries in the current buffer."
                  "|"))
          (state (cdr (assq 'state ampc-status)))
          (status (concat "State:     " state
-                         (when ampc-yield
+                         (when (and ampc-yield no-print)
                            (concat (make-string (- 10 (length state)) ? )
                                    (nth (% ampc-yield 4) '("|" "/" "-" "\\"))))
                          "\n"
@@ -1733,18 +2007,22 @@ If ARG is omitted, use the selected entries in the current buffer."
                          "Crossfade: " (cdr (assq 'xfade ampc-status))
                          (unless (equal flags "")
                            (concat "\n" flags)))))
-    (when (called-interactively-p 'interactive)
+    (unless no-print
       (message "%s" status))
     status))
 
-(defun ampc-delete-playlist ()
-  "Delete selected playlist."
+(defun ampc-delete-playlist (&optional at-point)
+  "Delete selected playlist.
+If optional argument AT-POINT is non-nil (or if no playlist is
+selected), use playlist at point rather than the selected one."
   (interactive)
   (assert (ampc-in-ampc-p))
-  (ampc-with-selection nil
-    (let ((name (get-text-property (point) 'data)))
-      (when (y-or-n-p (concat "Delete playlist " name "?"))
-        (ampc-send-command 'rm nil (ampc-quote name))))))
+  (if (ampc-playlist at-point)
+      (when (y-or-n-p (concat "Delete playlist " (ampc-playlist at-point) "?"))
+        (ampc-send-command 'rm nil (ampc-quote (ampc-playlist at-point))))
+    (if at-point
+        (error "No playlist at point")
+      (error "No playlist selected"))))
 
 (defun ampc-store (name)
   "Store current playlist as NAME.
@@ -1758,13 +2036,13 @@ Interactively, read NAME from the minibuffer."
   "Select the current playlist window and move point to the current song."
   (interactive)
   (assert (ampc-in-ampc-p))
-  (when song
-    (ampc-with-buffer 'current-playlist
-      no-se
-      (select-window (ampc-get-window 'current-playlist))
+  (ampc-with-buffer 'current-playlist
+    no-se
+    (select-window (ampc-get-window 'current-playlist))
+    (when song
       (goto-char (point-min))
-      (forward-line (string-to-number song))
-      (ampc-align-point))))
+      (forward-line (string-to-number song)))
+    (ampc-align-point)))
 
 (defun ampc-previous-line (&optional arg)
   "Go to previous ARG'th entry in the current buffer.
@@ -1789,34 +2067,36 @@ ARG defaults to 1."
 (defun* ampc-suspend (&optional (run-hook t))
   "Suspend ampc.
 This function resets the window configuration, but does not close
-the connection to mpd or destroy the internal cache of ampc.
+the connection to MPD or destroy the internal cache of ampc.
 This means subsequent startups of ampc will be faster."
   (interactive)
   (when ampc-working-timer
     (cancel-timer ampc-working-timer))
   (loop with found-window
         for w in (nreverse (ampc-windows t))
-        when (window-live-p w)
-        when found-window
-        do (delete-window w)
-        else
-        do (setf found-window t
-                 (window-dedicated-p w) nil)
-        end
-        end)
+        do (when (window-live-p w)
+             (if found-window
+                 (delete-window w)
+               (setf found-window t
+                     (window-dedicated-p w) nil))))
   (loop for b in ampc-all-buffers
-        when (buffer-live-p b)
-        do (kill-buffer b)
-        end)
+        do (when (buffer-live-p b)
+             (kill-buffer b)))
   (setf ampc-buffers nil
         ampc-all-buffers nil
         ampc-working-timer nil)
   (when run-hook
     (run-hooks 'ampc-suspend-hook)))
 
+(defun ampc-mini ()
+  "Select song to play via `completing-read'."
+  (interactive)
+  (assert (ampc-on-p))
+  (ampc-send-command 'mini-playlistinfo t))
+
 (defun ampc-quit (&optional arg)
   "Quit ampc.
-If called with a prefix argument ARG, kill the mpd instance that
+If called with a prefix argument ARG, kill the MPD instance that
 ampc is connected to."
   (interactive "P")
   (when (ampc-on-p)
@@ -1838,18 +2118,35 @@ ampc is connected to."
   (run-hooks 'ampc-quit-hook))
 
 ;;;###autoload
-(defun ampc (&optional host port)
+(defun ampc-suspended-p ()
+  "Return non-nil if ampc is suspended."
+  (interactive)
+  (and (ampc-on-p)
+       (not ampc-buffers)))
+
+;;;###autoload
+(defun ampc-on-p ()
+  "Return non-nil if ampc is connected to the daemon."
+  (interactive)
+  (and ampc-connection (memq (process-status ampc-connection) '(open run))))
+
+;;;###autoload
+(defun ampc (&optional host port suspend)
   "ampc is an asynchronous client for the MPD media player.
 This function is the main entry point for ampc.
 
-Non-interactively, HOST and PORT specify the MPD instance to
-connect to.  The values default to localhost:6600."
-  (interactive "MHost (localhost): \nMPort (6600): ")
+HOST and PORT specify the MPD instance to connect to.  The values
+default to the ones specified in `ampc-default-server'."
+  (interactive)
+  (unless (byte-code-function-p (symbol-function 'ampc))
+    (message "You should byte-compile ampc"))
   (run-hooks 'ampc-before-startup-hook)
-  (when (or (not host) (equal host ""))
-    (setf host "localhost"))
-  (when (or (not port) (equal port ""))
-    (setf port 6600))
+  (unless host
+    (setf host (or (car ampc-default-server)
+                   (read-string  "Host: "))))
+  (unless port
+    (setf port (or (cdr ampc-default-server)
+                   (read-string "Port: "))))
   (when (and ampc-connection
              (or (not (equal host ampc-host))
                  (not (equal port ampc-port))
@@ -1858,9 +2155,8 @@ connect to.  The values default to localhost:6600."
   (unless ampc-connection
     (let ((connection (open-network-stream "ampc"
                                            (with-current-buffer
-                                               (get-buffer-create " *mpc*")
-                                             (delete-region (point-min)
-                                                            (point-max))
+                                               (get-buffer-create " *ampc*")
+                                             (erase-buffer)
                                              (current-buffer))
                                            host
                                            port
@@ -1875,8 +2171,12 @@ connect to.  The values default to localhost:6600."
     (set-process-filter ampc-connection 'ampc-filter)
     (set-process-query-on-exit-flag ampc-connection nil)
     (setf ampc-outstanding-commands '((setup))))
-  (ampc-configure-frame (cddar ampc-views))
+  (if suspend
+      (ampc-update)
+    (ampc-configure-frame (cddar ampc-views)))
   (run-hooks 'ampc-connected-hook)
+  (when suspend
+    (ampc-suspend))
   (ampc-filter (process-buffer ampc-connection) nil))
 
 (provide 'ampc)