;;; mpd.el --- A complete ripoff of xwem-mpd.
-;; Copyright (C) 2008 Steve Youngs
+;; Copyright (C) 2008 - 2013 Steve Youngs
;; Original xwem-mpd:
;; Copyright (C) 2005 Richard Klinda
;;; Commentary:
;; You need MusicPD - Music Playing Daemon
-;; (http://musicpd.sourceforge.net/) to be set up and running.
+;; (http://www.musicpd.org/) to be set up and running.
+
+;; The xwem-mpd-dock from xwem-mpd.el was designed to sit in the xwem
+;; minibuffer (panel), and theoretically you would be able to have the
+;; mpd-dock-frame from here sit in a panel (KDE, GNOME, LXDE, etc)
+;; too. I say "theoretically" because I've never tried so I'm leaving
+;; it as an exercise for the reader. :-)
+
+;; No, instead of docking the mpd-dock-frame onto a panel I prefer to
+;; tweak its frame properties so it will be completely ignored by the
+;; WM. That gives me a frame that has no decorations, is visible on all
+;; workspaces, always "on top", and doesn't show up in "task switchers".
+;; To get tha magic you set the `override-redirect' property to `t', and
+;; for positioning set `left' and/or `top' properties.
+
+;;; Keybinding Suggestions:
+;;
+;; (global-set-key [XF86AudioPlay] #'mpd-playpause)
+;; (global-set-key [XF86AudioStop] #'mpd-stop)
+;; (global-set-key [XF86AudioNext] #'mpd-next-track)
+;; (global-set-key [XF86AudioPrev] #'mpd-previous-track)
+;; (global-set-key [XF86AudioRaiseVolume] #'mpd-volume-up)
+;; (global-set-key [XF86AudioLowerVolume] #'mpd-volume-down)
+;; (global-set-key [XF86AudioMute] #'mpd-volume-mute/unmute)
+;;
+;; Yeah, it helps if you have a keyboard with those fancy keys. :-)
+
+;;; Cover Art:
+;;
+;; First up you will need a 48x48 `nocover.jpg' and put it in
+;; `mpd-directory' (see the images directory in this repo) for albums
+;; that you don't have any art for. Secondly, album art is taken from
+;; `cover.jpg' files in the same directory as the album's audio files.
+;; Yep, sort your music by album (at least).
+;;
+;; Embedded art is not supported at this time, but hopefully one day.
+
+;;; Player control buttons:
+;;
+;; Put the XPM's in `mpd-directory'
;;; Code:
\f
-(eval-when-compile
- (autoload 'google-query "google-query" nil t))
-
(defgroup mpd nil
"Group to customize mpd."
:prefix "mpd-"
:type 'directory
:group 'mpd)
-(defcustom mpd-lyrics-dir
- (file-name-as-directory
- (expand-file-name "lyrics" mpd-directory))
- "Directory containing songs lyrics."
- :type 'directory
+(defcustom mpd-conf-file
+ (let ((locations
+ (list (expand-file-name
+ "mpd.conf"
+ (paths-construct-path (list (getenv "XDG_CONFIG_HOME") "mpd")))
+ (expand-file-name ".mpdconf" (user-home-directory))
+ (expand-file-name
+ "mpd.conf"
+ (paths-construct-path (list (user-home-directory) ".mpd")))
+ (expand-file-name "mpd.conf" "/etc"))))
+ (catch 'conf
+ (mapcar #'(lambda (file)
+ (and (file-exists-p file)
+ (throw 'conf file)))
+ locations)))
+ "The mpd config file.
+
+The default should be fine here because the file is searched for in
+the same places and in the same order as mpd itself does."
+ :type '(file :must-match t)
:group 'mpd)
(defcustom mpd-after-command-hook nil
(when (buffer-live-p mpd-dock-buffer)
(kill-buffer mpd-dock-buffer))))
+(defun mpd-music-directory ()
+ "Returns the value of \"music_directory\" from mpd config."
+ (with-current-buffer (find-file-noselect mpd-conf-file)
+ (re-search-forward "^music_directory[[:blank:]]+" nil t)
+ (buffer-substring (1+ (point)) (1- (point-at-eol)))))
+
+(defvar mpd-music-directory (mpd-music-directory)
+ "The music directory.")
+
;; mpd variables
(defvar mpd-zero-vars-p t)
(defvar mpd-status-update-p nil)
(setq mpd-itimer nil
mpd-process nil)))
+(defvar mpd-cover-glyph nil
+ "The extent holding the album cover art.")
+
+(defun mpd-update-cover ()
+ "Updates the cover art glyph."
+ (with-current-buffer mpd-dock-buffer
+ (let* ((songdir (file-dirname **mpd-var-file*))
+ (cover (expand-file-name
+ "cover.jpg"
+ (paths-construct-path
+ (list mpd-music-directory songdir))))
+ (nocover (expand-file-name "nocover.jpg" mpd-directory))
+ (scaled))
+ (if (file-exists-p cover)
+ (progn
+ (with-temp-buffer
+ (shell-command (concat "jpegtopnm " "'" cover "' 2>/dev/null"
+ "|pnmnorm 2>/dev/null"
+ "|pnmscale -height 48 -width 48"
+ "|pnmtojpeg")
+ 'insert)
+ (setq scaled (buffer-string)))
+ (set-extent-end-glyph
+ mpd-cover-glyph
+ (make-glyph (list (vector 'jpeg :data scaled)))))
+ (set-extent-end-glyph
+ mpd-cover-glyph
+ (make-glyph (list (vector 'jpeg :file nocover))))))))
+
(defun mpd-update-variables ()
"Requests status information."
(run-hooks 'mpd-before-variables-update-hook)
(setq mpd-zero-vars-p t)
(mpd-send "currentsong")
(setq mpd-status-update-p t)
- (mpd-send "status"))
-
-;;; Lyrics support
-(defun mpd-lyric-filename ()
- "Return lyric filename for now playing song."
- (when **mpd-var-file*
- (expand-file-name
- (concat (replace-in-string **mpd-var-file* "\/" "--") ".txt")
- mpd-lyrics-dir)))
-
-(defun mpd-lyric-check ()
- "Return non-nil if current track has local lyrics."
- (let ((fn (mpd-lyric-filename)))
- (and fn (file-exists-p fn))))
-
-(defun mpd-lyric-save ()
- "Save selected lyric to lyric file."
- (interactive "_")
- (if (mpd-lyric-check)
- (message "There is already a lyric for this song")
- (let ((text (get-selection-no-error)))
- (if (not text)
- (message "You should have selected the lyric first!")
- ;; everything is ok
- (with-current-buffer (find-file-noselect (mpd-lyric-filename))
- (insert text)
- (save-buffer))))))
-
-;; Haven't decided what to do with this one yet. --SY.
-;;(define-sawfish-command mpd-lyric-show ()
-;; "Show lyrics for now playing song."
-;; (sawfish-interactive)
-;; (if (mpd-lyric-check)
-;; (let ((temp-buffer-show-function 'sawfish-special-popup-frame)
-;; (header (format "\"%s\" (by: %s)"
-;; **mpd-var-Title*
-;; **mpd-var-Artist*))
-;; (title (format "Lyrics: %s" **mpd-var-Title*)))
-;; (with-output-to-temp-buffer title
-;; (set-buffer standard-output)
-;; (insert header "\n"
-;; (make-string (length header) ?=)
-;; "\n\n")
-;; (insert-file-contents (mpd-lyric-filename))
-;; (toggle-read-only 1)
-;; (view-mode nil #'(lambda (&rest not-used-buffer)
-;; (delete-frame (selected-frame))))))
-;; (when (and **mpd-var-Artist* **mpd-var-Title*)
-;; (let ((lyric-frame (new-frame)))
-;; (select-frame lyric-frame)
-;; (google-query (format "\"%s\" \"%s\" lyrics"
-;; **mpd-var-Artist* **mpd-var-Title*))
-;; (focus-frame lyric-frame)))))
+ (mpd-send "status")
+ (mpd-update-cover))
\f
;;;; Dockapp section
(defvar mpd-dock-frame-plist
'((name . "MpdDock")
- (height . 4)
- (width . 12)
+ (height . 3)
+ (width . 13)
(unsplittable . t)
(minibuffer . none)
(menubar-visible-p . nil)
map)
"Keymap for \"Next\" button.")
-(make-face 'mpd-dock-face
- "Face used in the mpd dock buffer.")
-
(defun mpd-new-frame ()
"Create new mpd frame."
(unless (frame-live-p mpd-dock-frame)
(save-excursion
(let (prev pause play next)
(set-buffer mpd-dock-buffer)
- (set-extent-properties
- (insert-face "[Song Info]" 'mpd-dock-face)
- `(mouse-face highlight read-only t
- balloon-help ,#'mpd-info))
- (insert "\n\n ")
(set-extent-end-glyph
(setq prev (make-extent (point-max) (point-max)))
(make-glyph
mpd-directory)))))
(set-extent-properties
next
- `(keymap ,mpd-next-map balloon-help "Next Track")))))
+ `(keymap ,mpd-next-map balloon-help "Next Track"))
+ (insert " ")
+ (set-extent-end-glyph
+ (setq mpd-cover-glyph (make-extent (point-max) (point-max)))
+ (make-glyph
+ (list (vector 'jpeg :file (expand-file-name "nocover.jpg"
+ mpd-directory)))))
+ (set-extent-properties
+ mpd-cover-glyph
+ `(keymap ,mpd-pause-map balloon-help ,#'mpd-info)))))
(set-specifier horizontal-scrollbar-visible-p nil
(cons mpd-dock-frame nil))
(set-specifier vertical-scrollbar-visible-p nil