emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Emacs-diffs] emacs etc/NEWS lisp/ChangeLog etc/images/mpc/ad...


From: Stefan Monnier
Subject: [Emacs-diffs] emacs etc/NEWS lisp/ChangeLog etc/images/mpc/ad...
Date: Tue, 01 Dec 2009 04:04:39 +0000

CVSROOT:        /sources/emacs
Module name:    emacs
Changes by:     Stefan Monnier <monnier>        09/12/01 04:04:38

Modified files:
        etc            : NEWS 
        lisp           : ChangeLog 
Added files:
        etc/images/mpc : add.xpm ffwd.xpm next.xpm pause.xpm play.xpm 
                         prev.xpm rewind.xpm stop.xpm 
        lisp           : mpc.el 

Log message:
        * mpc.el: New file.

CVSWeb URLs:
http://cvs.savannah.gnu.org/viewcvs/emacs/etc/NEWS?cvsroot=emacs&r1=1.2131&r2=1.2132
http://cvs.savannah.gnu.org/viewcvs/emacs/etc/images/mpc/add.xpm?cvsroot=emacs&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/emacs/etc/images/mpc/ffwd.xpm?cvsroot=emacs&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/emacs/etc/images/mpc/next.xpm?cvsroot=emacs&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/emacs/etc/images/mpc/pause.xpm?cvsroot=emacs&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/emacs/etc/images/mpc/play.xpm?cvsroot=emacs&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/emacs/etc/images/mpc/prev.xpm?cvsroot=emacs&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/emacs/etc/images/mpc/rewind.xpm?cvsroot=emacs&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/emacs/etc/images/mpc/stop.xpm?cvsroot=emacs&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/emacs/lisp/ChangeLog?cvsroot=emacs&r1=1.16781&r2=1.16782
http://cvs.savannah.gnu.org/viewcvs/emacs/lisp/mpc.el?cvsroot=emacs&rev=1.1

Patches:
Index: etc/NEWS
===================================================================
RCS file: /sources/emacs/emacs/etc/NEWS,v
retrieving revision 1.2131
retrieving revision 1.2132
diff -u -b -r1.2131 -r1.2132
--- etc/NEWS    30 Nov 2009 19:42:16 -0000      1.2131
+++ etc/NEWS    1 Dec 2009 04:04:33 -0000       1.2132
@@ -323,6 +323,7 @@
 
 * New Modes and Packages in Emacs 23.2
 
+** mpc.el is a front end for the Music Player Daemon.  Run it with M-x mpc.
 ** htmlfontify.el turns a fontified Emacs buffer into an HTML page.
 
 ** FIXME CEDET

Index: lisp/ChangeLog
===================================================================
RCS file: /sources/emacs/emacs/lisp/ChangeLog,v
retrieving revision 1.16781
retrieving revision 1.16782
diff -u -b -r1.16781 -r1.16782
--- lisp/ChangeLog      1 Dec 2009 03:15:01 -0000       1.16781
+++ lisp/ChangeLog      1 Dec 2009 04:04:34 -0000       1.16782
@@ -1,3 +1,7 @@
+2009-12-01  Stefan Monnier  <address@hidden>
+
+       * mpc.el: New file.
+
 2009-12-01  Glenn Morris  <address@hidden>
 
        * window.el (window-to-use): Define for compiler.

Index: etc/images/mpc/add.xpm
===================================================================
RCS file: etc/images/mpc/add.xpm
diff -N etc/images/mpc/add.xpm
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ etc/images/mpc/add.xpm      1 Dec 2009 04:04:33 -0000       1.1
@@ -0,0 +1,31 @@
+/* XPM */
+static char * stop_xpm[] = {
+"24 24 4 1",
+"      c None",
+".     s light1        c grey90",
+"X     s main  c grey70",
+"o     s shadow        c grey50",
+"                        ",
+" ...................... ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXX....XXXXXXXXo ",
+" .XXXXXXXX.XXoXXXXXXXXo ",
+" .XXXXXXXX.XXoXXXXXXXXo ",
+" .XXXXXXXX.XXoXXXXXXXXo ",
+" .XXXXXXXX.XXoXXXXXXXXo ",
+" .XXX......XXo.....XXXo ",
+" .XXX.XXXXXXXXXXXXoXXXo ",
+" .XXX.XXXXXXXXXXXXoXXXo ",
+" .XXX.oooooXXooooooXXXo ",
+" .XXXXXXXX.XXoXXXXXXXXo ",
+" .XXXXXXXX.XXoXXXXXXXXo ",
+" .XXXXXXXX.XXoXXXXXXXXo ",
+" .XXXXXXXX.XXoXXXXXXXXo ",
+" .XXXXXXXX.oooXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .ooooooooooooooooooooo ",
+"                        "};

Index: etc/images/mpc/ffwd.xpm
===================================================================
RCS file: etc/images/mpc/ffwd.xpm
diff -N etc/images/mpc/ffwd.xpm
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ etc/images/mpc/ffwd.xpm     1 Dec 2009 04:04:34 -0000       1.1
@@ -0,0 +1,34 @@
+/* XPM */
+static char * ffwd_xpm[] = {
+"24 24 5 1",
+"      c None",
+".     s light1        c grey90",
+"X     s main  c grey70",
+"o     s shadow        c grey50",
+"O     s light2        c grey80",
+"                        ",
+" ...................... ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXX.OXXX.OXXXXXXXXXXo ",
+" .XXX.XOXX.XOXXXXXXXXXo ",
+" .XXX.XXOO.XXOOXXXXXXXo ",
+" .XXX.XXXX.XXXXOXXXXXXo ",
+" .XXX.XXXX.XXXXXOXXXXXo ",
+" .XXX.XXXX.XXXXXXOOXXXo ",
+" .XXX.XXXX.XXXXXXXXOXXo ",
+" .XXX.XXXX.XXXXXXXXoXXo ",
+" .XXX.XXXX.XXXXXXooXXXo ",
+" .XXX.XXXX.XXXXXoXXXXXo ",
+" .XXX.XXXX.XXXXoXXXXXXo ",
+" .XXX.XXoo.XXooXXXXXXXo ",
+" .XXX.XoXX.XoXXXXXXXXXo ",
+" .XXX.oXXX.oXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .ooooooooooooooooooooo ",
+"                        "};
+
+/* arch-tag: 9b0fa3cf-1e36-4c20-ac68-948c2ae86b62 */

Index: etc/images/mpc/next.xpm
===================================================================
RCS file: etc/images/mpc/next.xpm
diff -N etc/images/mpc/next.xpm
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ etc/images/mpc/next.xpm     1 Dec 2009 04:04:34 -0000       1.1
@@ -0,0 +1,34 @@
+/* XPM */
+static char * next_xpm[] = {
+"24 24 5 1",
+"      c None",
+".     s light1        c grey90",
+"X     s main  c grey70",
+"o     s shadow        c grey50",
+"O     s light2        c grey80",
+"                        ",
+" ...................... ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXX.OXXXXXXXXX...XXXo ",
+" .XXX.XOXXXXXXXX.XoXXXo ",
+" .XXX.XXOOXXXXXX.XoXXXo ",
+" .XXX.XXXXOXXXXX.XoXXXo ",
+" .XXX.XXXXXOXXXX.XoXXXo ",
+" .XXX.XXXXXXOOXX.XoXXXo ",
+" .XXX.XXXXXXXXOX.XoXXXo ",
+" .XXX.XXXXXXXXoX.XoXXXo ",
+" .XXX.XXXXXXooXX.XoXXXo ",
+" .XXX.XXXXXoXXXX.XoXXXo ",
+" .XXX.XXXXoXXXXX.XoXXXo ",
+" .XXX.XXooXXXXXX.XoXXXo ",
+" .XXX.XoXXXXXXXX.XoXXXo ",
+" .XXX.oXXXXXXXXX.ooXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .ooooooooooooooooooooo ",
+"                        "};
+
+/* arch-tag: 69a2ee4e-e71f-432d-b17b-ac8055dacc93 */

Index: etc/images/mpc/pause.xpm
===================================================================
RCS file: etc/images/mpc/pause.xpm
diff -N etc/images/mpc/pause.xpm
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ etc/images/mpc/pause.xpm    1 Dec 2009 04:04:34 -0000       1.1
@@ -0,0 +1,33 @@
+/* XPM */
+static char * pause_xpm[] = {
+"24 24 4 1",
+"      c None",
+".     s light1        c grey90",
+"X     s main  c grey70",
+"o     s shadow        c grey50",
+"                        ",
+" ...................... ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXX.....XX.....XXXXo ",
+" .XXXX.XXXoXX.XXXoXXXXo ",
+" .XXXX.XXXoXX.XXXoXXXXo ",
+" .XXXX.XXXoXX.XXXoXXXXo ",
+" .XXXX.XXXoXX.XXXoXXXXo ",
+" .XXXX.XXXoXX.XXXoXXXXo ",
+" .XXXX.XXXoXX.XXXoXXXXo ",
+" .XXXX.XXXoXX.XXXoXXXXo ",
+" .XXXX.XXXoXX.XXXoXXXXo ",
+" .XXXX.XXXoXX.XXXoXXXXo ",
+" .XXXX.XXXoXX.XXXoXXXXo ",
+" .XXXX.XXXoXX.XXXoXXXXo ",
+" .XXXX.XXXoXX.XXXoXXXXo ",
+" .XXXX.ooooXX.ooooXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .ooooooooooooooooooooo ",
+"                        "};
+
+/* arch-tag: 3fe99afb-7dfd-49dd-b5b3-d8eedf14b362 */

Index: etc/images/mpc/play.xpm
===================================================================
RCS file: etc/images/mpc/play.xpm
diff -N etc/images/mpc/play.xpm
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ etc/images/mpc/play.xpm     1 Dec 2009 04:04:34 -0000       1.1
@@ -0,0 +1,34 @@
+/* XPM */
+static char * play_xpm[] = {
+"24 24 5 1",
+"      c None",
+".     s light1        c grey90",
+"X     s main  c grey70",
+"o     s shadow        c grey50",
+"O     s light2        c grey80",
+"                        ",
+" ...................... ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXX.OOXXXXXXXXXXXXXXo ",
+" .XXX.XXOOXXXXXXXXXXXXo ",
+" .XXX.XXXXOOXXXXXXXXXXo ",
+" .XXX.XXXXXXOOXXXXXXXXo ",
+" .XXX.XXXXXXXXOOXXXXXXo ",
+" .XXX.XXXXXXXXXXOOXXXXo ",
+" .XXX.XXXXXXXXXXXXOXXXo ",
+" .XXX.XXXXXXXXXXXXoXXXo ",
+" .XXX.XXXXXXXXXXooXXXXo ",
+" .XXX.XXXXXXXXooXXXXXXo ",
+" .XXX.XXXXXXooXXXXXXXXo ",
+" .XXX.XXXXooXXXXXXXXXXo ",
+" .XXX.XXooXXXXXXXXXXXXo ",
+" .XXX.ooXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .ooooooooooooooooooooo ",
+"                        "};
+
+/* arch-tag: 318eb8de-b126-48bd-818b-bb293df74ec8 */

Index: etc/images/mpc/prev.xpm
===================================================================
RCS file: etc/images/mpc/prev.xpm
diff -N etc/images/mpc/prev.xpm
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ etc/images/mpc/prev.xpm     1 Dec 2009 04:04:34 -0000       1.1
@@ -0,0 +1,33 @@
+/* XPM */
+static char * prev_xpm[] = {
+"24 24 4 1",
+"      c None",
+".     s light1        c grey90",
+"X     s main  c grey70",
+"o     s shadow        c grey50",
+"                        ",
+" ...................... ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXX...XXXXXXXXX..XXXo ",
+" .XXX.XoXXXXXXXX.XoXXXo ",
+" .XXX.XoXXXXXX..XXoXXXo ",
+" .XXX.XoXXXXX.XXXXoXXXo ",
+" .XXX.XoXXXX.XXXXXoXXXo ",
+" .XXX.XoXX..XXXXXXoXXXo ",
+" .XXX.XoX.XXXXXXXXoXXXo ",
+" .XXX.XoXoXXXXXXXXoXXXo ",
+" .XXX.XoXXooXXXXXXoXXXo ",
+" .XXX.XoXXXXoXXXXXoXXXo ",
+" .XXX.XoXXXXXoXXXXoXXXo ",
+" .XXX.XoXXXXXXooXXoXXXo ",
+" .XXX.XoXXXXXXXXoXoXXXo ",
+" .XXX.ooXXXXXXXXXooXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .ooooooooooooooooooooo ",
+"                        "};
+
+/* arch-tag: 284e0591-6e14-4dae-9cc3-c722fa0b9099 */

Index: etc/images/mpc/rewind.xpm
===================================================================
RCS file: etc/images/mpc/rewind.xpm
diff -N etc/images/mpc/rewind.xpm
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ etc/images/mpc/rewind.xpm   1 Dec 2009 04:04:34 -0000       1.1
@@ -0,0 +1,33 @@
+/* XPM */
+static char * rewind_xpm[] = {
+"24 24 4 1",
+"      c None",
+".     s light1        c grey90",
+"X     s main  c grey70",
+"o     s shadow        c grey50",
+"                        ",
+" ...................... ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXX..XXX..XXXo ",
+" .XXXXXXXXX.XoXX.XoXXXo ",
+" .XXXXXXX..XXo..XXoXXXo ",
+" .XXXXXX.XXXXoXXXXoXXXo ",
+" .XXXXX.XXXXXoXXXXoXXXo ",
+" .XXX..XXXXXXoXXXXoXXXo ",
+" .XX.XXXXXXXXoXXXXoXXXo ",
+" .XXoXXXXXXXXoXXXXoXXXo ",
+" .XXXooXXXXXXoXXXXoXXXo ",
+" .XXXXXoXXXXXoXXXXoXXXo ",
+" .XXXXXXoXXXXoXXXXoXXXo ",
+" .XXXXXXXooXXoooXXoXXXo ",
+" .XXXXXXXXXoXoXXoXoXXXo ",
+" .XXXXXXXXXXooXXXooXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .ooooooooooooooooooooo ",
+"                        "};
+
+/* arch-tag: 2bdb6c7f-8ddb-4110-b8ee-ffc3f06d1aa9 */

Index: etc/images/mpc/stop.xpm
===================================================================
RCS file: etc/images/mpc/stop.xpm
diff -N etc/images/mpc/stop.xpm
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ etc/images/mpc/stop.xpm     1 Dec 2009 04:04:34 -0000       1.1
@@ -0,0 +1,33 @@
+/* XPM */
+static char * stop_xpm[] = {
+"24 24 4 1",
+"      c None",
+".     s light1        c grey90",
+"X     s main  c grey70",
+"o     s shadow        c grey50",
+"                        ",
+" ...................... ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXX..............XXXo ",
+" .XXX.XXXXXXXXXXXXoXXXo ",
+" .XXX.XXXXXXXXXXXXoXXXo ",
+" .XXX.XXXXXXXXXXXXoXXXo ",
+" .XXX.XXXXXXXXXXXXoXXXo ",
+" .XXX.XXXXXXXXXXXXoXXXo ",
+" .XXX.XXXXXXXXXXXXoXXXo ",
+" .XXX.XXXXXXXXXXXXoXXXo ",
+" .XXX.XXXXXXXXXXXXoXXXo ",
+" .XXX.XXXXXXXXXXXXoXXXo ",
+" .XXX.XXXXXXXXXXXXoXXXo ",
+" .XXX.XXXXXXXXXXXXoXXXo ",
+" .XXX.XXXXXXXXXXXXoXXXo ",
+" .XXX.oooooooooooooXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .XXXXXXXXXXXXXXXXXXXXo ",
+" .ooooooooooooooooooooo ",
+"                        "};
+
+/* arch-tag: 184ad626-ea69-40ae-839a-f5b5929ebb93 */

Index: lisp/mpc.el
===================================================================
RCS file: lisp/mpc.el
diff -N lisp/mpc.el
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ lisp/mpc.el 1 Dec 2009 04:04:38 -0000       1.1
@@ -0,0 +1,2601 @@
+;;; mpc.el --- A client for the Music Player Daemon   -*- coding: utf-8 -*-
+
+;; Copyright (C) 2006, 2007, 2008, 2009  Free Software Foundation, Inc.
+
+;; Author: Stefan Monnier <address@hidden>
+;; Keywords: multimedia
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This is an Emacs front end to the Music Player Daemon.
+
+;; It mostly provides a browser inspired from Rhythmbox for your music
+;; collection and also allows you to play the music you select.  The basic
+;; interface is somewhat unusual in that it does not focus on the
+;; playlist as much as on the browser.
+;; I play albums rather than songs and thus don't have much need for
+;; playlists, and it shows.  Playlist support exists, but is still limited.
+
+;; Bugs:
+
+;; - when reaching end/start of song while ffwd/rewind, it may get wedged,
+;;   signal an error, ... or when mpc-next/prev is called while ffwd/rewind.
+;; - MPD errors are not reported to the user.
+
+;; Todo:
+
+;; - add bindings/buttons/menuentries for the various commands.
+;; - mpc-undo
+;; - visual feedback for drag'n'drop
+;; - display/set `repeat' and `random' state (and  maybe also `crossfade').
+;; - allow multiple *mpc* sessions in the same Emacs to control different mpds.
+;; - look for .folder.png (freedesktop) or folder.jpg (XP) as well.
+;; - fetch album covers and lyrics from the web?
+;; - improve MPC-Status: better volume control, add a way to show/hide the
+;;   rest, plus add the buttons currently in the toolbar.
+;; - improve mpc-songs-mode's header-line column-headings so they can be
+;;   dragged to resize.
+;; - allow selecting several entries by drag-mouse.
+;; - poll less often
+;;   - use the `idle' command
+;;   - do the time-ticking locally (and sync every once in a while)
+;;   - look at the end of play time to make sure we notice the end
+;;     as soon as possible
+;; - better volume widget.
+;; - add synthesized tags.
+;;   e.g. pseudo-artist = artist + composer + performer.
+;;   e.g. pseudo-performer = performer or artist
+;;   e.g. rewrite artist "Foo bar & baz" to "Foo bar".
+;;   e.g. filename regexp -> compilation flag
+;; - window/buffer management.
+;; - menubar, tooltips, ...
+;; - add mpc-describe-song, mpc-describe-album, ...
+;; - add import/export commands (especially export to an MP3 player).
+;; - add a real notion of album (as opposed to just album-name):
+;;   if all songs with same album-name have same artist -> it's an album
+;;   else it's either several albums or a compilation album (or both),
+;;   in which case we could use heuristics or user provided info:
+;;   - if the user followed the 1-album = 1-dir idea, then we can group songs
+;;     by their directory to create albums.
+;;   - if a `compilation' flag is available, and if <=1 of the songs have it
+;;     set, then we can group songs by their artist to create albums.
+;;   - if two songs have the same track-nb and disk-nb, they're not in the
+;;     same album.  So from the set of songs with identical album names, we
+;;     can get a lower bound on the number of albums involved, and then see
+;;     which of those may be non-compilations, etc...
+;;   - use a special directory name for compilations.
+;;   - ask the web ;-)
+
+;;; Code:
+
+;; Prefixes used in this code:
+;; mpc-proc   : management of connection (in/out formatting, ...)
+;; mpc-status : auto-updated status info
+;; mpc-volume : stuff handling the volume widget
+;; mpc-cmd    : mpdlib abstraction
+
+;; UI-commands       : mpc-
+;; internal          : mpc--
+
+(eval-when-compile (require 'cl))
+
+;;; Backward compatibility.
+;; This code is meant for Emacs-CVS, so to get it to run on anything else,
+;; we need to define some more things.
+
+(unless (fboundp 'tool-bar-local-item)
+  (defun tool-bar-local-item (icon def key map &rest props)
+    (define-key-after map (vector key)
+      `(menu-item ,(symbol-name key) ,def
+                  :image ,(find-image
+                           `((:type xpm :file ,(concat icon ".xpm"))))
+                  ,@props))))
+
+(unless (fboundp 'process-put)
+  (defconst mpc-process-hash (make-hash-table :weakness 'key))
+  (defun process-put (proc prop val)
+    (let ((sym (gethash proc mpc-process-hash)))
+      (unless sym
+        (setq sym (puthash proc (make-symbol "mpc-proc-sym") 
mpc-process-hash)))
+      (put sym prop val)))
+  (defun process-get (proc prop)
+    (let ((sym (gethash proc mpc-process-hash)))
+      (when sym (get sym prop))))
+  (defun process-plist (proc)
+    (let ((sym (gethash proc mpc-process-hash)))
+      (when sym (symbol-plist sym)))))
+(unless (fboundp 'with-local-quit)
+  (defmacro with-local-quit (&rest body)
+    `(condition-case nil (let ((inhibit-quit nil)) ,@body)
+       (quit (setq quit-flag t) nil))))
+(unless (fboundp 'balance-windows-area)
+  (defalias 'balance-windows-area 'balance-windows))
+(unless (fboundp 'posn-object) (defalias 'posn-object 'ignore))
+(unless (fboundp 'buffer-local-value)
+  (defun buffer-local-value (var buf)
+    (with-current-buffer buf (symbol-value var))))
+      
+
+;;; Main code starts here.
+
+(defgroup mpc ()
+  "A Client for the Music Player Daemon."
+  :prefix "mpc-"
+  :group 'multimedia
+  :group 'applications)
+
+(defcustom mpc-browser-tags '(Genre Artist Album Playlist)
+  "Tags for which a browser buffer should be created by default."
+  :type '(repeat string))
+
+;;; Misc utils ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun mpc-assq-all (key alist)
+  (let ((res ()) val)
+    (dolist (elem alist)
+      (if (and (eq (car elem) key)
+               (not (member (setq val (cdr elem)) res)))
+          (push val res)))
+    (nreverse res)))
+  
+(defun mpc-union (&rest lists)
+  (let ((res (nreverse (pop lists))))
+    (dolist (list lists)
+      (let ((seen res))           ;Don't remove duplicates within each list.
+        (dolist (elem list)
+          (unless (member elem seen) (push elem res)))))
+    (nreverse res)))
+
+(defun mpc-intersection (l1 l2 &optional selectfun)
+  "Return L1 after removing all elements not found in L2.
+SELECTFUN if non-nil elements aren't compared directly, but instead they
+are passed through SELECTFUN before comparison."
+  (let ((res ()))
+    (if selectfun (setq l2 (mapcar selectfun l2)))
+    (dolist (elem l1)
+      (when (member (if selectfun (funcall selectfun elem) elem) l2)
+        (push elem res)))
+    (nreverse res)))
+
+(defun mpc-event-set-point (event)
+  (condition-case nil (posn-set-point (event-end event))
+    (error (condition-case nil (mouse-set-point event)
+             (error nil)))))
+
+(defun mpc-compare-strings (str1 str2 &optional ignore-case)
+  "Compare strings STR1 and STR2.
+Contrary to `compare-strings', this tries to get numbers sorted
+numerically rather than lexicographically."
+  (let ((res (compare-strings str1 nil nil str2 nil nil ignore-case)))
+    (if (not (integerp res)) res
+      (let ((index (1- (abs res))))
+        (if (or (>= index (length str1)) (>= index (length str2)))
+            res
+          (let ((digit1 (memq (aref str1 index)
+                              '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)))
+                (digit2 (memq (aref str2 index)
+                              '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9))))
+            (if digit1
+                (if digit2
+                    (let ((num1 (progn (string-match "[0-9]+" str1 index)
+                                       (match-string 0 str1)))
+                          (num2 (progn (string-match "[0-9]+" str2 index)
+                                       (match-string 0 str2))))
+                      (cond
+                       ;; Here we presume that leading zeroes are only used
+                       ;; for same-length numbers.  So we'll incorrectly
+                       ;; consider that "000" comes after "01", but I don't
+                       ;; think it matters.
+                       ((< (length num1) (length num2)) (- (abs res)))
+                       ((> (length num1) (length num2)) (abs res))
+                       ((< (string-to-number num1) (string-to-number num2))
+                        (- (abs res)))
+                       (t (abs res))))
+                  ;; "1a" comes before "10", but "0" comes before "a".
+                  (if (and (not (zerop index))
+                           (memq (aref str1 (1- index))
+                                 '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)))
+                      (abs res)
+                    (- (abs res))))
+              (if digit2
+                  ;; "1a" comes before "10", but "0" comes before "a".
+                  (if (and (not (zerop index))
+                           (memq (aref str1 (1- index))
+                                 '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)))
+                      (- (abs res))
+                    (abs res))
+                res))))))))
+
+(defun mpc-string-prefix-p (str1 str2)
+  ;; FIXME: copied from pcvs-util.el.
+  "Tell whether STR1 is a prefix of STR2."
+  (eq t (compare-strings str2 nil (length str1) str1 nil nil)))
+
+;; This can speed up mpc--song-search significantly.  The table may grow
+;; very large, tho.  It's only bounded by the fact that it gets flushed
+;; whenever the connection is established; which seems to work OK thanks
+;; to the fact that MPD tends to disconnect fairly often, although our
+;; constant polling often prevents disconnection.
+(defvar mpc--find-memoize (make-hash-table :test 'equal)) ;; :weakness t
+(defvar mpc-tag nil) (make-variable-buffer-local 'mpc-tag)
+
+;;; Support for the actual connection and MPD command execution ;;;;;;;;;;;;
+
+(defcustom mpc-host
+  (concat (or (getenv "MPD_HOST") "localhost")
+          (if (getenv "MPD_PORT") (concat ":" (getenv "MPD_PORT"))))
+  "Host (and port) where the Music Player Daemon is running.
+The format is \"HOST\" or \"HOST:PORT\" where PORT defaults to 6600
+and HOST default to localhost."
+  :type 'string)
+
+(defvar mpc-proc nil)
+
+(defconst mpc--proc-end-re "^\\(?:OK\\(?: MPD .*\\)?\\|ACK \\(.*\\)\\)\n")
+
+(put 'mpc-proc-error 'error-conditions '(mpc-proc-error error))
+(put 'mpc-proc-error 'error-message "MPD error")
+
+(defun mpc--debug (format &rest args)
+  (if (get-buffer "*MPC-debug*")
+      (with-current-buffer "*MPC-debug*"
+        (goto-char (point-max))
+        (insert-before-markers          ;So it scrolls.
+         (replace-regexp-in-string "\n" "\n    "
+                                   (apply 'format format args))
+         "\n"))))
+
+(defun mpc--proc-filter (proc string)
+  (mpc--debug "Receive \"%s\"" string)
+  (with-current-buffer (process-buffer proc)
+    (if (process-get proc 'ready)
+        (if nil ;; (string-match "\\`\\(OK\n\\)+\\'" string)
+            ;; I haven't figured out yet why I get those extraneous OKs,
+            ;; so I'll just ignore them for now.
+            nil
+          (delete-process proc)
+          (set-process-buffer proc nil)
+          (pop-to-buffer (clone-buffer))
+          (error "MPD output while idle!?"))
+      (save-excursion
+        (let ((start (or (marker-position (process-mark proc)) (point-min))))
+          (goto-char start)
+          (insert string)
+          (move-marker (process-mark proc) (point))
+          (beginning-of-line)
+          (when (and (< start (point))
+                     (re-search-backward mpc--proc-end-re start t))
+            (process-put proc 'ready t)
+            (unless (eq (match-end 0) (point-max))
+              (error "Unexpected trailing text"))
+            (let ((error (match-string 1)))
+              (delete-region (point) (point-max))
+              (let ((callback (process-get proc 'callback)))
+                (process-put proc 'callback nil)
+                (if error (signal 'mpc-proc-error error))
+                (funcall callback)))))))))
+
+(defun mpc--proc-connect (host)
+  (mpc--debug "Connecting to %s..." host)
+  (with-current-buffer (get-buffer-create (format " *mpc-%s*" host))
+    ;; (pop-to-buffer (current-buffer))
+    (let (proc)
+      (while (and (setq proc (get-buffer-process (current-buffer)))
+                  (progn ;; (debug)
+                         (delete-process proc)))))
+    (erase-buffer)
+    (let ((port 6600))
+      (when (string-match ":[^.]+\\'" host)
+        (setq port (substring host (1+ (match-beginning 0))))
+        (setq host (substring host 0 (match-beginning 0)))
+        (unless (string-match "[^[:digit:]]" port)
+          (setq port (string-to-number port))))
+      (let* ((coding-system-for-read 'utf-8-unix)
+             (coding-system-for-write 'utf-8-unix)
+             (proc (open-network-stream "MPC" (current-buffer) host port)))
+        (when (processp mpc-proc)
+          ;; Inherit the properties of the previous connection.
+          (let ((plist (process-plist mpc-proc)))
+            (while plist (process-put proc (pop plist) (pop plist)))))
+        (mpc-proc-buffer proc 'mpd-commands (current-buffer))
+        (process-put proc 'callback 'ignore)
+        (process-put proc 'ready nil)
+        (clrhash mpc--find-memoize)
+        (set-process-filter proc 'mpc--proc-filter)
+        (set-process-sentinel proc 'ignore)
+        (set-process-query-on-exit-flag proc nil)
+        ;; This may be called within a process filter ;-(
+        (with-local-quit (mpc-proc-sync proc))
+        proc))))
+
+(defun mpc--proc-quote-string (s)
+  (if (numberp s) (number-to-string s)
+    (setq s (replace-regexp-in-string "[\"\\]" "\\\\\\&" s))
+    (if (string-match " " s) (concat "\"" s "\"") s)))
+
+(defconst mpc--proc-alist-to-alists-starters '(file directory))
+
+(defun mpc--proc-alist-to-alists (alist)
+  (assert (or (null alist)
+              (memq (caar alist) mpc--proc-alist-to-alists-starters)))
+  (let ((starter (caar alist))
+        (alists ())
+        tmp)
+    (dolist (pair alist)
+      (when (eq (car pair) starter)
+        (if tmp (push (nreverse tmp) alists))
+        (setq tmp ()))
+      (push pair tmp))
+    (if tmp (push (nreverse tmp) alists))
+    (nreverse alists)))
+
+(defun mpc-proc ()
+  (or (and mpc-proc
+           (buffer-live-p (process-buffer mpc-proc))
+           (not (memq (process-status mpc-proc) '(closed)))
+           mpc-proc)
+      (setq mpc-proc (mpc--proc-connect mpc-host))))
+
+(defun mpc-proc-sync (&optional proc)
+  "Wait for MPC process until it is idle again.
+Return the buffer in which the process is/was running."
+  (unless proc (setq proc (mpc-proc)))
+  (unwind-protect
+      (condition-case err
+          (progn
+            (while (and (not (process-get proc 'ready))
+                        (accept-process-output proc)))
+            (if (process-get proc 'ready) (process-buffer proc)
+              ;; (delete-process proc)
+              (error "No response from MPD")))
+        (error (message "MPC: %s" err) (signal (car err) (cdr err))))
+    (unless (process-get proc 'ready)
+      ;; (debug)
+      (message "Killing hung process")
+      (delete-process proc))))
+
+(defun mpc-proc-cmd (cmd &optional callback)
+  "Send command CMD to the MPD server.
+If CALLBACK is nil, wait for the command to finish before returning,
+otherwise return immediately and call CALLBACK with no argument
+when the command terminates.
+CMD can be a string which is passed as-is to MPD or a list of strings
+which will be concatenated with proper quoting before passing them to MPD."
+  (let ((proc (mpc-proc)))
+    (if (and callback (not (process-get proc 'ready)))
+        (lexical-let ((old (process-get proc 'callback))
+                      (callback callback)
+                      (cmd cmd))
+          (process-put proc 'callback
+                       (lambda ()
+                         (funcall old)
+                         (mpc-proc-cmd cmd callback))))
+      ;; Wait for any pending async command to terminate.
+      (mpc-proc-sync proc)
+      (process-put proc 'ready nil)
+      (with-current-buffer (process-buffer proc)
+        (erase-buffer)
+        (mpc--debug "Send \"%s\"" cmd)
+        (process-send-string
+         proc (concat (if (stringp cmd) cmd
+                        (mapconcat 'mpc--proc-quote-string cmd " "))
+                      "\n")))
+      (if callback
+          (lexical-let ((buf (current-buffer))
+                        (callback callback))
+            (process-put proc 'callback
+                         callback
+                         ;; (lambda ()
+                         ;;   (funcall callback
+                         ;;            (prog1 (current-buffer)
+                         ;;              (set-buffer buf))))
+                         ))
+        ;; If `callback' is nil, we're executing synchronously.
+        (process-put proc 'callback 'ignore)
+        ;; This returns the process's buffer.
+        (mpc-proc-sync proc)))))
+
+;; This function doesn't exist in Emacs-21.
+;; (put 'mpc-proc-cmd-list 'byte-optimizer 'byte-optimize-pure-func)
+(defun mpc-proc-cmd-list (cmds)
+  (concat "command_list_begin\n"
+          (mapconcat (lambda (cmd)
+                       (if (stringp cmd) cmd
+                         (mapconcat 'mpc--proc-quote-string cmd " ")))
+                     cmds
+                     "\n")
+          "\ncommand_list_end"))
+
+(defun mpc-proc-cmd-list-ok ()
+  ;; To implement this, we'll need to tweak the process filter since we'd
+  ;; then sometimes get "trailing" text after "OK\n".
+  (error "Not implemented yet"))
+
+(defun mpc-proc-buf-to-alist (&optional buf)
+  (with-current-buffer (or buf (current-buffer))
+    (let ((res ()))
+      (goto-char (point-min))
+      (while (re-search-forward "^\\([^:]+\\): \\(.*\\)\n" nil t)
+        (push (cons (intern (match-string 1)) (match-string 2)) res))
+      (nreverse res))))
+
+(defun mpc-proc-buf-to-alists (buf)
+  (mpc--proc-alist-to-alists (mpc-proc-buf-to-alist buf)))
+
+(defun mpc-proc-cmd-to-alist (cmd &optional callback)
+  (if callback
+      (lexical-let ((buf (current-buffer))
+                    (callback callback))
+        (mpc-proc-cmd cmd (lambda ()
+                            (funcall callback (prog1 (mpc-proc-buf-to-alist
+                                                      (current-buffer))
+                                                (set-buffer buf))))))
+    ;; (lexical-let ((res nil))
+    ;;   (mpc-proc-cmd-to-alist cmd (lambda (alist) (setq res alist)))
+    ;;   (mpc-proc-sync)
+    ;;   res)
+    (mpc-proc-buf-to-alist (mpc-proc-cmd cmd))))
+
+(defun mpc-proc-tag-string-to-sym (tag)
+  (intern (capitalize tag)))
+
+(defun mpc-proc-buffer (proc use &optional buffer)
+  (let* ((bufs (process-get proc 'buffers))
+         (buf (cdr (assoc use bufs))))
+    (cond
+     ((and buffer (buffer-live-p buf) (not (eq buffer buf)))
+      (error "Duplicate MPC buffer for %s" use))
+     (buffer
+      (if buf
+          (setcdr (assoc use bufs) buffer)
+        (process-put proc 'buffers (cons (cons use buffer) bufs))))
+     (t buf))))
+
+;;; Support for regularly updated current status information ;;;;;;;;;;;;;;;
+
+;; Exported elements:
+;; `mpc-status' holds the uptodate data.
+;; `mpc-status-callbacks' holds the registered callback functions.
+;; `mpc-status-refresh' forces a refresh of the data.
+;; `mpc-status-stop' stops the automatic updating.
+
+(defvar mpc-status nil)
+(defvar mpc-status-callbacks
+  '((state  . mpc--status-timers-refresh)
+    ;; (song   . mpc--queue-refresh)
+    ;; (state  . mpc--queue-refresh)       ;To detect the end of the last song.
+    (state  . mpc--faster-toggle-refresh) ;Only ffwd/rewind while play/pause.
+    (volume . mpc-volume-refresh)
+    (file   . mpc-songpointer-refresh)
+    ;; The song pointer may need updating even if the file doesn't change,
+    ;; if the same song appears multiple times in a row.
+    (song   . mpc-songpointer-refresh)
+    (updating_db . mpc-updated-db)
+    (updating_db . mpc--status-timers-refresh)
+    (t      . mpc-current-refresh))
+  "Alist associating properties to the functions that care about them.
+Each entry has the form (PROP . FUN) where PROP can be t to mean
+to call FUN for any change whatsoever.")
+
+(defun mpc--status-callback ()
+  (let ((old-status mpc-status))
+    ;; Update the alist.
+    (setq mpc-status (mpc-proc-buf-to-alist))
+    (assert mpc-status)
+    (unless (equal old-status mpc-status)
+      ;; Run the relevant refresher functions.
+      (dolist (pair mpc-status-callbacks)
+        (when (or (eq t (car pair))
+                  (not (equal (cdr (assq (car pair) old-status))
+                              (cdr (assq (car pair) mpc-status)))))
+          (funcall (cdr pair)))))))
+
+(defvar mpc--status-timer nil)
+(defun mpc--status-timer-start ()
+  (add-hook 'pre-command-hook 'mpc--status-timer-stop)
+  (unless mpc--status-timer
+    (setq mpc--status-timer (run-with-timer 1 1 'mpc--status-timer-run))))
+(defun mpc--status-timer-stop ()
+  (when mpc--status-timer
+    (cancel-timer mpc--status-timer)
+    (setq mpc--status-timer nil)))
+(defun mpc--status-timer-run ()
+  (when (process-get (mpc-proc) 'ready)
+    (condition-case err
+        (with-local-quit (mpc-status-refresh))
+      (error (message "MPC: %s" err)))))
+
+(defvar mpc--status-idle-timer nil)
+(defun mpc--status-idle-timer-start ()
+  (when mpc--status-idle-timer
+    ;; Turn it off even if we'll start it again, in case it changes the delay.
+    (cancel-timer mpc--status-idle-timer))
+  (setq mpc--status-idle-timer
+        (run-with-idle-timer 1 t 'mpc--status-idle-timer-run))
+  ;; Typically, the idle timer is started from the mpc--status-callback,
+  ;; which is run asynchronously while we're already idle (we typically
+  ;; just started idling), so the timer itself will only be run the next
+  ;; time we idle :-(
+  ;; To work around that, we immediately start the repeat timer.
+  (mpc--status-timer-start))
+(defun mpc--status-idle-timer-stop (&optional really)
+  (when mpc--status-idle-timer
+    ;; Turn it off even if we'll start it again, in case it changes the delay.
+    (cancel-timer mpc--status-idle-timer))
+  (setq mpc--status-idle-timer
+        (unless really
+          ;; We don't completely stop the timer, so that if some other MPD
+          ;; client starts playback, we may get a chance to notice it.
+          (run-with-idle-timer 10 t 'mpc--status-idle-timer-run))))
+(defun mpc--status-idle-timer-run ()
+  (when (process-get (mpc-proc) 'ready)
+    (condition-case err
+        (with-local-quit (mpc-status-refresh))
+      (error (message "MPC: %s" err))))
+  (mpc--status-timer-start))
+
+(defun mpc--status-timers-refresh ()
+  "Start/stop the timers according to whether a song is playing."
+  (if (or (member (cdr (assq 'state mpc-status)) '("play"))
+          (cdr (assq 'updating_db mpc-status)))
+      (mpc--status-idle-timer-start)
+    (mpc--status-idle-timer-stop)
+    (mpc--status-timer-stop)))
+
+(defun mpc-status-refresh (&optional callback)
+  "Refresh `mpc-status'."
+  (lexical-let ((cb callback))
+    (mpc-proc-cmd (mpc-proc-cmd-list '("status" "currentsong"))
+                  (lambda ()
+                    (mpc--status-callback)
+                    (if cb (funcall cb))))))
+
+(defun mpc-status-stop ()
+  "Stop the autorefresh of `mpc-status'.
+This is normally used only when quitting MPC.
+Any call to `mpc-status-refresh' may cause it to be restarted."
+  (setq mpc-status nil)
+  (mpc--status-idle-timer-stop 'really)
+  (mpc--status-timer-stop))
+
+;;; A thin layer above the raw protocol commands ;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; (defvar mpc-queue nil)
+;; (defvar mpc-queue-back nil)
+
+;; (defun mpc--queue-head ()
+;;   (if (stringp (car mpc-queue)) (car mpc-queue) (cadar mpc-queue)))
+;; (defun mpc--queue-pop ()
+;;   (when mpc-queue                       ;Can be nil if out of sync.
+;;     (let ((song (car mpc-queue)))
+;;       (assert song)
+;;       (push (if (and (consp song) (cddr song))
+;;                 ;; The queue's first element is itself a list of
+;;                 ;; songs, where the first element isn't itself a song
+;;                 ;; but a description of the list.
+;;                 (prog1 (cadr song) (setcdr song (cddr song)))
+;;               (prog1 (if (consp song) (cadr song) song)
+;;                 (setq mpc-queue (cdr mpc-queue))))
+;;             mpc-queue-back)
+;;       (assert (stringp (car mpc-queue-back))))))
+
+;; (defun mpc--queue-refresh ()
+;;   ;; Maintain the queue.
+;;   (mpc--debug "mpc--queue-refresh")
+;;   (let ((pos (cdr (or (assq 'Pos mpc-status) (assq 'song mpc-status)))))
+;;     (cond
+;;      ((null pos)
+;;       (mpc-cmd-clear 'ignore))
+;;      ((or (not (member pos '("0" nil)))
+;;           ;; There's only one song in the playlist and we've stopped.
+;;           ;; Maybe it's because of some external client that set the
+;;           ;; playlist like that and/or manually stopped the playback, but
+;;           ;; it's more likely that we've simply reached the end of
+;;           ;; the song.  So remove it.
+;;           (and (equal (assq 'state mpc-status) "stop")
+;;                (equal (assq 'playlistlength mpc-status) "1")
+;;                (setq pos "1")))
+;;       ;; We're not playing the first song in the queue/playlist any
+;;       ;; more, so update the queue.
+;;       (dotimes (i (string-to-number pos)) (mpc--queue-pop))
+;;       (mpc-proc-cmd (mpc-proc-cmd-list
+;;                      (make-list (string-to-number pos) "delete 0"))
+;;                     'ignore)
+;;       (if (not (equal (cdr (assq 'file mpc-status))
+;;                       (mpc--queue-head)))
+;;           (message "MPC's queue is out of sync"))))))
+
+(defun mpc-cmd-find (tag value)
+  "Return a list of all songs whose tag TAG has value VALUE.
+The songs are returned as alists."
+  (or (gethash (cons tag value) mpc--find-memoize)
+      (puthash (cons tag value)
+               (cond
+                ((eq tag 'Playlist)
+                 ;; Special case for pseudo-tag playlist.
+                 (let ((l (mpc-proc-buf-to-alists
+                           (mpc-proc-cmd (list "listplaylistinfo" value))))
+                       (i 0))
+                   (mapcar (lambda (s)
+                             (prog1 (cons (cons 'Pos (number-to-string i)) s)
+                               (incf i)))
+                           l)))
+                ((eq tag 'Search)
+                 (mpc-proc-buf-to-alists
+                  (mpc-proc-cmd (list "search" "any" value))))
+                ((eq tag 'Directory)
+                 (let ((pairs
+                        (mpc-proc-buf-to-alist
+                         (mpc-proc-cmd (list "listallinfo" value)))))
+                   (mpc--proc-alist-to-alists
+                    ;; Strip away the `directory' entries.
+                    (delq nil (mapcar (lambda (pair)
+                                        (if (eq (car pair) 'directory)
+                                            nil pair))
+                                      pairs)))))
+                (t
+                 (condition-case err
+                     (mpc-proc-buf-to-alists
+                      (mpc-proc-cmd (list "find" (symbol-name tag) value)))
+                   (mpc-proc-error
+                    ;; If `tag' is not one of the expected tags, MPD burps
+                    ;; about not having the relevant table.  FIXME: check
+                    ;; the kind of error.
+                    (error "Unknown tag %s" tag)
+                    (let ((res ()))
+                      (setq value (cons tag value))
+                      (dolist (song (mpc-proc-buf-to-alists
+                                     (mpc-proc-cmd "listallinfo")))
+                        (if (member value song) (push song res)))
+                      res)))))
+               mpc--find-memoize)))
+
+(defun mpc-cmd-list (tag &optional other-tag value)
+  ;; FIXME: we could also provide a `mpc-cmd-list' alternative which
+  ;; doesn't take an "other-tag value" constraint but a "song-list" instead.
+  ;; That might be more efficient in some cases.
+  (cond
+   ((eq tag 'Playlist)
+    (let ((pls (mpc-assq-all 'playlist (mpc-proc-cmd-to-alist "lsinfo"))))
+      (when other-tag
+        (dolist (pl (prog1 pls (setq pls nil)))
+          (let ((plsongs (mpc-cmd-find 'Playlist pl)))
+            (if (not (member other-tag '(Playlist Search Directory)))
+                (when (member (cons other-tag value)
+                              (apply 'append plsongs))
+                  (push pl pls))
+              ;; Problem N°2: we compute the intersection whereas all
+              ;; we care about is whether it's empty.  So we could
+              ;; speed this up significantly.
+              ;; We only compare file names, because the full song-entries
+              ;; are slightly different (the ones in plsongs include
+              ;; position and id info specific to the playlist), and it's
+              ;; good enough because this is only used with "search", which
+              ;; doesn't pay attention to playlists and URLs anyway.
+              (let* ((osongs (mpc-cmd-find other-tag value))
+                     (ofiles (mpc-assq-all 'file (apply 'append osongs)))
+                     (plfiles (mpc-assq-all 'file (apply 'append plsongs))))
+                (when (mpc-intersection plfiles ofiles)
+                  (push pl pls)))))))
+      pls))
+
+   ((eq tag 'Directory)
+    (if (null other-tag)
+        (apply 'nconc
+               (mpc-assq-all 'directory
+                             (mpc-proc-buf-to-alist
+                              (mpc-proc-cmd "lsinfo")))
+               (mapcar (lambda (dir)
+                         (let ((shortdir
+                                (if (get-text-property 0 'display dir)
+                                    (concat "   "
+                                            (get-text-property 0 'display dir))
+                                  " ↪ "))
+                               (subdirs
+                                (mpc-assq-all 'directory
+                                              (mpc-proc-buf-to-alist
+                                               (mpc-proc-cmd (list "lsinfo" 
dir))))))
+                           (dolist (subdir subdirs)
+                             (put-text-property 0 (1+ (length dir))
+                                                'display shortdir
+                                                subdir))
+                           subdirs))
+                       (process-get (mpc-proc) 'Directory)))
+      ;; If there's an other-tag, then just extract the dir info from the
+      ;; list of other-tag's songs.
+      (let* ((other-songs (mpc-cmd-find other-tag value))
+             (files (mpc-assq-all 'file (apply 'append other-songs)))
+             (dirs '()))
+        (dolist (file files)
+          (let ((dir (file-name-directory file)))
+            (if (and dir (setq dir (directory-file-name dir))
+                     (not (equal dir (car dirs))))
+                (push dir dirs))))
+        ;; Dirs might have duplicates still.
+        (setq dirs (delete-dups dirs))
+        (let ((newdirs dirs))
+          (while newdirs
+            (let ((dir (file-name-directory (pop newdirs))))
+              (when (and dir (setq dir (directory-file-name dir))
+                         (not (member dir dirs)))
+                (push dir newdirs)
+                (push dir dirs)))))
+        dirs)))
+
+   ;; The UI should not provide access to such a thing anyway currently.
+   ;; But I could imagine adding in the future a browser for the "search"
+   ;; tag, which would provide things like previous searches.  Not sure how
+   ;; useful that would be tho.
+   ((eq tag 'Search) (error "Not supported"))
+
+   ((null other-tag)
+    (condition-case nil
+        (mapcar 'cdr (mpc-proc-cmd-to-alist (list "list" (symbol-name tag))))
+      (mpc-proc-error
+       ;; If `tag' is not one of the expected tags, MPD burps about not
+       ;; having the relevant table.
+       ;; FIXME: check the kind of error.
+       (error "MPD does not know this tag %s" tag)
+       (mpc-assq-all tag (mpc-proc-cmd-to-alist "listallinfo")))))
+   (t
+    (condition-case nil
+        (if (member other-tag '(Search Playlist Directory))
+            (signal 'mpc-proc-error "Not implemented")
+          (mapcar 'cdr
+                  (mpc-proc-cmd-to-alist
+                   (list "list" (symbol-name tag)
+                         (symbol-name other-tag) value))))
+      (mpc-proc-error
+       ;; DAMN!! the 3-arg form of `list' is new in 0.12 !!
+       ;; FIXME: check the kind of error.
+       (let ((other-songs (mpc-cmd-find other-tag value)))
+         (mpc-assq-all tag
+                       ;; Don't use `nconc' now that mpc-cmd-find may
+                       ;; return a memoized result.
+                       (apply 'append other-songs))))))))
+
+(defun mpc-cmd-stop (&optional callback)
+  (mpc-proc-cmd "stop" callback))
+
+(defun mpc-cmd-clear (&optional callback)
+  (mpc-proc-cmd "clear" callback)
+  ;; (setq mpc-queue-back nil mpc-queue nil)
+  )
+
+(defun mpc-cmd-pause (&optional arg callback)
+  "Pause or resume playback of the queue of songs."
+  (lexical-let ((cb callback))
+    (mpc-proc-cmd (list "pause" arg)
+                  (lambda () (mpc-status-refresh) (if cb (funcall cb))))
+    (unless callback (mpc-proc-sync))))
+
+(defun mpc-cmd-status ()
+  (mpc-proc-cmd-to-alist "status"))
+
+(defun mpc-cmd-play ()
+  (mpc-proc-cmd "play")
+  (mpc-status-refresh))
+
+(defun mpc-cmd-add (files &optional playlist)
+  "Add the songs FILES to PLAYLIST.
+If PLAYLIST is t or nil or missing, use the main playlist."
+  (mpc-proc-cmd (mpc-proc-cmd-list
+                 (mapcar (lambda (file)
+                           (if (stringp playlist)
+                               (list "playlistadd" playlist file)
+                             (list "add" file)))
+                         files)))
+    (if (stringp playlist)
+        (puthash (cons 'Playlist playlist) nil mpc--find-memoize)))
+
+(defun mpc-cmd-delete (song-poss &optional playlist)
+  "Delete the songs at positions SONG-POSS from PLAYLIST.
+If PLAYLIST is t or nil or missing, use the main playlist."
+  (mpc-proc-cmd (mpc-proc-cmd-list
+                 (mapcar (lambda (song-pos)
+                           (if (stringp playlist)
+                               (list "playlistdelete" playlist song-pos)
+                             (list "delete" song-pos)))
+                         ;; Sort them from last to first, so the renumbering
+                         ;; caused by the earlier deletions don't affect
+                         ;; later ones.
+                         (sort song-poss '>))))
+    (if (stringp playlist)
+        (puthash (cons 'Playlist playlist) nil mpc--find-memoize)))
+  
+
+(defun mpc-cmd-move (song-poss dest-pos &optional playlist)
+  (let ((i 0))
+    (mpc-proc-cmd
+     (mpc-proc-cmd-list
+      (mapcar (lambda (song-pos)
+                (if (>= song-pos dest-pos)
+                    ;; positions past dest-pos have been
+                    ;; shifted by i.
+                    (setq song-pos (+ song-pos i)))
+                (prog1 (if (stringp playlist)
+                           (list "playlistmove" playlist song-pos dest-pos)
+                         (list "move" song-pos dest-pos))
+                  (if (< song-pos dest-pos)
+                      ;; This move has shifted dest-pos by 1.
+                      (decf dest-pos))
+                  (incf i)))
+              ;; Sort them from last to first, so the renumbering
+              ;; caused by the earlier deletions affect
+              ;; later ones a bit less.
+              (sort song-poss '>))))
+    (if (stringp playlist)
+        (puthash (cons 'Playlist playlist) nil mpc--find-memoize))))
+
+(defun mpc-cmd-update (&optional arg callback)
+  (lexical-let ((cb callback))
+    (mpc-proc-cmd (if arg (list "update" arg) "update")
+                  (lambda () (mpc-status-refresh) (if cb (funcall cb))))
+    (unless callback (mpc-proc-sync))))
+
+(defun mpc-cmd-tagtypes ()
+  (mapcar 'cdr (mpc-proc-cmd-to-alist "tagtypes")))
+
+;; This was never integrated into MPD.
+;; (defun mpc-cmd-download (file)
+;;   (with-current-buffer (generate-new-buffer " *mpc download*")
+;;     (set-buffer-multibyte nil)
+;;     (let* ((proc (mpc-proc))
+;;            (stdbuf (process-buffer proc))
+;;            (markpos (marker-position (process-mark proc)))
+;;            (stdcoding (process-coding-system proc)))
+;;       (unwind-protect
+;;           (progn
+;;             (set-process-buffer proc (current-buffer))
+;;             (set-process-coding-system proc 'binary (cdr stdcoding))
+;;             (set-marker (process-mark proc) (point))
+;;             (mpc-proc-cmd (list "download" file)))
+;;         (set-process-buffer proc stdbuf)
+;;         (set-marker (process-mark proc) markpos stdbuf)
+;;         (set-process-coding-system proc (car stdcoding) (cdr stdcoding)))
+;;       ;; The command has completed, let's decode.
+;;       (goto-char (point-max))
+;;       (delete-char -1)                    ;Delete final newline.
+;;       (while (re-search-backward "^>" nil t)
+;;         (delete-char 1))
+;;       (current-buffer))))
+
+;;; Misc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defcustom mpc-mpd-music-directory nil
+  "Location of MPD's music directory."
+  :type '(choice (const nil) directory))
+
+(defcustom mpc-data-directory
+  (if (and (not (file-directory-p "~/.mpc"))
+           (file-directory-p "~/.emacs.d"))
+      "~/.emacs.d/mpc" "~/.mpc")
+  "Directory where MPC.el stores auxiliary data."
+  :type 'directory)
+
+(defun mpc-data-directory ()
+  (unless (file-directory-p mpc-data-directory)
+    (make-directory mpc-data-directory))
+  mpc-data-directory)
+
+(defun mpc-file-local-copy (file)
+  ;; Try to set mpc-mpd-music-directory.
+  (when (and (null mpc-mpd-music-directory)
+             (string-match "\\`localhost" mpc-host))
+    (let ((files '("~/.mpdconf" "/etc/mpd.conf"))
+          file)
+      (while (and files (not file))
+        (if (file-exists-p (car files)) (setq file (car files)))
+        (setq files (cdr files)))
+      (with-temp-buffer
+        (ignore-errors (insert-file-contents file))
+        (goto-char (point-min))
+        (if (re-search-forward "^music_directory[      ]+\"\\([^\"]+\\)\"")
+            (setq mpc-mpd-music-directory
+                  (match-string 1))))))
+  ;; Use mpc-mpd-music-directory if applicable, or else try to use the
+  ;; `download' command, although it's never been accepted in `mpd' :-(
+  (if (and mpc-mpd-music-directory
+           (file-exists-p (expand-file-name file mpc-mpd-music-directory)))
+      (expand-file-name file mpc-mpd-music-directory)
+    ;; (let ((aux (expand-file-name (replace-regexp-in-string "[/]" "|" file)
+    ;;                              (mpc-data-directory))))
+    ;;   (unless (file-exists-p aux)
+    ;;     (condition-case err
+    ;;         (with-local-quit
+    ;;           (with-current-buffer (mpc-cmd-download file)
+    ;;             (write-region (point-min) (point-max) aux)
+    ;;             (kill-buffer (current-buffer))))
+    ;;       (mpc-proc-error (message "Download error: %s" err) (setq aux 
nil))))
+    ;;   aux)
+    ))
+
+;;; Formatter ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun mpc-secs-to-time (secs)
+  (if (stringp secs) (setq secs (string-to-number secs)))
+  (if (>= secs (* 60 100))              ;More than 100 minutes.
+      (format "%dh%02d" ;"%d:%02d:%02d"
+              (/ secs 3600) (% (/ secs 60) 60)) ;; (% secs 60)
+    (format "%d:%02d" (/ secs 60) (% secs 60))))
+
+(defvar mpc-tempfiles nil)
+(defconst mpc-tempfiles-reftable (make-hash-table :weakness 'key))
+
+(defun mpc-tempfiles-clean ()
+  (let ((live ()))
+    (maphash (lambda (k v) (push v live)) mpc-tempfiles-reftable)
+    (dolist (f mpc-tempfiles)
+      (unless (member f live) (ignore-errors (delete-file f))))
+    (setq mpc-tempfiles live)))
+
+(defun mpc-tempfiles-add (key file)
+  (mpc-tempfiles-clean)
+  (puthash key file mpc-tempfiles-reftable)
+  (push file mpc-tempfiles))
+
+(defun mpc-format (format-spec info &optional hscroll)
+  "Format the INFO according to FORMAT-SPEC, inserting the result at point."
+  (let* ((pos 0)
+         (start (point))
+         (col (if hscroll (- hscroll) 0))
+         (insert (lambda (str)
+                   (cond
+                    ((>= col 0) (insert str))
+                    (t (insert (substring str (min (length str) (- col))))))))
+         (pred nil))
+    (while (string-match 
"%\\(?:%\\|\\(-\\)?\\([0-9]+\\)?{\\([[:alpha:]][[:alnum:]]*\\)\\(?:-\\([^}]+\\)\\)?}\\)"
 format-spec pos)
+      (let ((pre-text (substring format-spec pos (match-beginning 0))))
+        (funcall insert pre-text)
+        (setq col (+ col (string-width pre-text))))
+      (setq pos (match-end 0))
+      (if (null (match-end 3))
+          (progn
+            (funcall insert "%")
+            (setq col (+ col 1)))
+        (let* ((size (match-string 2 format-spec))
+               (tag (intern (match-string 3 format-spec)))
+               (post (match-string 4 format-spec))
+               (right-align (match-end 1))
+               (text
+                (if (eq info 'self) (symbol-name tag)
+                  (case tag
+                    ((Time Duration)
+                     (let ((time (cdr (or (assq 'time info) (assq 'Time 
info)))))
+                       (setq pred (list nil)) ;Just assume it's never eq.
+                       (when time
+                         (mpc-secs-to-time (if (and (eq tag 'Duration)
+                                                    (string-match ":" time))
+                                               (substring time (match-end 0))
+                                             time)))))
+                    (Cover
+                     (let* ((dir (file-name-directory (cdr (assq 'file info))))
+                            (cover (concat dir "cover.jpg"))
+                            (file (condition-case err
+                                      (mpc-file-local-copy cover)
+                                    (error (message "MPC: %s" err))))
+                            image)
+                       ;; (debug)
+                       (push `(equal ',dir (file-name-directory (cdr (assq 
'file info)))) pred)
+                       (if (null file)
+                           ;; Make sure we return something on which we can
+                           ;; place the `mpc-pred' property, as
+                           ;; a negative-cache.  We could also use
+                           ;; a default cover.
+                           (progn (setq size nil) " ")
+                         (if (null size) (setq image (create-image file))
+                           (let ((tempfile (make-temp-file "mpc" nil ".jpg")))
+                             (call-process "convert" nil nil nil
+                                           "-scale" size file tempfile)
+                             (setq image (create-image tempfile))
+                             (mpc-tempfiles-add image tempfile)))
+                         (setq size nil)
+                         (propertize dir 'display image))))
+                    (t (let ((val (cdr (assq tag info))))
+                         ;; For Streaming URLs, there's no other info
+                         ;; than the URL in `file'.  Pretend it's in `Title'.
+                         (when (and (null val) (eq tag 'Title))
+                           (setq val (cdr (assq 'file info))))
+                         (push `(equal ',val (cdr (assq ',tag info))) pred)
+                         val)))))
+               (space (when size
+                        (setq size (string-to-number size))
+                        (propertize " " 'display
+                                    (list 'space :align-to (+ col size)))))
+               (textwidth (if text (string-width text) 0))
+               (postwidth (if post (string-width post) 0)))
+          (when text
+            (let ((display
+                   (if (and size
+                            (> (+ postwidth textwidth) size))
+                       ;; This doesn't even obey double-width chars :-(
+                       (propertize
+                        (if (zerop (- size postwidth 1))
+                            (substring text 0 1)
+                          (concat (substring text 0 (- size postwidth 
textwidth 1)) "…"))
+                        'help-echo text)
+                     text)))
+              (when (memq tag '(Artist Album Composer)) ;FIXME: wrong list.
+                (setq display
+                      (propertize display
+                                  'mouse-face 'highlight
+                                  'follow-link t
+                                  'keymap `(keymap
+                                            (mouse-2
+                                             . (lambda ()
+                                                 (interactive)
+                                                 (mpc-constraints-push 
'noerror)
+                                                 (mpc-constraints-restore
+                                                  ',(list (list tag 
text)))))))))
+              (funcall insert
+                       (concat (when size
+                                 (propertize " " 'display
+                                             (list 'space :align-to
+                                                   (+ col
+                                                      (if (and size 
right-align)
+                                                          (- size postwidth 
textwidth)
+                                                        0)))))
+                               display post))))
+          (if (null size) (setq col (+ col textwidth postwidth))
+            (insert space)
+            (setq col (+ col size))))))
+    (put-text-property start (point) 'mpc-pred
+                       `(lambda (info) (and ,@(nreverse pred))))))
+                  
+;;; The actual UI code ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defvar mpc-mode-map
+  (let ((map (make-keymap)))
+    (suppress-keymap map)
+    ;; (define-key map "\e" 'mpc-stop)
+    (define-key map "q" 'mpc-quit)
+    (define-key map "\r" 'mpc-select)
+    (define-key map [(shift return)] 'mpc-select-toggle)
+    (define-key map [mouse-2] 'mpc-select)
+    (define-key map [S-mouse-2] 'mpc-select-extend)
+    (define-key map [C-mouse-2] 'mpc-select-toggle)
+    (define-key map [drag-mouse-2] 'mpc-drag-n-drop)
+    ;; We use `always' because a binding to t is like a binding to nil.
+    (define-key map [follow-link] 'always)
+    ;; Doesn't work because the first click changes the buffer, so the second
+    ;; is applied elsewhere :-(
+    ;; (define-key map [(double mouse-2)] 'mpc-play-at-point)
+    (define-key map "p" 'mpc-pause)
+    map))
+
+(easy-menu-define mpc-mode-menu mpc-mode-map
+  "Menu for MPC.el."
+  '("MPC.el"
+    ["Add new browser" mpc-tagbrowser]
+    ["Update DB" mpc-update]
+    ["Quit" mpc-quit]))
+
+(defvar mpc-tool-bar-map
+  (let ((map (make-sparse-keymap)))
+    (tool-bar-local-item "mpc/prev" 'mpc-prev 'prev map
+     :enable '(not (equal (cdr (assq 'state mpc-status)) "stop")))
+    ;; FIXME: how can we bind it to the down-event?
+    (tool-bar-local-item "mpc/rewind" 'mpc-rewind 'rewind map
+     :enable '(not (equal (cdr (assq 'state mpc-status)) "stop"))
+     :button '(:toggle . (and mpc--faster-toggle-timer
+                             (not mpc--faster-toggle-forward))))
+    ;; We could use a single toggle command for pause/play, with 2 different
+    ;; icons depending on whether or not it's selected, but then it'd have
+    ;; to be a toggle-button, thus displayed depressed in one of the
+    ;; two states :-(
+    (tool-bar-local-item "mpc/pause" 'mpc-pause 'pause map
+     :visible '(equal (cdr (assq 'state mpc-status)) "play")
+     :help "Pause/play")
+    (tool-bar-local-item "mpc/play" 'mpc-play 'play map
+     :visible '(not (equal (cdr (assq 'state mpc-status)) "play"))
+     :help "Play/pause")
+    ;; FIXME: how can we bind it to the down-event?
+    (tool-bar-local-item "mpc/ffwd" 'mpc-ffwd 'ffwd map
+     :enable '(not (equal (cdr (assq 'state mpc-status)) "stop"))
+     :button '(:toggle . (and mpc--faster-toggle-timer
+                             mpc--faster-toggle-forward)))
+    (tool-bar-local-item "mpc/next" 'mpc-next 'next map
+     :enable '(not (equal (cdr (assq 'state mpc-status)) "stop")))
+    (tool-bar-local-item "mpc/stop" 'mpc-stop 'stop map)
+    (tool-bar-local-item "mpc/add" 'mpc-playlist-add 'add map
+     :help "Append to the playlist")
+    map))
+
+(define-derived-mode mpc-mode fundamental-mode "MPC"
+  "Major mode for the features common to all buffers of MPC."
+  (buffer-disable-undo)
+  (setq buffer-read-only t)
+  (set (make-local-variable 'tool-bar-map) mpc-tool-bar-map)
+  (set (make-local-variable 'truncate-lines) t))
+
+;;; The mpc-status-mode buffer ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define-derived-mode mpc-status-mode mpc-mode "MPC-Status"
+  "Major mode to display MPC status info."
+  (set (make-local-variable 'mode-line-format)
+       '("%e" mode-line-frame-identification mode-line-buffer-identification))
+  (set (make-local-variable 'window-area-factor) 3)
+  (set (make-local-variable 'header-line-format) '("MPC " mpc-volume)))
+
+(defvar mpc-status-buffer-format
+  '("%-5{Time} / %{Duration} %2{Disc--}%4{Track}" "%{Title}" "%{Album}" 
"%{Artist}" "%128{Cover}"))
+
+(defun mpc-status-buffer-refresh ()
+  (let ((buf (mpc-proc-buffer (mpc-proc) 'status)))
+    (when (buffer-live-p buf)
+      (with-current-buffer buf
+        (save-excursion
+          (goto-char (point-min))
+          (when (assq 'file mpc-status)
+            (let ((inhibit-read-only t))
+              (dolist (spec mpc-status-buffer-format)
+                (let ((pred (get-text-property (point) 'mpc-pred)))
+                  (if (and pred (funcall pred mpc-status))
+                      (forward-line)
+                    (delete-region (point) (line-beginning-position 2))
+                    (ignore-errors (mpc-format spec mpc-status))
+                    (insert "\n"))))
+              (unless (eobp) (delete-region (point) (point-max))))))))))
+
+(defun mpc-status-buffer-show ()
+  (interactive)
+  (let* ((buf (mpc-proc-buffer (mpc-proc) 'status))
+         (songs-buf (mpc-proc-buffer (mpc-proc) 'songs))
+         (songs-win (if songs-buf (get-buffer-window songs-buf 0))))
+    (unless (buffer-live-p buf)
+      (setq buf (get-buffer-create "*MPC-Status*"))
+      (with-current-buffer buf
+        (mpc-status-mode))
+      (mpc-proc-buffer (mpc-proc) 'status buf))
+    (if (null songs-win) (pop-to-buffer buf)
+      (let ((win (split-window songs-win 20 t)))
+        (set-window-dedicated-p songs-win nil)
+        (set-window-buffer songs-win buf)
+        (set-window-dedicated-p songs-win 'soft)))))
+
+;;; Selection management;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defvar mpc-separator-ol nil)
+
+(defvar mpc-select nil)
+(make-variable-buffer-local 'mpc-select)
+
+(defmacro mpc-select-save (&rest body)
+  "Execute BODY and restore the selection afterwards."
+  (declare (indent 0) (debug t))
+  `(let ((selection (mpc-select-get-selection))
+         (position (cons (buffer-substring-no-properties
+                          (line-beginning-position) (line-end-position))
+                         (current-column))))
+     ,@body
+     (mpc-select-restore selection)
+     (goto-char (point-min))
+     (if (re-search-forward
+          (concat "^" (regexp-quote (car position)) "$")
+          (if (overlayp mpc-separator-ol)
+              (overlay-end mpc-separator-ol))
+          t)
+         (move-to-column (cdr position)))
+     (let ((win (get-buffer-window (current-buffer) 0)))
+       (if win (set-window-point win (point))))))
+
+(defun mpc-select-get-selection ()
+  (mapcar (lambda (ol)
+            (buffer-substring-no-properties
+             (overlay-start ol) (1- (overlay-end ol))))
+          mpc-select))
+
+(defun mpc-select-restore (selection)
+  ;; Restore the selection.  I.e. move the overlays back to their
+  ;; corresponding location.  Actually which overlay is used for what
+  ;; doesn't matter.
+  (mapc 'delete-overlay mpc-select)
+  (setq mpc-select nil)
+  (dolist (elem selection)
+    ;; After an update, some elements may have disappeared.
+    (goto-char (point-min))
+    (when (re-search-forward
+           (concat "^" (regexp-quote elem) "$") nil t)
+      (mpc-select-make-overlay)))
+  (when mpc-tag (mpc-tagbrowser-all-select))
+  (beginning-of-line))
+
+(defun mpc-select-make-overlay ()
+  (assert (not (get-char-property (point) 'mpc-select)))
+  (let ((ol (make-overlay
+             (line-beginning-position) (line-beginning-position 2))))
+    (overlay-put ol 'mpc-select t)
+    (overlay-put ol 'face 'region)
+    (overlay-put ol 'evaporate t)
+    (push ol mpc-select)))
+
+(defun mpc-select (&optional event)
+  "Select the tag value at point."
+  (interactive (list last-nonmenu-event))
+  (mpc-event-set-point event)
+  (if (and (bolp) (eobp)) (forward-line -1))
+  (mapc 'delete-overlay mpc-select)
+  (setq mpc-select nil)
+  (if (mpc-tagbrowser-all-p)
+      nil
+    (mpc-select-make-overlay))
+  (when mpc-tag
+    (mpc-tagbrowser-all-select)
+    (mpc-selection-refresh)))
+
+(defun mpc-select-toggle (&optional event)
+  "Toggle the selection of the tag value at point."
+  (interactive (list last-nonmenu-event))
+  (mpc-event-set-point event)
+  (save-excursion
+    (cond
+     ;; The line is already selected: deselect it.
+     ((get-char-property (point) 'mpc-select)
+      (let ((ols nil))
+        (dolist (ol mpc-select)
+          (if (and (<= (overlay-start ol) (point))
+                   (> (overlay-end ol) (point)))
+              (delete-overlay ol)
+            (push ol ols)))
+        (assert (= (1+ (length ols)) (length mpc-select)))
+        (setq mpc-select ols)))
+     ;; We're trying to select *ALL* additionally to others.
+     ((mpc-tagbrowser-all-p) nil)
+     ;; Select the current line.
+     (t (mpc-select-make-overlay))))
+  (when mpc-tag
+    (mpc-tagbrowser-all-select)
+    (mpc-selection-refresh)))
+
+(defun mpc-select-extend (&optional event)
+  "Extend the selection up to point."
+  (interactive (list last-nonmenu-event))
+  (mpc-event-set-point event)
+  (if (null mpc-select)
+      ;; If nothing's selected yet, fallback to selecting the elem at point.
+      (mpc-select event)
+    (save-excursion
+      (cond
+       ;; The line is already in a selected area; truncate the area.
+       ((get-char-property (point) 'mpc-select)
+        (let ((before 0)
+              (after 0)
+              (mid (line-beginning-position))
+              start end)
+          (while (and (zerop (forward-line 1))
+                      (get-char-property (point) 'mpc-select))
+            (setq end (1+ (point)))
+            (incf after))
+          (goto-char mid)
+          (while (and (zerop (forward-line -1))
+                      (get-char-property (point) 'mpc-select))
+            (setq start (point))
+            (incf before))
+          (if (and (= after 0) (= before 0))
+              ;; Shortening an already minimum-size region: do nothing.
+              nil
+            (if (> after before)
+                (setq end mid)
+              (setq start (1+ mid)))
+            (let ((ols '()))
+              (dolist (ol mpc-select)
+                (if (and (>= (overlay-start ol) start)
+                         (< (overlay-start ol) end))
+                    (delete-overlay ol)
+                  (push ol ols)))
+              (setq mpc-select (nreverse ols))))))
+       ;; Extending a prior area.  Look for the closest selection.
+       (t
+        (when (mpc-tagbrowser-all-p)
+          (forward-line 1))
+        (let ((before 0)
+              (count 0)
+              (dir 1)
+              (start (line-beginning-position)))
+          (while (and (zerop (forward-line 1))
+                      (not (get-char-property (point) 'mpc-select)))
+            (incf count))
+          (unless (get-char-property (point) 'mpc-select)
+            (setq count nil))
+          (goto-char start)
+          (while (and (zerop (forward-line -1))
+                      (not (get-char-property (point) 'mpc-select)))
+            (incf before))
+          (unless (get-char-property (point) 'mpc-select)
+            (setq before nil))
+          (when (and before (or (null count) (< before count)))
+            (setq count before)
+            (setq dir -1))
+          (goto-char start)
+          (dotimes (i (1+ (or count 0)))
+            (mpc-select-make-overlay)
+            (forward-line dir))))))
+    (when mpc-tag
+      (mpc-tagbrowser-all-select)
+      (mpc-selection-refresh))))
+
+;;; Constraint sets ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defvar mpc--song-search nil)
+
+(defun mpc-constraints-get-current (&optional avoid-buf)
+  "Return currently selected set of constraints.
+If AVOID-BUF is non-nil, it specifies a buffer which should be ignored
+when constructing the set of constraints."
+  (let ((constraints (if mpc--song-search `((Search ,mpc--song-search))))
+        tag select)
+    (dolist (buf (process-get (mpc-proc) 'buffers))
+      (setq buf (cdr buf))
+      (when (and (setq tag (buffer-local-value 'mpc-tag buf))
+                 (not (eq buf avoid-buf))
+                 (setq select
+                       (with-current-buffer buf (mpc-select-get-selection))))
+        (push (cons tag select) constraints)))
+    constraints))
+
+(defun mpc-constraints-restore (constraints)
+  (let ((search (assq 'Search constraints)))
+    (setq mpc--song-search (cadr search))
+    (when search (setq constraints (delq search constraints))))
+  (dolist (buf (process-get (mpc-proc) 'buffers))
+    (setq buf (cdr buf))
+    (when (buffer-live-p buf)
+      (let* ((tag (buffer-local-value 'mpc-tag buf))
+             (constraint (assq tag constraints)))
+        (when tag
+          (with-current-buffer buf
+            (mpc-select-restore (cdr constraint)))))))
+  (mpc-selection-refresh))
+
+;; I don't get the ring.el code.  I think it doesn't do what I need, but
+;; then I don't understand when what it does would be useful.
+(defun mpc-ring-make (size) (cons 0 (cons 0 (make-vector size nil))))
+(defun mpc-ring-push (ring val)
+  (aset (cddr ring) (car ring) val)
+  (setcar (cdr ring) (max (cadr ring) (1+ (car ring))))
+  (setcar ring (mod (1+ (car ring)) (length (cddr ring)))))
+(defun mpc-ring-pop (ring)
+  (setcar ring (mod (1- (car ring)) (cadr ring)))
+  (aref (cddr ring) (car ring)))
+
+(defvar mpc-constraints-ring (mpc-ring-make 10))
+
+(defun mpc-constraints-push (&optional noerror)
+  "Push the current selection on the ring for later."
+  (interactive)
+  (let ((constraints (mpc-constraints-get-current)))
+    (if (null constraints)
+        (unless noerror (error "No selection to push"))
+      (mpc-ring-push mpc-constraints-ring constraints))))
+
+(defun mpc-constraints-pop ()
+  "Recall the most recently pushed selection."
+  (interactive)
+  (let ((constraints (mpc-ring-pop mpc-constraints-ring)))
+    (if (null constraints)
+        (error "No selection to return to")
+      (mpc-constraints-restore constraints))))
+
+;;; The TagBrowser mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defconst mpc-tagbrowser-all-name (propertize "*ALL*" 'face 'italic))
+(defvar mpc-tagbrowser-all-ol nil)
+(make-variable-buffer-local 'mpc-tagbrowser-all-ol)
+(defvar mpc-tag-name nil) (make-variable-buffer-local 'mpc-tag-name)
+(defun mpc-tagbrowser-all-p ()
+  (and (eq (point-min) (line-beginning-position))
+       (equal mpc-tagbrowser-all-name
+              (buffer-substring (point-min) (line-end-position)))))
+
+(define-derived-mode mpc-tagbrowser-mode mpc-mode '("MPC-" mpc-tag-name)
+  (set (make-local-variable 'mode-line-process) '("" mpc-tag-name))
+  (set (make-local-variable 'mode-line-format) nil)
+  (set (make-local-variable 'header-line-format) '("" mpc-tag-name ;; "s"
+                                                   ))
+  (set (make-local-variable 'buffer-undo-list) t)
+  )
+
+(defun mpc-tagbrowser-refresh ()
+  (mpc-select-save
+    (widen)
+    (goto-char (point-min))
+    (assert (looking-at (regexp-quote mpc-tagbrowser-all-name)))
+    (forward-line 1)
+    (let ((inhibit-read-only t))
+      (delete-region (point) (point-max))
+      (dolist (val (mpc-cmd-list mpc-tag)) (insert val "\n")))
+    (set-buffer-modified-p nil))
+  (mpc-reorder))
+
+(defun mpc-updated-db ()
+  ;; FIXME: This is not asynchronous, but is run from a process filter.
+  (unless (assq 'updating_db mpc-status)
+    (clrhash mpc--find-memoize)
+    (dolist (buf (process-get (mpc-proc) 'buffers))
+      (setq buf (cdr buf))
+      (when (buffer-local-value 'mpc-tag buf)
+        (with-current-buffer buf (with-local-quit (mpc-tagbrowser-refresh)))))
+    (with-local-quit (mpc-songs-refresh))))
+
+(defun mpc-tagbrowser-buf (tag)
+  (let ((buf (mpc-proc-buffer (mpc-proc) tag)))
+    (if (buffer-live-p buf) buf
+      (setq buf (get-buffer-create (format "*MPC %ss*" tag)))
+      (mpc-proc-buffer (mpc-proc) tag buf)
+      (with-current-buffer buf
+        (let ((inhibit-read-only t))
+          (erase-buffer)
+          (if (member tag '(Directory))
+              (mpc-tagbrowser-dir-mode)
+            (mpc-tagbrowser-mode))
+          (insert mpc-tagbrowser-all-name "\n"))
+        (forward-line -1)
+        (setq mpc-tag tag)
+        (setq mpc-tag-name
+              (if (string-match "y\\'" (symbol-name tag))
+                  (concat (substring (symbol-name tag) 0 -1) "ies")
+                (concat (symbol-name tag) "s")))
+        (mpc-tagbrowser-all-select)
+        (mpc-tagbrowser-refresh)
+        buf))))
+
+(defvar tag-browser-tagtypes
+  (lazy-completion-table tag-browser-tagtypes
+                         (lambda ()
+                           (append '("Playlist" "Directory")
+                                   (mpc-cmd-tagtypes)))))
+
+(defun mpc-tagbrowser (tag)
+  "Create a new browser for TAG."
+  (interactive
+   (list
+    (let ((completion-ignore-case t))
+      (intern
+       (completing-read "Tag: " tag-browser-tagtypes nil 'require-match)))))
+  (let* ((newbuf (mpc-tagbrowser-buf tag))
+         (win (get-buffer-window newbuf 0)))
+    (if win (select-window win)
+      (if (with-current-buffer (window-buffer (selected-window))
+            (derived-mode-p 'mpc-tagbrowser-mode))
+          (setq win (selected-window))
+        ;; Find a tagbrowser-mode buffer.
+        (let ((buffers (process-get (mpc-proc) 'buffers))
+              buffer)
+          (while
+              (and buffers
+                   (not (and (buffer-live-p (setq buffer (cdr (pop buffers))))
+                             (with-current-buffer buffer
+                               (derived-mode-p 'mpc-tagbrowser-mode))
+                             (setq win (get-buffer-window buffer 0))))))))
+      (if (not win)
+          (pop-to-buffer newbuf)
+        (setq win (split-window win nil 'horiz))
+        (set-window-buffer win newbuf)
+        (set-window-dedicated-p win 'soft)
+        (select-window win)
+        (balance-windows-area)))))
+
+(defun mpc-tagbrowser-all-select ()
+  "Select the special *ALL* entry if no other is selected."
+  (if mpc-select
+      (delete-overlay mpc-tagbrowser-all-ol)
+    (save-excursion
+      (goto-char (point-min))
+      (if mpc-tagbrowser-all-ol
+          (move-overlay mpc-tagbrowser-all-ol
+                        (point) (line-beginning-position 2))
+        (let ((ol (make-overlay (point) (line-beginning-position 2))))
+          (overlay-put ol 'face 'region)
+          (overlay-put ol 'evaporate t)
+          (set (make-local-variable 'mpc-tagbrowser-all-ol) ol))))))
+  
+;; (defvar mpc-constraints nil)
+(defun mpc-separator (active)
+  ;; Place a separator mark.
+  (unless mpc-separator-ol
+    (set (make-local-variable 'mpc-separator-ol)
+         (make-overlay (point) (point)))
+    (overlay-put mpc-separator-ol 'after-string
+                 (propertize "\n"
+                             'face '(:height 0.05 :inverse-video t))))
+  (goto-char (point-min))
+  (forward-line 1)
+  (while
+      (and (member (buffer-substring-no-properties
+                    (line-beginning-position) (line-end-position))
+                   active)
+           (zerop (forward-line 1))))
+  (if (or (eobp) (null active))
+      (delete-overlay mpc-separator-ol)
+    (move-overlay mpc-separator-ol (1- (point)) (point))))
+
+(defun mpc-sort (active)
+  ;; Sort the active elements at the front.
+  (let ((inhibit-read-only t))
+    (goto-char (point-min))
+    (if (mpc-tagbrowser-all-p) (forward-line 1))
+    (condition-case nil
+        (sort-subr nil 'forward-line 'end-of-line
+                   nil nil
+                   (lambda (s1 s2)
+                     (setq s1 (buffer-substring-no-properties
+                               (car s1) (cdr s1)))
+                     (setq s2 (buffer-substring-no-properties
+                               (car s2) (cdr s2)))
+                     (cond
+                      ((member s1 active)
+                       (if (member s2 active)
+                           (let ((cmp (mpc-compare-strings s1 s2 t)))
+                             (and (numberp cmp) (< cmp 0)))
+                         t))
+                      ((member s2 active) nil)
+                      (t (let ((cmp (mpc-compare-strings s1 s2 t)))
+                           (and (numberp cmp) (< cmp 0)))))))
+      ;; The comparison predicate arg is new in Emacs-22.
+      (wrong-number-of-arguments
+        (sort-subr nil 'forward-line 'end-of-line
+                   (lambda ()
+                     (let ((name (buffer-substring-no-properties
+                                  (point) (line-end-position))))
+                       (cond
+                        ((member name active) (concat "1" name))
+                        (t (concat "2" "name"))))))))))
+    
+(defvar mpc--changed-selection)
+
+(defun mpc-reorder (&optional nodeactivate)
+  "Reorder entries based on thre currently active selections.
+I.e. split the current browser buffer into a first part containing the
+entries included in the selection, then a separator, and then the entries
+not included in the selection.
+Return non-nil if a selection was deactivated."
+  (mpc-select-save
+    (let ((constraints (mpc-constraints-get-current (current-buffer)))
+          (active 'all))
+      ;; (unless (equal constraints mpc-constraints)
+      ;;   (set (make-local-variable 'mpc-constraints) constraints)
+      (dolist (cst constraints)
+        (let ((vals (apply 'mpc-union
+                           (mapcar (lambda (val)
+                                     (mpc-cmd-list mpc-tag (car cst) val))
+                                   (cdr cst)))))
+          (setq active
+                (if (listp active) (mpc-intersection active vals) vals))))
+     
+      (when (and (listp active))
+        ;; Remove the selections if they are all in conflict with
+        ;; other constraints.
+        (let ((deactivate t))
+          (dolist (sel selection)
+            (when (member sel active) (setq deactivate nil)))
+          (when deactivate
+            ;; Variable declared/used by `mpc-select-save'.
+            (when selection
+              (setq mpc--changed-selection t))
+            (unless nodeactivate
+              (setq selection nil)
+              (mapc 'delete-overlay mpc-select)
+              (setq mpc-select nil)
+              (mpc-tagbrowser-all-select)))))
+
+      ;; FIXME: This `mpc-sort' takes a lot of time.  Maybe we should
+      ;; be more clever and presume the buffer is mostly sorted already.
+      (mpc-sort (if (listp active) active))
+      (mpc-separator (if (listp active) active)))))
+
+(defun mpc-selection-refresh ()
+  (let ((mpc--changed-selection t))
+    (while mpc--changed-selection
+      (setq mpc--changed-selection nil)
+      (dolist (buf (process-get (mpc-proc) 'buffers))
+              (setq buf (cdr buf))
+              (when (and (buffer-local-value 'mpc-tag buf)
+                         (not (eq buf (current-buffer))))
+                (with-current-buffer buf (mpc-reorder)))))
+    ;; FIXME: reorder the current buffer last and prevent deactivation,
+    ;; since whatever selection we made here is the most recent one
+    ;; and should hence take precedence.
+    (when mpc-tag (mpc-reorder 'nodeactivate))
+    ;; FIXME: comment?
+    (if (and mpc--song-search mpc--changed-selection)
+        (progn
+          (setq mpc--song-search nil)
+          (mpc-selection-refresh))
+      (mpc-songs-refresh))))
+
+;;; Hierarchical tagbrowser ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Todo:
+;; - Add a button on each dir to open/close it (?)
+;; - add the parent dir on the previous line, greyed-out, if it's not
+;;   present (because we're in the non-selected part and the parent is
+;;   in the selected part).
+
+(defvar mpc-tagbrowser-dir-mode-map
+  (let ((map (make-sparse-keymap)))
+    (set-keymap-parent map mpc-tagbrowser-mode-map)
+    (define-key map [?\M-\C-m] 'mpc-tagbrowser-dir-toggle)
+    map))
+
+;; (defvar mpc-tagbrowser-dir-keywords
+;;   '(mpc-tagbrowser-dir-hide-prefix))
+
+(define-derived-mode mpc-tagbrowser-dir-mode mpc-tagbrowser-mode '("MPC-" 
mpc-tag-name)
+  ;; (set (make-local-variable 'font-lock-defaults)
+  ;;      '(mpc-tagbrowser-dir-keywords t))
+  )
+
+;; (defun mpc-tagbrowser-dir-hide-prefix (limit)
+;;   (while
+;;       (let ((prev (buffer-substring (line-beginning-position 0)
+;;                                     (line-end-position 0))))
+;;         (
+
+(defun mpc-tagbrowser-dir-toggle (event)
+  "Open or close the element at point."
+  (interactive (list last-nonmenu-event))
+  (mpc-event-set-point event)
+  (let ((name (buffer-substring (line-beginning-position)
+                                (line-end-position)))
+        (prop (intern mpc-tag)))
+    (if (not (member name (process-get (mpc-proc) prop)))
+        (process-put (mpc-proc) prop
+                     (cons name (process-get (mpc-proc) prop)))
+      (let ((new (delete name (process-get (mpc-proc) prop))))
+        (setq name (concat name "/"))
+        (process-put (mpc-proc) prop
+                     (delq nil
+                           (mapcar (lambda (x)
+                                     (if (mpc-string-prefix-p name x)
+                                         nil x))
+                                   new)))))
+    (mpc-tagbrowser-refresh)))
+    
+
+;;; Playlist management ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defvar mpc-songs-playlist nil
+  "Name of the currently selected playlist, if any.
+t means the main playlist.")
+(make-variable-buffer-local 'mpc-songs-playlist)
+
+(defun mpc-playlist-create (name)
+  "Save current playlist under name NAME."
+  (interactive "sPlaylist name: ")
+  (mpc-proc-cmd (list "save" name))
+  (let ((buf (mpc-proc-buffer (mpc-proc) 'Playlist)))
+    (when (buffer-live-p buf)
+      (with-current-buffer buf (mpc-tagbrowser-refresh)))))
+
+(defun mpc-playlist-destroy (name)
+  "Delete playlist named NAME."
+  (interactive
+   (list (completing-read "Delete playlist: " (mpc-cmd-list 'Playlist)
+                          nil 'require-match)))
+  (mpc-proc-cmd (list "rm" name))
+  (let ((buf (mpc-proc-buffer (mpc-proc) 'Playlist)))
+    (when (buffer-live-p buf)
+      (with-current-buffer buf (mpc-tagbrowser-refresh)))))
+
+(defun mpc-playlist-rename (oldname newname)
+  "Rename playlist OLDNAME to NEWNAME."
+  (interactive
+   (let* ((oldname (if (and (eq mpc-tag 'Playlist) (null current-prefix-arg))
+                       (buffer-substring (line-beginning-position)
+                                         (line-end-position))
+                     (completing-read "Rename playlist: "
+                                      (mpc-cmd-list 'Playlist)
+                                      nil 'require-match)))
+          (newname (read-string (format "Rename '%s' to: " oldname))))
+     (if (zerop (length newname))
+         (error "Aborted")
+       (list oldname newname))))
+  (mpc-proc-cmd (list "rename" oldname newname))
+  (let ((buf (mpc-proc-buffer (mpc-proc) 'Playlist)))
+    (if (buffer-live-p buf)
+        (with-current-buffer buf (mpc-tagbrowser-refresh)))))
+
+(defun mpc-playlist ()
+  "Show the current playlist."
+  (interactive)
+  (mpc-constraints-push 'noerror)
+  (mpc-constraints-restore '()))
+
+(defun mpc-playlist-add ()
+  "Add the selection to the playlist."
+  (interactive)
+  (let ((songs (mapcar #'car (mpc-songs-selection))))
+    (mpc-cmd-add songs)
+    (message "Appended %d songs" (length songs))
+    ;; Return the songs added.  Used in `mpc-play'.
+    songs))
+
+(defun mpc-playlist-delete ()
+  "Remove the selected songs from the playlist."
+  (interactive)
+  (unless mpc-songs-playlist
+    (error "The selected songs aren't part of a playlist."))
+  (let ((song-poss (mapcar #'cdr (mpc-songs-selection))))
+    (mpc-cmd-delete song-poss mpc-songs-playlist)
+    (mpc-songs-refresh)
+    (message "Deleted %d songs" (length song-poss))))
+
+;;; Volume management ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defvar mpc-volume-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map [down-mouse-1] 'mpc-volume-mouse-set)
+    (define-key map [mouse-1] 'ignore)
+    (define-key map [header-line down-mouse-1] 'mpc-volume-mouse-set)
+    (define-key map [header-line mouse-1] 'ignore)
+    (define-key map [mode-line down-mouse-1] 'mpc-volume-mouse-set)
+    (define-key map [mode-line mouse-1] 'ignore)
+    map))
+
+(defvar mpc-volume nil) (put 'mpc-volume 'risky-local-variable t)
+
+(defun mpc-volume-refresh ()
+  ;; Maintain the volume.
+  (setq mpc-volume
+        (mpc-volume-widget
+         (string-to-number (cdr (assq 'volume mpc-status))))))
+
+(defvar mpc-volume-step 5)
+
+(defun mpc-volume-mouse-set (&optional event)
+  "Change volume setting."
+  (interactive (list last-nonmenu-event))
+  (let* ((posn (event-start event))
+         (diff
+          (if (memq (if (stringp (car-safe (posn-object posn)))
+                        (aref (car (posn-object posn)) (cdr (posn-object 
posn)))
+                      (with-current-buffer (window-buffer (posn-window posn))
+                        (char-after (posn-point posn))))
+                    '(?◁ ?<))
+              (- mpc-volume-step) mpc-volume-step))
+         (newvol (+ (string-to-number (cdr (assq 'volume mpc-status))) diff)))
+    (mpc-proc-cmd (list "setvol" newvol) 'mpc-status-refresh)
+    (message "Set MPD volume to %s%%" newvol)))
+
+(defun mpc-volume-widget (vol &optional size)
+  (unless size (setq size 12.5))
+  (let ((scaledvol (* (/ vol 100.0) size)))
+    ;; (message "Volume sizes: %s - %s" (/ vol fact) (/ (- 100 vol) fact))
+    (list (propertize "<" ;; "◁"
+                      ;; 'face 'default
+                      'keymap mpc-volume-map
+                      'face '(:box (:line-width -2 :style pressed-button))
+                      'mouse-face '(:box (:line-width -2 :style 
released-button)))
+          " "
+          (propertize "a"
+                      'display (list 'space :width scaledvol)
+                      'face '(:inverse-video t
+                              :box (:line-width -2 :style released-button)))
+          (propertize "a"
+                      'display (list 'space :width (- size scaledvol))
+                      'face '(:box (:line-width -2 :style released-button)))
+          " "
+          (propertize ">" ;; "▷"
+                      ;; 'face 'default
+                      'keymap mpc-volume-map
+                      'face '(:box (:line-width -2 :style pressed-button))
+                      'mouse-face '(:box (:line-width -2 :style 
released-button))))))
+
+;;; MPC songs mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defvar mpc-current-song nil) (put 'mpc-current-song 'risky-local-variable t)
+(defvar mpc-current-updating nil) (put 'mpc-current-updating 
'risky-local-variable t)
+(defvar mpc-songs-format-description nil) (put 'mpc-songs-format-description 
'risky-local-variable t)
+
+(defvar mpc-previous-window-config nil)
+
+(defvar mpc-songs-mode-map
+  (let ((map (make-sparse-keymap)))
+    (set-keymap-parent map mpc-mode-map)
+    (define-key map [remap mpc-select] 'mpc-songs-jump-to)
+    map))
+
+(defvar mpc-songpointer-set-visible nil)
+
+(defvar mpc-songs-hashcons (make-hash-table :test 'equal :weakness t)
+  "Make song file name objects unique via hash consing.
+This is used so that they can be compared with `eq', which is needed for
+`text-property-any'.")
+(defun mpc-songs-hashcons (name)
+  (or (gethash name mpc-songs-hashcons) (puthash name name 
mpc-songs-hashcons)))
+(defcustom mpc-songs-format "%2{Disc--}%3{Track} %-5{Time} %25{Title} 
%20{Album} %20{Artist} %10{Date}"
+  "Format used to display each song in the list of songs."
+  :type 'string)
+
+(defvar mpc-songs-totaltime)
+
+(defun mpc-songs-refresh ()
+  (let ((buf (mpc-proc-buffer (mpc-proc) 'songs)))
+    (when (buffer-live-p buf)
+      (with-current-buffer buf
+        (let ((constraints (mpc-constraints-get-current (current-buffer)))
+              (dontsort nil)
+              (inhibit-read-only t)
+              (totaltime 0)
+              (curline (cons (count-lines (point-min)
+                                          (line-beginning-position))
+                             (buffer-substring (line-beginning-position)
+                                               (line-end-position))))
+              active)
+          (setq mpc-songs-playlist nil)
+          (if (null constraints)
+              ;; When there are no constraints, rather than show the list of
+              ;; all songs (which could take a while to download and
+              ;; format), we show the current playlist.
+              ;; FIXME: it would be good to be able to show the complete
+              ;; list, but that would probably require us to format it
+              ;; on-the-fly to make it bearable.
+              (setq dontsort t
+                    mpc-songs-playlist t
+                    active (mpc-proc-buf-to-alists
+                            (mpc-proc-cmd "playlistinfo")))
+            (dolist (cst constraints)
+              (if (and (eq (car cst) 'Playlist)
+                       (= 1 (length (cdr cst))))
+                  (setq mpc-songs-playlist (cadr cst)))
+              ;; We don't do anything really special here for playlists,
+              ;; because it's unclear what's a correct "union" of playlists.
+              (let ((vals (apply 'mpc-union
+                                 (mapcar (lambda (val)
+                                           (mpc-cmd-find (car cst) val))
+                                         (cdr cst)))))
+                (setq active (if (null active)
+                                 (progn
+                                   (if (eq (car cst) 'Playlist)
+                                       (setq dontsort t))
+                                   vals)
+                               (if (or dontsort
+                                       ;; Try to preserve ordering and
+                                       ;; repetitions from playlists.
+                                       (not (eq (car cst) 'Playlist)))
+                                   (mpc-intersection active vals
+                                                     (lambda (x) (assq 'file 
x)))
+                                 (setq dontsort t)
+                                 (mpc-intersection vals active
+                                                   (lambda (x) (assq 'file 
x)))))))))
+          (mpc-select-save
+            (erase-buffer)
+            ;; Sorting songs is surprisingly difficult: when comparing two
+            ;; songs with the same album name but different artist name, you
+            ;; have to know whether these are two different albums (with the
+            ;; same name) or a single album (typically a compilation).
+            ;; I punt on it and just use file-name sorting, which does the
+            ;; right thing if your library is properly arranged.
+            (dolist (song (if dontsort active
+                            (sort active
+                                  (lambda (song1 song2)
+                                    (let ((cmp (mpc-compare-strings
+                                                (cdr (assq 'file song1))
+                                                (cdr (assq 'file song2)))))
+                                      (and (integerp cmp) (< cmp 0)))))))
+              (incf totaltime (string-to-number (or (cdr (assq 'Time song)) 
"0")))
+              (mpc-format mpc-songs-format song)
+              (delete-char (- (skip-chars-backward " "))) ;Remove trailing 
space.
+              (insert "\n")
+              (put-text-property
+               (line-beginning-position 0) (line-beginning-position)
+               'mpc-file (mpc-songs-hashcons (cdr (assq 'file song))))
+              (let ((pos (assq 'Pos song)))
+                (if pos
+                    (put-text-property
+                     (line-beginning-position 0) (line-beginning-position)
+                     'mpc-file-pos (string-to-number (cdr pos)))))
+              ))
+          (goto-char (point-min))
+          (forward-line (car curline))
+          (when (or (search-forward (cdr curline) nil t)
+                    (search-backward (cdr curline) nil t))
+            (beginning-of-line))
+          (set (make-local-variable 'mpc-songs-totaltime)
+               (unless (zerop totaltime)
+                 (list " " (mpc-secs-to-time totaltime))))
+          ))))
+  (let ((mpc-songpointer-set-visible t))
+    (mpc-songpointer-refresh)))
+
+(defun mpc-songs-search (string)
+  "Filter songs to those who include STRING in their metadata."
+  (interactive "sSearch for: ")
+  (setq mpc--song-search
+        (if (zerop (length string)) nil string))
+  (let ((mpc--changed-selection t))
+    (while mpc--changed-selection
+      (setq mpc--changed-selection nil)
+      (dolist (buf (process-get (mpc-proc) 'buffers))
+        (setq buf (cdr buf))
+        (when (buffer-local-value 'mpc-tag buf)
+          (with-current-buffer buf (mpc-reorder))))
+      (mpc-songs-refresh))))
+  
+(defun mpc-songs-kill-search ()
+  "Turn off the current search restriction."
+  (interactive)
+  (mpc-songs-search nil))
+
+(defun mpc-songs-selection ()
+  "Return the list of songs currently selected."
+  (let ((buf (mpc-proc-buffer (mpc-proc) 'songs)))
+    (when (buffer-live-p buf)
+      (with-current-buffer buf
+        (save-excursion
+          (let ((files ()))
+            (if mpc-select
+                (dolist (ol mpc-select)
+                  (push (cons
+                         (get-text-property (overlay-start ol) 'mpc-file)
+                         (get-text-property (overlay-start ol) 'mpc-file-pos))
+                        files))
+              (goto-char (point-min))
+              (while (not (eobp))
+                (push (cons
+                       (get-text-property (point) 'mpc-file)
+                       (get-text-property (point) 'mpc-file-pos))
+                      files)
+                (forward-line 1)))
+            (nreverse files)))))))
+
+(defun mpc-songs-jump-to (song-file &optional posn)
+  "Jump to song SONG-FILE, interactively, this is the song at point."
+  (interactive
+   (let* ((event last-nonmenu-event)
+          (posn (event-end event)))
+     (with-selected-window (posn-window posn)
+       (goto-char (posn-point posn))
+       (list (get-text-property (point) 'mpc-file)
+             posn))))
+  (let* ((plbuf (mpc-proc-cmd "playlist"))
+         (re (concat "^\\([0-9]+\\):" (regexp-quote song-file) "$"))
+         (sn (with-current-buffer plbuf
+               (goto-char (point-min))
+               (when (re-search-forward re nil t)
+                 (match-string 1)))))
+    (cond
+     ((null sn) (error "This song is not in the playlist"))
+     ((null (with-current-buffer plbuf (re-search-forward re nil t)))
+      ;; song-file only appears once in the playlist: no ambiguity,
+      ;; we're good to go!
+      (mpc-proc-cmd (list "play" sn)))
+     (t
+      ;; The song appears multiple times in the playlist.  If the current
+      ;; buffer holds not only the destination song but also the current
+      ;; song, then we will move in the playlist to the same relative
+      ;; position as in the buffer.  Otherwise, we will simply choose the
+      ;; song occurrence closest to the current song.
+      (with-selected-window (posn-window posn)
+        (let* ((cur (and (markerp overlay-arrow-position)
+                         (marker-position overlay-arrow-position)))
+               (dest (save-excursion
+                       (goto-char (posn-point posn))
+                       (line-beginning-position)))
+               (lines (when cur (* (if (< cur dest) 1 -1)
+                                   (count-lines cur dest)))))
+          (with-current-buffer plbuf
+            (goto-char (point-min))
+            ;; Start the search from the current song.
+            (forward-line (string-to-number
+                           (or (cdr (assq 'song mpc-status)) "0")))
+            ;; If the current song is also displayed in the buffer,
+            ;; then try to move to the same relative position.
+            (if lines (forward-line lines))
+            ;; Now search the closest occurrence.
+            (let* ((next (save-excursion
+                           (when (re-search-forward re nil t)
+                             (cons (point) (match-string 1)))))
+                   (prev (save-excursion
+                           (when (re-search-backward re nil t)
+                             (cons (point) (match-string 1)))))
+                   (sn (cdr (if (and next prev)
+                                (if (< (- (car next) (point))
+                                       (- (point) (car prev)))
+                                    next prev)
+                              (or next prev)))))
+              (assert sn)
+              (mpc-proc-cmd (concat "play " sn))))))))))
+
+(define-derived-mode mpc-songs-mode mpc-mode "MPC-song"
+  (setq mpc-songs-format-description
+        (with-temp-buffer (mpc-format mpc-songs-format 'self) (buffer-string)))
+  (set (make-local-variable 'header-line-format)
+       ;; '("MPC " mpc-volume " " mpc-current-song)
+       (list (propertize " " 'display '(space :align-to 0))
+             ;; 'mpc-songs-format-description
+             '(:eval
+               (let ((hscroll (window-hscroll)))
+                 (with-temp-buffer
+                   (mpc-format mpc-songs-format 'self hscroll)
+                   ;; That would be simpler than the hscroll handling in
+                   ;; mpc-format, but currently move-to-column does not
+                   ;; recognize :space display properties.
+                   ;; (move-to-column hscroll)
+                   ;; (delete-region (point-min) (point))
+                   (buffer-string))))))
+  (set (make-local-variable 'mode-line-format)
+       '("%e" mode-line-frame-identification mode-line-buffer-identification
+         #("   " 0 3
+           (help-echo "mouse-1: Select (drag to resize)\nmouse-2: Make current 
window occupy the whole frame\nmouse-3: Remove current window from display"))
+         mode-line-position
+         #("  " 0 2
+           (help-echo "mouse-1: Select (drag to resize)\nmouse-2: Make current 
window occupy the whole frame\nmouse-3: Remove current window from display"))
+         mpc-songs-totaltime
+         mpc-current-updating
+         #("   " 0 2
+           (help-echo "mouse-1: Select (drag to resize)\nmouse-2: Make current 
window occupy the whole frame\nmouse-3: Remove current window from display"))
+         (mpc--song-search
+          (:propertize
+           ("Search=\"" mpc--song-search "\"")
+           help-echo "mouse-2: kill this search"
+           follow-link t
+           mouse-face mode-line-highlight
+           keymap (keymap (mode-line keymap
+                                     (mouse-2 . mpc-songs-kill-search))))
+          (:propertize "NoSearch"
+           help-echo "mouse-2: set a search restriction"
+           follow-link t
+           mouse-face mode-line-highlight
+           keymap (keymap (mode-line keymap (mouse-2 . mpc-songs-search)))))))
+           
+  ;; (set (make-local-variable 'mode-line-process)
+  ;;      '("" ;; mpc-volume " "
+  ;;        mpc-songs-totaltime
+  ;;        mpc-current-updating))
+  )
+
+(defun mpc-songpointer-set (pos)
+  (let* ((win (get-buffer-window (current-buffer) t))
+         (visible (when win
+                    (or mpc-songpointer-set-visible
+                        (and (markerp overlay-arrow-position)
+                             (eq (marker-buffer overlay-arrow-position)
+                                 (current-buffer))
+                             (<= (window-start win) overlay-arrow-position)
+                             (< overlay-arrow-position (window-end win)))))))
+    (unless (local-variable-p 'overlay-arrow-position)
+      (set (make-local-variable 'overlay-arrow-position) (make-marker)))
+    (move-marker overlay-arrow-position pos)
+    ;; If the arrow was visible, try to keep it that way.
+    (if (and visible pos
+             (or (> (window-start win) pos) (>= pos (window-end win t))))
+      (set-window-point win pos))))
+
+(defun mpc-songpointer-refresh ()
+  (let ((buf (mpc-proc-buffer (mpc-proc) 'songs)))
+    (when (buffer-live-p buf)
+      (with-current-buffer buf
+        (let* ((pos (text-property-any
+                     (point-min) (point-max)
+                     'mpc-file (mpc-songs-hashcons
+                                (cdr (assq 'file mpc-status)))))
+               (other (when pos
+                        (save-excursion
+                          (goto-char pos)
+                          (text-property-any
+                           (line-beginning-position 2) (point-max)
+                           'mpc-file (mpc-songs-hashcons
+                                      (cdr (assq 'file mpc-status))))))))
+          (if other
+              ;; The song appears multiple times in the buffer.
+              ;; We need to be careful to choose the right occurrence.
+              (mpc-proc-cmd "playlist" 'mpc-songpointer-refresh-hairy)
+            (mpc-songpointer-set pos)))))))
+
+(defun mpc-songpointer-context (size plbuf)
+  (with-current-buffer plbuf
+    (goto-char (point-min))
+    (forward-line (string-to-number (or (cdr (assq 'song mpc-status)) "0")))
+    (let ((context-before '())
+          (context-after '()))
+      (save-excursion
+        (dotimes (i size)
+          (when (re-search-backward "^[0-9]+:\\(.*\\)" nil t)
+            (push (mpc-songs-hashcons (match-string 1)) context-before))))
+      ;; Skip the actual current song.
+      (forward-line 1)
+      (dotimes (i size)
+        (when (re-search-forward "^[0-9]+:\\(.*\\)" nil t)
+          (push (mpc-songs-hashcons (match-string 1)) context-after)))
+      ;; If there isn't `size' context, then return nil.
+      (unless (and (< (length context-before) size)
+                   (< (length context-after) size))
+        (cons (nreverse context-before) (nreverse context-after))))))
+
+(defun mpc-songpointer-score (context pos)
+  (let ((count 0))
+    (goto-char pos)
+    (dolist (song (car context))
+      (and (zerop (forward-line -1))
+           (eq (get-text-property (point) 'mpc-file) song)
+           (incf count)))
+    (goto-char pos)
+    (dolist (song (cdr context))
+      (and (zerop (forward-line 1))
+           (eq (get-text-property (point) 'mpc-file) song)
+           (incf count)))
+    count))
+
+(defun mpc-songpointer-refresh-hairy ()
+  ;; Based on the complete playlist, we should figure out where in the
+  ;; song buffer is the currently playing song.
+  (let ((plbuf (current-buffer))
+        (buf (mpc-proc-buffer (mpc-proc) 'songs)))
+    (when (buffer-live-p buf)
+      (with-current-buffer buf
+        (let* ((context-size 0)
+               (context '(() . ()))
+               (pos (text-property-any
+                     (point-min) (point-max)
+                     'mpc-file (mpc-songs-hashcons
+                                (cdr (assq 'file mpc-status)))))
+               (score 0)
+               (other pos))
+          (while
+              (setq other
+                    (save-excursion
+                      (goto-char other)
+                      (text-property-any
+                       (line-beginning-position 2) (point-max)
+                       'mpc-file (mpc-songs-hashcons
+                                  (cdr (assq 'file mpc-status))))))
+            ;; There is an `other' contestant.
+            (let ((other-score (mpc-songpointer-score context other)))
+              (cond
+               ;; `other' is worse: try the next one.
+               ((< other-score score) nil)
+               ;; `other' is better: remember it and then search further.
+               ((> other-score score)
+                (setq pos other)
+                (setq score other-score))
+               ;; Both are equal and increasing the context size won't help.
+               ;; Arbitrarily choose one of the two and keep looking
+               ;; for a better match.
+               ((< score context-size) nil)
+               (t
+                ;; Score is equal and increasing context might help: try it.
+                (incf context-size)
+                (let ((new-context
+                       (mpc-songpointer-context context-size plbuf)))
+                  (if (null new-context)
+                      ;; There isn't more context: choose one arbitrarily
+                      ;; and keep looking for a better match elsewhere.
+                      (decf context-size)
+                    (setq context new-context)
+                    (setq score (mpc-songpointer-score context pos))
+                    (save-excursion
+                      (goto-char other)
+                      ;; Go back one line so we find `other' again.
+                      (setq other (line-beginning-position 0)))))))))
+          (mpc-songpointer-set pos))))))
+
+(defun mpc-current-refresh ()
+  ;; Maintain the current data.
+  (mpc-status-buffer-refresh)
+  (setq mpc-current-updating
+        (if (assq 'updating_db mpc-status) " Updating-DB"))
+  (ignore-errors
+    (setq mpc-current-song
+          (when (assq 'file mpc-status)
+            (concat " "
+                    (mpc-secs-to-time (cdr (assq 'time mpc-status)))
+                    " "
+                    (cdr (assq 'Title mpc-status))
+                    " ("
+                    (cdr (assq 'Artist mpc-status))
+                    " / "
+                    (cdr (assq 'Album mpc-status))
+                    ")"))))
+  (force-mode-line-update t))
+
+(defun mpc-songs-buf ()
+  (let ((buf (mpc-proc-buffer (mpc-proc) 'songs)))
+    (if (buffer-live-p buf) buf
+      (with-current-buffer (setq buf (get-buffer-create "*MPC-Songs*"))
+        (mpc-proc-buffer (mpc-proc) 'songs buf)
+        (mpc-songs-mode)
+        buf))))
+
+(defun mpc-update ()
+  "Tell MPD to refresh its database."
+  (interactive)
+  (mpc-cmd-update))
+
+(defun mpc-quit ()
+  "Quit Music Player Daemon."
+  (interactive)
+  (let* ((proc mpc-proc)
+         (bufs (mapcar 'cdr (if proc (process-get proc 'buffers))))
+         (wins (mapcar (lambda (buf) (get-buffer-window buf 0)) bufs))
+         (song-buf (mpc-songs-buf))
+         frames)
+    ;; Collect all the frames where MPC buffers appear.
+    (dolist (win wins)
+      (when (and win (not (memq (window-frame win) frames)))
+        (push (window-frame win) frames)))
+    (if (and frames song-buf
+             (with-current-buffer song-buf mpc-previous-window-config))
+        (progn
+          (select-frame (car frames))
+          (set-window-configuration
+           (with-current-buffer song-buf mpc-previous-window-config)))
+      ;; Now delete the ones that show nothing else than MPC buffers.
+      (dolist (frame frames)
+        (let ((delete t))
+          (dolist (win (window-list frame))
+            (unless (memq (window-buffer win) bufs) (setq delete nil)))
+          (if delete (ignore-errors (delete-frame frame))))))
+    ;; Then kill the buffers.
+    (mapc 'kill-buffer bufs)
+    (mpc-status-stop)
+    (if proc (delete-process proc))))
+      
+(defun mpc-stop ()
+  "Stop playing the current queue of songs."
+  (interactive)
+  (mpc-cmd-stop)
+  (mpc-cmd-clear)
+  (mpc-status-refresh))
+
+(defun mpc-pause ()
+  "Pause playing."
+  (interactive)
+  (mpc-cmd-pause "1"))
+
+(defun mpc-resume ()
+  "Pause playing."
+  (interactive)
+  (mpc-cmd-pause "0"))
+
+(defun mpc-play ()
+  "Start playing whatever is selected."
+  (interactive)
+  (if (member (cdr (assq 'state (mpc-cmd-status))) '("pause"))
+      (mpc-resume)
+    ;; When playing the playlist ends, the playlist isn't cleared, but the
+    ;; user probably doesn't want to re-listen to it before getting to
+    ;; listen to what he just selected.
+    ;; (if (member (cdr (assq 'state (mpc-cmd-status))) '("stop"))
+    ;;     (mpc-cmd-clear))
+    ;; Actually, we don't use mpc-play to append to the playlist any more,
+    ;; so we can just always empty the playlist.
+    (mpc-cmd-clear)
+    (if (mpc-playlist-add)
+        (if (member (cdr (assq 'state (mpc-cmd-status))) '("stop"))
+            (mpc-cmd-play))
+      (error "Don't know what to play"))))
+
+(defun mpc-next ()
+  "Jump to the next song in the queue."
+  (interactive)
+  (mpc-proc-cmd "next")
+  (mpc-status-refresh))
+
+(defun mpc-prev ()
+  "Jump to the beginning of the current song, or to the previous song."
+  (interactive)
+  (let ((time (cdr (assq 'time mpc-status))))
+    ;; Here we rely on the fact that string-to-number silently ignores
+    ;; everything after a non-digit char.
+    (cond
+     ;; Go back to the beginning of current song.
+     ((and time (> (string-to-number time) 0))
+      (mpc-proc-cmd (list "seekid" (cdr (assq 'songid mpc-status)) 0)))
+     ;; We're at the beginning of the first song of the playlist.
+     ;; Fetch the previous one from `mpc-queue-back'.
+     ;; ((and (zerop (string-to-number (cdr (assq 'song mpc-status))))
+     ;;       mpc-queue-back)
+     ;;  ;; Because we use cmd-list rather than cmd-play, the queue is not
+     ;;  ;; automatically updated.
+     ;;  (let ((prev (pop mpc-queue-back)))
+     ;;    (push prev mpc-queue)
+     ;;    (mpc-proc-cmd
+     ;;     (mpc-proc-cmd-list
+     ;;      (list (list "add" prev)
+     ;;            (list "move" (cdr (assq 'playlistlength mpc-status)) "0")
+     ;;            "previous")))))
+     ;; We're at the beginning of a song, but not the first one.
+     (t (mpc-proc-cmd "previous")))
+    (mpc-status-refresh)))
+
+(defvar mpc-last-seek-time '(0 . 0))
+
+(defun mpc--faster (event speedup step)
+  "Fast forward."
+  (interactive (list last-nonmenu-event))
+  (let ((repeat-delay (/ (abs (float step)) speedup)))
+    (if (not (memq 'down (event-modifiers event)))
+        (let* ((currenttime (float-time))
+               (last-time (- currenttime (car mpc-last-seek-time))))
+          (if (< last-time (* 0.9 repeat-delay))
+              nil ;; Trottle
+            (let* ((status (if (< last-time 1.0)
+                               mpc-status (mpc-cmd-status)))
+                   (songid (cdr (assq 'songid status)))
+                   (time (if songid
+                             (if (< last-time 1.0)
+                                 (cdr mpc-last-seek-time)
+                               (string-to-number
+                                (cdr (assq 'time status)))))))
+              (setq mpc-last-seek-time
+                    (cons currenttime (setq time (+ time step))))
+              (mpc-proc-cmd (list "seekid" songid time)
+                            'mpc-status-refresh))))
+      (let ((status (mpc-cmd-status)))
+        (lexical-let* ((songid (cdr (assq 'songid status)))
+                       (step step)
+                       (time (if songid (string-to-number
+                                         (cdr (assq 'time status))))))
+          (let ((timer (run-with-timer
+                        t repeat-delay
+                        (lambda ()
+                          (mpc-proc-cmd (list "seekid" songid
+                                              (setq time (+ time step)))
+                                        'mpc-status-refresh)))))
+            (while (mouse-movement-p
+                    (event-basic-type (setq event (read-event)))))
+            (cancel-timer timer)))))))
+
+(defvar mpc--faster-toggle-timer nil)
+(defun mpc--faster-stop ()
+  (when mpc--faster-toggle-timer
+    (cancel-timer mpc--faster-toggle-timer)
+    (setq mpc--faster-toggle-timer nil)))
+
+(defun mpc--faster-toggle-refresh ()
+  (if (equal (cdr (assq 'state mpc-status)) "stop")
+      (mpc--faster-stop)))
+
+(defun mpc--songduration ()
+  (string-to-number
+   (let ((s (cdr (assq 'time mpc-status))))
+     (if (not (string-match ":" s))
+         (error "Unexpected time format %S" s)
+       (substring s (match-end 0))))))
+
+(defvar mpc--faster-toggle-forward nil)
+(defvar mpc--faster-acceleration 0.5)
+(defun mpc--faster-toggle (speedup step)
+  (setq speedup (float speedup))
+  (if mpc--faster-toggle-timer
+      (mpc--faster-stop)
+    (mpc-status-refresh) (mpc-proc-sync)
+    (lexical-let* ((speedup speedup)
+                   songid       ;The ID of the currently ffwd/rewinding song.
+                   songnb       ;The position of that song in the playlist.
+                   songduration ;The duration of that song.
+                   songtime     ;The time of the song last time we ran.
+                   oldtime      ;The timeoftheday last time we ran.
+                   prevsongid)  ;The song we're in the process leaving.
+      (let ((fun
+             (lambda ()
+               (let ((newsongid (cdr (assq 'songid mpc-status)))
+                     (newsongnb (cdr (assq 'song mpc-status))))
+                 
+                 (if (and (equal prevsongid newsongid)
+                          (not (equal prevsongid songid)))
+                     ;; We left prevsongid and came back to it.  Pretend it
+                     ;; didn't happen.
+                     (setq newsongid songid))
+                 
+                 (cond
+                  ((null newsongid) (mpc--faster-stop))
+                  ((not (equal songid newsongid))
+                   ;; We jumped to another song: reset.
+                   (setq songid newsongid)
+                   (setq songtime (string-to-number
+                                   (cdr (assq 'time mpc-status))))
+                   (setq songduration (mpc--songduration))
+                   (setq oldtime (float-time)))
+                  ((and (>= songtime songduration) mpc--faster-toggle-forward)
+                   ;; Skip to the beginning of the next song.
+                   (if (not (equal (cdr (assq 'state mpc-status)) "play"))
+                       (mpc-proc-cmd "next" 'mpc-status-refresh)
+                     ;; If we're playing, this is done automatically, so we
+                     ;; don't need to do anything, or rather we *shouldn't*
+                     ;; do anything otherwise there's a race condition where
+                     ;; we could skip straight to the next next song.
+                     nil))
+                  ((and (<= songtime 0) (not mpc--faster-toggle-forward))
+                   ;; Skip to the end of the previous song.
+                   (setq prevsongid songid)
+                   (mpc-proc-cmd "previous"
+                    (lambda ()
+                      (mpc-status-refresh
+                       (lambda ()
+                         (setq songid (cdr (assq 'songid mpc-status)))
+                         (setq songtime (setq songduration 
(mpc--songduration)))
+                         (setq oldtime (float-time))
+                         (mpc-proc-cmd (list "seekid" songid songtime)))))))
+                  (t
+                   (setq speedup (+ speedup mpc--faster-acceleration))
+                   (let ((newstep
+                          (truncate (* speedup (- (float-time) oldtime)))))
+                     (if (<= newstep 1) (setq newstep 1))
+                     (setq oldtime (+ oldtime (/ newstep speedup)))
+                     (if (not mpc--faster-toggle-forward)
+                         (setq newstep (- newstep)))
+                     (setq songtime (min songduration (+ songtime newstep)))
+                     (unless (>= songtime songduration)
+                       (condition-case nil
+                           (mpc-proc-cmd
+                            (list "seekid" songid songtime)
+                            'mpc-status-refresh)
+                         (mpc-proc-error (mpc-status-refresh)))))))
+                 (setq songnb newsongnb)))))
+        (setq mpc--faster-toggle-forward (> step 0))
+        (funcall fun)                   ;Initialize values.
+        (setq mpc--faster-toggle-timer
+              (run-with-timer t 0.3 fun))))))
+
+
+
+(defvar mpc-faster-speedup 8)
+
+(defun mpc-ffwd (event)
+  "Fast forward."
+  (interactive (list last-nonmenu-event))
+  ;; (mpc--faster event 4.0 1)
+  (mpc--faster-toggle mpc-faster-speedup 1))
+      
+(defun mpc-rewind (event)
+  "Fast rewind."
+  (interactive (list last-nonmenu-event))
+  ;; (mpc--faster event 4.0 -1)
+  (mpc--faster-toggle mpc-faster-speedup -1))
+      
+      
+(defun mpc-play-at-point (&optional event)
+  (interactive (list last-nonmenu-event))
+  (mpc-select event)
+  (mpc-play))
+
+;; (defun mpc-play-tagval ()
+;;   "Play all the songs of the tag at point."
+;;   (interactive)
+;;   (let* ((val (buffer-substring (line-beginning-position) 
(line-end-position)))
+;;          (songs (mapcar 'cdar
+;;                         (mpc-proc-buf-to-alists
+;;                          (mpc-proc-cmd (list "find" mpc-tag val))))))
+;;     (mpc-cmd-add songs)
+;;     (if (member (cdr (assq 'state (mpc-cmd-status))) '("stop"))
+;;         (mpc-cmd-play))))
+
+;;; Drag'n'drop support ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Todo:
+;; the main thing to do here, is to provide visual feedback during the drag:
+;; - change the mouse-cursor.
+;; - highlight/select the source and the current destination.
+
+(defun mpc-drag-n-drop (event)
+  "DWIM for a drag EVENT."
+  (interactive "e")
+  (let* ((start (event-start event))
+         (end (event-end event))
+         (start-buf (window-buffer (posn-window start)))
+         (end-buf (window-buffer (posn-window end)))
+         (songs
+          (with-current-buffer start-buf
+            (goto-char (posn-point start))
+            (if (get-text-property (point) 'mpc-select)
+                ;; FIXME: actually we should only consider the constraints
+                ;; corresponding to the selection in this particular buffer.
+                (mpc-songs-selection)
+              (cond
+               ((and (derived-mode-p 'mpc-songs-mode)
+                     (get-text-property (point) 'mpc-file))
+                (list (cons (get-text-property (point) 'mpc-file)
+                            (get-text-property (point) 'mpc-file-pos))))
+               ((and mpc-tag (not (mpc-tagbrowser-all-p)))
+                (mapcar (lambda (song)
+                          (list (cdr (assq 'file song))))
+                        (mpc-cmd-find
+                         mpc-tag
+                         (buffer-substring (line-beginning-position)
+                                           (line-end-position)))))
+               (t
+                (error "Unsupported starting position for drag'n'drop 
gesture")))))))
+    (with-current-buffer end-buf
+      (goto-char (posn-point end))
+      (cond
+       ((eq mpc-tag 'Playlist)
+        ;; Adding elements to a named playlist.
+        (let ((playlist (if (or (mpc-tagbrowser-all-p)
+                               (and (bolp) (eolp)))
+                           (error "Not a playlist")
+                         (buffer-substring (line-beginning-position)
+                                           (line-end-position)))))
+         (mpc-cmd-add (mapcar 'car songs) playlist)
+         (message "Added %d songs to %s" (length songs) playlist)
+         (if (member playlist
+                     (cdr (assq 'Playlist (mpc-constraints-get-current))))
+             (mpc-songs-refresh))))
+       ((derived-mode-p 'mpc-songs-mode)
+        (cond
+         ((null mpc-songs-playlist)
+          (error "The songs shown do not belong to a playlist"))
+         ((eq start-buf end-buf)
+          ;; Moving songs within the shown playlist.
+          (let ((dest-pos (get-text-property (point) 'mpc-file-pos)))
+            (mpc-cmd-move (mapcar 'cdr songs) dest-pos mpc-songs-playlist)
+            (message "Moved %d songs" (length songs))))
+         (t
+          ;; Adding songs to the shown playlist.
+          (let ((dest-pos (get-text-property (point) 'mpc-file-pos))
+                (pl (if (stringp mpc-songs-playlist)
+                        (mpc-cmd-find 'Playlist mpc-songs-playlist)
+                      (mpc-proc-cmd-to-alist "playlist"))))
+            ;; MPD's protocol does not let us add songs at a particular
+            ;; position in a playlist, so we first have to add them to the
+            ;; end, and then move them to their final destination.
+            (mpc-cmd-add (mapcar 'car songs) mpc-songs-playlist)
+            (mpc-cmd-move (let ((poss '()))
+                            (dotimes (i (length songs))
+                                     (push (+ i (length pl)) poss))
+                            (nreverse poss)) dest-pos mpc-songs-playlist)
+            (message "Added %d songs" (length songs)))))
+        (mpc-songs-refresh))
+      (t
+       (error "Unsupported drag'n'drop gesture"))))))
+
+;;; Toplevel ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defcustom mpc-frame-alist '((name . "MPC") (tool-bar-lines . 1)
+                             (font . "Sans"))
+  "Alist of frame parameters for the MPC frame."
+  :type 'alist)
+
+;;;###autoload
+(defun mpc ()
+  "Main entry point for MPC."
+  (interactive
+   (progn
+     (if current-prefix-arg
+         (setq mpc-host (read-string "MPD host and port: " nil nil mpc-host)))
+     nil))
+  (let* ((song-buf (mpc-songs-buf))
+         (song-win (get-buffer-window song-buf 0)))
+    (if song-win
+        (select-window song-win)
+      (if (or (window-dedicated-p (selected-window))
+              (window-minibuffer-p))
+          (ignore-errors (select-frame (make-frame mpc-frame-alist)))
+        (with-current-buffer song-buf
+          (set (make-local-variable 'mpc-previous-window-config)
+               (current-window-configuration))))
+      (let* ((win1 (selected-window))
+             (win2 (split-window))
+             (tags mpc-browser-tags))
+        (unless tags (error "Need at least one entry in `mpc-browser-tags'"))
+        (set-window-buffer win2 song-buf)
+        (set-window-dedicated-p win2 'soft)
+        (mpc-status-buffer-show)
+        (while
+            (progn
+              (set-window-buffer win1 (mpc-tagbrowser-buf (pop tags)))
+              (set-window-dedicated-p win1 'soft)
+              tags)
+          (setq win1 (split-window win1 nil 'horiz)))))
+    (balance-windows-area))
+  (mpc-songs-refresh)
+  (mpc-status-refresh))
+
+(provide 'mpc)
+
+;; arch-tag: 4794b2f5-59e6-4f26-b695-650b3e002f37
+;;; mpc.el ends here




reply via email to

[Prev in Thread] Current Thread [Next in Thread]