[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[ELPA-diffs] /srv/bzr/emacs/elpa r231: * ampc.el: Sync to version 0.1.3.
From: |
Stefan Monnier |
Subject: |
[ELPA-diffs] /srv/bzr/emacs/elpa r231: * ampc.el: Sync to version 0.1.3. |
Date: |
Tue, 05 Jun 2012 20:56:29 -0400 |
User-agent: |
Bazaar (2.5.0) |
------------------------------------------------------------
revno: 231 [merge]
committer: Stefan Monnier <address@hidden>
branch nick: elpa
timestamp: Tue 2012-06-05 20:56:29 -0400
message:
* ampc.el: Sync to version 0.1.3.
removed:
packages/ampc/
packages/ampc/ampc.el
added:
packages/ampc/
packages/ampc/ampc.el
=== removed directory 'packages/ampc'
=== added directory 'packages/ampc'
=== removed file 'packages/ampc/ampc.el'
--- a/packages/ampc/ampc.el 2012-03-15 11:48:41 +0000
+++ b/packages/ampc/ampc.el 1970-01-01 00:00:00 +0000
@@ -1,1864 +0,0 @@
-;;; ampc.el --- Asynchronous Music Player Controller
-
-;; Copyright (C) 2011-2012 Free Software Foundation, Inc.
-
-;; Author: Christopher Schmidt <address@hidden>
-;; Maintainer: Christopher Schmidt <address@hidden>
-;; Version: 0.1.2
-;; Created: 2011-12-06
-;; Keywords: ampc, mpc, mpd
-;; Compatibility: GNU Emacs: 24.x
-
-;; This file is part of GNU Emacs.
-
-;; This program 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.
-
-;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
-
-;;; Commentary:
-;;; * description
-;; ampc is a controller for the Music Player Daemon (http://mpd.wikia.com/).
-
-;;; ** installation
-;; If you use GNU ELPA, install ampc via M-x package-list-packages RET or
-;; (package-install 'ampc). Otherwise, grab this file and put it somewhere in
-;; your load-path or add the directory the file is in to it, e.g.:
-;;
-;; (add-to-list 'load-path "~/.emacs.d/ampc")
-;;
-;; Then add one autoload definition:
-;;
-;; (autoload 'ampc "ampc" nil t)
-;;
-;; Optionally bind a key to this function, e.g.:
-;;
-;; (global-set-key (kbd "<f9>") 'ampc)
-
-;; Byte-compile ampc (M-x byte-compile-file RET /path/to/ampc.el RET) to
improve
-;; its performance!
-
-;;; ** usage
-;; To invoke ampc, call the command `ampc', e.g. via M-x ampc RET. Once ampc
is
-;; connected to the daemon, it creates its window configuration in the selected
-;; window. To make ampc use the full frame rather than the selected window,
-;; customise `ampc-use-full-frame'.
-;;
-;; ampc offers three independent views which expose different parts of the user
-;; interface. The current playlist view, the default view at startup, may be
-;; accessed using the `J' (that is `S-j') key. The playlist view may be
-;; accessed using the `K' key. The outputs view may be accessed using the `L'
-;; key.
-
-;;; *** current playlist view
-;; The playlist view should look like this
-;;
-;; .........................
-;; . 1 . 3 . 4 . 5 .
-;; .......... . . .
-;; . 2 . . . .
-;; . . . . .
-;; . . . . .
-;; . ................
-;; . . 6 .
-;; . . .
-;; .........................
-;;
-;; Window one exposes basic information about the daemon, such as the current
-;; state (stop/play/pause), the song currently playing, or the volume.
-;;
-;; All windows, except the status window, contain a tabular list of items.
Each
-;; item may be selected/marked. There may be multiple selections.
-;;
-;; To mark an entry, move the point to the entry and press `m' (ampc-mark). To
-;; unmark an entry, press `u' (ampc-unmark). To unmark all entries, press `U'
-;; (ampc-unmark-all). To toggle marks, press `t' (ampc-toggle-marks). To
-;; navigate to the next entry, press `n' (ampc-next-line). Analogous, pressing
-;; `p' (ampc-previous-line) moves the point to the previous entry.
-;;
-;; Window two shows the current playlist. The song that is currently played by
-;; the daemon, if any, is highlighted. To delete the selected songs from the
-;; playlist, press `d' (ampc-delete). To move the selected songs up, press
-;; `<up>' (ampc-up). Analogous, press `<down>' (ampc-down) to move the
selected
-;; songs down.
-;;
-;; Windows three to five are tag browsers. You use them to narrow the song
-;; database to certain songs. Think of tag browsers as filters, analogous to
-;; piping `grep' outputs through additional `grep' filters. The property of
the
-;; songs that is filtered is displayed in the header line of the window.
-;;
-;; Window six shows the songs that match the filters defined by windows three
to
-;; five. To add the selected song to the playlist, press `a' (ampc-add). This
-;; key binding works in tag browsers as well. Calling ampc-add in a tag
browser
-;; adds all songs filtered up to the selected browser to the playlist.
-;;
-;; The tag browsers of the (default) current playlist view (accessed via `J')
-;; are `Genre' (window 3), `Artist' (window 4) and `Album' (window 5). The key
-;; `M' may be used to fire up a slightly modified current playlist view. There
-;; is no difference to the default current playlist view other than that the
tag
-;; browsers filter to `Genre' (window 3), `Album' (window 4) and `Artist'
-;; (window 5). Metaphorically speaking, the order of the `grep' filters
defined
-;; by the tag browsers is different.
-
-;;; *** playlist view
-;; The playlist view resembles the current playlist view. The window, which
-;; exposes the playlist content, is split, though. The bottom half shows a
list
-;; of stored playlists. The upper half does not expose the current playlist
-;; anymore. Instead, the content of the selected (stored) playlist is shown.
-;; All commands that used to work in the current playlist view and modify the
-;; current playlist now modify the selected (stored) playlist. The list of
-;; stored playlists is the only view in ampc that may have only one marked
-;; entry.
-;;
-;; Again, the key `<' may be used to setup a playlist view with a different
-;; order of tag browsers.
-
-;;; *** outputs view
-;; The outputs view contains a single list which shows the configured outputs
of
-;; mpd. To toggle the enabled property of the selected outputs, press `a'
-;; (ampc-toggle-output-enabled).
-
-;;; *** global keys
-;; Aside from `J', `M', `K', `<' and `L', which may be used to select different
-;; views, ampc defines the following global keys, which may be used in every
-;; window associated with ampc:
-;;
-;; `k' (ampc-toggle-play): Toggle play state. If mpd does not play a song
-;; already, start playing the song at point if the current buffer is the
-;; playlist buffer, otherwise start at the beginning of the playlist. With
-;; prefix argument 4, stop player rather than pause if applicable.
-;;
-;; `l' (ampc-next): Play next song.
-;; `j' (ampc-previous): Play previous song
-;;
-;; `c' (ampc-clear): Clear playlist.
-;; `s' (ampc-shuffle): Shuffle playlist.
-;;
-;; `S' (ampc-store): Store playlist.
-;; `O' (ampc-load): Load selected playlist in the current playlist.
-;; `R' (ampc-rename-playlist): Rename selected playlist.
-;; `D' (ampc-delete-playlist): Delete selected playlist.
-;;
-;; `y' (ampc-increase-volume): Increase volume.
-;; `M-y' (ampc-decrease-volume): Decrease volume.
-;; `h' (ampc-increase-crossfade): Increase crossfade.
-;; `M-h' (ampc-decrease-crossfade): Decrease crossfade.
-;;
-;; `e' (ampc-toggle-repeat): Toggle repeat state.
-;; `r' (ampc-toggle-random): Toggle random state.
-;; `f' (ampc-toggle-consume): Toggle consume state.
-;;
-;; `P' (ampc-goto-current-song): Select the current playlist window and move
-;; point to the current song.
-;;
-;; `T' (ampc-trigger-update): Trigger a database update.
-;; `Z' (ampc-suspend): Suspend ampc.
-;; `q' (ampc-quit): Quit ampc.
-;;
-;; The keymap of ampc is designed to fit the QWERTY United States keyboard
-;; layout. If you use another keyboard layout, feel free to modify
-;; ampc-mode-map. For example, I use a regular QWERTZ German keyboard
(layout),
-;; so I modify `ampc-mode-map' in my init.el like this:
-;;
-;; (eval-after-load 'ampc
-;; '(flet ((substitute-ampc-key
-;; (from to)
-;; (define-key ampc-mode-map to (lookup-key ampc-mode-map from))
-;; (define-key ampc-mode-map from nil)))
-;; (substitute-ampc-key (kbd "z") (kbd "Z"))
-;; (substitute-ampc-key (kbd "y") (kbd "z"))
-;; (substitute-ampc-key (kbd "M-y") (kbd "M-z"))
-;; (substitute-ampc-key (kbd "<") (kbd ";"))))
-;;
-;; If ampc is suspended, you can still use every interactive command that does
-;; not directly operate on or with the user interace of ampc. For example it
is
-;; perfectly fine to call `ampc-increase-volume' or `ampc-toggle-play' via M-x
-;; RET. To display the information that is displayed by the status window of
-;; ampc, call `ampc-status'.
-
-;;; Code:
-;;; * code
-(eval-when-compile
- (require 'easymenu)
- (require 'cl))
-(require 'network-stream)
-(require 'avl-tree)
-
-;;; ** declarations
-;;; *** variables
-(defgroup ampc ()
- "Asynchronous client for the Music Player Daemon."
- :prefix "ampc-"
- :group 'multimedia
- :group 'applications)
-
-;;; *** customs
-(defcustom ampc-debug nil
- "Non-nil means log communication between ampc and MPD."
- :type 'boolean)
-
-(defcustom ampc-use-full-frame nil
- "If non-nil, ampc will use the entire Emacs screen."
- :type 'boolean)
-
-(defcustom ampc-truncate-lines t
- "If non-nil, truncate lines in ampc buffers."
- :type 'boolean)
-
-(defcustom ampc-status-tags nil
- "List of additional tags of the current song that are added to
-the internal status of ampc and thus are passed to the functions
-in `ampc-status-changed-hook'. Each element may be a string that
-specifies a tag that is returned by MPD's `currentsong'
-command.")
-
-;;; **** hooks
-(defcustom ampc-before-startup-hook nil
- "A hook run before startup.
-This hook is called as the first thing when ampc is started."
- :type 'hook)
-
-(defcustom ampc-connected-hook nil
- "A hook run after ampc connected to MPD."
- :type 'hook)
-
-(defcustom ampc-suspend-hook nil
- "A hook run when suspending ampc."
- :type 'hook)
-
-(defcustom ampc-quit-hook nil
- "A hook run when exiting ampc."
- :type 'hook)
-
-(defcustom ampc-status-changed-hook nil
- "A hook run whenever the status of the daemon (that is volatile
-properties such as volume or current song) changes. The hook is
-run with one arg, an alist that contains the new status. The car
-of each entry is a symbol, the cdr is a string. Valid keys are:
-
- volume
- repeat
- random
- consume
- xfade
- state
- song
- Artist
- Title
-
-and the keys in `ampc-status-tags'. Not all keys may be present
-all the time!"
- :type 'hook)
-
-;;; *** faces
-(defface ampc-mark-face '((t (:inherit font-lock-constant-face)))
- "Face of the mark.")
-(defface ampc-marked-face '((t (:inherit warning)))
- "Face of marked entries.")
-(defface ampc-face '((t (:inerhit default)))
- "Face of unmarked entries.")
-(defface ampc-current-song-mark-face '((t (:inherit region)))
- "Face of mark of the current song.")
-(defface ampc-current-song-marked-face '((t (:inherit region)))
- "Face of the current song if marked.")
-
-;;; *** internal variables
-(defvar ampc-views
- (let* ((songs '(1.0 song :properties (("Track" :title "#")
- ("Title" :offset 6)
- ("Time" :offset 26))))
- (rs_a `(1.0 vertical
- (0.7 horizontal
- (0.33 tag :tag "Genre" :id 1)
- (0.33 tag :tag "Artist" :id 2)
- (1.0 tag :tag "Album" :id 3))
- ,songs))
- (rs_b `(1.0 vertical
- (0.7 horizontal
- (0.33 tag :tag "Genre" :id 1)
- (0.33 tag :tag "Album" :id 2)
- (1.0 tag :tag "Artist" :id 3))
- ,songs))
- (pl-prop '(("Title")
- ("Artist" :offset 20)
- ("Album" :offset 40)
- ("Time" :offset 60))))
- `((,(kbd "J")
- horizontal
- (0.4 vertical
- (6 status)
- (1.0 current-playlist :properties ,pl-prop))
- ,rs_a)
- (,(kbd "M")
- horizontal
- (0.4 vertical
- (6 status)
- (1.0 current-playlist :properties ,pl-prop))
- ,rs_b)
- (,(kbd "K")
- horizontal
- (0.4 vertical
- (6 status)
- (1.0 vertical
- (0.8 playlist :properties ,pl-prop)
- (1.0 playlists)))
- ,rs_a)
- (,(kbd "<")
- horizontal
- (0.4 vertical
- (6 status)
- (1.0 vertical
- (0.8 playlist :properties ,pl-prop)
- (1.0 playlists)))
- ,rs_b)
- (,(kbd "L")
- outputs :properties (("outputname" :title "Name")
- ("outputenabled" :title "Enabled" :offset 10))))))
-
-(defvar ampc-connection nil)
-(defvar ampc-host nil)
-(defvar ampc-port nil)
-(defvar ampc-outstanding-commands nil)
-
-(defvar ampc-working-timer nil)
-(defvar ampc-yield nil)
-
-(defvar ampc-buffers nil)
-(defvar ampc-buffers-unordered nil)
-(defvar ampc-all-buffers nil)
-
-(defvar ampc-type nil)
-(make-variable-buffer-local 'ampc-type)
-(defvar ampc-dirty nil)
-(make-variable-buffer-local 'ampc-dirty)
-
-(defvar ampc-internal-db nil)
-(defvar ampc-status nil)
-
-;;; *** mode maps
-(defvar ampc-mode-map
- (let ((map (make-sparse-keymap)))
- (suppress-keymap map)
- (define-key map (kbd "k") 'ampc-toggle-play)
- (define-key map (kbd "l") 'ampc-next)
- (define-key map (kbd "j") 'ampc-previous)
- (define-key map (kbd "c") 'ampc-clear)
- (define-key map (kbd "s") 'ampc-shuffle)
- (define-key map (kbd "S") 'ampc-store)
- (define-key map (kbd "O") 'ampc-load)
- (define-key map (kbd "R") 'ampc-rename-playlist)
- (define-key map (kbd "D") 'ampc-delete-playlist)
- (define-key map (kbd "y") 'ampc-increase-volume)
- (define-key map (kbd "M-y") 'ampc-decrease-volume)
- (define-key map (kbd "h") 'ampc-increase-crossfade)
- (define-key map (kbd "M-h") 'ampc-decrease-crossfade)
- (define-key map (kbd "e") 'ampc-toggle-repeat)
- (define-key map (kbd "r") 'ampc-toggle-random)
- (define-key map (kbd "f") 'ampc-toggle-consume)
- (define-key map (kbd "P") 'ampc-goto-current-song)
- (define-key map (kbd "q") 'ampc-quit)
- (define-key map (kbd "z") 'ampc-suspend)
- (define-key map (kbd "T") 'ampc-trigger-update)
- (loop for view in ampc-views
- do (define-key map (car view)
- `(lambda ()
- (interactive)
- (ampc-configure-frame ',(cdr view)))))
- map))
-
-(defvar ampc-item-mode-map
- (let ((map (make-sparse-keymap)))
- (suppress-keymap map)
- (define-key map (kbd "m") 'ampc-mark)
- (define-key map (kbd "u") 'ampc-unmark)
- (define-key map (kbd "U") 'ampc-unmark-all)
- (define-key map (kbd "n") 'ampc-next-line)
- (define-key map (kbd "p") 'ampc-previous-line)
- map))
-
-(defvar ampc-current-playlist-mode-map
- (let ((map (make-sparse-keymap)))
- (suppress-keymap map)
- (define-key map (kbd "<return>") 'ampc-play-this)
- map))
-
-(defvar ampc-playlist-mode-map
- (let ((map (make-sparse-keymap)))
- (suppress-keymap map)
- (define-key map (kbd "t") 'ampc-toggle-marks)
- (define-key map (kbd "d") 'ampc-delete)
- (define-key map (kbd "<up>") 'ampc-up)
- (define-key map (kbd "<down>") 'ampc-down)
- map))
-
-(defvar ampc-playlists-mode-map
- (let ((map (make-sparse-keymap)))
- (suppress-keymap map)
- (define-key map (kbd "l") 'ampc-load)
- (define-key map (kbd "r") 'ampc-rename-playlist)
- (define-key map (kbd "d") 'ampc-delete-playlist)
- map))
-
-(defvar ampc-tag-song-mode-map
- (let ((map (make-sparse-keymap)))
- (suppress-keymap map)
- (define-key map (kbd "t") 'ampc-toggle-marks)
- (define-key map (kbd "a") 'ampc-add)
- map))
-
-(defvar ampc-outputs-mode-map
- (let ((map (make-sparse-keymap)))
- (suppress-keymap map)
- (define-key map (kbd "t") 'ampc-toggle-marks)
- (define-key map (kbd "a") 'ampc-toggle-output-enabled)
- map))
-
-;;; **** menu
-(easy-menu-define ampc-menu ampc-mode-map
- "Main Menu for ampc"
- '("ampc"
- ["Play" ampc-toggle-play
- :visible (and ampc-status
- (not (equal (cdr (assq 'state ampc-status)) "play")))]
- ["Pause" ampc-toggle-play
- :visible (and ampc-status
- (equal (cdr (assq 'state ampc-status)) "play"))]
- "--"
- ["Clear playlist" ampc-clear]
- ["Shuffle playlist" ampc-shuffle]
- ["Store playlist" ampc-store]
- ["Queue Playlist" ampc-load :visible (ampc-playlist)]
- ["Rename Playlist" ampc-rename-playlist :visible (ampc-playlist)]
- ["Delete Playlist" ampc-delete-playlist :visible (ampc-playlist)]
- "--"
- ["Increase volume" ampc-increase-volume]
- ["Decrease volume" ampc-decrease-volume]
- ["Increase crossfade" ampc-increase-crossfade]
- ["Decrease crossfade" ampc-decrease-crossfade]
- ["Toggle repeat" ampc-toggle-repeat]
- ["Toggle random" ampc-toggle-random]
- ["Toggle consume" ampc-toggle-consume]
- "--"
- ["Trigger update" ampc-trigger-update]
- ["Quit" ampc-quit]))
-
-(easy-menu-define ampc-selection-menu ampc-item-mode-map
- "Selection menu for ampc"
- '("ampc Mark"
- ["Add to playlist" ampc-add
- :visible (not (eq (car ampc-type) 'outputs))]
- ["Toggle enabled" ampc-toggle-output-enabled
- :visible (eq (car ampc-type) 'outputs)]
- "--"
- ["Next line" ampc-next-line]
- ["Previous line" ampc-previous-line]
- ["Mark" ampc-mark]
- ["Unmark" ampc-unmark]
- ["Unmark all" ampc-unmark-all]
- ["Toggle marks" ampc-toggle-marks
- :visible (not (eq (car ampc-type) 'playlists))]))
-
-;;; ** code
-;;; *** macros
-(defmacro ampc-with-buffer (type &rest body)
- (declare (indent 1) (debug t))
- `(let* ((type- ,type)
- (b (loop for b in ampc-buffers
- when (with-current-buffer b
- (cond ((windowp type-)
- (eq (window-buffer type-)
- (current-buffer)))
- ((symbolp type-)
- (eq (car ampc-type) type-))
- (t
- (equal ampc-type type-))))
- return b
- end)))
- (when b
- (with-current-buffer b
- (let ((buffer-read-only))
- ,@(if (eq (car body) 'no-se)
- (cdr body)
- `((save-excursion
- (goto-char (point-min))
- ,@body))))))))
-
-(defmacro ampc-fill-skeleton (tag &rest body)
- (declare (indent 1) (debug t))
- `(let ((tag- ,tag)
- (data-buffer (current-buffer)))
- (ampc-with-buffer tag-
- no-se
- (let ((point (point)))
- (goto-char (point-min))
- (loop until (eobp)
- do (put-text-property (point) (1+ (point)) 'updated t)
- (forward-line))
- (goto-char (point-min))
- ,@body
- (goto-char (point-min))
- (loop until (eobp)
- when (get-text-property (point) 'updated)
- do (delete-region (point) (1+ (line-end-position)))
- else
- do (forward-line nil)
- end)
- (goto-char point)
- (ampc-align-point))
- (ampc-set-dirty nil)
- (with-selected-window (if (windowp tag-) tag- (ampc-get-window tag-))
- (recenter)))))
-
-(defmacro ampc-with-selection (arg &rest body)
- (declare (indent 1) (debug t))
- `(let ((arg- ,arg))
- (if (and (not arg-)
- (save-excursion
- (goto-char (point-min))
- (search-forward-regexp "^* " nil t)))
- (loop initially (goto-char (point-min))
- finally (ampc-align-point)
- while (search-forward-regexp "^* " nil t)
- for index from 0
- do (save-excursion
- ,@body))
- (loop until (eobp)
- for index from 0 to (1- (prefix-numeric-value arg-))
- do (save-excursion
- (goto-char (line-end-position))
- ,@body)
- until (ampc-next-line)))))
-
-;;; *** modes
-(define-derived-mode ampc-outputs-mode ampc-item-mode "ampc-o"
- nil)
-
-(define-derived-mode ampc-tag-song-mode ampc-item-mode "ampc-ts"
- nil)
-
-(define-derived-mode ampc-current-playlist-mode ampc-playlist-mode "ampc-cpl"
- nil)
-
-(define-derived-mode ampc-playlist-mode ampc-item-mode "ampc-pl"
- nil)
-
-(define-derived-mode ampc-playlists-mode ampc-item-mode "ampc-pls"
- nil)
-
-(define-derived-mode ampc-item-mode ampc-mode ""
- nil)
-
-(define-derived-mode ampc-mode fundamental-mode "ampc"
- nil
- (buffer-disable-undo)
- (setf buffer-read-only t
- truncate-lines ampc-truncate-lines
- font-lock-defaults '((("^\\(\\*\\)\\(.*\\)$"
- (1 'ampc-mark-face)
- (2 'ampc-marked-face))
- ("^ .*$" 0 'ampc-face))
- t)))
-
-(define-minor-mode ampc-highlight-current-song-mode ""
- nil
- nil
- nil
- (funcall (if ampc-highlight-current-song-mode
- 'font-lock-add-keywords
- 'font-lock-remove-keywords)
- nil
- '((ampc-find-current-song
- (1 'ampc-current-song-mark-face)
- (2 'ampc-current-song-marked-face)))))
-
-;;; *** internal functions
-(defun ampc-quote (string)
- (concat "\"" (replace-regexp-in-string "\"" "\\\"" string) "\""))
-
-(defun ampc-on-p ()
- (and ampc-connection
- (member (process-status ampc-connection) '(open run))))
-
-(defun ampc-in-ampc-p ()
- (when (ampc-on-p)
- ampc-type))
-
-(defun ampc-add-impl (&optional data)
- (cond ((null data)
- (loop for d in (get-text-property (line-end-position) 'data)
- do (ampc-add-impl d)))
- ((avl-tree-p data)
- (avl-tree-mapc (lambda (e) (ampc-add-impl (cdr e))) data))
- ((stringp data)
- (if (ampc-playlist)
- (ampc-send-command 'playlistadd
- t
- (ampc-quote (ampc-playlist))
- data)
- (ampc-send-command 'add t (ampc-quote data))))
- (t
- (loop for d in data
- do (ampc-add-impl (cdr (assoc "file" d)))))))
-
-(defun* ampc-skip (N &aux (song (cdr-safe (assq 'song ampc-status))))
- (when song
- (ampc-send-command 'play nil (max 0 (+ (string-to-number song) N)))))
-
-(defun* ampc-find-current-song
- (limit &aux (point (point)) (song (cdr-safe (assq 'song ampc-status))))
- (when (and song
- (<= (1- (line-number-at-pos (point)))
- (setf song (string-to-number song)))
- (>= (1- (line-number-at-pos limit)) song))
- (goto-char (point-min))
- (forward-line song)
- (save-restriction
- (narrow-to-region (max point (point)) (min limit (line-end-position)))
- (search-forward-regexp "\\(?1:\\(\\`\\*\\)?\\)\\(?2:.*\\)$"))))
-
-(defun ampc-set-volume (arg func)
- (when (or arg ampc-status)
- (ampc-send-command
- 'setvol
- nil
- (or (and arg (prefix-numeric-value arg))
- (max (min (funcall func
- (string-to-number
- (cdr (assq 'volume ampc-status)))
- 5)
- 100)
- 0)))))
-
-(defun ampc-set-crossfade (arg func)
- (when (or arg ampc-status)
- (ampc-send-command
- 'crossfade
- nil
- (or (and arg (prefix-numeric-value arg))
- (max (funcall func
- (string-to-number (cdr (assq 'xfade ampc-status)))
- 5)
- 0)))))
-
-(defun* ampc-fix-pos (f &aux buffer-read-only)
- (save-excursion
- (move-beginning-of-line nil)
- (let* ((data (get-text-property (+ 2 (point)) 'data))
- (pos (assoc "Pos" data)))
- (setf (cdr pos) (funcall f (cdr pos)))
- (put-text-property (+ 2 (point))
- (line-end-position)
- 'data
- data))))
-
-(defun* ampc-move-impl (up &aux (line (1- (line-number-at-pos))))
- (when (or (and up (eq line 0))
- (and (not up) (eq (1+ line) (line-number-at-pos (1-
(point-max))))))
- (return-from ampc-move-impl t))
- (save-excursion
- (move-beginning-of-line nil)
- (if (ampc-playlist)
- (ampc-send-command 'playlistmove
- nil
- (ampc-quote (ampc-playlist))
- line
- (funcall (if up '1- '1+)
- line))
- (ampc-send-command 'move nil line (funcall (if up '1- '1+) line)))
- (unless up
- (forward-line))
- (unless (ampc-playlist)
- (save-excursion
- (forward-line -1)
- (ampc-fix-pos '1+))
- (ampc-fix-pos '1-))
- (let ((buffer-read-only))
- (transpose-lines 1)))
- (if up
- (ampc-align-point)
- (ampc-next-line))
- nil)
-
-(defun* ampc-move (up N &aux (point (point)))
- (goto-char (if up (point-min) (point-max)))
- (if (and (not N)
- (funcall (if up 'search-forward-regexp 'search-backward-regexp)
- "^* "
- nil
- t))
- (loop until (ampc-move-impl up)
- unless up
- do (search-backward-regexp "^* " nil t)
- end
- until (not (funcall (if up
- 'search-forward-regexp
- 'search-backward-regexp)
- "^* "
- nil
- t))
- finally (unless up
- (forward-char 2)))
- (goto-char point)
- (unless (eobp)
- (unless N
- (setf N 1))
- (unless up
- (unless (eq (1- N) 0)
- (setf N (- (- (forward-line (1- N)) (1- N))))))
- (loop repeat N
- until (ampc-move-impl up)))))
-
-(defun ampc-toggle-state (state arg)
- (when (or arg ampc-status)
- (ampc-send-command
- state
- nil
- (cond ((null arg)
- (if (equal (cdr (assq state ampc-status)) "1")
- 0
- 1))
- ((> (prefix-numeric-value arg) 0) 1)
- (t 0)))))
-
-(defun ampc-playlist ()
- (ampc-with-buffer 'playlists
- (if (search-forward-regexp "^* \\(.*\\)$" nil t)
- (match-string 1)
- (unless (eobp)
- (buffer-substring-no-properties
- (+ (line-beginning-position) 2)
- (line-end-position))))))
-
-(defun* ampc-mark-impl (select N &aux result buffer-read-only)
- (when (eq (car ampc-type) 'playlists)
- (assert (or (not select) (null N) (eq N 1)))
- (ampc-with-buffer 'playlists
- (loop while (search-forward-regexp "^\\* " nil t)
- do (replace-match " " nil nil))))
- (loop repeat (or N 1)
- until (eobp)
- do (move-beginning-of-line nil)
- (delete-char 1)
- (insert (if select "*" " "))
- (setf result (ampc-next-line nil)))
- (ampc-post-mark-change-update)
- result)
-
-(defun ampc-post-mark-change-update ()
- (ecase (car ampc-type)
- ((current-playlist playlist outputs))
- (playlists
- (ampc-update-playlist))
- ((song tag)
- (loop for w in (ampc-windows)
- with found
- when found
- do (with-current-buffer (window-buffer w)
- (when (member (car ampc-type) '(song tag))
- (ampc-set-dirty t)))
- end
- if (eq w (selected-window))
- do (setf found t)
- end)
- (ampc-fill-tag-song))))
-
-(defun ampc-align-point ()
- (unless (eobp)
- (move-beginning-of-line nil)
- (forward-char 2)))
-
-(defun ampc-pad (alist)
- (loop for (offset . data) in alist
- with first = t
- with current-offset = 0
- when (<= current-offset offset)
- when (and (not first) (eq (- offset current-offset) 0))
- do (incf offset)
- end
- and concat (make-string (- offset current-offset) ? )
- and do (setf current-offset offset)
- else
- concat " "
- and do (incf current-offset)
- end
- concat data
- do (setf current-offset (+ current-offset (length data))
- first nil)))
-
-(defun ampc-update-header ()
- (if (eq (car ampc-type) 'status)
- (setf header-line-format nil)
- (setf header-line-format
- (concat
- (make-string (floor (fringe-columns 'left t)) ? )
- (ecase (car ampc-type)
- (tag
- (concat " " (plist-get (cdr ampc-type) :tag)))
- (playlists
- " Playlists")
- (t
- (ampc-pad (loop for p in (plist-get (cdr ampc-type) :properties)
- collect `(,(or (plist-get (cdr p) :offset) 2) .
- ,(or (plist-get (cdr p) :title)
- (car p)))))))
- (when ampc-dirty
- " [ Updating... ]")))))
-
-(defun ampc-set-dirty (tag-or-dirty &optional dirty)
- (if (or (null tag-or-dirty) (eq tag-or-dirty t))
- (progn (setf ampc-dirty tag-or-dirty)
- (ampc-update-header))
- (loop for w in (ampc-windows)
- do (with-current-buffer (window-buffer w)
- (when (eq (car ampc-type) tag-or-dirty)
- (ampc-set-dirty dirty))))))
-
-(defun ampc-update ()
- (if ampc-status
- (loop for b in ampc-buffers
- do (with-current-buffer b
- (when ampc-dirty
- (ecase (car ampc-type)
- (outputs
- (ampc-send-command 'outputs))
- (playlist
- (ampc-update-playlist))
- ((tag song)
- (if (assoc (ampc-tags) ampc-internal-db)
- (ampc-fill-tag-song)
- (push `(,(ampc-tags) . ,(ampc-create-tree))
- ampc-internal-db)
- (ampc-send-command 'listallinfo)))
- (status
- (ampc-send-command 'status)
- (ampc-send-command 'currentsong))
- (playlists
- (ampc-send-command 'listplaylists))
- (current-playlist
- (ampc-send-command 'playlistinfo))))))
- (ampc-send-command 'status)
- (ampc-send-command 'currentsong)))
-
-(defun ampc-update-playlist ()
- (ampc-with-buffer 'playlists
- (if (search-forward-regexp "^\\* " nil t)
- (ampc-send-command 'listplaylistinfo
- nil
- (get-text-property (point) 'data))
- (ampc-with-buffer 'playlist
- (delete-region (point-min) (point-max))
- (ampc-set-dirty nil)))))
-
-(defun ampc-send-command-impl (command)
- (when ampc-debug
- (message (concat "ampc: " command)))
- (process-send-string ampc-connection (concat command "\n")))
-
-(defun ampc-send-command (command &optional unique &rest args)
- (if (equal command 'idle)
- (when ampc-working-timer
- (cancel-timer ampc-working-timer)
- (setf ampc-yield nil
- ampc-working-timer nil)
- (ampc-fill-status))
- (unless ampc-working-timer
- (setf ampc-yield 0
- ampc-working-timer (run-at-time nil 0.1 'ampc-yield))))
- (setf command `(,command ,@args))
- (when (equal (car-safe ampc-outstanding-commands) '(idle))
- (setf (car ampc-outstanding-commands) '(noidle))
- (ampc-send-command-impl "noidle"))
- (setf ampc-outstanding-commands
- (nconc (if unique
- ampc-outstanding-commands
- (remove command ampc-outstanding-commands))
- `(,command))))
-
-(defun ampc-send-next-command ()
- (unless ampc-outstanding-commands
- (ampc-send-command 'idle))
- (ampc-send-command-impl (concat (symbol-name (caar
ampc-outstanding-commands))
- (loop for a in
- (cdar ampc-outstanding-commands)
- concat " "
- concat (cond ((integerp a)
- (number-to-string a))
- (t a))))))
-
-(defun ampc-tree< (a b)
- (not (string< (if (listp a) (car a) a) (if (listp b) (car b) b))))
-
-(defun ampc-create-tree ()
- (avl-tree-create 'ampc-tree<))
-
-(defun ampc-extract (tag &optional buffer)
- (with-current-buffer (or buffer (current-buffer))
- (if (listp tag)
- (ampc-extract (plist-get tag :tag))
- (save-excursion
- (goto-char (point-min))
- (when (search-forward-regexp
- (concat "^" (regexp-quote tag) ": \\(.*\\)$")
- nil
- t)
- (let ((result (match-string 1)))
- (when (equal tag "Time")
- (setf result (ampc-transform-time result)))
- result))))))
-
-(defun ampc-insert (element data &optional cmp)
- (save-excursion
- (goto-char (point-min))
- (ecase
- (loop until (eobp)
- for tp = (get-text-property (+ (point) 2) 'data)
- finally return 'insert
- thereis
- (cond ((eq cmp t)
- (let ((s (buffer-substring-no-properties
- (+ (point) 2)
- (line-end-position))))
- (cond ((equal s element)
- (unless (member data tp)
- (put-text-property (+ (point) 2)
- (1+ (line-end-position))
- 'data
- `(,data . ,tp)))
- 'update)
- ((string< element s)
- 'insert))))
- (cmp
- (let ((r (funcall cmp data tp)))
- (if (memq r '(update insert))
- r
- (forward-line (1- r))
- nil)))
- ((equal tp data)
- 'update)
- (t
- (let ((s (buffer-substring-no-properties
- (+ (point) 2)
- (line-end-position))))
- (unless (string< s element)
- 'insert))))
- do (forward-line))
- (insert
- (insert " ")
- (let ((start (point)))
- (insert element "\n")
- (put-text-property start (point) 'data (if (eq cmp t)
- `(,data)
- data))))
- (update
- (remove-text-properties (point) (1+ (point)) '(updated))
- (equal (buffer-substring (point) (1+ (point))) "*")))))
-
-(defun ampc-fill-tag (trees)
- (put-text-property (point-min) (point-max) 'data nil)
- (loop with new-trees
- finally return new-trees
- for tree in trees
- do (avl-tree-mapc (lambda (e)
- (when (ampc-insert (car e) (cdr e) t)
- (push (cdr e) new-trees)))
- tree)))
-
-(defun ampc-fill-song (trees)
- (loop
- for songs in trees
- do (loop for song in songs
- do (ampc-insert
- (ampc-pad
- (loop for (p . v) in (plist-get (cdr ampc-type) :properties)
- collect `(,(- (or (plist-get v :offset) 2) 2)
- . ,(or (cdr-safe (assoc p song)) ""))))
- `((,song))))))
-
-(defun* ampc-narrow-entry (&optional (delimiter "file"))
- (narrow-to-region (move-beginning-of-line nil)
- (or (progn (goto-char (line-end-position))
- (when (search-forward-regexp
- (concat "^" (regexp-quote delimiter) ":
")
- nil
- t)
- (move-beginning-of-line nil)
- (1- (point))))
- (point-max))))
-
-(defun ampc-get-window (type)
- (loop for w in (ampc-windows)
- thereis (with-current-buffer (window-buffer w)
- (when (eq (car ampc-type) type)
- w))))
-
-(defun* ampc-fill-playlist (&aux properties)
- (ampc-fill-skeleton 'playlist
- (setf properties (plist-get (cdr ampc-type) :properties))
- (with-current-buffer data-buffer
- (loop
- for i from 0
- while (search-forward-regexp "^file: " nil t)
- do (save-restriction
- (ampc-narrow-entry)
- (let ((file (ampc-extract "file"))
- (text
- (ampc-pad
- (loop for (tag . tag-properties) in properties
- collect `(,(- (or (plist-get tag-properties
- :offset)
- 2)
- 2)
- . ,(or (ampc-extract tag)
- "[Not Specified]"))))))
- (ampc-with-buffer 'playlist
- (ampc-insert text
- `(("file" . ,file)
- (index . ,i))
- (lambda (a b)
- (let ((p1 (cdr (assoc 'index a)))
- (p2 (cdr (assoc 'index b))))
- (cond ((< p1 p2) 'update)
- ((eq p1 p2)
- (if (equal (cdr (assoc "file" a))
- (cdr (assoc "file" b)))
- 'update
- 'insert))
- (t (- p1 p2)))))))))))))
-
-(defun* ampc-fill-outputs (&aux properties)
- (ampc-fill-skeleton 'outputs
- (setf properties (plist-get (cdr ampc-type) :properties))
- (with-current-buffer data-buffer
- (loop
- while (search-forward-regexp "^outputid: " nil t)
- do (save-restriction
- (ampc-narrow-entry "outputid")
- (let ((outputid (ampc-extract "outputid"))
- (outputenabled (ampc-extract "outputenabled"))
- (text
- (ampc-pad
- (loop for (tag . tag-properties) in properties
- collect `(,(- (or (plist-get tag-properties :offset)
- 2)
- 2)
- . ,(ampc-extract tag))))))
- (ampc-with-buffer 'outputs
- (ampc-insert text `(("outputid" . ,outputid)
- ("outputenabled" . ,outputenabled))))))))))
-
-(defun* ampc-fill-current-playlist (&aux properties)
- (ampc-fill-skeleton 'current-playlist
- (setf properties (plist-get (cdr ampc-type) :properties))
- (with-current-buffer data-buffer
- (loop
- while (search-forward-regexp "^file: " nil t)
- do (save-restriction
- (ampc-narrow-entry)
- (let ((file (ampc-extract "file"))
- (pos (ampc-extract "Pos"))
- (text
- (ampc-pad
- (loop for (tag . tag-properties) in properties
- collect `(,(- (or (plist-get tag-properties :offset)
- 2)
- 2)
- . ,(or (ampc-extract tag)
- "[Not Specified]"))))))
- (ampc-with-buffer 'current-playlist
- (ampc-insert text
- `(("file" . ,file)
- ("Pos" . ,(string-to-number pos)))
- (lambda (a b)
- (let ((p1 (cdr (assoc "Pos" a)))
- (p2 (cdr (assoc "Pos" b))))
- (cond ((< p1 p2) 'insert)
- ((eq p1 p2)
- (if (equal (cdr (assoc "file" a))
- (cdr (assoc "file" b)))
- 'update
- 'insert))
- (t (- p1 p2)))))))))))))
-
-(defun ampc-fill-playlists ()
- (ampc-fill-skeleton 'playlists
- (with-current-buffer data-buffer
- (loop while (search-forward-regexp "^playlist: \\(.*\\)$" nil t)
- for playlist = (match-string 1)
- do (ampc-with-buffer 'playlists
- (ampc-insert playlist playlist))))))
-
-(defun ampc-yield ()
- (setf ampc-yield (1+ ampc-yield))
- (ampc-fill-status))
-
-(defun ampc-fill-status ()
- (ampc-with-buffer 'status
- (delete-region (point-min) (point-max))
- (funcall (or (plist-get (cadr ampc-type) :filler)
- (lambda (_)
- (insert (ampc-status) "\n")))
- ampc-status)
- (ampc-set-dirty nil)))
-
-(defun ampc-fill-tag-song ()
- (loop
- with trees = `(,(cdr (assoc (ampc-tags) ampc-internal-db)))
- for w in (ampc-windows)
- do
- (ampc-with-buffer w
- (when (member (car ampc-type) '(tag song))
- (if ampc-dirty
- (ampc-fill-skeleton w
- (ecase (car ampc-type)
- (tag (setf trees (ampc-fill-tag trees)))
- (song (ampc-fill-song trees))))
- (setf trees nil)
- (loop while (search-forward-regexp "^* " nil t)
- do (setf trees (append (get-text-property (point) 'data)
- trees))))))))
-
-(defun* ampc-transform-time (data &aux (time (string-to-number data)))
- (concat (number-to-string (/ time 60))
- ":"
- (when (< (% time 60) 10)
- "0")
- (number-to-string (% time 60))))
-
-(defun ampc-handle-idle ()
- (loop until (eobp)
- for subsystem = (buffer-substring (point) (line-end-position))
- when (string-match "^changed: \\(.*\\)$" subsystem)
- do (case (intern (match-string 1 subsystem))
- (database
- (setf ampc-internal-db nil)
- (ampc-set-dirty 'tag t)
- (ampc-set-dirty 'song t))
- (output
- (ampc-set-dirty 'outputs t))
- ((player options mixer)
- (setf ampc-status nil)
- (ampc-set-dirty 'status t))
- (stored_playlist
- (ampc-set-dirty 'playlists t)
- (ampc-set-dirty 'playlist t))
- (playlist
- (ampc-set-dirty 'current-playlist t)
- (ampc-set-dirty 'status t)))
- end
- do (forward-line))
- (ampc-update))
-
-(defun ampc-handle-setup (status)
- (unless (and (string-match "^ MPD \\(.+\\)\\.\\(.+\\)\\.\\(.+\\)$"
- status)
- (let ((version-a (string-to-number (match-string 1 status)))
- (version-b (string-to-number (match-string 2 status)))
- ;; (version-c (string-to-number (match-string 2 status)))
- )
- (or (> version-a 0)
- (>= version-b 15))))
- (error (concat "Your version of MPD is not supported. "
- "ampc supports MPD (protocol version) 0.15.0 "
- "and later"))))
-
-(defun ampc-fill-internal-db (running)
- (loop for origin = (and (search-forward-regexp "^file: " nil t)
- (line-beginning-position))
- then next
- while origin
- for next = (progn
- (forward-char)
- (and (search-forward-regexp "^file: " nil t)
- (move-beginning-of-line nil)))
- while next
- do (save-restriction
- (narrow-to-region origin next)
- (ampc-fill-internal-db-entry))
- (goto-char origin)
- (when running
- (delete-region origin next)
- (setf next origin))))
-
-(defun ampc-tags ()
- (loop for w in (ampc-windows)
- for tag = (with-current-buffer (window-buffer w)
- (when (eq (car ampc-type) 'tag)
- (plist-get (cdr ampc-type) :tag)))
- when tag
- collect tag
- end))
-
-(defun ampc-fill-internal-db-entry ()
- (loop
- with data-buffer = (current-buffer)
- with tree = `(nil . ,(cdr (assoc (ampc-tags) ampc-internal-db)))
- for w in (ampc-windows)
- do
- (with-current-buffer (window-buffer w)
- (ampc-set-dirty t)
- (ecase (car ampc-type)
- (tag
- (let* ((data (or (ampc-extract (cdr ampc-type) data-buffer)
- "[Not Specified]"))
- (member (and (cdr tree) (avl-tree-member (cdr tree) data))))
- (cond (member (setf tree member))
- ((cdr tree)
- (setf member `(,data . nil))
- (avl-tree-enter (cdr tree) member)
- (setf tree member))
- (t
- (setf (cdr tree) (ampc-create-tree) member`(,data . nil))
- (avl-tree-enter (cdr tree) member)
- (setf tree member)))))
- (song
- (push (loop for p in `(("file")
- ,@(plist-get (cdr ampc-type) :properties))
- for data = (ampc-extract (car p) data-buffer)
- when data
- collect `(,(car p) . ,data)
- end)
- (cdr tree))
- (return))))))
-
-(defun ampc-handle-current-song ()
- (loop for k in (append ampc-status-tags '("Artist" "Title"))
- for s = (ampc-extract k)
- when s
- do (push `(,(intern k) . ,s) ampc-status)
- end)
- (ampc-fill-status)
- (run-hook-with-args ampc-status-changed-hook ampc-status))
-
-(defun ampc-handle-status ()
- (loop for k in '("volume" "repeat" "random" "consume" "xfade" "state" "song")
- for v = (ampc-extract k)
- when v
- do (push `(,(intern k) . ,v) ampc-status)
- end)
- (ampc-with-buffer 'current-playlist
- (when ampc-highlight-current-song-mode
- (font-lock-fontify-region (point-min) (point-max)))))
-
-(defun ampc-handle-update ()
- (message "Database update started"))
-
-(defun ampc-handle-command (status)
- (cond
- ((eq status 'error)
- (pop ampc-outstanding-commands))
- ((eq status 'running)
- (case (caar ampc-outstanding-commands)
- (listallinfo (ampc-fill-internal-db t))))
- (t
- (case (car (pop ampc-outstanding-commands))
- (idle
- (ampc-handle-idle))
- (setup
- (ampc-handle-setup status))
- (currentsong
- (ampc-handle-current-song))
- (status
- (ampc-handle-status))
- (update
- (ampc-handle-update))
- (listplaylistinfo
- (ampc-fill-playlist))
- (listplaylists
- (ampc-fill-playlists))
- (playlistinfo
- (ampc-fill-current-playlist))
- (listallinfo
- (ampc-fill-internal-db nil))
- (outputs
- (ampc-fill-outputs)))
- (unless ampc-outstanding-commands
- (ampc-update)))))
-
-(defun ampc-filter (_process string)
- (assert (buffer-live-p (process-buffer ampc-connection)))
- (with-current-buffer (process-buffer ampc-connection)
- (when string
- (when ampc-debug
- (message "ampc: -> %s" string))
- (goto-char (process-mark ampc-connection))
- (insert string)
- (set-marker (process-mark ampc-connection) (point)))
- (save-excursion
- (goto-char (point-min))
- (let ((success))
- (if (or (and (search-forward-regexp
- "^ACK \\[\\(.*\\)\\] {.*} \\(.*\\)\n\\'"
- nil
- t)
- (message "ampc command error: %s (%s)"
- (match-string 2)
- (match-string 1))
- t)
- (and (search-forward-regexp "^OK\\(.*\\)\n\\'" nil t)
- (setf success t)))
- (progn
- (let ((match-end (match-end 0)))
- (save-restriction
- (narrow-to-region (point-min) match-end)
- (goto-char (point-min))
- (ampc-handle-command (if success (match-string 1) 'error)))
- (delete-region (point-min) match-end))
- (ampc-send-next-command))
- (ampc-handle-command 'running))))))
-
-;;; **** window management
-(defun ampc-windows (&optional unordered)
- (loop for f being the frame
- thereis (loop for w being the windows of f
- when (eq (window-buffer w) (car-safe ampc-buffers))
- return (loop for b in (if unordered
- ampc-buffers-unordered
- ampc-buffers)
- collect
- (loop for w being the windows of f
- thereis (and (eq (window-buffer w)
- b)
- w))))))
-
-(defun* ampc-configure-frame-1 (split &aux (split-type (car split)))
- (if (member split-type '(vertical horizontal))
- (let* ((sizes))
- (loop with length = (if (eq split-type 'horizontal)
- (window-width)
- (window-height))
- with rest = length
- with rest-car
- for subsplit in (cdr split)
- for s = (car subsplit)
- if (equal s 1.0)
- do (push t sizes)
- and do (setf rest-car sizes)
- else
- do (let ((l (if (integerp s) s (floor (* s length)))))
- (setf rest (- rest l))
- (push l sizes))
- finally do (setf (car rest-car) rest))
- (let ((first-window (selected-window)))
- (setf sizes (nreverse sizes))
- (loop for size in (loop for s in sizes
- collect s)
- for window on (cdr sizes)
- do (select-window
- (setf (car window)
- (split-window nil
- size
- (eq split-type 'horizontal)))))
- (setf (car sizes) first-window))
- (loop for subsplit in (cdr split)
- for window in sizes
- do (with-selected-window window
- (ampc-configure-frame-1 (cdr subsplit)))
- if (plist-get (cddr subsplit) :point)
- do (select-window window)
- end))
- (setf (window-dedicated-p (selected-window)) nil)
- (ecase split-type
- ((tag song)
- (pop-to-buffer-same-window
- (get-buffer-create (concat "*ampc "
- (or (plist-get (cdr split) :tag) "Song")
- "*")))
- (ampc-tag-song-mode))
- (outputs
- (pop-to-buffer-same-window (get-buffer-create "*ampc Outputs*"))
- (ampc-outputs-mode))
- (current-playlist
- (pop-to-buffer-same-window (get-buffer-create "*ampc Current
Playlist*"))
- (ampc-current-playlist-mode)
- (ampc-highlight-current-song-mode 1))
- (playlist
- (pop-to-buffer-same-window (get-buffer-create "*ampc Playlist*"))
- (ampc-playlist-mode))
- (playlists
- (pop-to-buffer-same-window (get-buffer-create "*ampc Playlists*"))
- (ampc-playlists-mode))
- (status
- (pop-to-buffer-same-window (get-buffer-create "*ampc Status*"))
- (ampc-mode)))
- (destructuring-bind (&key (dedicated t) (mode-line t) &allow-other-keys)
- (cdr split)
- (setf (window-dedicated-p (selected-window)) dedicated)
- (unless mode-line
- (setf mode-line-format nil)))
- (setf ampc-type split)
- (add-to-list 'ampc-all-buffers (current-buffer))
- (push `(,(or (plist-get (cdr split) :id)
- (if (eq (car ampc-type) 'song) 9998 9999))
- . ,(current-buffer))
- ampc-buffers)
- (ampc-set-dirty t)))
-
-(defun ampc-configure-frame (split)
- (if ampc-use-full-frame
- (progn (setf (window-dedicated-p (selected-window)) nil)
- (delete-other-windows))
- (loop with live-window = nil
- for w in (nreverse (ampc-windows t))
- if (window-live-p w)
- if (not live-window)
- do (setf live-window w)
- else
- do (delete-window w)
- end
- end
- finally do (if live-window (select-window live-window))))
- (setf ampc-buffers nil)
- (ampc-configure-frame-1 split)
- (setf ampc-buffers-unordered (mapcar 'cdr ampc-buffers)
- ampc-buffers (mapcar 'cdr (sort ampc-buffers
- (lambda (a b) (< (car a) (car b))))))
- (ampc-update))
-
-;;; *** interactives
-(defun* ampc-unmark-all (&aux buffer-read-only)
- "Remove all marks."
- (interactive)
- (assert (ampc-in-ampc-p))
- (save-excursion
- (goto-char (point-min))
- (loop while (search-forward-regexp "^\\* " nil t)
- do (replace-match " " nil nil)))
- (ampc-post-mark-change-update))
-
-(defun ampc-trigger-update ()
- "Trigger a database update."
- (interactive)
- (assert (ampc-on-p))
- (ampc-send-command 'update))
-
-(defun* ampc-toggle-marks (&aux buffer-read-only)
- "Toggle marks. Marked entries become unmarked, and vice versa."
- (interactive)
- (assert (ampc-in-ampc-p))
- (save-excursion
- (loop for (a . b) in '(("* " . "T ")
- (" " . "* ")
- ("T " . " "))
- do (goto-char (point-min))
- (loop while (search-forward-regexp (concat "^" (regexp-quote a))
- nil
- t)
- do (replace-match b nil nil))))
- (ampc-post-mark-change-update))
-
-(defun ampc-up (&optional arg)
- "Go to the previous ARG'th entry.
-With optional prefix ARG, move the next ARG entries after point
-rather than the selection."
- (interactive "P")
- (assert (ampc-in-ampc-p))
- (ampc-move t arg))
-
-(defun ampc-down (&optional arg)
- "Go to the next ARG'th entry.
-With optional prefix ARG, move the next ARG entries after point
-rather than the selection."
- (interactive "P")
- (assert (ampc-in-ampc-p))
- (ampc-move nil arg))
-
-(defun ampc-mark (&optional arg)
- "Mark the next ARG'th entries.
-ARG defaults to 1."
- (interactive "p")
- (assert (ampc-in-ampc-p))
- (ampc-mark-impl t arg))
-
-(defun ampc-unmark (&optional arg)
- "Unmark the next ARG'th entries.
-ARG defaults to 1."
- (interactive "p")
- (assert (ampc-in-ampc-p))
- (ampc-mark-impl nil arg))
-
-(defun ampc-increase-volume (&optional arg)
- "Decrease volume.
-With prefix argument ARG, set volume to ARG percent."
- (interactive "P")
- (assert (ampc-on-p))
- (ampc-set-volume arg '+))
-
-(defun ampc-decrease-volume (&optional arg)
- "Decrease volume.
-With prefix argument ARG, set volume to ARG percent."
- (interactive "P")
- (assert (ampc-on-p))
- (ampc-set-volume arg '-))
-
-(defun ampc-increase-crossfade (&optional arg)
- "Increase crossfade.
-With prefix argument ARG, set crossfading to ARG seconds."
- (interactive "P")
- (assert (ampc-on-p))
- (ampc-set-crossfade arg '+))
-
-(defun ampc-decrease-crossfade (&optional arg)
- "Decrease crossfade.
-With prefix argument ARG, set crossfading to ARG seconds."
- (interactive "P")
- (assert (ampc-on-p))
- (ampc-set-crossfade arg '-))
-
-(defun ampc-toggle-repeat (&optional arg)
- "Toggle MPD's repeat state.
-With prefix argument ARG, enable repeating if ARG is positive,
-otherwise disable it."
- (interactive "P")
- (assert (ampc-on-p))
- (ampc-toggle-state 'repeat arg))
-
-(defun ampc-toggle-consume (&optional arg)
- "Toggle MPD's consume state.
-With prefix argument ARG, enable consuming if ARG is positive,
-otherwise disable it.
-
-When consume is activated, each song played is removed from the playlist."
- (interactive "P")
- (assert (ampc-on-p))
- (ampc-toggle-state 'consume arg))
-
-(defun ampc-toggle-random (&optional arg)
- "Toggle MPD's random state.
-With prefix argument ARG, enable random playing if ARG is positive,
-otherwise disable it."
- (interactive "P")
- (ampc-toggle-state 'random arg))
-
-(defun ampc-play-this ()
- "Play selected song."
- (interactive)
- (assert (ampc-in-ampc-p))
- (unless (eobp)
- (ampc-send-command 'play nil (1- (line-number-at-pos)))
- (ampc-send-command 'pause nil 0)))
-
-(defun* ampc-toggle-play
- (&optional arg &aux (state (cdr-safe (assq 'state ampc-status))))
- "Toggle play state.
-If mpd does not play a song already, start playing the song at
-point if the current buffer is the playlist buffer, otherwise
-start at the beginning of the playlist.
-
-If ARG is 4, stop player rather than pause if applicable."
- (interactive "P")
- (assert (ampc-on-p))
- (when state
- (when arg
- (setf arg (prefix-numeric-value arg)))
- (ecase (intern state)
- (stop
- (when (or (null arg) (> arg 0))
- (ampc-send-command
- 'play
- nil
- (if (and (eq (car ampc-type) 'current-playlist) (not (eobp)))
- (1- (line-number-at-pos))
- 0))))
- (pause
- (when (or (null arg) (> arg 0))
- (ampc-send-command 'pause nil 0)))
- (play
- (cond ((or (null arg) (< arg 0))
- (ampc-send-command 'pause nil 1))
- ((eq arg 4)
- (ampc-send-command 'stop)))))))
-
-(defun ampc-next (&optional arg)
- "Play next song.
-With prefix argument ARG, skip ARG songs."
- (interactive "p")
- (assert (ampc-on-p))
- (ampc-skip (or arg 1)))
-
-(defun ampc-previous (&optional arg)
- "Play previous song.
-With prefix argument ARG, skip ARG songs."
- (interactive "p")
- (assert (ampc-on-p))
- (ampc-skip (- (or arg 1))))
-
-(defun ampc-rename-playlist (new-name)
- "Rename selected playlist to NEW-NAME.
-Interactively, read NEW-NAME from the minibuffer."
- (interactive "MNew name: ")
- (assert (ampc-in-ampc-p))
- (if (ampc-playlist)
- (ampc-send-command 'rename nil (ampc-playlist) new-name)
- (error "No playlist selected")))
-
-(defun ampc-load ()
- "Load selected playlist in the current playlist."
- (interactive)
- (assert (ampc-in-ampc-p))
- (if (ampc-playlist)
- (ampc-send-command 'load nil (ampc-quote (ampc-playlist)))
- (error "No playlist selected")))
-
-(defun ampc-toggle-output-enabled (&optional arg)
- "Toggle the next ARG outputs.
-If ARG is omitted, use the selected entries."
- (interactive "P")
- (assert (ampc-in-ampc-p))
- (ampc-with-selection arg
- (let ((data (get-text-property (point) 'data)))
- (ampc-send-command (if (equal (cdr (assoc "outputenabled" data)) "1")
- 'disableoutput
- 'enableoutput)
- nil
- (cdr (assoc "outputid" data))))))
-
-(defun ampc-delete (&optional arg)
- "Delete the next ARG songs from the playlist.
-If ARG is omitted, use the selected entries."
- (interactive "P")
- (assert (ampc-in-ampc-p))
- (let ((point (point)))
- (ampc-with-selection arg
- (let ((val (1- (- (line-number-at-pos) index))))
- (if (ampc-playlist)
- (ampc-send-command 'playlistdelete
- t
- (ampc-quote (ampc-playlist))
- val)
- (ampc-send-command 'delete t val))))
- (goto-char point)
- (ampc-align-point)))
-
-(defun ampc-shuffle ()
- "Shuffle playlist."
- (interactive)
- (assert (ampc-on-p))
- (if (not (ampc-playlist))
- (ampc-send-command 'shuffle)
- (ampc-with-buffer 'playlist
- (let ((shuffled
- (mapcar
- 'car
- (sort (loop until (eobp)
- collect `(,(cdr (assoc "file" (get-text-property
- (+ 2 (point))
- 'data)))
- . ,(random))
- do (forward-line))
- (lambda (a b)
- (< (cdr a) (cdr b)))))))
- (ampc-clear)
- (loop for s in shuffled
- do (ampc-add-impl s))))))
-
-(defun ampc-clear ()
- "Clear playlist."
- (interactive)
- (assert (ampc-on-p))
- (if (ampc-playlist)
- (ampc-send-command 'playlistclear nil (ampc-quote (ampc-playlist)))
- (ampc-send-command 'clear)))
-
-(defun ampc-add (&optional arg)
- "Add the next ARG songs associated with the entries after point
-to the playlist.
-If ARG is omitted, use the selected entries in the current buffer."
- (interactive "P")
- (assert (ampc-in-ampc-p))
- (ampc-with-selection arg
- (ampc-add-impl)))
-
-(defun ampc-status ()
- "Display the information that is displayed in the status window."
- (interactive)
- (assert (ampc-on-p))
- (let* ((flags (mapconcat
- 'identity
- (loop for (f . n) in '((repeat . "Repeat")
- (random . "Random")
- (consume . "Consume"))
- when (equal (cdr (assq f ampc-status)) "1")
- collect n
- end)
- "|"))
- (state (cdr (assq 'state ampc-status)))
- (status (concat "State: " state
- (when ampc-yield
- (concat (make-string (- 10 (length state)) ? )
- (nth (% ampc-yield 4) '("|" "/" "-" "\\"))))
- "\n"
- (when (equal state "play")
- (concat "Playing: "
- (or (cdr-safe (assq 'Artist ampc-status))
- "[Not Specified]")
- " - "
- (or (cdr-safe (assq 'Title ampc-status))
- "[Not Specified]")
- "\n"))
- "Volume: " (cdr (assq 'volume ampc-status)) "\n"
- "Crossfade: " (cdr (assq 'xfade ampc-status))
- (unless (equal flags "")
- (concat "\n" flags)))))
- (when (called-interactively-p 'interactive)
- (message "%s" status))
- status))
-
-(defun ampc-delete-playlist ()
- "Delete selected playlist."
- (interactive)
- (assert (ampc-in-ampc-p))
- (ampc-with-selection nil
- (let ((name (get-text-property (point) 'data)))
- (when (y-or-n-p (concat "Delete playlist " name "?"))
- (ampc-send-command 'rm nil (ampc-quote name))))))
-
-(defun ampc-store (name)
- "Store current playlist as NAME.
-Interactively, read NAME from the minibuffer."
- (interactive "MSave playlist as: ")
- (assert (ampc-in-ampc-p))
- (ampc-send-command 'save nil (ampc-quote name)))
-
-(defun* ampc-goto-current-song
- (&aux (song (cdr-safe (assq 'song ampc-status))))
- "Select the current playlist window and move point to the current song."
- (interactive)
- (assert (ampc-in-ampc-p))
- (when song
- (ampc-with-buffer 'current-playlist
- no-se
- (select-window (ampc-get-window 'current-playlist))
- (goto-char (point-min))
- (forward-line (string-to-number song))
- (ampc-align-point))))
-
-(defun ampc-previous-line (&optional arg)
- "Go to previous ARG'th entry in the current buffer.
-ARG defaults to 1."
- (interactive "p")
- (assert (ampc-in-ampc-p))
- (ampc-next-line (* (or arg 1) -1)))
-
-(defun ampc-next-line (&optional arg)
- "Go to next ARG'th entry in the current buffer.
-ARG defaults to 1."
- (interactive "p")
- (assert (ampc-in-ampc-p))
- (forward-line arg)
- (if (eobp)
- (progn (forward-line -1)
- (forward-char 2)
- t)
- (ampc-align-point)
- nil))
-
-(defun* ampc-suspend (&optional (run-hook t))
- "Suspend ampc.
-This function resets the window configuration, but does not close
-the connection to mpd or destroy the internal cache of ampc.
-This means subsequent startups of ampc will be faster."
- (interactive)
- (when ampc-working-timer
- (cancel-timer ampc-working-timer))
- (loop with found-window
- for w in (nreverse (ampc-windows t))
- when (window-live-p w)
- when found-window
- do (delete-window w)
- else
- do (setf found-window t
- (window-dedicated-p w) nil)
- end
- end)
- (loop for b in ampc-all-buffers
- when (buffer-live-p b)
- do (kill-buffer b)
- end)
- (setf ampc-buffers nil
- ampc-all-buffers nil
- ampc-working-timer nil)
- (when run-hook
- (run-hooks 'ampc-suspend-hook)))
-
-(defun ampc-quit (&optional arg)
- "Quit ampc.
-If called with a prefix argument ARG, kill the mpd instance that
-ampc is connected to."
- (interactive "P")
- (when (ampc-on-p)
- (set-process-filter ampc-connection nil)
- (when (equal (car-safe ampc-outstanding-commands) '(idle))
- (ampc-send-command-impl "noidle")
- (with-current-buffer (process-buffer ampc-connection)
- (loop do (goto-char (point-min))
- until (search-forward-regexp "^\\(ACK\\)\\|\\(OK\\).*\n\\'" nil
t)
- do (accept-process-output ampc-connection nil 50))))
- (ampc-send-command-impl (if arg "kill" "close")))
- (when ampc-working-timer
- (cancel-timer ampc-working-timer))
- (ampc-suspend nil)
- (setf ampc-connection nil
- ampc-internal-db nil
- ampc-outstanding-commands nil
- ampc-status nil)
- (run-hooks 'ampc-quit-hook))
-
-;;;###autoload
-(defun ampc (&optional host port)
- "ampc is an asynchronous client for the MPD media player.
-This function is the main entry point for ampc.
-
-Non-interactively, HOST and PORT specify the MPD instance to
-connect to. The values default to localhost:6600."
- (interactive "MHost (localhost): \nMPort (6600): ")
- (run-hooks 'ampc-before-startup-hook)
- (when (or (not host) (equal host ""))
- (setf host "localhost"))
- (when (or (not port) (equal port ""))
- (setf port 6600))
- (when (and ampc-connection
- (or (not (equal host ampc-host))
- (not (equal port ampc-port))
- (not (ampc-on-p))))
- (ampc-quit))
- (unless ampc-connection
- (let ((connection (open-network-stream "ampc"
- (with-current-buffer
- (get-buffer-create " *mpc*")
- (delete-region (point-min)
- (point-max))
- (current-buffer))
- host
- port
- :type 'plain :return-list t)))
- (unless (car connection)
- (error "Failed connecting to server: %s"
- (plist-get ampc-connection :error)))
- (setf ampc-connection (car connection)
- ampc-host host
- ampc-port port))
- (set-process-coding-system ampc-connection 'utf-8-unix 'utf-8-unix)
- (set-process-filter ampc-connection 'ampc-filter)
- (set-process-query-on-exit-flag ampc-connection nil)
- (setf ampc-outstanding-commands '((setup))))
- (ampc-configure-frame (cdar ampc-views))
- (run-hooks 'ampc-connected-hook)
- (ampc-filter (process-buffer ampc-connection) nil))
-
-(provide 'ampc)
-
-;; Local Variables:
-;; eval: (outline-minor-mode 1)
-;; outline-regexp: ";;; \\*+"
-;; lexical-binding: t
-;; fill-column: 80
-;; indent-tabs-mode: nil
-;; End:
=== added file 'packages/ampc/ampc.el'
--- a/packages/ampc/ampc.el 1970-01-01 00:00:00 +0000
+++ b/packages/ampc/ampc.el 2012-06-06 00:56:29 +0000
@@ -0,0 +1,1984 @@
+;;; ampc.el --- Asynchronous Music Player Controller
+
+;; Copyright (C) 2011-2012 Free Software Foundation, Inc.
+
+;; Author: Christopher Schmidt <address@hidden>
+;; Maintainer: Christopher Schmidt <address@hidden>
+;; Version: 0.1.3
+;; Created: 2011-12-06
+;; Keywords: ampc, mpc, mpd
+;; Compatibility: GNU Emacs: 24.x
+
+;; This file is part of ampc.
+
+;; This program 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.
+
+;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;; * description
+;; ampc is a controller for the Music Player Daemon (http://mpd.wikia.com/).
+
+;;; ** installation
+;; If you use GNU ELPA, install ampc via M-x package-list-packages RET or
+;; (package-install 'ampc). Otherwise, grab this file and put it somewhere in
+;; your load-path or add the directory the file is in to it, e.g.:
+;;
+;; (add-to-list 'load-path "~/.emacs.d/ampc")
+;;
+;; Then add one autoload definition:
+;;
+;; (autoload 'ampc "ampc" nil t)
+;;
+;; Optionally bind a key to this function, e.g.:
+;;
+;; (global-set-key (kbd "<f9>") 'ampc)
+;;
+;; or
+;;
+;; (global-set-key (kbd "<f9>") (lambda () (interactive) (ampc "host" "port")))
+;;
+;; Byte-compile ampc (M-x byte-compile-file RET /path/to/ampc.el RET) to
improve
+;; its performance!
+
+;;; ** usage
+;; To invoke ampc, call the command `ampc', e.g. via M-x ampc RET. When called
+;; interactively, `ampc' reads host address and port from the minibuffer. If
+;; called non-interactively, the first argument to `ampc' is the host, the
+;; second is the port. Both values default to nil, which will make ampc
connect
+;; to localhost:6600. Once ampc is connected to the daemon, it creates its
+;; window configuration in the selected window. To make ampc use the full
frame
+;; rather than the selected window, customise `ampc-use-full-frame'.
+;;
+;; ampc offers three independent views which expose different parts of the user
+;; interface. The current playlist view, the default view at startup, may be
+;; accessed using the `J' (that is `S-j') key. The playlist view may be
+;; accessed using the `K' key. The outputs view may be accessed using the `L'
+;; key.
+
+;;; *** current playlist view
+;; The playlist view should look like this
+;;
+;; .........................
+;; . 1 . 3 . 4 . 5 .
+;; .......... . . .
+;; . 2 . . . .
+;; . . . . .
+;; . . . . .
+;; . ................
+;; . . 6 .
+;; . . .
+;; .........................
+;;
+;; Window one exposes basic information about the daemon, such as the current
+;; state (stop/play/pause), the song currently playing, or the volume.
+;;
+;; All windows, except the status window, contain a tabular list of items.
Each
+;; item may be selected/marked. There may be multiple selections.
+;;
+;; To mark an entry, move the point to the entry and press `m' (ampc-mark). To
+;; unmark an entry, press `u' (ampc-unmark). To unmark all entries, press `U'
+;; (ampc-unmark-all). To toggle marks, press `t' (ampc-toggle-marks).
Pressing
+;; `<down-mouse-1>' with the mouse mouse cursor on a list entry will move point
+;; to the entry and toggle the mark. To navigate to the next entry, press `n'
+;; (ampc-next-line). Analogous, pressing `p' (ampc-previous-line) moves the
+;; point to the previous entry.
+;;
+;; Window two shows the current playlist. The song that is currently played by
+;; the daemon, if any, is highlighted. To delete the selected songs from the
+;; playlist, press `d' (ampc-delete). Pressing `<down-mouse-3>' will move the
+;; point to the entry under cursor and delete it from the playlist. To move
the
+;; selected songs up, press `<up>' (ampc-up). Analogous, press `<down>'
+;; (ampc-down) to move the selected songs down. Pressing `<return>'
+;; (ampc-play-this) or `<down-mouse-2>' will play the song at point/cursor.
+;;
+;; Windows three to five are tag browsers. You use them to narrow the song
+;; database to certain songs. Think of tag browsers as filters, analogous to
+;; piping `grep' outputs through additional `grep' filters. The property of
the
+;; songs that is filtered is displayed in the header line of the window.
+;;
+;; Window six shows the songs that match the filters defined by windows three
to
+;; five. To add the selected song to the playlist, press `a' (ampc-add).
+;; Pressing `<down-mouse-3>' will move the point to the entry under the cursor
+;; and execute `ampc-add'. These key bindings works in tag browsers as well.
+;; Calling `ampc-add' in a tag browser adds all songs filtered up to the
+;; selected browser to the playlist.
+;;
+;; The tag browsers of the (default) current playlist view (accessed via `J')
+;; are `Genre' (window 3), `Artist' (window 4) and `Album' (window 5). The key
+;; `M' may be used to fire up a slightly modified current playlist view. There
+;; is no difference to the default current playlist view other than that the
tag
+;; browsers filter to `Genre' (window 3), `Album' (window 4) and `Artist'
+;; (window 5). Metaphorically speaking, the order of the `grep' filters
defined
+;; by the tag browsers is different.
+
+;;; *** playlist view
+;; The playlist view resembles the current playlist view. The window, which
+;; exposes the playlist content, is split, though. The bottom half shows a
list
+;; of stored playlists. The upper half does not expose the current playlist
+;; anymore. Instead, the content of the selected (stored) playlist is shown.
+;; All commands that used to work in the current playlist view and modify the
+;; current playlist now modify the selected (stored) playlist. The list of
+;; stored playlists is the only view in ampc that may have only one marked
+;; entry.
+;;
+;; Again, the key `<' may be used to setup a playlist view with a different
+;; order of tag browsers.
+
+;;; *** outputs view
+;; The outputs view contains a single list which shows the configured outputs
of
+;; mpd. To toggle the enabled property of the selected outputs, press `a'
+;; (ampc-toggle-output-enabled) or `<mouse-3>'.
+
+;;; *** global keys
+;; Aside from `J', `M', `K', `<' and `L', which may be used to select different
+;; views, ampc defines the following global keys, which may be used in every
+;; window associated with ampc:
+;;
+;; `k' (ampc-toggle-play): Toggle play state. If mpd does not play a song
+;; already, start playing the song at point if the current buffer is the
+;; playlist buffer, otherwise start at the beginning of the playlist. With
+;; prefix argument 4, stop player rather than pause if applicable.
+;;
+;; `l' (ampc-next): Play next song.
+;; `j' (ampc-previous): Play previous song
+;;
+;; `c' (ampc-clear): Clear playlist.
+;; `s' (ampc-shuffle): Shuffle playlist.
+;;
+;; `S' (ampc-store): Store playlist.
+;; `O' (ampc-load): Load selected playlist in the current playlist.
+;; `R' (ampc-rename-playlist): Rename selected playlist.
+;; `D' (ampc-delete-playlist): Delete selected playlist.
+;;
+;; `y' (ampc-increase-volume): Increase volume.
+;; `M-y' (ampc-decrease-volume): Decrease volume.
+;; `h' (ampc-increase-crossfade): Increase crossfade.
+;; `M-h' (ampc-decrease-crossfade): Decrease crossfade.
+;;
+;; `e' (ampc-toggle-repeat): Toggle repeat state.
+;; `r' (ampc-toggle-random): Toggle random state.
+;; `f' (ampc-toggle-consume): Toggle consume state.
+;;
+;; `P' (ampc-goto-current-song): Select the current playlist window and move
+;; point to the current song.
+;;
+;; `T' (ampc-trigger-update): Trigger a database update.
+;; `Z' (ampc-suspend): Suspend ampc.
+;; `q' (ampc-quit): Quit ampc.
+;;
+;; The keymap of ampc is designed to fit the QWERTY United States keyboard
+;; layout. If you use another keyboard layout, feel free to modify
+;; `ampc-mode-map'. For example, I use a regular QWERTZ German keyboard
+;; (layout), so I modify `ampc-mode-map' in my init.el like this:
+;;
+;; (eval-after-load 'ampc
+;; '(flet ((substitute-ampc-key
+;; (from to)
+;; (define-key ampc-mode-map to (lookup-key ampc-mode-map from))
+;; (define-key ampc-mode-map from nil)))
+;; (substitute-ampc-key (kbd "z") (kbd "Z"))
+;; (substitute-ampc-key (kbd "y") (kbd "z"))
+;; (substitute-ampc-key (kbd "M-y") (kbd "M-z"))
+;; (substitute-ampc-key (kbd "<") (kbd ";"))))
+;;
+;; If ampc is suspended, you can still use every interactive command that does
+;; not directly operate on or with the user interace of ampc. For example it
is
+;; perfectly fine to call `ampc-increase-volume' or `ampc-toggle-play' via M-x
+;; RET. To display the information that is displayed by the status window of
+;; ampc, call `ampc-status'.
+
+;;; Code:
+;;; * code
+(eval-when-compile
+ (require 'easymenu)
+ (require 'cl))
+(require 'network-stream)
+(require 'avl-tree)
+
+;;; ** declarations
+;;; *** variables
+(defgroup ampc ()
+ "Asynchronous client for the Music Player Daemon."
+ :prefix "ampc-"
+ :group 'multimedia
+ :group 'applications)
+
+;;; *** customs
+(defcustom ampc-debug nil
+ "Non-nil means log communication between ampc and MPD."
+ :type 'boolean)
+
+(defcustom ampc-use-full-frame nil
+ "If non-nil, ampc will use the entire Emacs screen."
+ :type 'boolean)
+
+(defcustom ampc-truncate-lines t
+ "If non-nil, truncate lines in ampc buffers."
+ :type 'boolean)
+
+(defcustom ampc-status-tags nil
+ "List of additional tags of the current song that are added to
+the internal status of ampc and thus are passed to the functions
+in `ampc-status-changed-hook'. Each element may be a string that
+specifies a tag that is returned by MPD's `currentsong'
+command.")
+
+;;; **** hooks
+(defcustom ampc-before-startup-hook nil
+ "A hook run before startup.
+This hook is called as the first thing when ampc is started."
+ :type 'hook)
+
+(defcustom ampc-connected-hook nil
+ "A hook run after ampc connected to MPD."
+ :type 'hook)
+
+(defcustom ampc-suspend-hook nil
+ "A hook run when suspending ampc."
+ :type 'hook)
+
+(defcustom ampc-quit-hook nil
+ "A hook run when exiting ampc."
+ :type 'hook)
+
+(defcustom ampc-status-changed-hook nil
+ "A hook run whenever the status of the daemon (that is volatile
+properties such as volume or current song) changes. The hook is
+run with one arg, an alist that contains the new status. The car
+of each entry is a symbol, the cdr is a string. Valid keys are:
+
+ volume
+ repeat
+ random
+ consume
+ xfade
+ state
+ song
+ Artist
+ Title
+
+and the keys in `ampc-status-tags'. Not all keys may be present
+all the time!"
+ :type 'hook)
+
+;;; *** faces
+(defface ampc-mark-face '((t (:inherit font-lock-constant-face)))
+ "Face of the mark.")
+(defface ampc-marked-face '((t (:inherit warning)))
+ "Face of marked entries.")
+(defface ampc-face '((t (:inerhit default)))
+ "Face of unmarked entries.")
+(defface ampc-current-song-mark-face '((t (:inherit region)))
+ "Face of mark of the current song.")
+(defface ampc-current-song-marked-face '((t (:inherit region)))
+ "Face of the current song if marked.")
+
+;;; *** internal variables
+(defvar ampc-views
+ (let* ((songs '(1.0 song :properties (("Track" :title "#")
+ ("Title" :offset 6)
+ ("Time" :offset 26))))
+ (rs_a `(1.0 vertical
+ (0.7 horizontal
+ (0.33 tag :tag "Genre" :id 1)
+ (0.33 tag :tag "Artist" :id 2)
+ (1.0 tag :tag "Album" :id 3))
+ ,songs))
+ (rs_b `(1.0 vertical
+ (0.7 horizontal
+ (0.33 tag :tag "Genre" :id 1)
+ (0.33 tag :tag "Album" :id 2)
+ (1.0 tag :tag "Artist" :id 3))
+ ,songs))
+ (pl-prop '(("Title")
+ ("Artist" :offset 20)
+ ("Album" :offset 40)
+ ("Time" :offset 60))))
+ `(("Current playlist view (Genre|Artist|Album)"
+ ,(kbd "J")
+ horizontal
+ (0.4 vertical
+ (6 status)
+ (1.0 current-playlist :properties ,pl-prop))
+ ,rs_a)
+ ("Current playlist view (Genre|Album|Artist)"
+ ,(kbd "M")
+ horizontal
+ (0.4 vertical
+ (6 status)
+ (1.0 current-playlist :properties ,pl-prop))
+ ,rs_b)
+ ("Playlist view (Genre|Artist|Album)"
+ ,(kbd "K")
+ horizontal
+ (0.4 vertical
+ (6 status)
+ (1.0 vertical
+ (0.8 playlist :properties ,pl-prop)
+ (1.0 playlists)))
+ ,rs_a)
+ ("Playlist view (Genre|Album|Artist)"
+ ,(kbd "<")
+ horizontal
+ (0.4 vertical
+ (6 status)
+ (1.0 vertical
+ (0.8 playlist :properties ,pl-prop)
+ (1.0 playlists)))
+ ,rs_b)
+ ("Outputs view"
+ ,(kbd "L")
+ outputs :properties (("outputname" :title "Name")
+ ("outputenabled" :title "Enabled" :offset 10))))))
+
+(defvar ampc-connection nil)
+(defvar ampc-host nil)
+(defvar ampc-port nil)
+(defvar ampc-outstanding-commands nil)
+
+(defvar ampc-working-timer nil)
+(defvar ampc-yield nil)
+
+(defvar ampc-buffers nil)
+(defvar ampc-buffers-unordered nil)
+(defvar ampc-all-buffers nil)
+
+(defvar ampc-type nil)
+(make-variable-buffer-local 'ampc-type)
+(defvar ampc-dirty nil)
+(make-variable-buffer-local 'ampc-dirty)
+
+(defvar ampc-internal-db nil)
+(defvar ampc-status nil)
+
+;;; *** mode maps
+(defvar ampc-mode-map
+ (let ((map (make-sparse-keymap)))
+ (suppress-keymap map)
+ (define-key map (kbd "k") 'ampc-toggle-play)
+ (define-key map (kbd "l") 'ampc-next)
+ (define-key map (kbd "j") 'ampc-previous)
+ (define-key map (kbd "c") 'ampc-clear)
+ (define-key map (kbd "s") 'ampc-shuffle)
+ (define-key map (kbd "S") 'ampc-store)
+ (define-key map (kbd "O") 'ampc-load)
+ (define-key map (kbd "R") 'ampc-rename-playlist)
+ (define-key map (kbd "D") 'ampc-delete-playlist)
+ (define-key map (kbd "y") 'ampc-increase-volume)
+ (define-key map (kbd "M-y") 'ampc-decrease-volume)
+ (define-key map (kbd "h") 'ampc-increase-crossfade)
+ (define-key map (kbd "M-h") 'ampc-decrease-crossfade)
+ (define-key map (kbd "e") 'ampc-toggle-repeat)
+ (define-key map (kbd "r") 'ampc-toggle-random)
+ (define-key map (kbd "f") 'ampc-toggle-consume)
+ (define-key map (kbd "P") 'ampc-goto-current-song)
+ (define-key map (kbd "q") 'ampc-quit)
+ (define-key map (kbd "z") 'ampc-suspend)
+ (define-key map (kbd "T") 'ampc-trigger-update)
+ (loop for view in ampc-views
+ do (define-key map (cadr view)
+ `(lambda ()
+ (interactive)
+ (ampc-change-view ',view))))
+ map))
+
+(defvar ampc-item-mode-map
+ (let ((map (make-sparse-keymap)))
+ (suppress-keymap map)
+ (define-key map (kbd "m") 'ampc-mark)
+ (define-key map (kbd "u") 'ampc-unmark)
+ (define-key map (kbd "U") 'ampc-unmark-all)
+ (define-key map (kbd "n") 'ampc-next-line)
+ (define-key map (kbd "p") 'ampc-previous-line)
+ (define-key map [remap next-line] 'ampc-next-line)
+ (define-key map [remap previous-line] 'ampc-previous-line)
+ (define-key map (kbd "<down-mouse-1>") 'ampc-mouse-toggle-mark)
+ (define-key map (kbd "<mouse-1>") 'ampc-mouse-align-point)
+ map))
+
+(defvar ampc-current-playlist-mode-map
+ (let ((map (make-sparse-keymap)))
+ (suppress-keymap map)
+ (define-key map (kbd "<return>") 'ampc-play-this)
+ (define-key map (kbd "<down-mouse-2>") 'ampc-mouse-play-this)
+ (define-key map (kbd "<mouse-2>") 'ampc-mouse-align-point)
+ (define-key map (kbd "<down-mouse-3>") 'ampc-mouse-delete)
+ map))
+
+(defvar ampc-playlist-mode-map
+ (let ((map (make-sparse-keymap)))
+ (suppress-keymap map)
+ (define-key map (kbd "t") 'ampc-toggle-marks)
+ (define-key map (kbd "d") 'ampc-delete)
+ (define-key map (kbd "<up>") 'ampc-up)
+ (define-key map (kbd "<down>") 'ampc-down)
+ (define-key map (kbd "<down-mouse-3>") 'ampc-mouse-delete)
+ map))
+
+(defvar ampc-playlists-mode-map
+ (let ((map (make-sparse-keymap)))
+ (suppress-keymap map)
+ (define-key map (kbd "l") 'ampc-load)
+ (define-key map (kbd "r") 'ampc-rename-playlist)
+ (define-key map (kbd "d") 'ampc-delete-playlist)
+ map))
+
+(defvar ampc-tag-song-mode-map
+ (let ((map (make-sparse-keymap)))
+ (suppress-keymap map)
+ (define-key map (kbd "t") 'ampc-toggle-marks)
+ (define-key map (kbd "a") 'ampc-add)
+ (define-key map (kbd "<down-mouse-3>") 'ampc-mouse-add)
+ (define-key map (kbd "<mouse-3>") 'ampc-mouse-align-point)
+ map))
+
+(defvar ampc-outputs-mode-map
+ (let ((map (make-sparse-keymap)))
+ (suppress-keymap map)
+ (define-key map (kbd "t") 'ampc-toggle-marks)
+ (define-key map (kbd "a") 'ampc-toggle-output-enabled)
+ (define-key map (kbd "<down-mouse-3>") 'ampc-mouse-toggle-output-enabled)
+ (define-key map (kbd "<mouse-3>") 'ampc-mouse-align-point)
+ map))
+
+;;; **** menu
+(easy-menu-define nil ampc-mode-map nil
+ `("ampc"
+ ("Change view" ,@(loop for view in ampc-views
+ collect (vector (car view)
+ `(lambda ()
+ (interactive)
+ (ampc-change-view ',view)))))
+ "--"
+ ["Play" ampc-toggle-play
+ :visible (and ampc-status
+ (not (equal (cdr (assq 'state ampc-status)) "play")))]
+ ["Pause" ampc-toggle-play
+ :visible (and ampc-status
+ (equal (cdr (assq 'state ampc-status)) "play"))]
+ ["Stop" (lambda () (interactive) (ampc-toggle-play 4))
+ :visible (and ampc-status
+ (equal (cdr (assq 'state ampc-status)) "play"))]
+ ["Next" ampc-next]
+ ["Previous" ampc-previous]
+ "--"
+ ["Clear playlist" ampc-clear]
+ ["Shuffle playlist" ampc-shuffle]
+ ["Store playlist" ampc-store]
+ ["Queue Playlist" ampc-load :visible (ampc-playlist)]
+ ["Rename Playlist" ampc-rename-playlist :visible (ampc-playlist)]
+ ["Delete Playlist" ampc-delete-playlist :visible (ampc-playlist)]
+ "--"
+ ["Increase volume" ampc-increase-volume]
+ ["Decrease volume" ampc-decrease-volume]
+ ["Increase crossfade" ampc-increase-crossfade]
+ ["Decrease crossfade" ampc-decrease-crossfade]
+ ["Toggle repeat" ampc-toggle-repeat
+ :style toggle
+ :selected (equal (cdr-safe (assq 'repeat ampc-status)) "1")]
+ ["Toggle random" ampc-toggle-random
+ :style toggle
+ :selected (equal (cdr-safe (assq 'random ampc-status)) "1")]
+ ["Toggle consume" ampc-toggle-consume
+ :style toggle
+ :selected (equal (cdr-safe (assq 'consume ampc-status)) "1")]
+ "--"
+ ["Trigger update" ampc-trigger-update]
+ ["Suspend" ampc-suspend]
+ ["Quit" ampc-quit]))
+
+(easy-menu-define ampc-selection-menu ampc-item-mode-map
+ "Selection menu for ampc"
+ '("ampc Mark"
+ ["Add to playlist" ampc-add
+ :visible (not (eq (car ampc-type) 'outputs))]
+ ["Toggle enabled" ampc-toggle-output-enabled
+ :visible (eq (car ampc-type) 'outputs)]
+ "--"
+ ["Next line" ampc-next-line]
+ ["Previous line" ampc-previous-line]
+ ["Mark" ampc-mark]
+ ["Unmark" ampc-unmark]
+ ["Unmark all" ampc-unmark-all]
+ ["Toggle marks" ampc-toggle-marks
+ :visible (not (eq (car ampc-type) 'playlists))]))
+
+(defvar ampc-tool-bar-map
+ (let ((map (make-sparse-keymap)))
+ (tool-bar-local-item
+ "mpc/prev" 'ampc-previous 'previous map
+ :help "Previous")
+ (tool-bar-local-item
+ "mpc/play" 'ampc-toggle-play 'play map
+ :help "Play"
+ :visible '(and ampc-status
+ (not (equal (cdr (assq 'state ampc-status)) "play"))))
+ (tool-bar-local-item
+ "mpc/pause" 'ampc-toggle-play 'pause map
+ :help "Pause"
+ :visible '(and ampc-status
+ (equal (cdr (assq 'state ampc-status)) "play")))
+ (tool-bar-local-item
+ "mpc/stop" (lambda () (interactive) (ampc-toggle-play 4)) 'stop map
+ :help "Stop"
+ :visible '(and ampc-status
+ (equal (cdr (assq 'state ampc-status)) "play")))
+ (tool-bar-local-item
+ "mpc/next" 'ampc-next 'next map
+ :help "Next")
+ map))
+
+;;; ** code
+;;; *** macros
+(defmacro ampc-with-buffer (type &rest body)
+ (declare (indent 1) (debug t))
+ `(let* ((type- ,type)
+ (b (loop for b in ampc-buffers
+ when (with-current-buffer b
+ (cond ((windowp type-)
+ (eq (window-buffer type-)
+ (current-buffer)))
+ ((symbolp type-)
+ (eq (car ampc-type) type-))
+ (t
+ (equal ampc-type type-))))
+ return b
+ end)))
+ (when b
+ (with-current-buffer b
+ (let ((buffer-read-only))
+ ,@(if (eq (car body) 'no-se)
+ (cdr body)
+ `((save-excursion
+ (goto-char (point-min))
+ ,@body))))))))
+
+(defmacro ampc-fill-skeleton (tag &rest body)
+ (declare (indent 1) (debug t))
+ `(let ((tag- ,tag)
+ (data-buffer (current-buffer)))
+ (ampc-with-buffer tag-
+ no-se
+ (let ((point (point)))
+ (goto-char (point-min))
+ (loop until (eobp)
+ do (put-text-property (point) (1+ (point)) 'updated t)
+ (forward-line))
+ (goto-char (point-min))
+ ,@body
+ (goto-char (point-min))
+ (loop until (eobp)
+ when (get-text-property (point) 'updated)
+ do (delete-region (point) (1+ (line-end-position)))
+ else
+ do (add-text-properties
+ (+ (point) 2)
+ (progn (forward-line nil)
+ (1- (point)))
+ '(mouse-face highlight))
+ end)
+ (goto-char point)
+ (ampc-align-point))
+ (ampc-set-dirty nil)
+ (with-selected-window (if (windowp tag-) tag- (ampc-get-window tag-))
+ (recenter)))))
+
+(defmacro ampc-with-selection (arg &rest body)
+ (declare (indent 1) (debug t))
+ `(let ((arg- ,arg))
+ (if (and (not arg-)
+ (save-excursion
+ (goto-char (point-min))
+ (search-forward-regexp "^* " nil t)))
+ (loop initially (goto-char (point-min))
+ finally (ampc-align-point)
+ while (search-forward-regexp "^* " nil t)
+ for index from 0
+ do (save-excursion
+ ,@body))
+ (loop until (eobp)
+ for index from 0 to (1- (if (numberp arg-)
+ arg-
+ (prefix-numeric-value arg-)))
+ do (save-excursion
+ (goto-char (line-end-position))
+ ,@body)
+ until (ampc-next-line)))))
+
+;;; *** modes
+(define-derived-mode ampc-outputs-mode ampc-item-mode "ampc-o"
+ nil)
+
+(define-derived-mode ampc-tag-song-mode ampc-item-mode "ampc-ts"
+ nil)
+
+(define-derived-mode ampc-current-playlist-mode ampc-playlist-mode "ampc-cpl"
+ nil)
+
+(define-derived-mode ampc-playlist-mode ampc-item-mode "ampc-pl"
+ nil)
+
+(define-derived-mode ampc-playlists-mode ampc-item-mode "ampc-pls"
+ nil)
+
+(define-derived-mode ampc-item-mode ampc-mode ""
+ nil)
+
+(define-derived-mode ampc-mode special-mode "ampc"
+ nil
+ (buffer-disable-undo)
+ (set (make-local-variable 'tool-bar-map) ampc-tool-bar-map)
+ (setf truncate-lines ampc-truncate-lines
+ font-lock-defaults '((("^\\(\\*\\)\\(.*\\)$"
+ (1 'ampc-mark-face)
+ (2 'ampc-marked-face))
+ ("^ .*$" 0 'ampc-face))
+ t)))
+
+(define-minor-mode ampc-highlight-current-song-mode ""
+ nil
+ nil
+ nil
+ (funcall (if ampc-highlight-current-song-mode
+ 'font-lock-add-keywords
+ 'font-lock-remove-keywords)
+ nil
+ '((ampc-find-current-song
+ (1 'ampc-current-song-mark-face)
+ (2 'ampc-current-song-marked-face)))))
+
+;;; *** internal functions
+(defun ampc-change-view (view)
+ (if (equal ampc-outstanding-commands '((idle)))
+ (ampc-configure-frame (cddr view))
+ (message "ampc is busy, cannot change window layout")))
+
+(defun ampc-quote (string)
+ (concat "\"" (replace-regexp-in-string "\"" "\\\"" string) "\""))
+
+(defun ampc-on-p ()
+ (and ampc-connection
+ (member (process-status ampc-connection) '(open run))))
+
+(defun ampc-in-ampc-p ()
+ (when (ampc-on-p)
+ ampc-type))
+
+(defun ampc-add-impl (&optional data)
+ (cond ((null data)
+ (loop for d in (get-text-property (line-end-position) 'data)
+ do (ampc-add-impl d)))
+ ((avl-tree-p data)
+ (avl-tree-mapc (lambda (e) (ampc-add-impl (cdr e))) data))
+ ((stringp data)
+ (if (ampc-playlist)
+ (ampc-send-command 'playlistadd
+ t
+ (ampc-quote (ampc-playlist))
+ data)
+ (ampc-send-command 'add t (ampc-quote data))))
+ (t
+ (loop for d in (reverse data)
+ do (ampc-add-impl (cdr (assoc "file" d)))))))
+
+(defun* ampc-skip (N &aux (song (cdr-safe (assq 'song ampc-status))))
+ (when song
+ (ampc-send-command 'play nil (max 0 (+ (string-to-number song) N)))))
+
+(defun* ampc-find-current-song
+ (limit &aux (point (point)) (song (cdr-safe (assq 'song ampc-status))))
+ (when (and song
+ (<= (1- (line-number-at-pos (point)))
+ (setf song (string-to-number song)))
+ (>= (1- (line-number-at-pos limit)) song))
+ (goto-char (point-min))
+ (forward-line song)
+ (save-restriction
+ (narrow-to-region (max point (point)) (min limit (line-end-position)))
+ (search-forward-regexp "\\(?1:\\(\\`\\*\\)?\\)\\(?2:.*\\)$"))))
+
+(defun ampc-set-volume (arg func)
+ (when (or arg ampc-status)
+ (ampc-send-command
+ 'setvol
+ nil
+ (or (and arg (prefix-numeric-value arg))
+ (max (min (funcall func
+ (string-to-number
+ (cdr (assq 'volume ampc-status)))
+ 5)
+ 100)
+ 0)))))
+
+(defun ampc-set-crossfade (arg func)
+ (when (or arg ampc-status)
+ (ampc-send-command
+ 'crossfade
+ nil
+ (or (and arg (prefix-numeric-value arg))
+ (max (funcall func
+ (string-to-number (cdr (assq 'xfade ampc-status)))
+ 5)
+ 0)))))
+
+(defun* ampc-fix-pos (f &aux buffer-read-only)
+ (save-excursion
+ (move-beginning-of-line nil)
+ (let* ((data (get-text-property (+ 2 (point)) 'data))
+ (pos (assoc "Pos" data)))
+ (setf (cdr pos) (funcall f (cdr pos)))
+ (put-text-property (+ 2 (point))
+ (line-end-position)
+ 'data
+ data))))
+
+(defun* ampc-move-impl (up &aux (line (1- (line-number-at-pos))))
+ (when (or (and up (eq line 0))
+ (and (not up) (eq (1+ line) (line-number-at-pos (1-
(point-max))))))
+ (return-from ampc-move-impl t))
+ (save-excursion
+ (move-beginning-of-line nil)
+ (if (ampc-playlist)
+ (ampc-send-command 'playlistmove
+ nil
+ (ampc-quote (ampc-playlist))
+ line
+ (funcall (if up '1- '1+)
+ line))
+ (ampc-send-command 'move nil line (funcall (if up '1- '1+) line)))
+ (unless up
+ (forward-line))
+ (unless (ampc-playlist)
+ (save-excursion
+ (forward-line -1)
+ (ampc-fix-pos '1+))
+ (ampc-fix-pos '1-))
+ (let ((buffer-read-only))
+ (transpose-lines 1)))
+ (if up
+ (ampc-align-point)
+ (ampc-next-line))
+ nil)
+
+(defun* ampc-move (up N &aux (point (point)))
+ (goto-char (if up (point-min) (point-max)))
+ (if (and (not N)
+ (funcall (if up 'search-forward-regexp 'search-backward-regexp)
+ "^* "
+ nil
+ t))
+ (loop until (ampc-move-impl up)
+ unless up
+ do (search-backward-regexp "^* " nil t)
+ end
+ until (not (funcall (if up
+ 'search-forward-regexp
+ 'search-backward-regexp)
+ "^* "
+ nil
+ t))
+ finally (unless up
+ (forward-char 2)))
+ (goto-char point)
+ (unless (eobp)
+ (unless N
+ (setf N 1))
+ (unless up
+ (unless (eq (1- N) 0)
+ (setf N (- (- (forward-line (1- N)) (1- N))))))
+ (loop repeat N
+ until (ampc-move-impl up)))))
+
+(defun ampc-toggle-state (state arg)
+ (when (or arg ampc-status)
+ (ampc-send-command
+ state
+ nil
+ (cond ((null arg)
+ (if (equal (cdr (assq state ampc-status)) "1")
+ 0
+ 1))
+ ((> (prefix-numeric-value arg) 0) 1)
+ (t 0)))))
+
+(defun ampc-playlist ()
+ (ampc-with-buffer 'playlists
+ (if (search-forward-regexp "^* \\(.*\\)$" nil t)
+ (match-string 1)
+ (unless (eobp)
+ (buffer-substring-no-properties
+ (+ (line-beginning-position) 2)
+ (line-end-position))))))
+
+(defun* ampc-mark-impl (select N &aux result buffer-read-only)
+ (when (eq (car ampc-type) 'playlists)
+ (assert (or (not select) (null N) (eq N 1)))
+ (ampc-with-buffer 'playlists
+ (loop while (search-forward-regexp "^\\* " nil t)
+ do (replace-match " " nil nil))))
+ (loop repeat (or N 1)
+ until (eobp)
+ do (move-beginning-of-line nil)
+ (delete-char 1)
+ (insert (if select "*" " "))
+ (setf result (ampc-next-line nil)))
+ (ampc-post-mark-change-update)
+ result)
+
+(defun ampc-post-mark-change-update ()
+ (ecase (car ampc-type)
+ ((current-playlist playlist outputs))
+ (playlists
+ (ampc-update-playlist))
+ ((song tag)
+ (loop for w in (ampc-windows)
+ with found
+ when found
+ do (with-current-buffer (window-buffer w)
+ (when (member (car ampc-type) '(song tag))
+ (ampc-set-dirty t)))
+ end
+ if (eq w (selected-window))
+ do (setf found t)
+ end)
+ (ampc-fill-tag-song))))
+
+(defun ampc-align-point ()
+ (unless (eobp)
+ (move-beginning-of-line nil)
+ (forward-char 2)))
+
+(defun ampc-pad (alist)
+ (loop for (offset . data) in alist
+ with first = t
+ with current-offset = 0
+ when (<= current-offset offset)
+ when (and (not first) (eq (- offset current-offset) 0))
+ do (incf offset)
+ end
+ and concat (make-string (- offset current-offset) ? )
+ and do (setf current-offset offset)
+ else
+ concat " "
+ and do (incf current-offset)
+ end
+ concat data
+ do (setf current-offset (+ current-offset (length data))
+ first nil)))
+
+(defun ampc-update-header ()
+ (if (eq (car ampc-type) 'status)
+ (setf header-line-format nil)
+ (setf header-line-format
+ (concat
+ (make-string (floor (fringe-columns 'left t)) ? )
+ (ecase (car ampc-type)
+ (tag
+ (concat " " (plist-get (cdr ampc-type) :tag)))
+ (playlists
+ " Playlists")
+ (t
+ (ampc-pad (loop for p in (plist-get (cdr ampc-type) :properties)
+ collect `(,(or (plist-get (cdr p) :offset) 2) .
+ ,(or (plist-get (cdr p) :title)
+ (car p)))))))
+ (when ampc-dirty
+ " [ Updating... ]")))))
+
+(defun ampc-set-dirty (tag-or-dirty &optional dirty)
+ (if (or (null tag-or-dirty) (eq tag-or-dirty t))
+ (progn (setf ampc-dirty tag-or-dirty)
+ (ampc-update-header))
+ (loop for w in (ampc-windows)
+ do (with-current-buffer (window-buffer w)
+ (when (eq (car ampc-type) tag-or-dirty)
+ (ampc-set-dirty dirty))))))
+
+(defun ampc-update ()
+ (if ampc-status
+ (loop for b in ampc-buffers
+ do (with-current-buffer b
+ (when ampc-dirty
+ (ecase (car ampc-type)
+ (outputs
+ (ampc-send-command 'outputs))
+ (playlist
+ (ampc-update-playlist))
+ ((tag song)
+ (if (assoc (ampc-tags) ampc-internal-db)
+ (ampc-fill-tag-song)
+ (push `(,(ampc-tags) . nil) ampc-internal-db)
+ (ampc-send-command 'listallinfo)))
+ (status
+ (ampc-send-command 'status)
+ (ampc-send-command 'currentsong))
+ (playlists
+ (ampc-send-command 'listplaylists))
+ (current-playlist
+ (ampc-send-command 'playlistinfo))))))
+ (ampc-send-command 'status)
+ (ampc-send-command 'currentsong)))
+
+(defun ampc-update-playlist ()
+ (ampc-with-buffer 'playlists
+ (if (search-forward-regexp "^\\* " nil t)
+ (ampc-send-command 'listplaylistinfo
+ nil
+ (get-text-property (point) 'data))
+ (ampc-with-buffer 'playlist
+ (delete-region (point-min) (point-max))
+ (ampc-set-dirty nil)))))
+
+(defun ampc-send-command-impl (command)
+ (when ampc-debug
+ (message (concat "ampc: " command)))
+ (process-send-string ampc-connection (concat command "\n")))
+
+(defun ampc-send-command (command &optional unique &rest args)
+ (if (equal command 'idle)
+ (when ampc-working-timer
+ (cancel-timer ampc-working-timer)
+ (setf ampc-yield nil
+ ampc-working-timer nil)
+ (ampc-fill-status))
+ (unless ampc-working-timer
+ (setf ampc-yield 0
+ ampc-working-timer (run-at-time nil 0.1 'ampc-yield))))
+ (setf command `(,command ,@args))
+ (when (equal (car-safe ampc-outstanding-commands) '(idle))
+ (setf (car ampc-outstanding-commands) '(noidle))
+ (ampc-send-command-impl "noidle"))
+ (setf ampc-outstanding-commands
+ (nconc (if unique
+ ampc-outstanding-commands
+ (remove command ampc-outstanding-commands))
+ `(,command))))
+
+(defun ampc-send-next-command ()
+ (unless ampc-outstanding-commands
+ (ampc-send-command 'idle))
+ (ampc-send-command-impl (concat (symbol-name (caar
ampc-outstanding-commands))
+ (loop for a in
+ (cdar ampc-outstanding-commands)
+ concat " "
+ concat (cond ((integerp a)
+ (number-to-string a))
+ (t a))))))
+
+(defun ampc-tree< (a b)
+ (string< (car a) (car b)))
+
+(defun ampc-create-tree ()
+ (avl-tree-create 'ampc-tree<))
+
+(defun ampc-extract (tag &optional buffer)
+ (with-current-buffer (or buffer (current-buffer))
+ (if (listp tag)
+ (ampc-extract (plist-get tag :tag))
+ (save-excursion
+ (goto-char (point-min))
+ (when (search-forward-regexp
+ (concat "^" (regexp-quote tag) ": \\(.*\\)$")
+ nil
+ t)
+ (let ((result (match-string 1)))
+ (when (equal tag "Time")
+ (setf result (ampc-transform-time result)))
+ result))))))
+
+(defun ampc-insert (element data &optional cmp)
+ (save-excursion
+ (goto-char (point-min))
+ (ecase
+ (loop until (eobp)
+ for tp = (get-text-property (+ (point) 2) 'data)
+ finally return 'insert
+ thereis
+ (cond ((eq cmp t)
+ (let ((s (buffer-substring-no-properties
+ (+ (point) 2)
+ (line-end-position))))
+ (cond ((equal s element)
+ (unless (member data tp)
+ (put-text-property (+ (point) 2)
+ (1+ (line-end-position))
+ 'data
+ `(,data . ,tp)))
+ 'update)
+ ((string< element s)
+ 'insert))))
+ (cmp
+ (let ((r (funcall cmp data tp)))
+ (if (memq r '(update insert))
+ r
+ (forward-line (1- r))
+ nil)))
+ ((equal tp data)
+ 'update)
+ (t
+ (let ((s (buffer-substring-no-properties
+ (+ (point) 2)
+ (line-end-position))))
+ (unless (string< s element)
+ 'insert))))
+ do (forward-line))
+ (insert
+ (insert " ")
+ (let ((start (point)))
+ (insert element "\n")
+ (put-text-property start (point) 'data (if (eq cmp t)
+ `(,data)
+ data))))
+ (update
+ (remove-text-properties (point) (1+ (point)) '(updated))
+ (equal (buffer-substring (point) (1+ (point))) "*")))))
+
+(defun ampc-fill-tag (trees)
+ (put-text-property (point-min) (point-max) 'data nil)
+ (loop with new-trees
+ finally return new-trees
+ for tree in trees
+ when tree
+ do (avl-tree-mapc (lambda (e)
+ (when (ampc-insert (car e) (cdr e) t)
+ (push (cdr e) new-trees)))
+ tree)
+ end))
+
+(defun ampc-fill-song (trees)
+ (loop
+ for songs in trees
+ do (loop for song in songs
+ do (ampc-insert
+ (ampc-pad
+ (loop for (p . v) in (plist-get (cdr ampc-type) :properties)
+ collect `(,(- (or (plist-get v :offset) 2) 2)
+ . ,(or (cdr-safe (assoc p song)) ""))))
+ `((,song))))))
+
+(defun* ampc-narrow-entry (&optional (delimiter "file"))
+ (narrow-to-region (move-beginning-of-line nil)
+ (or (progn (goto-char (line-end-position))
+ (when (search-forward-regexp
+ (concat "^" (regexp-quote delimiter) ":
")
+ nil
+ t)
+ (move-beginning-of-line nil)
+ (1- (point))))
+ (point-max))))
+
+(defun ampc-get-window (type)
+ (loop for w in (ampc-windows)
+ thereis (with-current-buffer (window-buffer w)
+ (when (eq (car ampc-type) type)
+ w))))
+
+(defun* ampc-fill-playlist (&aux properties)
+ (ampc-fill-skeleton 'playlist
+ (setf properties (plist-get (cdr ampc-type) :properties))
+ (with-current-buffer data-buffer
+ (loop
+ for i from 0
+ while (search-forward-regexp "^file: " nil t)
+ do (save-restriction
+ (ampc-narrow-entry)
+ (let ((file (ampc-extract "file"))
+ (text
+ (ampc-pad
+ (loop for (tag . tag-properties) in properties
+ collect `(,(- (or (plist-get tag-properties
+ :offset)
+ 2)
+ 2)
+ . ,(or (ampc-extract tag)
+ "[Not Specified]"))))))
+ (ampc-with-buffer 'playlist
+ (ampc-insert text
+ `(("file" . ,file)
+ (index . ,i))
+ (lambda (a b)
+ (let ((p1 (cdr (assoc 'index a)))
+ (p2 (cdr (assoc 'index b))))
+ (cond ((< p1 p2) 'update)
+ ((eq p1 p2)
+ (if (equal (cdr (assoc "file" a))
+ (cdr (assoc "file" b)))
+ 'update
+ 'insert))
+ (t (- p1 p2)))))))))))))
+
+(defun* ampc-fill-outputs (&aux properties)
+ (ampc-fill-skeleton 'outputs
+ (setf properties (plist-get (cdr ampc-type) :properties))
+ (with-current-buffer data-buffer
+ (loop
+ while (search-forward-regexp "^outputid: " nil t)
+ do (save-restriction
+ (ampc-narrow-entry "outputid")
+ (let ((outputid (ampc-extract "outputid"))
+ (outputenabled (ampc-extract "outputenabled"))
+ (text
+ (ampc-pad
+ (loop for (tag . tag-properties) in properties
+ collect `(,(- (or (plist-get tag-properties :offset)
+ 2)
+ 2)
+ . ,(ampc-extract tag))))))
+ (ampc-with-buffer 'outputs
+ (ampc-insert text `(("outputid" . ,outputid)
+ ("outputenabled" . ,outputenabled))))))))))
+
+(defun* ampc-fill-current-playlist (&aux properties)
+ (ampc-fill-skeleton 'current-playlist
+ (setf properties (plist-get (cdr ampc-type) :properties))
+ (with-current-buffer data-buffer
+ (loop
+ while (search-forward-regexp "^file: " nil t)
+ do (save-restriction
+ (ampc-narrow-entry)
+ (let ((file (ampc-extract "file"))
+ (pos (ampc-extract "Pos"))
+ (text
+ (ampc-pad
+ (loop for (tag . tag-properties) in properties
+ collect `(,(- (or (plist-get tag-properties :offset)
+ 2)
+ 2)
+ . ,(or (ampc-extract tag)
+ "[Not Specified]"))))))
+ (ampc-with-buffer 'current-playlist
+ (ampc-insert text
+ `(("file" . ,file)
+ ("Pos" . ,(string-to-number pos)))
+ (lambda (a b)
+ (let ((p1 (cdr (assoc "Pos" a)))
+ (p2 (cdr (assoc "Pos" b))))
+ (cond ((< p1 p2) 'insert)
+ ((eq p1 p2)
+ (if (equal (cdr (assoc "file" a))
+ (cdr (assoc "file" b)))
+ 'update
+ 'insert))
+ (t (- p1 p2)))))))))))))
+
+(defun ampc-fill-playlists ()
+ (ampc-fill-skeleton 'playlists
+ (with-current-buffer data-buffer
+ (loop while (search-forward-regexp "^playlist: \\(.*\\)$" nil t)
+ for playlist = (match-string 1)
+ do (ampc-with-buffer 'playlists
+ (ampc-insert playlist playlist))))))
+
+(defun ampc-yield ()
+ (setf ampc-yield (1+ ampc-yield))
+ (ampc-fill-status))
+
+(defun ampc-fill-status ()
+ (ampc-with-buffer 'status
+ (delete-region (point-min) (point-max))
+ (funcall (or (plist-get (cadr ampc-type) :filler)
+ (lambda (_)
+ (insert (ampc-status) "\n")))
+ ampc-status)
+ (ampc-set-dirty nil)))
+
+(defun ampc-fill-tag-song ()
+ (loop
+ with trees = `(,(cdr (assoc (ampc-tags) ampc-internal-db)))
+ for w in (ampc-windows)
+ do
+ (ampc-with-buffer w
+ (when (member (car ampc-type) '(tag song))
+ (if ampc-dirty
+ (ampc-fill-skeleton w
+ (ecase (car ampc-type)
+ (tag (setf trees (ampc-fill-tag trees)))
+ (song (ampc-fill-song trees))))
+ (setf trees nil)
+ (loop while (search-forward-regexp "^* " nil t)
+ do (setf trees (append (get-text-property (point) 'data)
+ trees))))))))
+
+(defun* ampc-transform-time (data &aux (time (string-to-number data)))
+ (concat (number-to-string (/ time 60))
+ ":"
+ (when (< (% time 60) 10)
+ "0")
+ (number-to-string (% time 60))))
+
+(defun ampc-handle-idle ()
+ (loop until (eobp)
+ for subsystem = (buffer-substring (point) (line-end-position))
+ when (string-match "^changed: \\(.*\\)$" subsystem)
+ do (case (intern (match-string 1 subsystem))
+ (database
+ (setf ampc-internal-db nil)
+ (ampc-set-dirty 'tag t)
+ (ampc-set-dirty 'song t))
+ (output
+ (ampc-set-dirty 'outputs t))
+ ((player options mixer)
+ (setf ampc-status nil)
+ (ampc-set-dirty 'status t))
+ (stored_playlist
+ (ampc-set-dirty 'playlists t)
+ (ampc-set-dirty 'playlist t))
+ (playlist
+ (ampc-set-dirty 'current-playlist t)
+ (ampc-set-dirty 'status t)))
+ end
+ do (forward-line))
+ (ampc-update))
+
+(defun ampc-handle-setup (status)
+ (unless (and (string-match "^ MPD \\(.+\\)\\.\\(.+\\)\\.\\(.+\\)$"
+ status)
+ (let ((version-a (string-to-number (match-string 1 status)))
+ (version-b (string-to-number (match-string 2 status)))
+ ;; (version-c (string-to-number (match-string 2 status)))
+ )
+ (or (> version-a 0)
+ (>= version-b 15))))
+ (error (concat "Your version of MPD is not supported. "
+ "ampc supports MPD (protocol version) 0.15.0 "
+ "and later"))))
+
+(defun ampc-fill-internal-db (running)
+ (loop for origin = (and (search-forward-regexp "^file: " nil t)
+ (line-beginning-position))
+ then next
+ while origin
+ do (goto-char (1+ origin))
+ for next = (and (search-forward-regexp "^file: " nil t)
+ (line-beginning-position))
+ while (or (not running) next)
+ do (save-restriction
+ (narrow-to-region origin (or next (point-max)))
+ (ampc-fill-internal-db-entry))
+ do (when running
+ (delete-region origin next)
+ (setf next origin))))
+
+(defun ampc-tags ()
+ (loop for w in (ampc-windows)
+ for tag = (with-current-buffer (window-buffer w)
+ (when (eq (car ampc-type) 'tag)
+ (plist-get (cdr ampc-type) :tag)))
+ when tag
+ collect tag
+ end))
+
+(defun ampc-fill-internal-db-entry ()
+ (loop
+ with data-buffer = (current-buffer)
+ with tree = (assoc (ampc-tags) ampc-internal-db)
+ for w in (ampc-windows)
+ do
+ (with-current-buffer (window-buffer w)
+ (ampc-set-dirty t)
+ (ecase (car ampc-type)
+ (tag
+ (let ((data (or (ampc-extract (cdr ampc-type) data-buffer)
+ "[Not Specified]")))
+ (unless (cdr tree)
+ (setf (cdr tree) (ampc-create-tree)))
+ (setf tree (avl-tree-enter (cdr tree)
+ `(,data . nil)
+ (lambda (data match)
+ match)))))
+ (song
+ (push (loop for p in `(("file")
+ ,@(plist-get (cdr ampc-type) :properties))
+ for data = (ampc-extract (car p) data-buffer)
+ when data
+ collect `(,(car p) . ,data)
+ end)
+ (cdr tree))
+ (return))))))
+
+(defun ampc-handle-current-song ()
+ (loop for k in (append ampc-status-tags '("Artist" "Title"))
+ for s = (ampc-extract k)
+ when s
+ do (push `(,(intern k) . ,s) ampc-status)
+ end)
+ (ampc-fill-status)
+ (run-hook-with-args ampc-status-changed-hook ampc-status))
+
+(defun ampc-handle-status ()
+ (loop for k in '("volume" "repeat" "random" "consume" "xfade" "state" "song")
+ for v = (ampc-extract k)
+ when v
+ do (push `(,(intern k) . ,v) ampc-status)
+ end)
+ (ampc-with-buffer 'current-playlist
+ (when ampc-highlight-current-song-mode
+ (font-lock-fontify-region (point-min) (point-max)))))
+
+(defun ampc-handle-update ()
+ (message "Database update started"))
+
+(defun ampc-handle-command (status)
+ (cond
+ ((eq status 'error)
+ (pop ampc-outstanding-commands))
+ ((eq status 'running)
+ (case (caar ampc-outstanding-commands)
+ (listallinfo (ampc-fill-internal-db t))))
+ (t
+ (case (car (pop ampc-outstanding-commands))
+ (idle
+ (ampc-handle-idle))
+ (setup
+ (ampc-handle-setup status))
+ (currentsong
+ (ampc-handle-current-song))
+ (status
+ (ampc-handle-status))
+ (update
+ (ampc-handle-update))
+ (listplaylistinfo
+ (ampc-fill-playlist))
+ (listplaylists
+ (ampc-fill-playlists))
+ (playlistinfo
+ (ampc-fill-current-playlist))
+ (listallinfo
+ (ampc-fill-internal-db nil))
+ (outputs
+ (ampc-fill-outputs)))
+ (unless ampc-outstanding-commands
+ (ampc-update)))))
+
+(defun ampc-filter (_process string)
+ (assert (buffer-live-p (process-buffer ampc-connection)))
+ (with-current-buffer (process-buffer ampc-connection)
+ (when string
+ (when ampc-debug
+ (message "ampc: -> %s" string))
+ (goto-char (process-mark ampc-connection))
+ (insert string)
+ (set-marker (process-mark ampc-connection) (point)))
+ (save-excursion
+ (goto-char (point-min))
+ (let ((success))
+ (if (or (and (search-forward-regexp
+ "^ACK \\[\\(.*\\)\\] {.*} \\(.*\\)\n\\'"
+ nil
+ t)
+ (message "ampc command error: %s (%s)"
+ (match-string 2)
+ (match-string 1))
+ t)
+ (and (search-forward-regexp "^OK\\(.*\\)\n\\'" nil t)
+ (setf success t)))
+ (progn
+ (let ((match-end (match-end 0)))
+ (save-restriction
+ (narrow-to-region (point-min) match-end)
+ (goto-char (point-min))
+ (ampc-handle-command (if success (match-string 1) 'error)))
+ (delete-region (point-min) match-end))
+ (ampc-send-next-command))
+ (ampc-handle-command 'running))))))
+
+;;; **** window management
+(defun ampc-windows (&optional unordered)
+ (loop for f being the frame
+ thereis (loop for w being the windows of f
+ when (eq (window-buffer w) (car-safe ampc-buffers))
+ return (loop for b in (if unordered
+ ampc-buffers-unordered
+ ampc-buffers)
+ collect
+ (loop for w being the windows of f
+ thereis (and (eq (window-buffer w)
+ b)
+ w))))))
+
+(defun* ampc-configure-frame-1 (split &aux (split-type (car split)))
+ (if (member split-type '(vertical horizontal))
+ (let* ((sizes))
+ (loop with length = (if (eq split-type 'horizontal)
+ (window-width)
+ (window-height))
+ with rest = length
+ with rest-car
+ for subsplit in (cdr split)
+ for s = (car subsplit)
+ if (equal s 1.0)
+ do (push t sizes)
+ and do (setf rest-car sizes)
+ else
+ do (let ((l (if (integerp s) s (floor (* s length)))))
+ (setf rest (- rest l))
+ (push l sizes))
+ finally do (setf (car rest-car) rest))
+ (let ((first-window (selected-window)))
+ (setf sizes (nreverse sizes))
+ (loop for size in (loop for s in sizes
+ collect s)
+ for window on (cdr sizes)
+ do (select-window
+ (setf (car window)
+ (split-window nil
+ size
+ (eq split-type 'horizontal)))))
+ (setf (car sizes) first-window))
+ (loop for subsplit in (cdr split)
+ for window in sizes
+ do (with-selected-window window
+ (ampc-configure-frame-1 (cdr subsplit)))
+ if (plist-get (cddr subsplit) :point)
+ do (select-window window)
+ end))
+ (setf (window-dedicated-p (selected-window)) nil)
+ (ecase split-type
+ ((tag song)
+ (pop-to-buffer-same-window
+ (get-buffer-create (concat "*ampc "
+ (or (plist-get (cdr split) :tag) "Song")
+ "*")))
+ (ampc-tag-song-mode))
+ (outputs
+ (pop-to-buffer-same-window (get-buffer-create "*ampc Outputs*"))
+ (ampc-outputs-mode))
+ (current-playlist
+ (pop-to-buffer-same-window (get-buffer-create "*ampc Current
Playlist*"))
+ (ampc-current-playlist-mode)
+ (ampc-highlight-current-song-mode 1))
+ (playlist
+ (pop-to-buffer-same-window (get-buffer-create "*ampc Playlist*"))
+ (ampc-playlist-mode))
+ (playlists
+ (pop-to-buffer-same-window (get-buffer-create "*ampc Playlists*"))
+ (ampc-playlists-mode))
+ (status
+ (pop-to-buffer-same-window (get-buffer-create "*ampc Status*"))
+ (ampc-mode)))
+ (destructuring-bind (&key (dedicated t) (mode-line t) &allow-other-keys)
+ (cdr split)
+ (setf (window-dedicated-p (selected-window)) dedicated)
+ (unless mode-line
+ (setf mode-line-format nil)))
+ (setf ampc-type split)
+ (add-to-list 'ampc-all-buffers (current-buffer))
+ (push `(,(or (plist-get (cdr split) :id)
+ (if (eq (car ampc-type) 'song) 9998 9999))
+ . ,(current-buffer))
+ ampc-buffers)
+ (ampc-set-dirty t)))
+
+(defun ampc-configure-frame (split)
+ (if ampc-use-full-frame
+ (progn (setf (window-dedicated-p (selected-window)) nil)
+ (delete-other-windows))
+ (loop with live-window = nil
+ for w in (nreverse (ampc-windows t))
+ if (window-live-p w)
+ if (not live-window)
+ do (setf live-window w)
+ else
+ do (delete-window w)
+ end
+ end
+ finally do (if live-window (select-window live-window))))
+ (setf ampc-buffers nil)
+ (ampc-configure-frame-1 split)
+ (setf ampc-buffers-unordered (mapcar 'cdr ampc-buffers)
+ ampc-buffers (mapcar 'cdr (sort ampc-buffers
+ (lambda (a b) (< (car a) (car b))))))
+ (ampc-update))
+
+(defun ampc-mouse-play-this (event)
+ (interactive "e")
+ (select-window (posn-window (event-end event)))
+ (goto-char (posn-point (event-end event)))
+ (ampc-play-this))
+
+(defun ampc-mouse-delete (event)
+ (interactive "e")
+ (select-window (posn-window (event-end event)))
+ (goto-char (posn-point (event-end event)))
+ (ampc-delete 1))
+
+(defun ampc-mouse-add (event)
+ (interactive "e")
+ (select-window (posn-window (event-end event)))
+ (goto-char (posn-point (event-end event)))
+ (ampc-add-impl))
+
+(defun ampc-mouse-toggle-output-enabled (event)
+ (interactive "e")
+ (select-window (posn-window (event-end event)))
+ (goto-char (posn-point (event-end event)))
+ (ampc-toggle-output-enabled 1))
+
+(defun* ampc-mouse-toggle-mark (event &aux buffer-read-only)
+ (interactive "e")
+ (let ((window (posn-window (event-end event))))
+ (when (with-selected-window window
+ (goto-char (posn-point (event-end event)))
+ (unless (eobp)
+ (move-beginning-of-line nil)
+ (ampc-mark-impl (not (eq (char-after) ?*)) 1)
+ t))
+ (select-window window))))
+
+(defun ampc-mouse-align-point (event)
+ (interactive "e")
+ (select-window (posn-window (event-end event)))
+ (goto-char (posn-point (event-end event)))
+ (ampc-align-point))
+
+;;; *** interactives
+(defun* ampc-unmark-all (&aux buffer-read-only)
+ "Remove all marks."
+ (interactive)
+ (assert (ampc-in-ampc-p))
+ (save-excursion
+ (goto-char (point-min))
+ (loop while (search-forward-regexp "^\\* " nil t)
+ do (replace-match " " nil nil)))
+ (ampc-post-mark-change-update))
+
+(defun ampc-trigger-update ()
+ "Trigger a database update."
+ (interactive)
+ (assert (ampc-on-p))
+ (ampc-send-command 'update))
+
+(defun* ampc-toggle-marks (&aux buffer-read-only)
+ "Toggle marks. Marked entries become unmarked, and vice versa."
+ (interactive)
+ (assert (ampc-in-ampc-p))
+ (save-excursion
+ (loop for (a . b) in '(("* " . "T ")
+ (" " . "* ")
+ ("T " . " "))
+ do (goto-char (point-min))
+ (loop while (search-forward-regexp (concat "^" (regexp-quote a))
+ nil
+ t)
+ do (replace-match b nil nil))))
+ (ampc-post-mark-change-update))
+
+(defun ampc-up (&optional arg)
+ "Go to the previous ARG'th entry.
+With optional prefix ARG, move the next ARG entries after point
+rather than the selection."
+ (interactive "P")
+ (assert (ampc-in-ampc-p))
+ (ampc-move t arg))
+
+(defun ampc-down (&optional arg)
+ "Go to the next ARG'th entry.
+With optional prefix ARG, move the next ARG entries after point
+rather than the selection."
+ (interactive "P")
+ (assert (ampc-in-ampc-p))
+ (ampc-move nil arg))
+
+(defun ampc-mark (&optional arg)
+ "Mark the next ARG'th entries.
+ARG defaults to 1."
+ (interactive "p")
+ (assert (ampc-in-ampc-p))
+ (ampc-mark-impl t arg))
+
+(defun ampc-unmark (&optional arg)
+ "Unmark the next ARG'th entries.
+ARG defaults to 1."
+ (interactive "p")
+ (assert (ampc-in-ampc-p))
+ (ampc-mark-impl nil arg))
+
+(defun ampc-increase-volume (&optional arg)
+ "Decrease volume.
+With prefix argument ARG, set volume to ARG percent."
+ (interactive "P")
+ (assert (ampc-on-p))
+ (ampc-set-volume arg '+))
+
+(defun ampc-decrease-volume (&optional arg)
+ "Decrease volume.
+With prefix argument ARG, set volume to ARG percent."
+ (interactive "P")
+ (assert (ampc-on-p))
+ (ampc-set-volume arg '-))
+
+(defun ampc-increase-crossfade (&optional arg)
+ "Increase crossfade.
+With prefix argument ARG, set crossfading to ARG seconds."
+ (interactive "P")
+ (assert (ampc-on-p))
+ (ampc-set-crossfade arg '+))
+
+(defun ampc-decrease-crossfade (&optional arg)
+ "Decrease crossfade.
+With prefix argument ARG, set crossfading to ARG seconds."
+ (interactive "P")
+ (assert (ampc-on-p))
+ (ampc-set-crossfade arg '-))
+
+(defun ampc-toggle-repeat (&optional arg)
+ "Toggle MPD's repeat state.
+With prefix argument ARG, enable repeating if ARG is positive,
+otherwise disable it."
+ (interactive "P")
+ (assert (ampc-on-p))
+ (ampc-toggle-state 'repeat arg))
+
+(defun ampc-toggle-consume (&optional arg)
+ "Toggle MPD's consume state.
+With prefix argument ARG, enable consuming if ARG is positive,
+otherwise disable it.
+
+When consume is activated, each song played is removed from the playlist."
+ (interactive "P")
+ (assert (ampc-on-p))
+ (ampc-toggle-state 'consume arg))
+
+(defun ampc-toggle-random (&optional arg)
+ "Toggle MPD's random state.
+With prefix argument ARG, enable random playing if ARG is positive,
+otherwise disable it."
+ (interactive "P")
+ (ampc-toggle-state 'random arg))
+
+(defun ampc-play-this ()
+ "Play selected song."
+ (interactive)
+ (assert (ampc-in-ampc-p))
+ (unless (eobp)
+ (ampc-send-command 'play nil (1- (line-number-at-pos)))
+ (ampc-send-command 'pause nil 0)))
+
+(defun* ampc-toggle-play
+ (&optional arg &aux (state (cdr-safe (assq 'state ampc-status))))
+ "Toggle play state.
+If mpd does not play a song already, start playing the song at
+point if the current buffer is the playlist buffer, otherwise
+start at the beginning of the playlist.
+
+If ARG is 4, stop player rather than pause if applicable."
+ (interactive "P")
+ (assert (ampc-on-p))
+ (when state
+ (when arg
+ (setf arg (prefix-numeric-value arg)))
+ (ecase (intern state)
+ (stop
+ (when (or (null arg) (> arg 0))
+ (ampc-send-command
+ 'play
+ nil
+ (if (and (eq (car ampc-type) 'current-playlist) (not (eobp)))
+ (1- (line-number-at-pos))
+ 0))))
+ (pause
+ (when (or (null arg) (> arg 0))
+ (ampc-send-command 'pause nil 0)))
+ (play
+ (cond ((or (null arg) (< arg 0))
+ (ampc-send-command 'pause nil 1))
+ ((eq arg 4)
+ (ampc-send-command 'stop)))))))
+
+(defun ampc-next (&optional arg)
+ "Play next song.
+With prefix argument ARG, skip ARG songs."
+ (interactive "p")
+ (assert (ampc-on-p))
+ (ampc-skip (or arg 1)))
+
+(defun ampc-previous (&optional arg)
+ "Play previous song.
+With prefix argument ARG, skip ARG songs."
+ (interactive "p")
+ (assert (ampc-on-p))
+ (ampc-skip (- (or arg 1))))
+
+(defun ampc-rename-playlist (new-name)
+ "Rename selected playlist to NEW-NAME.
+Interactively, read NEW-NAME from the minibuffer."
+ (interactive "MNew name: ")
+ (assert (ampc-in-ampc-p))
+ (if (ampc-playlist)
+ (ampc-send-command 'rename nil (ampc-playlist) new-name)
+ (error "No playlist selected")))
+
+(defun ampc-load ()
+ "Load selected playlist in the current playlist."
+ (interactive)
+ (assert (ampc-in-ampc-p))
+ (if (ampc-playlist)
+ (ampc-send-command 'load nil (ampc-quote (ampc-playlist)))
+ (error "No playlist selected")))
+
+(defun ampc-toggle-output-enabled (&optional arg)
+ "Toggle the next ARG outputs.
+If ARG is omitted, use the selected entries."
+ (interactive "P")
+ (assert (ampc-in-ampc-p))
+ (ampc-with-selection arg
+ (let ((data (get-text-property (point) 'data)))
+ (ampc-send-command (if (equal (cdr (assoc "outputenabled" data)) "1")
+ 'disableoutput
+ 'enableoutput)
+ nil
+ (cdr (assoc "outputid" data))))))
+
+(defun ampc-delete (&optional arg)
+ "Delete the next ARG songs from the playlist.
+If ARG is omitted, use the selected entries. If ARG is non-nil,
+all marks after point are removed nontheless."
+ (interactive "P")
+ (assert (ampc-in-ampc-p))
+ (let ((point (point)))
+ (ampc-with-selection arg
+ (let ((val (1- (- (line-number-at-pos) index))))
+ (if (ampc-playlist)
+ (ampc-send-command 'playlistdelete
+ t
+ (ampc-quote (ampc-playlist))
+ val)
+ (ampc-send-command 'delete t val))))
+ (goto-char point)
+ (ampc-align-point)))
+
+(defun ampc-shuffle ()
+ "Shuffle playlist."
+ (interactive)
+ (assert (ampc-on-p))
+ (if (not (ampc-playlist))
+ (ampc-send-command 'shuffle)
+ (ampc-with-buffer 'playlist
+ (let ((shuffled
+ (mapcar
+ 'car
+ (sort (loop until (eobp)
+ collect `(,(cdr (assoc "file" (get-text-property
+ (+ 2 (point))
+ 'data)))
+ . ,(random))
+ do (forward-line))
+ (lambda (a b)
+ (< (cdr a) (cdr b)))))))
+ (ampc-clear)
+ (loop for s in shuffled
+ do (ampc-add-impl s))))))
+
+(defun ampc-clear ()
+ "Clear playlist."
+ (interactive)
+ (assert (ampc-on-p))
+ (if (ampc-playlist)
+ (ampc-send-command 'playlistclear nil (ampc-quote (ampc-playlist)))
+ (ampc-send-command 'clear)))
+
+(defun ampc-add (&optional arg)
+ "Add the songs associated with the next ARG entries after point
+to the playlist.
+If ARG is omitted, use the selected entries in the current buffer."
+ (interactive "P")
+ (assert (ampc-in-ampc-p))
+ (ampc-with-selection arg
+ (ampc-add-impl)))
+
+(defun ampc-status ()
+ "Display the information that is displayed in the status window."
+ (interactive)
+ (assert (ampc-on-p))
+ (let* ((flags (mapconcat
+ 'identity
+ (loop for (f . n) in '((repeat . "Repeat")
+ (random . "Random")
+ (consume . "Consume"))
+ when (equal (cdr (assq f ampc-status)) "1")
+ collect n
+ end)
+ "|"))
+ (state (cdr (assq 'state ampc-status)))
+ (status (concat "State: " state
+ (when ampc-yield
+ (concat (make-string (- 10 (length state)) ? )
+ (nth (% ampc-yield 4) '("|" "/" "-" "\\"))))
+ "\n"
+ (when (equal state "play")
+ (concat "Playing: "
+ (or (cdr-safe (assq 'Artist ampc-status))
+ "[Not Specified]")
+ " - "
+ (or (cdr-safe (assq 'Title ampc-status))
+ "[Not Specified]")
+ "\n"))
+ "Volume: " (cdr (assq 'volume ampc-status)) "\n"
+ "Crossfade: " (cdr (assq 'xfade ampc-status))
+ (unless (equal flags "")
+ (concat "\n" flags)))))
+ (when (called-interactively-p 'interactive)
+ (message "%s" status))
+ status))
+
+(defun ampc-delete-playlist ()
+ "Delete selected playlist."
+ (interactive)
+ (assert (ampc-in-ampc-p))
+ (ampc-with-selection nil
+ (let ((name (get-text-property (point) 'data)))
+ (when (y-or-n-p (concat "Delete playlist " name "?"))
+ (ampc-send-command 'rm nil (ampc-quote name))))))
+
+(defun ampc-store (name)
+ "Store current playlist as NAME.
+Interactively, read NAME from the minibuffer."
+ (interactive "MSave playlist as: ")
+ (assert (ampc-in-ampc-p))
+ (ampc-send-command 'save nil (ampc-quote name)))
+
+(defun* ampc-goto-current-song
+ (&aux (song (cdr-safe (assq 'song ampc-status))))
+ "Select the current playlist window and move point to the current song."
+ (interactive)
+ (assert (ampc-in-ampc-p))
+ (when song
+ (ampc-with-buffer 'current-playlist
+ no-se
+ (select-window (ampc-get-window 'current-playlist))
+ (goto-char (point-min))
+ (forward-line (string-to-number song))
+ (ampc-align-point))))
+
+(defun ampc-previous-line (&optional arg)
+ "Go to previous ARG'th entry in the current buffer.
+ARG defaults to 1."
+ (interactive "p")
+ (assert (ampc-in-ampc-p))
+ (ampc-next-line (* (or arg 1) -1)))
+
+(defun ampc-next-line (&optional arg)
+ "Go to next ARG'th entry in the current buffer.
+ARG defaults to 1."
+ (interactive "p")
+ (assert (ampc-in-ampc-p))
+ (forward-line arg)
+ (if (eobp)
+ (progn (forward-line -1)
+ (forward-char 2)
+ t)
+ (ampc-align-point)
+ nil))
+
+(defun* ampc-suspend (&optional (run-hook t))
+ "Suspend ampc.
+This function resets the window configuration, but does not close
+the connection to mpd or destroy the internal cache of ampc.
+This means subsequent startups of ampc will be faster."
+ (interactive)
+ (when ampc-working-timer
+ (cancel-timer ampc-working-timer))
+ (loop with found-window
+ for w in (nreverse (ampc-windows t))
+ when (window-live-p w)
+ when found-window
+ do (delete-window w)
+ else
+ do (setf found-window t
+ (window-dedicated-p w) nil)
+ end
+ end)
+ (loop for b in ampc-all-buffers
+ when (buffer-live-p b)
+ do (kill-buffer b)
+ end)
+ (setf ampc-buffers nil
+ ampc-all-buffers nil
+ ampc-working-timer nil)
+ (when run-hook
+ (run-hooks 'ampc-suspend-hook)))
+
+(defun ampc-quit (&optional arg)
+ "Quit ampc.
+If called with a prefix argument ARG, kill the mpd instance that
+ampc is connected to."
+ (interactive "P")
+ (when (ampc-on-p)
+ (set-process-filter ampc-connection nil)
+ (when (equal (car-safe ampc-outstanding-commands) '(idle))
+ (ampc-send-command-impl "noidle")
+ (with-current-buffer (process-buffer ampc-connection)
+ (loop do (goto-char (point-min))
+ until (search-forward-regexp "^\\(ACK\\)\\|\\(OK\\).*\n\\'" nil
t)
+ do (accept-process-output ampc-connection nil 50))))
+ (ampc-send-command-impl (if arg "kill" "close")))
+ (when ampc-working-timer
+ (cancel-timer ampc-working-timer))
+ (ampc-suspend nil)
+ (setf ampc-connection nil
+ ampc-internal-db nil
+ ampc-outstanding-commands nil
+ ampc-status nil)
+ (run-hooks 'ampc-quit-hook))
+
+;;;###autoload
+(defun ampc (&optional host port)
+ "ampc is an asynchronous client for the MPD media player.
+This function is the main entry point for ampc.
+
+Non-interactively, HOST and PORT specify the MPD instance to
+connect to. The values default to localhost:6600."
+ (interactive "MHost (localhost): \nMPort (6600): ")
+ (run-hooks 'ampc-before-startup-hook)
+ (when (or (not host) (equal host ""))
+ (setf host "localhost"))
+ (when (or (not port) (equal port ""))
+ (setf port 6600))
+ (when (and ampc-connection
+ (or (not (equal host ampc-host))
+ (not (equal port ampc-port))
+ (not (ampc-on-p))))
+ (ampc-quit))
+ (unless ampc-connection
+ (let ((connection (open-network-stream "ampc"
+ (with-current-buffer
+ (get-buffer-create " *ampc*")
+ (delete-region (point-min)
+ (point-max))
+ (current-buffer))
+ host
+ port
+ :type 'plain :return-list t)))
+ (unless (car connection)
+ (error "Failed connecting to server: %s"
+ (plist-get ampc-connection :error)))
+ (setf ampc-connection (car connection)
+ ampc-host host
+ ampc-port port))
+ (set-process-coding-system ampc-connection 'utf-8-unix 'utf-8-unix)
+ (set-process-filter ampc-connection 'ampc-filter)
+ (set-process-query-on-exit-flag ampc-connection nil)
+ (setf ampc-outstanding-commands '((setup))))
+ (ampc-configure-frame (cddar ampc-views))
+ (run-hooks 'ampc-connected-hook)
+ (ampc-filter (process-buffer ampc-connection) nil))
+
+(provide 'ampc)
+
+;; Local Variables:
+;; eval: (outline-minor-mode 1)
+;; outline-regexp: ";;; \\*+"
+;; lexical-binding: t
+;; fill-column: 80
+;; indent-tabs-mode: nil
+;; End:
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [ELPA-diffs] /srv/bzr/emacs/elpa r231: * ampc.el: Sync to version 0.1.3.,
Stefan Monnier <=