Various mpd updates and improvements.
[slh] / mpd.el
1 ;;; mpd.el --- A complete ripoff of xwem-mpd.
2
3 ;; Copyright (C) 2008 - 2013 Steve Youngs
4
5 ;; Original xwem-mpd:
6 ;; Copyright (C) 2005 Richard Klinda
7 ;; Author: Richard Klinda <ignotus@freemail.hu>
8 ;;         Zajcev Evgeny <zevlg@yandex.ru>
9 ;; Created: 2004
10
11 ;; Keywords: music, entertainment
12
13 ;; This file is NOT part of anything.
14
15 ;; The original xwem-mpd.el was released under the terms of the GPLv2.
16 ;; mpd.el uses the BSD licence.
17
18 ;; Redistribution and use in source and binary forms, with or without
19 ;; modification, are permitted provided that the following conditions
20 ;; are met:
21 ;;
22 ;; 1. Redistributions of source code must retain the above copyright
23 ;;    notice, this list of conditions and the following disclaimer.
24 ;;
25 ;; 2. Redistributions in binary form must reproduce the above copyright
26 ;;    notice, this list of conditions and the following disclaimer in the
27 ;;    documentation and/or other materials provided with the distribution.
28 ;;
29 ;; 3. Neither the name of the author nor the names of any contributors
30 ;;    may be used to endorse or promote products derived from this
31 ;;    software without specific prior written permission.
32 ;;
33 ;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
34 ;; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
35 ;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
36 ;; DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
37 ;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
38 ;; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
39 ;; SUBSTITUTE GOODS OR SERVICES# LOSS OF USE, DATA, OR PROFITS# OR
40 ;; BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
41 ;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
42 ;; OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
43 ;; IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44
45 ;;; Commentary:
46
47 ;; You need MusicPD - Music Playing Daemon
48 ;; (http://www.musicpd.org/) to be set up and running.
49
50 ;; The xwem-mpd-dock from xwem-mpd.el was designed to sit in the xwem
51 ;; minibuffer (panel), and theoretically you would be able to have the
52 ;; mpd-dock-frame from here sit in a panel (KDE, GNOME, LXDE, etc)
53 ;; too.  I say "theoretically" because I've never tried so I'm leaving
54 ;; it as an exercise for the reader. :-)
55
56 ;; No, instead of docking the mpd-dock-frame onto a panel I prefer to
57 ;; tweak its frame properties so it will be completely ignored by the
58 ;; WM.  That gives me a frame that has no decorations, is visible on all
59 ;; workspaces, always "on top", and doesn't show up in "task switchers".
60 ;; To get tha magic you set the `override-redirect' property to `t', and
61 ;; for positioning set `left' and/or `top' properties.
62
63 ;;; Keybinding Suggestions:
64 ;;
65 ;;  (global-set-key [XF86AudioPlay] #'mpd-playpause)
66 ;;  (global-set-key [XF86AudioStop] #'mpd-stop)
67 ;;  (global-set-key [XF86AudioNext] #'mpd-next-track)
68 ;;  (global-set-key [XF86AudioPrev] #'mpd-previous-track)
69 ;;  (global-set-key [XF86AudioRaiseVolume] #'mpd-volume-up)
70 ;;  (global-set-key [XF86AudioLowerVolume] #'mpd-volume-down)
71 ;;  (global-set-key [XF86AudioMute] #'mpd-volume-mute/unmute)
72 ;;
73 ;; Yeah, it helps if you have a keyboard with those fancy keys. :-)
74
75 ;;; Cover Art:
76 ;;
77 ;; First up you will need a 48x48 `nocover.jpg' and put it in
78 ;; `mpd-directory' (see the images directory in this repo) for albums
79 ;; that you don't have any art for.  Secondly, album art is taken from
80 ;; `cover.jpg' files in the same directory as the album's audio files.
81 ;; Yep, sort your music by album (at least).
82 ;;
83 ;; Embedded art is not supported at this time, but hopefully one day.
84
85 ;;; Player control buttons:
86 ;;
87 ;; Put the XPM's in `mpd-directory'
88
89 ;;; Code:
90 \f
91 (defgroup mpd nil
92   "Group to customize mpd."
93   :prefix "mpd-"
94   :group 'hypermedia)
95
96 (defcustom mpd-update-rate 5
97   "MPD variables updating rate in seconds."
98   :type 'number
99   :group 'mpd)
100
101 (defcustom mpd-directory
102   (file-name-as-directory
103    (expand-file-name ".mpd" (user-home-directory)))
104   "Base mpd directory."
105   :type 'directory
106   :group 'mpd)
107
108 (defcustom mpd-conf-file
109   (let ((locations
110          (list (expand-file-name
111                 "mpd.conf"
112                 (paths-construct-path (list (getenv "XDG_CONFIG_HOME") "mpd")))
113                (expand-file-name ".mpdconf" (user-home-directory))
114                (expand-file-name
115                 "mpd.conf"
116                 (paths-construct-path (list (user-home-directory) ".mpd")))
117                (expand-file-name "mpd.conf" "/etc"))))
118     (catch 'conf
119       (mapcar #'(lambda (file)
120                   (and (file-exists-p file)
121                        (throw 'conf file)))
122               locations)))
123   "The mpd config file.
124
125 The default should be fine here because the file is searched for in
126 the same places and in the same order as mpd itself does."
127   :type '(file :must-match t)
128   :group 'mpd)
129
130 (defcustom mpd-after-command-hook nil
131   "Hooks to run after MPD command is executed.
132 Executed command name stored in `mpd-this-command'."
133   :type 'hook
134   :group 'mpd)
135
136 (defcustom mpd-before-variables-update-hook nil
137   "Hooks to run before updating mpd variables."
138   :type 'hook
139   :group 'mpd)
140
141 (defcustom mpd-after-variables-update-hook nil
142   "Hooks to run after mpd variables has been updated."
143   :type 'hook
144   :group 'mpd)
145
146 \f
147 (defvar mpd-process nil)
148 (defvar mpd-itimer nil)
149 (defvar mpd-dock-frame nil)
150 (defvar mpd-dock-buffer nil)
151
152 (defun mpd-start-connection ()
153   "Open connection to MusicPD daemon.
154 Set `mpd-process' by side effect."
155   (when (or (not mpd-process)
156             (not (eq mpd-process 'open)))
157     (setq mpd-process (open-network-stream "mpd" " *mpd connection*"
158                                            "localhost" 6600))
159     (when (fboundp 'set-process-coding-system)
160       (set-process-coding-system mpd-process 'utf-8 'utf-8))
161     (set-process-filter mpd-process 'mpd-process-filter)
162     (set-process-sentinel mpd-process 'mpd-process-sentinel)
163     (process-kill-without-query mpd-process)
164     (add-hook 'mpd-after-command-hook #'mpd-update-variables)
165     (add-hook 'mpd-before-variables-update-hook #'mpd-clear-variables)
166     (setq mpd-itimer
167           (start-itimer "mpd-vars-update" #'mpd-update-variables
168                         mpd-update-rate mpd-update-rate))))
169
170 (defun mpd-disconnect ()
171   "Disconnect from the mpd daemon.
172
173 Also removes the update hook, kills the itimer, and removes the dock
174 frame."
175   (interactive)
176   (let ((proc (get-process (process-name mpd-process)))
177         (timer (get-itimer (itimer-name mpd-itimer))))
178     (remove-hook 'mpd-after-command-hook #'mpd-update-variables)
179     (remove-hook 'mpd-before-variables-update-hook #'mpd-clear-variables)
180     (when (itimerp timer)
181       (delete-itimer timer))
182     (when (process-live-p proc)
183       (delete-process (get-process (process-name mpd-process))))
184     (when (frame-live-p mpd-dock-frame)
185       (delete-frame mpd-dock-frame))
186     (when (buffer-live-p mpd-dock-buffer)
187       (kill-buffer mpd-dock-buffer))))
188
189 (defun mpd-music-directory ()
190   "Returns the value of \"music_directory\" from mpd config."
191   (with-temp-buffer
192     (insert-file-contents-literally mpd-conf-file)
193     (re-search-forward "^music_directory[[:blank:]]+" nil t)
194     (buffer-substring (1+ (point)) (1- (point-at-eol)))))
195
196 (defvar mpd-music-directory (mpd-music-directory)
197   "The music directory.")
198
199 (defvar mpd-zero-vars-p t)
200 (defvar mpd-status-update-p nil)
201
202 (defvar mpd-pre-mute-volume nil
203   "Holds the value of `**mpd-var-volume* prior to muting.
204 The purpose of this is so that when you unmute, it goes back to the
205 volume you had it set to before you muted.")
206
207 (defvar mpd-this-command nil
208   "The mpd command currently executing.
209 Useful to use in `mpd-after-command-hook' hooks.")
210
211 (defmacro define-mpd-command (cmd args &rest body)
212   "Define new mpd command.
213
214 This will run the hooks in `mpd-after-command-hook' while
215 `mpd-this-command' is let-bound to the name of the command."
216   `(defun ,cmd ,args
217      ,@body
218      (let ((mpd-this-command ',cmd))
219        (run-hooks 'mpd-after-command-hook))))
220
221 (defun mpd-send (format &rest args)
222   "Send formated string into connection.
223 FORMAT and ARGS are passed directly to `format' as arguments."
224   (let ((string (concat (apply #'format format args) "\n")))
225     (if (eq (process-status mpd-process) 'open)
226         (process-send-string mpd-process string)
227       (mpd-start-connection)
228       (process-send-string mpd-process string))))
229
230 (defun mpd-state ()
231   "Return the current state of mpd as a string.
232
233 Possible values are: \"play\", \"pause\", and \"stop\"."
234   (and-boundp '**mpd-var-state*
235     **mpd-var-state*))
236
237 (defun mpd-stopped-p ()
238   "Return t if the music is stopped."
239     (string= (mpd-state) "stop"))
240
241 (defun mpd-paused-p ()
242   "Return t if the music is paused."
243     (string= (mpd-state) "pause"))
244
245 ;; Volume
246 (defun mpd-volume ()
247   "Return the current mpd volume as an integer."
248   (or (and-boundp '**mpd-var-volume*
249         (string-to-int **mpd-var-volume*))
250       0))
251
252 (defun mpd-muted-p ()
253   "Return t when the volume is muted."
254   (zerop (mpd-volume)))
255
256 (defun mpd-volume-up (step)
257   "Increase the volume by STEP increments.
258 STEP can be given via numeric prefix arg and defaults to 1 if omitted."
259   (interactive "p")
260   (let* ((oldvol (mpd-volume))
261          (newvol (+ oldvol step)))
262     (when (>= newvol 100)
263       (setq newvol 100))
264     (mpd-send "setvol %d" newvol)))
265
266 (defun mpd-volume-down (step)
267   "Decrease the volume by STEP increments.
268 STEP can be given via numeric prefix arg and defaults to 1 if omitted."
269   (interactive "p")
270   (let* ((oldvol (mpd-volume))
271          (newvol (- oldvol step)))
272     (when (<= newvol 0)
273       (setq newvol 0))
274     (mpd-send "setvol %d" newvol)))
275
276 (define-mpd-command mpd-volume-mute (&optional unmute)
277   "Mute the volume.
278 With prefix arg, UNMUTE, let the tunes blast again."
279   (interactive "P")
280   (if unmute
281       (mpd-send "setvol %s" mpd-pre-mute-volume)
282     (setq mpd-pre-mute-volume (mpd-volume))
283     (mpd-send "setvol 0")))
284
285 (defun mpd-volume-mute/unmute ()
286   "Wrapper around #'mpd-volume-mute to mute and unmute."
287   (interactive)
288   (if (mpd-muted-p)
289       (mpd-volume-mute 'unmute)
290     (mpd-volume-mute)))
291
292 (define-mpd-command mpd-volume-max ()
293   "Set volume to maximum."
294   (interactive)
295   (mpd-send "setvol 100"))
296
297 (define-mpd-command mpd-volume-min ()
298   "Set volume to minimum.
299 Mutes the mpd audio by side effect."
300   (interactive)
301   (setq mpd-pre-mute-volume (mpd-volume))
302   (mpd-send "setvol 0"))
303
304 ;; Seek
305 (defun mpd-songpos ()
306   "Return position in song as a cons of elapsed and total seconds."
307   (and-boundp '**mpd-var-time*
308     (destructuring-bind (a b)
309         (split-string **mpd-var-time* ":")
310       (cons (string-to-int a) (string-to-int b)))))
311
312 (define-mpd-command mpd-seek (time)
313   "Seek current track to TIME."
314   (and-boundp '**mpd-var-Id*
315     (mpd-send "seekid %s %d"
316               **mpd-var-Id* (+ (car (mpd-songpos)) time))))
317
318 (defun mpd-seek-forward ()
319   "Seek forward 10 seconds."
320   (interactive)
321   (mpd-seek 10))
322
323 (defun mpd-seek-backward ()
324   "Seek backward 10 seconds."
325   (interactive)
326   (mpd-seek -10))
327
328 ;; Playing operations
329 (define-mpd-command mpd-next-track ()
330   "Start playing next track."
331   (interactive)
332   (mpd-send "next"))
333
334 (define-mpd-command mpd-previous-track ()
335   "Start playing previous track."
336   (interactive)
337   (mpd-send "previous"))
338
339 (define-mpd-command mpd-stop ()
340   "Stop playing."
341   (interactive)
342   (mpd-send "stop"))
343
344 (define-mpd-command mpd-play ()
345   "Start playing."
346   (interactive)
347   (mpd-send "play"))
348
349 (define-mpd-command mpd-pause ()
350   "Pause playing."
351   (interactive)
352   (mpd-send "pause"))
353
354 (define-mpd-command mpd-playpause ()
355   "Resume playing or pause."
356   (interactive)
357   (if (mpd-stopped-p)
358       (mpd-send "play")
359     (mpd-send "pause")))
360
361 (defun mpd-process-filter (process output)
362   "MPD proccess filter."
363   (with-temp-buffer
364     (insert output)
365     (goto-char (point-min))
366     (while (not (eobp))
367       (when (looking-at "\\(.*?\\): \\(.*\\)")
368         (set (intern (format "**mpd-var-%s*" (match-string 1)))
369              (match-string 2)))
370       (forward-line 1)))
371   (when mpd-status-update-p
372     (setq mpd-status-update-p nil)
373     (setq mpd-zero-vars-p nil)
374     (run-hooks 'mpd-after-variables-update-hook)))
375
376 (defun mpd-process-sentinel (proc &optional evstr)
377   (let ((timer (get-itimer mpd-itimer)))
378     (message "[MPD]: %s" evstr)
379     (delete-process proc)
380     (when (itimerp timer)
381       (delete-itimer timer))
382     (setq mpd-itimer nil
383           mpd-process nil)))
384
385 (defvar mpd-cover-glyph nil
386   "The extent holding the album cover art.")
387
388 (defvar mpd-current-filename nil
389   "Filename of the currently playing mpd track.
390
391 This differs from `**mpd-var-file*' in that it is only updated once
392 per track change instead of every time `mpd-itimer' fires.")
393
394 (defun mpd-file ()
395   "Return the file name of current track from mpd.
396
397 Note that this is just what is reported by mpd.  To perform operations
398 on the file on disc you will need to prepend `mpd-music-directory' to
399 it first."
400   (and-boundp '**mpd-var-file*
401     **mpd-var-file*))
402
403 (defun mpd-cover-file ()
404   "Return a possible coverart filename.
405
406 The file may not exist on disc so call `file-exists-p' on it, or see
407 `mpd-has-cover-p'."
408   (let ((dir (paths-construct-path
409               (list mpd-music-directory (file-dirname (mpd-file))))))
410     (expand-file-name "cover.jpg" dir)))
411
412 (defun mpd-has-cover-p ()
413   "Return t when coverart exists for the current track."
414   (file-exists-p (mpd-cover-file)))
415
416 (defun mpd-scale-cover (cover height &optional width)
417   "Scale image, COVER to HEIGHT x WIDTH.
418
419 Argument COVER is the image filename.  No checks are made on its
420 existence, or even if it is an image file \(only JPEG is supported
421 incidently\).  So you should do some rudimentary checks before
422 calling this.  The file on disc is left unchanged.
423
424 Argument HEIGHT is the height in pixels to scale the image to.
425
426 Optional argument WIDTH is the width in pixels to scale the image to.
427 If omitted it defaults to HEIGHT.
428
429 A string is returned that can be used in the :data key of `make-glyph'."
430   (with-temp-buffer
431     (shell-command
432      (format (concat "jpegtopnm " "'" cover "' 2>/dev/null"
433                      "|pnmnorm 2>/dev/null"
434                      "|pnmscale -height %d -width %d"
435                      "|pnmtojpeg") height (or width height))
436                    'insert)
437     (buffer-string)))
438
439 (defun mpd-update-cover ()
440   "Updates the cover art glyph."
441   (unless (equal mpd-current-filename (mpd-file))
442     (with-current-buffer mpd-dock-buffer
443       (let ((cover (mpd-cover-file))
444             (nocover (expand-file-name "nocover.jpg" mpd-directory)))
445         (if (mpd-has-cover-p)
446             (set-extent-end-glyph
447              mpd-cover-glyph
448              (make-glyph
449               (list (vector 'jpeg :data (mpd-scale-cover cover 48)))))
450           (set-extent-end-glyph
451            mpd-cover-glyph
452            (make-glyph (list (vector 'jpeg :file nocover)))))))
453     (setq mpd-current-filename (mpd-file))))
454
455 (defun mpd-update-variables ()
456   "Requests status information."
457   (run-hooks 'mpd-before-variables-update-hook)
458   (setq mpd-zero-vars-p t)
459   (mpd-send "currentsong")
460   (setq mpd-status-update-p t)
461   (mpd-send "status")
462   (mpd-update-cover))
463
464 (defun mpd-clear-variables ()
465   "Clears the most relevant mpd variables."
466   (with-boundp '(**mpd-var-Title* **mpd-var-Artist* **mpd-var-Album*
467                                   **mpd-var-Genre* **mpd-var-Date*)
468     (setq **mpd-var-Title* nil
469           **mpd-var-Artist* nil
470           **mpd-var-Album* nil
471           **mpd-var-Genre* nil
472           **mpd-var-Date* nil)))
473
474 \f
475 ;;;; Dockapp section
476 (defvar mpd-dock-frame-plist
477   '((name . "MpdDock")
478     (height . 3)
479     (width . 13)
480     (unsplittable . t)
481     (minibuffer . none)
482     (menubar-visible-p . nil)
483     (has-modeline-p . nil)
484     (default-gutter-visible-p . nil)
485     (default-toolbar-visible-p . nil)
486     (scrollbar-height . 0)
487     (scrollbar-width . 0)
488     (text-cursor-visible-p . nil))
489   "Frame properties for mpd dock.")
490
491 (defun mpd-info (&rest args)
492   "Returns a string for use in the mpd balloon-help frame."
493   (with-boundp '(**mpd-var-Title* **mpd-var-Artist* **mpd-var-Album*
494                                   **mpd-var-Genre* **mpd-var-Date*)
495     (let ((title (or **mpd-var-Title* "Unknown"))
496           (artist (or **mpd-var-Artist* "Unknown"))
497           (album (or **mpd-var-Album* "Unknown"))
498           (genre (or **mpd-var-Genre* "Unknown"))
499           (year (or **mpd-var-Date* "Unknown"))
500           (file (file-name-nondirectory (mpd-file))))
501       (format (concat
502                (when (mpd-has-cover-p)
503                  "\n\n")
504                "--[ %s ]\n
505 Artist: %s
506 Album: %s
507 Year: %s  Genre: %s\n
508 --[ %s ]"
509                (when (mpd-has-cover-p)
510                  "\n\n\n\n\n\n\n\n"))
511               title artist album year genre file))))
512
513 (defun mpd-balloon-cover ()
514   "Inserts coverart into mpd balloon."
515   (let ((cover (mpd-cover-file)))
516     (when (mpd-has-cover-p)
517       (set-extent-begin-glyph
518        (make-extent (point-min) (point-min))
519        (make-glyph
520         (list (vector 'jpeg :data (mpd-scale-cover cover 128))))))))
521
522 (defadvice balloon-help-display-help (after mpd-balloon-cover (&rest args) activate)
523   "Display cover art image in the balloon."
524   (when (process-live-p (get-process "mpd"))
525     (set-buffer balloon-help-buffer)
526     (goto-char (point-max))
527     (and (re-search-backward (file-name-nondirectory (mpd-file)) nil t)
528          (mpd-balloon-cover))))
529
530 (defconst mpd-prev-map
531   (let* ((map (make-sparse-keymap 'mpd-prev-map)))
532     (define-key map [button1] 'mpd-previous-track)
533     map)
534   "Keymap for \"Prev\" button.")
535
536 (defconst mpd-pause-map
537   (let* ((map (make-sparse-keymap 'mpd-pause-map)))
538     (define-key map [button1] 'mpd-pause)
539     map)
540   "Keymap for \"Pause\" button.")
541
542 (defconst mpd-play-map
543   (let* ((map (make-sparse-keymap 'mpd-play-map)))
544     (define-key map [button1] 'mpd-play)
545     map)
546   "Keymap for \"Play\" button.")
547
548 (defconst mpd-next-map
549   (let* ((map (make-sparse-keymap 'mpd-next-map)))
550     (define-key map [button1] 'mpd-next-track)
551     map)
552   "Keymap for \"Next\" button.")
553
554 (defun mpd-new-frame ()
555   "Create new mpd frame."
556   (unless (frame-live-p mpd-dock-frame)
557     (setq mpd-dock-frame (new-frame mpd-dock-frame-plist))
558     (select-frame mpd-dock-frame)
559     (unless (buffer-live-p mpd-dock-buffer)
560       (setq mpd-dock-buffer (get-buffer-create "*MpdDock*"))
561       (set-buffer-dedicated-frame mpd-dock-buffer mpd-dock-frame)
562       (save-excursion
563         (let (prev pause play next)
564           (set-buffer mpd-dock-buffer)
565           (set-extent-end-glyph
566            (setq prev (make-extent (point-max) (point-max)))
567            (make-glyph
568             (list (vector 'xpm :file (expand-file-name "Rewind.xpm"
569                                                        mpd-directory)))))
570           (set-extent-properties 
571            prev
572            `(keymap ,mpd-prev-map balloon-help "Previous Track"))
573           (set-extent-end-glyph
574            (setq pause (make-extent (point-max) (point-max)))
575            (make-glyph
576             (list (vector 'xpm :file (expand-file-name "Pause.xpm"
577                                                        mpd-directory)))))
578           (set-extent-properties
579            pause
580            `(keymap ,mpd-pause-map balloon-help "Pause"))
581           (set-extent-end-glyph
582            (setq play (make-extent (point-max) (point-max)))
583            (make-glyph
584             (list (vector 'xpm :file (expand-file-name "Play.xpm"
585                                                        mpd-directory)))))
586           (set-extent-properties
587            play
588            `(keymap ,mpd-play-map balloon-help "Play"))
589           (set-extent-end-glyph
590            (setq next (make-extent (point-max) (point-max)))
591            (make-glyph
592             (list (vector 'xpm :file (expand-file-name "FFwd.xpm"
593                                                        mpd-directory)))))
594           (set-extent-properties
595            next
596            `(keymap ,mpd-next-map balloon-help "Next Track"))
597           (insert " ")
598           (set-extent-end-glyph
599            (setq mpd-cover-glyph (make-extent (point-max) (point-max)))
600            (make-glyph
601             (list (vector 'jpeg :file (expand-file-name "nocover.jpg"
602                                                         mpd-directory)))))
603           (set-extent-properties
604            mpd-cover-glyph
605            `(keymap ,mpd-pause-map balloon-help ,#'mpd-info)))))
606     (set-specifier horizontal-scrollbar-visible-p nil
607                    (cons mpd-dock-frame nil))
608     (set-specifier vertical-scrollbar-visible-p nil
609                    (cons mpd-dock-frame nil))
610     (set-window-buffer nil mpd-dock-buffer)))
611
612 (defun mpd ()
613   "Start mpd dockapp to interact with MusicPD."
614   (interactive)
615   (let ((cframe (selected-frame)))
616     ;; Start client connection
617     (mpd-start-connection)
618     (mpd-new-frame)
619     (focus-frame cframe)
620     (mpd-update-variables)))
621
622
623 (provide 'mpd)
624
625 ;;; mpd.el ends here