;;; tiny-xmms.el --- Control XMMS from Emacs ;; Author: Mark Triggs ;; This file 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 2, or (at your option) ;; any later version. ;; This file 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; see the file COPYING. If not, write to ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, ;; Boston, MA 02111-1307, USA. ;;; Commentary: ;; A very minimal XMMS interface. If you're looking for a serious XMMS ;; interface for Emacs you'd be better to try http://www.emacswiki.org/. ;; Requires the small program tiny-xmms-remote.c (which should be available ;; where you found this file). ;;; Code: (require 'cl) (require 'highline) (defcustom tiny-xmms-remote-cmd "tiny-xmms-remote" "The location of the tiny-xmms-remote command") (defcustom tiny-xmms-buffer "*xmms playlist*" "The buffer used by tiny-xmms") (defvar tiny-xmms-playlist-identifier nil "An identifier for the last observed playlist") (when (not (boundp 'tiny-xmms-mode-map)) (defvar tiny-xmms-mode-map (make-sparse-keymap) "The keymap for tiny-xmms") (define-key tiny-xmms-mode-map (kbd "RET") 'tiny-xmms-skip-to-current) (define-key tiny-xmms-mode-map (kbd "d") 'tiny-xmms-remove-track) (define-key tiny-xmms-mode-map (kbd "s") 'tiny-xmms-sort) (define-key tiny-xmms-mode-map (kbd "q") 'tiny-xmms-quit) (define-key tiny-xmms-mode-map (kbd "g") 'tiny-xmms-generate-playlist) (define-key tiny-xmms-mode-map (kbd "n") 'tiny-xmms-next) (define-key tiny-xmms-mode-map (kbd "p") 'tiny-xmms-prev) (define-key tiny-xmms-mode-map (kbd "r") 'tiny-xmms-jump-to-random) (define-key tiny-xmms-mode-map (kbd "C-_") 'tiny-xmms-prev) (define-key tiny-xmms-mode-map (kbd "S") 'tiny-xmms-stop) (define-key tiny-xmms-mode-map (kbd "SPC") 'tiny-xmms-play/pause) (define-key tiny-xmms-mode-map (kbd ".") 'tiny-xmms-update-buffer) (define-key tiny-xmms-mode-map (kbd "C-s") (lambda () (interactive) (goto-char (point-min)) (call-interactively 'isearch-forward)))) (defface tiny-xmms-highline '((t (:background "light blue" :foreground "black" :weight bold))) "The faced used to show the currently highlighted track") (defun tiny-xmms-overlays () (remove-if-not (lambda (o) (eq (overlay-get o 'type) 'tiny-xmms)) (overlays-in (point-min) (point-max)))) (defun tiny-xmms-generate-playlist (&optional sorted) "Generate the playlist buffer" (interactive) (with-current-buffer (get-buffer-create tiny-xmms-buffer) (setq buffer-read-only nil) (delete-region (point-min) (point-max))) (let ((callback (lambda (contents) (let ((playlist (butlast (split-string contents (concat "[\n" (string 0) "]"))))) (with-current-buffer (get-buffer-create tiny-xmms-buffer) (setq buffer-read-only nil) (delete-region (point-min) (point-max)) (loop for (filename pos) on playlist by 'cddr do (when (not (string= "" filename)) (let ((p (point))) (insert (concat filename "\n")) (let ((o (make-overlay p (point)))) (overlay-put o 'pos pos) (overlay-put o 'type 'tiny-xmms))))) (setq buffer-read-only t) (goto-char (point-min)) (tiny-xmms-update-buffer)))))) (if sorted (tiny-xmms-remote 'get-sorted callback) (tiny-xmms-remote 'get callback))) (setq tiny-xmms-playlist-identifier (tiny-xmms-remote 'id))) (defun tiny-xmms () "Jump to a playlist buffer" (interactive) (let ((first-run (not (get-buffer tiny-xmms-buffer))) (buffer (get-buffer-create tiny-xmms-buffer))) (pop-to-buffer buffer) (cond (first-run (tiny-xmms-generate-playlist) (tiny-xmms-mode)) ((or (and tiny-xmms-playlist-identifier (/= (tiny-xmms-remote 'id) tiny-xmms-playlist-identifier)) (/= (save-excursion (goto-char (point-max)) (1- (current-line-number))) (tiny-xmms-remote 'length))) (tiny-xmms-generate-playlist)) (t (tiny-xmms-update-buffer))))) (defun tiny-xmms-update-buffer () "Update the playlist buffer from XMMS" (interactive) (with-current-buffer (get-buffer tiny-xmms-buffer) (let ((playing (tiny-xmms-remote 'playing))) (setq header-line-format (format "[tiny-xmms] %s: %s (%s)" (upcase-initials (tiny-xmms-remote 'state)) playing (tiny-xmms-remote 'status))) (let ((line (format "^%s$" (regexp-quote playing)))) (unless (save-excursion (beginning-of-line) (looking-at line)) (goto-char (point-min)) (let ((pos (search-forward-regexp line nil t))) (when pos (goto-char pos)))))) (recenter) (set-buffer-modified-p nil))) (defun tiny-xmms-overlays-at (point) (remove-if-not (lambda (o) (eq (overlay-get o 'type) 'tiny-xmms)) (overlays-at point))) (defun tiny-xmms-remove-track () "Remove the current track from the playlist" (interactive) (let ((overlay (car (tiny-xmms-overlays-at (point))))) (unless overlay (error "No track under point")) (let ((pos (overlay-get overlay 'pos))) (unless pos (setq test overlay) (debug)) (tiny-xmms-remote 'remove pos) (mapc (lambda (overlay) (let ((removed-pos (string-to-number pos)) (this-pos (string-to-number (overlay-get overlay 'pos)))) (when (> this-pos removed-pos) (overlay-put overlay 'pos (number-to-string (1- this-pos)))))) (tiny-xmms-overlays)))) (let ((inhibit-read-only t)) (mapc 'delete-overlay (tiny-xmms-overlays-at (point))) (delete-region (line-beginning-position) (1+ (line-end-position))) (when (eobp) (forward-line -1)))) (defun tiny-xmms-remote (command &rest args) "Interface to XMMS. A thin wrapper around the tiny-xmms-remote program." (case command (skip-to (call-process tiny-xmms-remote-cmd nil nil nil "skip" (car args))) ((get get-sorted) (let* ((buf (get-buffer-create (generate-new-buffer-name " *temp*"))) (p (start-process "xmms-playlist" buf tiny-xmms-remote-cmd "get"))) (set-process-sentinel p `(lambda (process state) (let ((state (process-status process)) (status (process-exit-status process))) (when (eq state 'exit) (with-current-buffer ,buf (when (eq ',command 'get-sorted) (sort-lines nil (point-min) (point-max))) (progn (funcall ',(car args) (buffer-string)) (kill-buffer ,buf))))))))) (length (with-temp-buffer (call-process tiny-xmms-remote-cmd nil t nil "length") (string-to-number (buffer-string)))) (state (with-temp-buffer (call-process tiny-xmms-remote-cmd nil t nil "state") (subseq (buffer-string) 0 -1))) (playing (with-temp-buffer (call-process tiny-xmms-remote-cmd nil t nil "playing") (car (split-string (buffer-string) "\n" t)))) (status (with-temp-buffer (call-process tiny-xmms-remote-cmd nil t nil "status") (car (split-string (buffer-string) "\n" t)))) (play (call-process tiny-xmms-remote-cmd nil t nil "play")) (stop (call-process tiny-xmms-remote-cmd nil t nil "stop")) (pause (call-process tiny-xmms-remote-cmd nil t nil "pause")) (next (call-process tiny-xmms-remote-cmd nil t nil "next")) (prev (call-process tiny-xmms-remote-cmd nil t nil "prev")) (remove (apply 'call-process tiny-xmms-remote-cmd nil t nil "remove" args)) (id (with-temp-buffer (call-process tiny-xmms-remote-cmd nil t nil "id") (string-to-number (buffer-string)))))) (defun tiny-xmms-play () "Play XMMS" (interactive) (tiny-xmms-remote 'play) (tiny-xmms-update-buffer)) (defun tiny-xmms-prev () "Skip back a last track" (interactive) (tiny-xmms-remote 'prev) (sit-for 0.2) ; yuck. (tiny-xmms-update-buffer)) (defun tiny-xmms-buffer-lines (&optional buffer) "Return the number of lines in BUFFER." (save-excursion (with-current-buffer (or buffer (current-buffer)) (goto-char (point-min)) (loop while (progn (end-of-line) (not (eobp))) count t do (next-line 1))))) (defun tiny-xmms-jump-to-random () "Skip back a last track" (interactive) (goto-line (random (1+ (tiny-xmms-buffer-lines))))) (defun tiny-xmms-next () "Skip to the next track" (interactive) (tiny-xmms-remote 'next) (sit-for 0.2) ; yuck. (tiny-xmms-update-buffer)) (defun tiny-xmms-stop () "Stop XMMS" (interactive) (tiny-xmms-remote 'stop) (tiny-xmms-update-buffer)) (defun tiny-xmms-pause () "Pause XMMS" (interactive) (tiny-xmms-remote 'pause) (tiny-xmms-update-buffer)) (defun tiny-xmms-play/pause () "Toggle between play/pause" (interactive) (if (string= (tiny-xmms-remote 'state) "playing") (tiny-xmms-pause) (tiny-xmms-play))) (defun tiny-xmms-skip-to-current () "Jump to the track under point" (interactive) (tiny-xmms-remote 'skip-to (overlay-get (car (tiny-xmms-overlays-at (point))) 'pos)) (tiny-xmms-update-buffer)) (defun tiny-xmms-sort () "Sort the playlist" (interactive) (tiny-xmms-generate-playlist 'string<)) (defun tiny-xmms-quit () "Bury the current buffer and delete its window" (interactive) (bury-buffer) (delete-window)) (define-derived-mode tiny-xmms-mode fundamental-mode "tiny-xmms" "Mode for jumping around the XMMS playlist" (use-local-map tiny-xmms-mode-map) (make-variable-buffer-local 'tiny-xmms-now-playling) (set (make-local-variable 'highline-face) 'tiny-xmms-highline) (highline-local-on) (setq cursor-type '(bar . 1))) (provide 'tiny-xmms) ;;; tiny-xmms.el ends here