(set-process-filter mpd-process 'mpd-process-filter)
(set-process-sentinel mpd-process 'mpd-process-sentinel)
(process-kill-without-query mpd-process)
-
(add-hook 'mpd-after-command-hook #'mpd-update-variables)
+ (add-hook 'mpd-before-variables-update-hook #'mpd-clear-variables)
(setq mpd-itimer
(start-itimer "mpd-vars-update" #'mpd-update-variables
mpd-update-rate mpd-update-rate))))
(let ((proc (get-process (process-name mpd-process)))
(timer (get-itimer (itimer-name mpd-itimer))))
(remove-hook 'mpd-after-command-hook #'mpd-update-variables)
+ (remove-hook 'mpd-before-variables-update-hook #'mpd-clear-variables)
(when (itimerp timer)
(delete-itimer timer))
(when (process-live-p proc)
(defvar mpd-music-directory (mpd-music-directory)
"The music directory.")
-;; mpd variables
(defvar mpd-zero-vars-p t)
(defvar mpd-status-update-p nil)
-(defvar **mpd-var-Album* nil)
-(defvar **mpd-var-Artist* nil)
-(defvar **mpd-var-Date* nil)
-(defvar **mpd-var-Genre* nil)
-(defvar **mpd-var-Id* nil)
-(defvar **mpd-var-Pos* nil)
-(defvar **mpd-var-Time* nil)
-(defvar **mpd-var-Title* nil)
-(defvar **mpd-var-Track* nil)
-(defvar **mpd-var-audio* nil)
-(defvar **mpd-var-bitrate* nil)
-(defvar **mpd-var-file* nil)
-(defvar **mpd-var-length* nil)
-(defvar **mpd-var-playlist* nil)
-(defvar **mpd-var-playlistlength* nil)
-(defvar **mpd-var-random* nil)
-(defvar **mpd-var-repeat* nil)
-(defvar **mpd-var-song* nil)
-(defvar **mpd-var-songid* nil)
-(defvar **mpd-var-state* nil)
-(defvar **mpd-var-time* nil)
-(defvar **mpd-var-volume* nil)
-(defvar **mpd-var-xfade* nil)
-
(defvar mpd-pre-mute-volume nil
"Holds the value of `**mpd-var-volume* prior to muting.
The purpose of this is so that when you unmute, it goes back to the
Useful to use in `mpd-after-command-hook' hooks.")
(defmacro define-mpd-command (cmd args &rest body)
- "Define new mpd command."
+ "Define new mpd command.
+
+This will run the hooks in `mpd-after-command-hook' while
+`mpd-this-command' is let-bound to the name of the command."
`(defun ,cmd ,args
,@body
(let ((mpd-this-command ',cmd))
(mpd-start-connection)
(process-send-string mpd-process string))))
+(defun mpd-state ()
+ "Return the current state of mpd as a string.
+
+Possible values are: \"play\", \"pause\", and \"stop\"."
+ (and-boundp '**mpd-var-state*
+ **mpd-var-state*))
+
(defun mpd-stopped-p ()
- (string= **mpd-var-state* "stop"))
+ "Return t if the music is stopped."
+ (string= (mpd-state) "stop"))
+
(defun mpd-paused-p ()
- (string= **mpd-var-state* "pause"))
-(defun mpd-muted-p ()
- (zerop (string-to-number **mpd-var-volume*)))
+ "Return t if the music is paused."
+ (string= (mpd-state) "pause"))
-;; (mpd-songpos)
-(defun mpd-songpos ()
- (if **mpd-var-time*
- (destructuring-bind (a b)
- (split-string **mpd-var-time* ":")
- (cons (string-to-int a) (string-to-int b)))
- (cons 0 1))) ; todo?
+;; Volume
+(defun mpd-volume ()
+ "Return the current mpd volume as an integer."
+ (or (and-boundp '**mpd-var-volume*
+ (string-to-int **mpd-var-volume*))
+ 0))
+
+(defun mpd-muted-p ()
+ "Return t when the volume is muted."
+ (zerop (mpd-volume)))
(defun mpd-volume-up (step)
"Increase the volume by STEP increments.
STEP can be given via numeric prefix arg and defaults to 1 if omitted."
(interactive "p")
- (let* ((oldvol (string-to-number **mpd-var-volume*))
- (newvol (+ oldvol step))
- (mpd-this-command 'mpd-volume-down))
+ (let* ((oldvol (mpd-volume))
+ (newvol (+ oldvol step)))
(when (>= newvol 100)
(setq newvol 100))
- (mpd-send "setvol %d" newvol)
- (run-hooks 'mpd-after-command-hook)))
+ (mpd-send "setvol %d" newvol)))
(defun mpd-volume-down (step)
"Decrease the volume by STEP increments.
STEP can be given via numeric prefix arg and defaults to 1 if omitted."
(interactive "p")
- (let* ((oldvol (string-to-number **mpd-var-volume*))
- (newvol (- oldvol step))
- (mpd-this-command 'mpd-volume-down))
+ (let* ((oldvol (mpd-volume))
+ (newvol (- oldvol step)))
(when (<= newvol 0)
(setq newvol 0))
- (mpd-send "setvol %d" newvol)
- (run-hooks 'mpd-after-command-hook)))
+ (mpd-send "setvol %d" newvol)))
-(defun mpd-volume-mute (&optional unmute)
+(define-mpd-command mpd-volume-mute (&optional unmute)
"Mute the volume.
With prefix arg, UNMUTE, let the tunes blast again."
(interactive "P")
(if unmute
(mpd-send "setvol %s" mpd-pre-mute-volume)
- (setq mpd-pre-mute-volume **mpd-var-volume*)
- (mpd-send "setvol 0"))
- (let ((mpd-this-command 'mpd-volume-mute))
- (run-hooks 'mpd-after-command-hook)))
+ (setq mpd-pre-mute-volume (mpd-volume))
+ (mpd-send "setvol 0")))
(defun mpd-volume-mute/unmute ()
"Wrapper around #'mpd-volume-mute to mute and unmute."
(define-mpd-command mpd-volume-min ()
"Set volume to minimum.
-Sets state to \"muted\" by side effect."
+Mutes the mpd audio by side effect."
(interactive)
- (setq mpd-pre-mute-volume **mpd-var-volume*)
+ (setq mpd-pre-mute-volume (mpd-volume))
(mpd-send "setvol 0"))
+;; Seek
+(defun mpd-songpos ()
+ "Return position in song as a cons of elapsed and total seconds."
+ (and-boundp '**mpd-var-time*
+ (destructuring-bind (a b)
+ (split-string **mpd-var-time* ":")
+ (cons (string-to-int a) (string-to-int b)))))
+
(define-mpd-command mpd-seek (time)
"Seek current track to TIME."
- (mpd-send "seekid %s %d" **mpd-var-Id* (+ (car (mpd-songpos)) time)))
+ (and-boundp '**mpd-var-Id*
+ (mpd-send "seekid %s %d"
+ **mpd-var-Id* (+ (car (mpd-songpos)) time))))
(defun mpd-seek-forward ()
+ "Seek forward 10 seconds."
(interactive)
(mpd-seek 10))
(defun mpd-seek-backward ()
+ "Seek backward 10 seconds."
(interactive)
(mpd-seek -10))
-;; Plaing operations
+;; Playing operations
(define-mpd-command mpd-next-track ()
"Start playing next track."
(interactive)
This differs from `**mpd-var-file*' in that it is only updated once
per track change instead of every time `mpd-itimer' fires.")
+(defun mpd-file ()
+ "Return the file name of current track from mpd.
+
+Note that this is just what is reported by mpd. To perform operations
+on the file on disc you will need to prepend `mpd-music-directory' to
+it first."
+ (and-boundp '**mpd-var-file*
+ **mpd-var-file*))
+
+(defun mpd-cover-file ()
+ "Return a possible coverart filename.
+
+The file may not exist on disc so call `file-exists-p' on it, or see
+`mpd-has-cover-p'."
+ (let ((dir (paths-construct-path
+ (list mpd-music-directory (file-dirname (mpd-file))))))
+ (expand-file-name "cover.jpg" dir)))
+
+(defun mpd-has-cover-p ()
+ "Return t when coverart exists for the current track."
+ (file-exists-p (mpd-cover-file)))
+
+(defun mpd-scale-cover (cover height &optional width)
+ "Scale image, COVER to HEIGHT x WIDTH.
+
+Argument COVER is the image filename. No checks are made on its
+existence, or even if it is an image file \(only JPEG is supported
+incidently\). So you should do some rudimentary checks before
+calling this. The file on disc is left unchanged.
+
+Argument HEIGHT is the height in pixels to scale the image to.
+
+Optional argument WIDTH is the width in pixels to scale the image to.
+If omitted it defaults to HEIGHT.
+
+A string is returned that can be used in the :data key of `make-glyph'."
+ (with-temp-buffer
+ (shell-command
+ (format (concat "jpegtopnm " "'" cover "' 2>/dev/null"
+ "|pnmnorm 2>/dev/null"
+ "|pnmscale -height %d -width %d"
+ "|pnmtojpeg") height (or width height))
+ 'insert)
+ (buffer-string)))
+
(defun mpd-update-cover ()
"Updates the cover art glyph."
- (unless (equal mpd-current-filename **mpd-var-file*)
+ (unless (equal mpd-current-filename (mpd-file))
(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)))))
+ (let ((cover (mpd-cover-file))
+ (nocover (expand-file-name "nocover.jpg" mpd-directory)))
+ (if (mpd-has-cover-p)
+ (set-extent-end-glyph
+ mpd-cover-glyph
+ (make-glyph
+ (list (vector 'jpeg :data (mpd-scale-cover cover 48)))))
(set-extent-end-glyph
mpd-cover-glyph
(make-glyph (list (vector 'jpeg :file nocover)))))))
- (setq mpd-current-filename **mpd-var-file*)))
+ (setq mpd-current-filename (mpd-file))))
(defun mpd-update-variables ()
"Requests status information."
(mpd-send "status")
(mpd-update-cover))
+(defun mpd-clear-variables ()
+ "Clears the most relevant mpd variables."
+ (with-boundp '(**mpd-var-Title* **mpd-var-Artist* **mpd-var-Album*
+ **mpd-var-Genre* **mpd-var-Date*)
+ (setq **mpd-var-Title* nil
+ **mpd-var-Artist* nil
+ **mpd-var-Album* nil
+ **mpd-var-Genre* nil
+ **mpd-var-Date* nil)))
+
\f
;;;; Dockapp section
(defvar mpd-dock-frame-plist
"Frame properties for mpd dock.")
(defun mpd-info (&rest args)
- (let ((title (or **mpd-var-Title* "Unknown"))
- (artist (or **mpd-var-Artist* "Unknown"))
- (album (or **mpd-var-Album* "Unknown"))
- (genre (or **mpd-var-Genre* "Unknown"))
- (year (or **mpd-var-Date* "Unknown"))
- (file (file-name-nondirectory **mpd-var-file*)))
- (format "--[ %s ]\n
+ "Returns a string for use in the mpd balloon-help frame."
+ (with-boundp '(**mpd-var-Title* **mpd-var-Artist* **mpd-var-Album*
+ **mpd-var-Genre* **mpd-var-Date*)
+ (let ((title (or **mpd-var-Title* "Unknown"))
+ (artist (or **mpd-var-Artist* "Unknown"))
+ (album (or **mpd-var-Album* "Unknown"))
+ (genre (or **mpd-var-Genre* "Unknown"))
+ (year (or **mpd-var-Date* "Unknown"))
+ (file (file-name-nondirectory (mpd-file))))
+ (format (concat
+ (when (mpd-has-cover-p)
+ "\n\n")
+ "--[ %s ]\n
Artist: %s
Album: %s
Year: %s Genre: %s\n
--[ %s ]"
- title artist album year genre file)))
+ (when (mpd-has-cover-p)
+ "\n\n\n\n\n\n\n\n"))
+ title artist album year genre file))))
+
+(defun mpd-balloon-cover ()
+ "Inserts coverart into mpd balloon."
+ (let ((cover (mpd-cover-file)))
+ (when (mpd-has-cover-p)
+ (set-extent-begin-glyph
+ (make-extent (point-min) (point-min))
+ (make-glyph
+ (list (vector 'jpeg :data (mpd-scale-cover cover 128))))))))
+
+(defadvice balloon-help-display-help (after mpd-balloon-cover (&rest args) activate)
+ "Display cover art image in the balloon."
+ (when (process-live-p (get-process "mpd"))
+ (set-buffer balloon-help-buffer)
+ (goto-char (point-max))
+ (and (re-search-backward (file-name-nondirectory (mpd-file)) nil t)
+ (mpd-balloon-cover))))
(defconst mpd-prev-map
(let* ((map (make-sparse-keymap 'mpd-prev-map)))