123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631 |
- (eval-when-compile (require 'cl))
- (defgroup mpc ()
- "A Client for the Music Player Daemon."
- :prefix "mpc-"
- :group 'multimedia
- :group 'applications)
- (defcustom mpc-browser-tags '(Genre Artist|Composer|Performer
- Album|Playlist)
- "Tags for which a browser buffer should be created by default."
-
- :type '(repeat symbol))
- (defun mpc-assq-all (key alist)
- (let ((res ()) val)
- (dolist (elem alist)
- (if (and (eq (car elem) key)
- (not (member (setq val (cdr elem)) res)))
- (push val res)))
- (nreverse res)))
- (defun mpc-union (&rest lists)
- (let ((res (nreverse (pop lists))))
- (dolist (list lists)
- (let ((seen res))
- (dolist (elem list)
- (unless (member elem seen) (push elem res)))))
- (nreverse res)))
- (defun mpc-intersection (l1 l2 &optional selectfun)
- "Return L1 after removing all elements not found in L2.
- If SELECTFUN is non-nil, elements aren't compared directly, but instead
- they are passed through SELECTFUN before comparison."
- (let ((res ()))
- (if selectfun (setq l2 (mapcar selectfun l2)))
- (dolist (elem l1)
- (when (member (if selectfun (funcall selectfun elem) elem) l2)
- (push elem res)))
- (nreverse res)))
- (defun mpc-event-set-point (event)
- (condition-case nil (posn-set-point (event-end event))
- (error (condition-case nil (mouse-set-point event)
- (error nil)))))
- (defun mpc-compare-strings (str1 str2 &optional ignore-case)
- "Compare strings STR1 and STR2.
- Contrary to `compare-strings', this tries to get numbers sorted
- numerically rather than lexicographically."
- (let ((res (compare-strings str1 nil nil str2 nil nil ignore-case)))
- (if (not (integerp res)) res
- (let ((index (1- (abs res))))
- (if (or (>= index (length str1)) (>= index (length str2)))
- res
- (let ((digit1 (memq (aref str1 index)
- '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)))
- (digit2 (memq (aref str2 index)
- '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9))))
- (if digit1
- (if digit2
- (let ((num1 (progn (string-match "[0-9]+" str1 index)
- (match-string 0 str1)))
- (num2 (progn (string-match "[0-9]+" str2 index)
- (match-string 0 str2))))
- (cond
-
-
-
-
- ((< (length num1) (length num2)) (- (abs res)))
- ((> (length num1) (length num2)) (abs res))
- ((< (string-to-number num1) (string-to-number num2))
- (- (abs res)))
- (t (abs res))))
-
- (if (and (not (zerop index))
- (memq (aref str1 (1- index))
- '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)))
- (abs res)
- (- (abs res))))
- (if digit2
-
- (if (and (not (zerop index))
- (memq (aref str1 (1- index))
- '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)))
- (- (abs res))
- (abs res))
- res))))))))
- (defun mpc-string-prefix-p (str1 str2)
-
- "Tell whether STR1 is a prefix of STR2."
- (eq t (compare-strings str2 nil (length str1) str1 nil nil)))
- (defvar mpc--find-memoize (make-hash-table :test 'equal))
- (defvar mpc-tag nil) (make-variable-buffer-local 'mpc-tag)
- (defcustom mpc-host
- (concat (or (getenv "MPD_HOST") "localhost")
- (if (getenv "MPD_PORT") (concat ":" (getenv "MPD_PORT"))))
- "Host (and port) where the Music Player Daemon is running.
- The format is \"HOST\" or \"HOST:PORT\" where PORT defaults to 6600
- and HOST defaults to localhost."
- :type 'string)
- (defvar mpc-proc nil)
- (defconst mpc--proc-end-re "^\\(?:OK\\(?: MPD .*\\)?\\|ACK \\(.*\\)\\)\n")
- (put 'mpc-proc-error 'error-conditions '(mpc-proc-error error))
- (put 'mpc-proc-error 'error-message "MPD error")
- (defun mpc--debug (format &rest args)
- (if (get-buffer "*MPC-debug*")
- (with-current-buffer "*MPC-debug*"
- (goto-char (point-max))
- (insert-before-markers
- (replace-regexp-in-string "\n" "\n "
- (apply 'format format args))
- "\n"))))
- (defun mpc--proc-filter (proc string)
- (mpc--debug "Receive \"%s\"" string)
- (with-current-buffer (process-buffer proc)
- (if (process-get proc 'ready)
- (if nil
-
-
- nil
- (delete-process proc)
- (set-process-buffer proc nil)
- (pop-to-buffer (clone-buffer))
- (error "MPD output while idle!?"))
- (save-excursion
- (let ((start (or (marker-position (process-mark proc)) (point-min))))
- (goto-char start)
- (insert string)
- (move-marker (process-mark proc) (point))
- (beginning-of-line)
- (when (and (< start (point))
- (re-search-backward mpc--proc-end-re start t))
- (process-put proc 'ready t)
- (unless (eq (match-end 0) (point-max))
- (error "Unexpected trailing text"))
- (let ((error-text (match-string 1)))
- (delete-region (point) (point-max))
- (let ((callback (process-get proc 'callback)))
- (process-put proc 'callback nil)
- (if error-text
- (process-put proc 'mpc-proc-error error-text))
- (funcall callback)))))))))
- (defun mpc--proc-connect (host)
- (mpc--debug "Connecting to %s..." host)
- (with-current-buffer (get-buffer-create (format " *mpc-%s*" host))
-
- (let (proc)
- (while (and (setq proc (get-buffer-process (current-buffer)))
- (progn
- (delete-process proc)))))
- (erase-buffer)
- (let ((port 6600))
- (when (string-match ":[^.]+\\'" host)
- (setq port (substring host (1+ (match-beginning 0))))
- (setq host (substring host 0 (match-beginning 0)))
- (unless (string-match "[^[:digit:]]" port)
- (setq port (string-to-number port))))
- (let* ((coding-system-for-read 'utf-8-unix)
- (coding-system-for-write 'utf-8-unix)
- (proc (open-network-stream "MPC" (current-buffer) host port)))
- (when (processp mpc-proc)
-
- (let ((plist (process-plist mpc-proc)))
- (while plist (process-put proc (pop plist) (pop plist)))))
- (mpc-proc-buffer proc 'mpd-commands (current-buffer))
- (process-put proc 'callback 'ignore)
- (process-put proc 'ready nil)
- (clrhash mpc--find-memoize)
- (set-process-filter proc 'mpc--proc-filter)
- (set-process-sentinel proc 'ignore)
- (set-process-query-on-exit-flag proc nil)
-
- (with-local-quit (mpc-proc-sync proc))
- proc))))
- (defun mpc--proc-quote-string (s)
- (if (numberp s) (number-to-string s)
- (setq s (replace-regexp-in-string "[\"\\]" "\\\\\\&" s))
- (if (string-match " " s) (concat "\"" s "\"") s)))
- (defconst mpc--proc-alist-to-alists-starters '(file directory))
- (defun mpc--proc-alist-to-alists (alist)
- (assert (or (null alist)
- (memq (caar alist) mpc--proc-alist-to-alists-starters)))
- (let ((starter (caar alist))
- (alists ())
- tmp)
- (dolist (pair alist)
- (when (eq (car pair) starter)
- (if tmp (push (nreverse tmp) alists))
- (setq tmp ()))
- (push pair tmp))
- (if tmp (push (nreverse tmp) alists))
- (nreverse alists)))
- (defun mpc-proc ()
- (or (and mpc-proc
- (buffer-live-p (process-buffer mpc-proc))
- (not (memq (process-status mpc-proc) '(closed)))
- mpc-proc)
- (setq mpc-proc (mpc--proc-connect mpc-host))))
- (defun mpc-proc-check (proc)
- (let ((error-text (process-get proc 'mpc-proc-error)))
- (when error-text
- (process-put proc 'mpc-proc-error nil)
- (signal 'mpc-proc-error error-text))))
- (defun mpc-proc-sync (&optional proc)
- "Wait for MPC process until it is idle again.
- Return the buffer in which the process is/was running."
- (unless proc (setq proc (mpc-proc)))
- (unwind-protect
- (progn
- (while (and (not (process-get proc 'ready))
- (accept-process-output proc)))
- (mpc-proc-check proc)
- (if (process-get proc 'ready) (process-buffer proc)
- (error "No response from MPD")))
- (unless (process-get proc 'ready)
-
- (message "Killing hung process")
- (delete-process proc))))
- (defun mpc-proc-cmd (cmd &optional callback)
- "Send command CMD to the MPD server.
- If CALLBACK is nil, wait for the command to finish before returning,
- otherwise return immediately and call CALLBACK with no argument
- when the command terminates.
- CMD can be a string which is passed as-is to MPD or a list of strings
- which will be concatenated with proper quoting before passing them to MPD."
- (let ((proc (mpc-proc)))
- (if (and callback (not (process-get proc 'ready)))
- (let ((old (process-get proc 'callback)))
- (process-put proc 'callback
- (lambda ()
- (funcall old)
- (mpc-proc-cmd cmd callback))))
-
- (mpc-proc-sync proc)
- (process-put proc 'ready nil)
- (with-current-buffer (process-buffer proc)
- (erase-buffer)
- (mpc--debug "Send \"%s\"" cmd)
- (process-send-string
- proc (concat (if (stringp cmd) cmd
- (mapconcat 'mpc--proc-quote-string cmd " "))
- "\n")))
- (if callback
-
- (process-put proc 'callback
- callback
-
-
-
-
- )
-
- (process-put proc 'callback 'ignore)
-
- (mpc-proc-sync proc)))))
- (defun mpc-proc-cmd-list (cmds)
- (concat "command_list_begin\n"
- (mapconcat (lambda (cmd)
- (if (stringp cmd) cmd
- (mapconcat 'mpc--proc-quote-string cmd " ")))
- cmds
- "\n")
- "\ncommand_list_end"))
- (defun mpc-proc-cmd-list-ok ()
-
-
- (error "Not implemented yet"))
- (defun mpc-proc-buf-to-alist (&optional buf)
- (with-current-buffer (or buf (current-buffer))
- (let ((res ()))
- (goto-char (point-min))
- (while (re-search-forward "^\\([^:]+\\): \\(.*\\)\n" nil t)
- (push (cons (intern (match-string 1)) (match-string 2)) res))
- (nreverse res))))
- (defun mpc-proc-buf-to-alists (buf)
- (mpc--proc-alist-to-alists (mpc-proc-buf-to-alist buf)))
- (defun mpc-proc-cmd-to-alist (cmd &optional callback)
- (if callback
- (let ((buf (current-buffer)))
- (mpc-proc-cmd cmd (lambda ()
- (funcall callback (prog1 (mpc-proc-buf-to-alist
- (current-buffer))
- (set-buffer buf))))))
-
-
-
-
- (mpc-proc-buf-to-alist (mpc-proc-cmd cmd))))
- (defun mpc-proc-tag-string-to-sym (tag)
- (intern (capitalize tag)))
- (defun mpc-proc-buffer (proc use &optional buffer)
- (let* ((bufs (process-get proc 'buffers))
- (buf (cdr (assoc use bufs))))
- (cond
- ((and buffer (buffer-live-p buf) (not (eq buffer buf)))
- (error "Duplicate MPC buffer for %s" use))
- (buffer
- (if buf
- (setcdr (assoc use bufs) buffer)
- (process-put proc 'buffers (cons (cons use buffer) bufs))))
- (t buf))))
- (defvar mpc-status nil)
- (defvar mpc-status-callbacks
- '((state . mpc--status-timers-refresh)
-
-
- (state . mpc--faster-toggle-refresh)
- (volume . mpc-volume-refresh)
- (file . mpc-songpointer-refresh)
-
-
- (song . mpc-songpointer-refresh)
- (updating_db . mpc-updated-db)
- (updating_db . mpc--status-timers-refresh)
- (t . mpc-current-refresh))
- "Alist associating properties to the functions that care about them.
- Each entry has the form (PROP . FUN) where PROP can be t to mean
- to call FUN for any change whatsoever.")
- (defun mpc--status-callback ()
- (let ((old-status mpc-status))
-
- (setq mpc-status (mpc-proc-buf-to-alist))
- (assert mpc-status)
- (unless (equal old-status mpc-status)
-
- (dolist (pair mpc-status-callbacks)
- (when (or (eq t (car pair))
- (not (equal (cdr (assq (car pair) old-status))
- (cdr (assq (car pair) mpc-status)))))
- (funcall (cdr pair)))))))
- (defvar mpc--status-timer nil)
- (defun mpc--status-timer-start ()
- (add-hook 'pre-command-hook 'mpc--status-timer-stop)
- (unless mpc--status-timer
- (setq mpc--status-timer (run-with-timer 1 1 'mpc--status-timer-run))))
- (defun mpc--status-timer-stop ()
- (when mpc--status-timer
- (cancel-timer mpc--status-timer)
- (setq mpc--status-timer nil)))
- (defun mpc--status-timer-run ()
- (when (process-get (mpc-proc) 'ready)
- (condition-case err
- (with-local-quit (mpc-status-refresh))
- (error (message "MPC: %s" err)))))
- (defvar mpc--status-idle-timer nil)
- (defun mpc--status-idle-timer-start ()
- (when mpc--status-idle-timer
-
- (cancel-timer mpc--status-idle-timer))
- (setq mpc--status-idle-timer
- (run-with-idle-timer 1 t 'mpc--status-idle-timer-run))
-
-
-
-
-
- (mpc--status-timer-start))
- (defun mpc--status-idle-timer-stop (&optional really)
- (when mpc--status-idle-timer
-
- (cancel-timer mpc--status-idle-timer))
- (setq mpc--status-idle-timer
- (unless really
-
-
- (run-with-idle-timer 10 t 'mpc--status-idle-timer-run))))
- (defun mpc--status-idle-timer-run ()
- (when (process-get (mpc-proc) 'ready)
- (condition-case err
- (with-local-quit (mpc-status-refresh))
- (error (message "MPC: %s" err))))
- (mpc--status-timer-start))
- (defun mpc--status-timers-refresh ()
- "Start/stop the timers according to whether a song is playing."
- (if (or (member (cdr (assq 'state mpc-status)) '("play"))
- (cdr (assq 'updating_db mpc-status)))
- (mpc--status-idle-timer-start)
- (mpc--status-idle-timer-stop)
- (mpc--status-timer-stop)))
- (defun mpc-status-refresh (&optional callback)
- "Refresh `mpc-status'."
- (let ((cb callback))
- (mpc-proc-cmd (mpc-proc-cmd-list '("status" "currentsong"))
- (lambda ()
- (mpc--status-callback)
- (if cb (funcall cb))))))
- (defun mpc-status-stop ()
- "Stop the autorefresh of `mpc-status'.
- This is normally used only when quitting MPC.
- Any call to `mpc-status-refresh' may cause it to be restarted."
- (setq mpc-status nil)
- (mpc--status-idle-timer-stop 'really)
- (mpc--status-timer-stop))
- (defvar mpc--find-memoize-union-tags nil)
- (defun mpc-cmd-flush (tag value)
- (puthash (cons tag value) nil mpc--find-memoize)
- (dolist (uniontag mpc--find-memoize-union-tags)
- (if (member (symbol-name tag) (split-string (symbol-name uniontag) "|"))
- (puthash (cons uniontag value) nil mpc--find-memoize))))
- (defun mpc-cmd-special-tag-p (tag)
- (or (memq tag '(Playlist Search Directory))
- (string-match "|" (symbol-name tag))))
- (defun mpc-cmd-find (tag value)
- "Return a list of all songs whose tag TAG has value VALUE.
- The songs are returned as alists."
- (or (gethash (cons tag value) mpc--find-memoize)
- (puthash (cons tag value)
- (cond
- ((eq tag 'Playlist)
-
- (let ((l (condition-case nil
- (mpc-proc-buf-to-alists
- (mpc-proc-cmd (list "listplaylistinfo" value)))
- (mpc-proc-error
-
- nil)))
- (i 0))
- (mapcar (lambda (s)
- (prog1 (cons (cons 'Pos (number-to-string i)) s)
- (incf i)))
- l)))
- ((eq tag 'Search)
- (mpc-proc-buf-to-alists
- (mpc-proc-cmd (list "search" "any" value))))
- ((eq tag 'Directory)
- (let ((pairs
- (mpc-proc-buf-to-alist
- (mpc-proc-cmd (list "listallinfo" value)))))
- (mpc--proc-alist-to-alists
-
- (delq nil (mapcar (lambda (pair)
- (if (eq (car pair) 'directory)
- nil pair))
- pairs)))))
- ((string-match "|" (symbol-name tag))
- (add-to-list 'mpc--find-memoize-union-tags tag)
- (let ((tag1 (intern (substring (symbol-name tag)
- 0 (match-beginning 0))))
- (tag2 (intern (substring (symbol-name tag)
- (match-end 0)))))
- (mpc-union (mpc-cmd-find tag1 value)
- (mpc-cmd-find tag2 value))))
- (t
- (condition-case nil
- (mpc-proc-buf-to-alists
- (mpc-proc-cmd (list "find" (symbol-name tag) value)))
- (mpc-proc-error
-
-
-
- (error "Unknown tag %s" tag)
- (let ((res ()))
- (setq value (cons tag value))
- (dolist (song (mpc-proc-buf-to-alists
- (mpc-proc-cmd "listallinfo")))
- (if (member value song) (push song res)))
- res)))))
- mpc--find-memoize)))
- (defun mpc-cmd-list (tag &optional other-tag value)
-
-
-
- (cond
- ((eq tag 'Playlist)
- (let ((pls (mpc-assq-all 'playlist (mpc-proc-cmd-to-alist "lsinfo"))))
- (when other-tag
- (dolist (pl (prog1 pls (setq pls nil)))
- (let ((plsongs (mpc-cmd-find 'Playlist pl)))
- (if (not (mpc-cmd-special-tag-p other-tag))
- (when (member (cons other-tag value)
- (apply 'append plsongs))
- (push pl pls))
-
-
-
-
-
-
-
-
- (let* ((osongs (mpc-cmd-find other-tag value))
- (ofiles (mpc-assq-all 'file (apply 'append osongs)))
- (plfiles (mpc-assq-all 'file (apply 'append plsongs))))
- (when (mpc-intersection plfiles ofiles)
- (push pl pls)))))))
- pls))
- ((eq tag 'Directory)
- (if (null other-tag)
- (apply 'nconc
- (mpc-assq-all 'directory
- (mpc-proc-buf-to-alist
- (mpc-proc-cmd "lsinfo")))
- (mapcar (lambda (dir)
- (let ((shortdir
- (if (get-text-property 0 'display dir)
- (concat " "
- (get-text-property 0 'display dir))
- " ↪ "))
- (subdirs
- (mpc-assq-all 'directory
- (mpc-proc-buf-to-alist
- (mpc-proc-cmd (list "lsinfo" dir))))))
- (dolist (subdir subdirs)
- (put-text-property 0 (1+ (length dir))
- 'display shortdir
- subdir))
- subdirs))
- (process-get (mpc-proc) 'Directory)))
-
-
- (let* ((other-songs (mpc-cmd-find other-tag value))
- (files (mpc-assq-all 'file (apply 'append other-songs)))
- (dirs '()))
- (dolist (file files)
- (let ((dir (file-name-directory file)))
- (if (and dir (setq dir (directory-file-name dir))
- (not (equal dir (car dirs))))
- (push dir dirs))))
-
- (setq dirs (delete-dups dirs))
- (let ((newdirs dirs))
- (while newdirs
- (let ((dir (file-name-directory (pop newdirs))))
- (when (and dir (setq dir (directory-file-name dir))
- (not (member dir dirs)))
- (push dir newdirs)
- (push dir dirs)))))
- dirs)))
-
-
-
-
- ((eq tag 'Search) (error "Not supported"))
- ((string-match "|" (symbol-name tag))
- (let ((tag1 (intern (substring (symbol-name tag)
- 0 (match-beginning 0))))
- (tag2 (intern (substring (symbol-name tag)
- (match-end 0)))))
- (mpc-union (mpc-cmd-list tag1 other-tag value)
- (mpc-cmd-list tag2 other-tag value))))
- ((null other-tag)
- (condition-case nil
- (mapcar 'cdr (mpc-proc-cmd-to-alist (list "list" (symbol-name tag))))
- (mpc-proc-error
-
-
-
- (error "MPD does not know this tag %s" tag)
- (mpc-assq-all tag (mpc-proc-cmd-to-alist "listallinfo")))))
- (t
- (condition-case nil
- (if (mpc-cmd-special-tag-p other-tag)
- (signal 'mpc-proc-error "Not implemented")
- (mapcar 'cdr
- (mpc-proc-cmd-to-alist
- (list "list" (symbol-name tag)
- (symbol-name other-tag) value))))
- (mpc-proc-error
-
-
- (let ((other-songs (mpc-cmd-find other-tag value)))
- (mpc-assq-all tag
-
-
- (apply 'append other-songs))))))))
- (defun mpc-cmd-stop (&optional callback)
- (mpc-proc-cmd "stop" callback))
- (defun mpc-cmd-clear (&optional callback)
- (mpc-proc-cmd "clear" callback)
-
- )
- (defun mpc-cmd-pause (&optional arg callback)
- "Pause or resume playback of the queue of songs."
- (let ((cb callback))
- (mpc-proc-cmd (list "pause" arg)
- (lambda () (mpc-status-refresh) (if cb (funcall cb))))
- (unless callback (mpc-proc-sync))))
- (defun mpc-cmd-status ()
- (mpc-proc-cmd-to-alist "status"))
- (defun mpc-cmd-play ()
- (mpc-proc-cmd "play")
- (mpc-status-refresh))
- (defun mpc-cmd-add (files &optional playlist)
- "Add the songs FILES to PLAYLIST.
- If PLAYLIST is t or nil or missing, use the main playlist."
- (mpc-proc-cmd (mpc-proc-cmd-list
- (mapcar (lambda (file)
- (if (stringp playlist)
- (list "playlistadd" playlist file)
- (list "add" file)))
- files)))
- (if (stringp playlist)
- (mpc-cmd-flush 'Playlist playlist)))
- (defun mpc-cmd-delete (song-poss &optional playlist)
- "Delete the songs at positions SONG-POSS from PLAYLIST.
- If PLAYLIST is t or nil or missing, use the main playlist."
- (mpc-proc-cmd (mpc-proc-cmd-list
- (mapcar (lambda (song-pos)
- (if (stringp playlist)
- (list "playlistdelete" playlist song-pos)
- (list "delete" song-pos)))
-
-
-
- (sort song-poss '>))))
- (if (stringp playlist)
- (puthash (cons 'Playlist playlist) nil mpc--find-memoize)))
- (defun mpc-cmd-move (song-poss dest-pos &optional playlist)
- (let ((i 0))
- (mpc-proc-cmd
- (mpc-proc-cmd-list
- (mapcar (lambda (song-pos)
- (if (>= song-pos dest-pos)
-
-
- (setq song-pos (+ song-pos i)))
- (prog1 (if (stringp playlist)
- (list "playlistmove" playlist song-pos dest-pos)
- (list "move" song-pos dest-pos))
- (if (< song-pos dest-pos)
-
- (decf dest-pos))
- (incf i)))
-
-
-
- (sort song-poss '>))))
- (if (stringp playlist)
- (puthash (cons 'Playlist playlist) nil mpc--find-memoize))))
- (defun mpc-cmd-update (&optional arg callback)
- (let ((cb callback))
- (mpc-proc-cmd (if arg (list "update" arg) "update")
- (lambda () (mpc-status-refresh) (if cb (funcall cb))))
- (unless callback (mpc-proc-sync))))
- (defun mpc-cmd-tagtypes ()
- (mapcar 'cdr (mpc-proc-cmd-to-alist "tagtypes")))
- (defcustom mpc-mpd-music-directory nil
- "Location of MPD's music directory."
- :type '(choice (const nil) directory))
- (defcustom mpc-data-directory
- (if (and (not (file-directory-p "~/.mpc"))
- (file-directory-p "~/.emacs.d"))
- "~/.emacs.d/mpc" "~/.mpc")
- "Directory where MPC.el stores auxiliary data."
- :type 'directory)
- (defun mpc-data-directory ()
- (unless (file-directory-p mpc-data-directory)
- (make-directory mpc-data-directory))
- mpc-data-directory)
- (defun mpc-file-local-copy (file)
-
- (when (and (null mpc-mpd-music-directory)
- (string-match "\\`localhost" mpc-host))
- (let ((files '("~/.mpdconf" "/etc/mpd.conf"))
- file)
- (while (and files (not file))
- (if (file-exists-p (car files)) (setq file (car files)))
- (setq files (cdr files)))
- (with-temp-buffer
- (ignore-errors (insert-file-contents file))
- (goto-char (point-min))
- (if (re-search-forward "^music_directory[ ]+\"\\([^\"]+\\)\"")
- (setq mpc-mpd-music-directory
- (match-string 1))))))
-
-
- (if (and mpc-mpd-music-directory
- (file-exists-p (expand-file-name file mpc-mpd-music-directory)))
- (expand-file-name file mpc-mpd-music-directory)
-
-
-
-
-
-
-
-
-
-
- ))
- (defun mpc-secs-to-time (secs)
-
-
-
-
- (if (stringp secs) (setq secs (string-to-number secs)))
- (if (>= secs (* 60 100))
- (format "%dh%02d"
- (/ secs 3600) (% (/ secs 60) 60))
- (format "%d:%02d" (/ secs 60) (% secs 60))))
- (defvar mpc-tempfiles nil)
- (defconst mpc-tempfiles-reftable (make-hash-table :weakness 'key))
- (defun mpc-tempfiles-clean ()
- (let ((live ()))
- (maphash (lambda (_k v) (push v live)) mpc-tempfiles-reftable)
- (dolist (f mpc-tempfiles)
- (unless (member f live) (ignore-errors (delete-file f))))
- (setq mpc-tempfiles live)))
- (defun mpc-tempfiles-add (key file)
- (mpc-tempfiles-clean)
- (puthash key file mpc-tempfiles-reftable)
- (push file mpc-tempfiles))
- (defun mpc-format (format-spec info &optional hscroll)
- "Format the INFO according to FORMAT-SPEC, inserting the result at point."
- (let* ((pos 0)
- (start (point))
- (col (if hscroll (- hscroll) 0))
- (insert (lambda (str)
- (cond
- ((>= col 0) (insert str))
- (t (insert (substring str (min (length str) (- col))))))))
- (pred nil))
- (while (string-match "%\\(?:%\\|\\(-\\)?\\([0-9]+\\)?{\\([[:alpha:]][[:alnum:]]*\\)\\(?:-\\([^}]+\\)\\)?}\\)" format-spec pos)
- (let ((pre-text (substring format-spec pos (match-beginning 0))))
- (funcall insert pre-text)
- (setq col (+ col (string-width pre-text))))
- (setq pos (match-end 0))
- (if (null (match-end 3))
- (progn
- (funcall insert "%")
- (setq col (+ col 1)))
- (let* ((size (match-string 2 format-spec))
- (tag (intern (match-string 3 format-spec)))
- (post (match-string 4 format-spec))
- (right-align (match-end 1))
- (text
- (if (eq info 'self) (symbol-name tag)
- (case tag
- ((Time Duration)
- (let ((time (cdr (or (assq 'time info) (assq 'Time info)))))
- (setq pred (list nil))
- (when time
- (mpc-secs-to-time (if (and (eq tag 'Duration)
- (string-match ":" time))
- (substring time (match-end 0))
- time)))))
- (Cover
- (let* ((dir (file-name-directory (cdr (assq 'file info))))
- (cover (concat dir "cover.jpg"))
- (file (condition-case err
- (mpc-file-local-copy cover)
- (error (message "MPC: %s" err))))
- image)
-
- (push `(equal ',dir (file-name-directory (cdr (assq 'file info)))) pred)
- (if (null file)
-
-
-
-
- (progn (setq size nil) " ")
- (if (null size) (setq image (create-image file))
- (let ((tempfile (make-temp-file "mpc" nil ".jpg")))
- (call-process "convert" nil nil nil
- "-scale" size file tempfile)
- (setq image (create-image tempfile))
- (mpc-tempfiles-add image tempfile)))
- (setq size nil)
- (propertize dir 'display image))))
- (t (let ((val (cdr (assq tag info))))
-
-
- (when (and (null val) (eq tag 'Title))
- (setq val (cdr (assq 'file info))))
- (push `(equal ',val (cdr (assq ',tag info))) pred)
- val)))))
- (space (when size
- (setq size (string-to-number size))
- (propertize " " 'display
- (list 'space :align-to (+ col size)))))
- (textwidth (if text (string-width text) 0))
- (postwidth (if post (string-width post) 0)))
- (when text
- (let ((display
- (if (and size
- (> (+ postwidth textwidth) size))
-
- (propertize
- (if (zerop (- size postwidth 1))
- (substring text 0 1)
- (concat (substring text 0 (- size postwidth textwidth 1)) "…"))
- 'help-echo text)
- text)))
- (when (memq tag '(Artist Album Composer))
- (setq display
- (propertize display
- 'mouse-face 'highlight
- 'follow-link t
- 'keymap `(keymap
- (mouse-2
- . (lambda ()
- (interactive)
- (mpc-constraints-push 'noerror)
- (mpc-constraints-restore
- ',(list (list tag text)))))))))
- (funcall insert
- (concat (when size
- (propertize " " 'display
- (list 'space :align-to
- (+ col
- (if (and size right-align)
- (- size postwidth textwidth)
- 0)))))
- display post))))
- (if (null size) (setq col (+ col textwidth postwidth))
- (insert space)
- (setq col (+ col size))))))
- (put-text-property start (point) 'mpc-pred
- `(lambda (info) (and ,@(nreverse pred))))))
- (defvar mpc-mode-map
- (let ((map (make-keymap)))
- (suppress-keymap map)
-
- (define-key map "q" 'mpc-quit)
- (define-key map "\r" 'mpc-select)
- (define-key map [(shift return)] 'mpc-select-toggle)
- (define-key map [mouse-2] 'mpc-select)
- (define-key map [S-mouse-2] 'mpc-select-extend)
- (define-key map [C-mouse-2] 'mpc-select-toggle)
- (define-key map [drag-mouse-2] 'mpc-drag-n-drop)
-
- (define-key map [follow-link] 'always)
-
-
-
- (define-key map "p" 'mpc-pause)
- map))
- (easy-menu-define mpc-mode-menu mpc-mode-map
- "Menu for MPC.el."
- '("MPC.el"
- ["Add new browser" mpc-tagbrowser]
- ["Update DB" mpc-update]
- ["Quit" mpc-quit]))
- (defvar mpc-tool-bar-map
- (let ((map (make-sparse-keymap)))
- (tool-bar-local-item "mpc/prev" 'mpc-prev 'prev map
- :enable '(not (equal (cdr (assq 'state mpc-status)) "stop"))
- :label "Prev" :vert-only t)
-
- (tool-bar-local-item "mpc/rewind" 'mpc-rewind 'rewind map
- :enable '(not (equal (cdr (assq 'state mpc-status)) "stop"))
- :label "Rew" :vert-only t
- :button '(:toggle . (and mpc--faster-toggle-timer
- (not mpc--faster-toggle-forward))))
-
-
-
-
- (tool-bar-local-item "mpc/pause" 'mpc-pause 'pause map
- :label "Pause" :vert-only t
- :visible '(equal (cdr (assq 'state mpc-status)) "play")
- :help "Pause/play")
- (tool-bar-local-item "mpc/play" 'mpc-play 'play map
- :label "Play" :vert-only t
- :visible '(not (equal (cdr (assq 'state mpc-status)) "play"))
- :help "Play/pause")
-
- (tool-bar-local-item "mpc/ffwd" 'mpc-ffwd 'ffwd map
- :enable '(not (equal (cdr (assq 'state mpc-status)) "stop"))
- :label "Ffwd" :vert-only t
- :button '(:toggle . (and mpc--faster-toggle-timer
- mpc--faster-toggle-forward)))
- (tool-bar-local-item "mpc/next" 'mpc-next 'next map
- :label "Next" :vert-only t
- :enable '(not (equal (cdr (assq 'state mpc-status)) "stop")))
- (tool-bar-local-item "mpc/stop" 'mpc-stop 'stop map
- :label "Stop" :vert-only t)
- (tool-bar-local-item "mpc/add" 'mpc-playlist-add 'add map
- :label "Add" :vert-only t
- :help "Append to the playlist")
- map))
- (define-derived-mode mpc-mode fundamental-mode "MPC"
- "Major mode for the features common to all buffers of MPC."
- (buffer-disable-undo)
- (setq buffer-read-only t)
- (set (make-local-variable 'tool-bar-map) mpc-tool-bar-map)
- (set (make-local-variable 'truncate-lines) t))
- (define-derived-mode mpc-status-mode mpc-mode "MPC-Status"
- "Major mode to display MPC status info."
- (set (make-local-variable 'mode-line-format)
- '("%e" mode-line-frame-identification mode-line-buffer-identification))
- (set (make-local-variable 'window-area-factor) 3)
- (set (make-local-variable 'header-line-format) '("MPC " mpc-volume)))
- (defvar mpc-status-buffer-format
- '("%-5{Time} / %{Duration} %2{Disc--}%4{Track}" "%{Title}" "%{Album}" "%{Artist}" "%128{Cover}"))
- (defun mpc-status-buffer-refresh ()
- (let ((buf (mpc-proc-buffer (mpc-proc) 'status)))
- (when (buffer-live-p buf)
- (with-current-buffer buf
- (save-excursion
- (goto-char (point-min))
- (when (assq 'file mpc-status)
- (let ((inhibit-read-only t))
- (dolist (spec mpc-status-buffer-format)
- (let ((pred (get-text-property (point) 'mpc-pred)))
- (if (and pred (funcall pred mpc-status))
- (forward-line)
- (delete-region (point) (line-beginning-position 2))
- (ignore-errors (mpc-format spec mpc-status))
- (insert "\n"))))
- (unless (eobp) (delete-region (point) (point-max))))))))))
- (defun mpc-status-buffer-show ()
- (interactive)
- (let* ((buf (mpc-proc-buffer (mpc-proc) 'status))
- (songs-buf (mpc-proc-buffer (mpc-proc) 'songs))
- (songs-win (if songs-buf (get-buffer-window songs-buf 0))))
- (unless (buffer-live-p buf)
- (setq buf (get-buffer-create "*MPC-Status*"))
- (with-current-buffer buf
- (mpc-status-mode))
- (mpc-proc-buffer (mpc-proc) 'status buf))
- (if (null songs-win) (pop-to-buffer buf)
- (let ((_win (split-window songs-win 20 t)))
- (set-window-dedicated-p songs-win nil)
- (set-window-buffer songs-win buf)
- (set-window-dedicated-p songs-win 'soft)))))
- (defvar mpc-separator-ol nil)
- (defvar mpc-select nil)
- (make-variable-buffer-local 'mpc-select)
- (defmacro mpc-select-save (&rest body)
- "Execute BODY and restore the selection afterwards."
- (declare (indent 0) (debug t))
- `(let ((selection (mpc-select-get-selection))
- (position (cons (buffer-substring-no-properties
- (line-beginning-position) (line-end-position))
- (current-column))))
- ,@body
- (mpc-select-restore selection)
- (goto-char (point-min))
- (if (re-search-forward
- (concat "^" (regexp-quote (car position)) "$")
- (if (overlayp mpc-separator-ol)
- (overlay-end mpc-separator-ol))
- t)
- (move-to-column (cdr position)))
- (let ((win (get-buffer-window (current-buffer) 0)))
- (if win (set-window-point win (point))))))
- (defun mpc-select-get-selection ()
- (mapcar (lambda (ol)
- (buffer-substring-no-properties
- (overlay-start ol) (1- (overlay-end ol))))
- mpc-select))
- (defun mpc-select-restore (selection)
-
-
-
- (mapc 'delete-overlay mpc-select)
- (setq mpc-select nil)
- (dolist (elem selection)
-
- (goto-char (point-min))
- (when (re-search-forward
- (concat "^" (regexp-quote elem) "$") nil t)
- (mpc-select-make-overlay)))
- (when mpc-tag (mpc-tagbrowser-all-select))
- (beginning-of-line))
- (defun mpc-select-make-overlay ()
- (assert (not (get-char-property (point) 'mpc-select)))
- (let ((ol (make-overlay
- (line-beginning-position) (line-beginning-position 2))))
- (overlay-put ol 'mpc-select t)
- (overlay-put ol 'face 'region)
- (overlay-put ol 'evaporate t)
- (push ol mpc-select)))
- (defun mpc-select (&optional event)
- "Select the tag value at point."
- (interactive (list last-nonmenu-event))
- (mpc-event-set-point event)
- (if (and (bolp) (eobp)) (forward-line -1))
- (mapc 'delete-overlay mpc-select)
- (setq mpc-select nil)
- (if (mpc-tagbrowser-all-p)
- nil
- (mpc-select-make-overlay))
- (when mpc-tag
- (mpc-tagbrowser-all-select)
- (mpc-selection-refresh)))
- (defun mpc-select-toggle (&optional event)
- "Toggle the selection of the tag value at point."
- (interactive (list last-nonmenu-event))
- (mpc-event-set-point event)
- (save-excursion
- (cond
-
- ((get-char-property (point) 'mpc-select)
- (let ((ols nil))
- (dolist (ol mpc-select)
- (if (and (<= (overlay-start ol) (point))
- (> (overlay-end ol) (point)))
- (delete-overlay ol)
- (push ol ols)))
- (assert (= (1+ (length ols)) (length mpc-select)))
- (setq mpc-select ols)))
-
- ((mpc-tagbrowser-all-p) nil)
-
- (t (mpc-select-make-overlay))))
- (when mpc-tag
- (mpc-tagbrowser-all-select)
- (mpc-selection-refresh)))
- (defun mpc-select-extend (&optional event)
- "Extend the selection up to point."
- (interactive (list last-nonmenu-event))
- (mpc-event-set-point event)
- (if (null mpc-select)
-
- (mpc-select event)
- (save-excursion
- (cond
-
- ((get-char-property (point) 'mpc-select)
- (let ((before 0)
- (after 0)
- (mid (line-beginning-position))
- start end)
- (while (and (zerop (forward-line 1))
- (get-char-property (point) 'mpc-select))
- (setq end (1+ (point)))
- (incf after))
- (goto-char mid)
- (while (and (zerop (forward-line -1))
- (get-char-property (point) 'mpc-select))
- (setq start (point))
- (incf before))
- (if (and (= after 0) (= before 0))
-
- nil
- (if (> after before)
- (setq end mid)
- (setq start (1+ mid)))
- (let ((ols '()))
- (dolist (ol mpc-select)
- (if (and (>= (overlay-start ol) start)
- (< (overlay-start ol) end))
- (delete-overlay ol)
- (push ol ols)))
- (setq mpc-select (nreverse ols))))))
-
- (t
- (when (mpc-tagbrowser-all-p)
- (forward-line 1))
- (let ((before 0)
- (count 0)
- (dir 1)
- (start (line-beginning-position)))
- (while (and (zerop (forward-line 1))
- (not (get-char-property (point) 'mpc-select)))
- (incf count))
- (unless (get-char-property (point) 'mpc-select)
- (setq count nil))
- (goto-char start)
- (while (and (zerop (forward-line -1))
- (not (get-char-property (point) 'mpc-select)))
- (incf before))
- (unless (get-char-property (point) 'mpc-select)
- (setq before nil))
- (when (and before (or (null count) (< before count)))
- (setq count before)
- (setq dir -1))
- (goto-char start)
- (dotimes (_i (1+ (or count 0)))
- (mpc-select-make-overlay)
- (forward-line dir))))))
- (when mpc-tag
- (mpc-tagbrowser-all-select)
- (mpc-selection-refresh))))
- (defvar mpc--song-search nil)
- (defun mpc-constraints-get-current (&optional avoid-buf)
- "Return currently selected set of constraints.
- If AVOID-BUF is non-nil, it specifies a buffer which should be ignored
- when constructing the set of constraints."
- (let ((constraints (if mpc--song-search `((Search ,mpc--song-search))))
- tag select)
- (dolist (buf (process-get (mpc-proc) 'buffers))
- (setq buf (cdr buf))
- (when (and (setq tag (buffer-local-value 'mpc-tag buf))
- (not (eq buf avoid-buf))
- (setq select
- (with-current-buffer buf (mpc-select-get-selection))))
- (push (cons tag select) constraints)))
- constraints))
- (defun mpc-constraints-tag-lookup (buffer-tag constraints)
- (let (res)
- (dolist (constraint constraints)
- (when (or (eq (car constraint) buffer-tag)
- (and (string-match "|" (symbol-name buffer-tag))
- (member (symbol-name (car constraint))
- (split-string (symbol-name buffer-tag) "|"))))
- (setq res (cdr constraint))))
- res))
- (defun mpc-constraints-restore (constraints)
- (let ((search (assq 'Search constraints)))
- (setq mpc--song-search (cadr search))
- (when search (setq constraints (delq search constraints))))
- (dolist (buf (process-get (mpc-proc) 'buffers))
- (setq buf (cdr buf))
- (when (buffer-live-p buf)
- (let* ((tag (buffer-local-value 'mpc-tag buf))
- (constraint (mpc-constraints-tag-lookup tag constraints)))
- (when tag
- (with-current-buffer buf
- (mpc-select-restore constraint))))))
- (mpc-selection-refresh))
- (defun mpc-ring-make (size) (cons 0 (cons 0 (make-vector size nil))))
- (defun mpc-ring-push (ring val)
- (aset (cddr ring) (car ring) val)
- (setcar (cdr ring) (max (cadr ring) (1+ (car ring))))
- (setcar ring (mod (1+ (car ring)) (length (cddr ring)))))
- (defun mpc-ring-pop (ring)
- (setcar ring (mod (1- (car ring)) (cadr ring)))
- (aref (cddr ring) (car ring)))
- (defvar mpc-constraints-ring (mpc-ring-make 10))
- (defun mpc-constraints-push (&optional noerror)
- "Push the current selection on the ring for later."
- (interactive)
- (let ((constraints (mpc-constraints-get-current)))
- (if (null constraints)
- (unless noerror (error "No selection to push"))
- (mpc-ring-push mpc-constraints-ring constraints))))
- (defun mpc-constraints-pop ()
- "Recall the most recently pushed selection."
- (interactive)
- (let ((constraints (mpc-ring-pop mpc-constraints-ring)))
- (if (null constraints)
- (error "No selection to return to")
- (mpc-constraints-restore constraints))))
- (defconst mpc-tagbrowser-all-name (propertize "*ALL*" 'face 'italic))
- (defvar mpc-tagbrowser-all-ol nil)
- (make-variable-buffer-local 'mpc-tagbrowser-all-ol)
- (defvar mpc-tag-name nil) (make-variable-buffer-local 'mpc-tag-name)
- (defun mpc-tagbrowser-all-p ()
- (and (eq (point-min) (line-beginning-position))
- (equal mpc-tagbrowser-all-name
- (buffer-substring (point-min) (line-end-position)))))
- (define-derived-mode mpc-tagbrowser-mode mpc-mode '("MPC-" mpc-tag-name)
- (set (make-local-variable 'mode-line-process) '("" mpc-tag-name))
- (set (make-local-variable 'mode-line-format) nil)
- (set (make-local-variable 'header-line-format) '("" mpc-tag-name
- ))
- (set (make-local-variable 'buffer-undo-list) t)
- )
- (defun mpc-tagbrowser-refresh ()
- (mpc-select-save
- (widen)
- (goto-char (point-min))
- (assert (looking-at (regexp-quote mpc-tagbrowser-all-name)))
- (forward-line 1)
- (let ((inhibit-read-only t))
- (delete-region (point) (point-max))
- (dolist (val (mpc-cmd-list mpc-tag)) (insert val "\n")))
- (set-buffer-modified-p nil))
- (mpc-reorder))
- (defun mpc-updated-db ()
-
- (unless (assq 'updating_db mpc-status)
- (clrhash mpc--find-memoize)
- (dolist (buf (process-get (mpc-proc) 'buffers))
- (setq buf (cdr buf))
- (when (buffer-local-value 'mpc-tag buf)
- (with-current-buffer buf (with-local-quit (mpc-tagbrowser-refresh)))))
- (with-local-quit (mpc-songs-refresh))))
- (defun mpc-tagbrowser-tag-name (tag)
- (cond
- ((string-match "|" (symbol-name tag))
- (let ((tag1 (intern (substring (symbol-name tag)
- 0 (match-beginning 0))))
- (tag2 (intern (substring (symbol-name tag)
- (match-end 0)))))
- (concat (mpc-tagbrowser-tag-name tag1)
- " | "
- (mpc-tagbrowser-tag-name tag2))))
- ((string-match "y\\'" (symbol-name tag))
- (concat (substring (symbol-name tag) 0 -1) "ies"))
- (t (concat (symbol-name tag) "s"))))
- (defun mpc-tagbrowser-buf (tag)
- (let ((buf (mpc-proc-buffer (mpc-proc) tag)))
- (if (buffer-live-p buf) buf
- (setq buf (get-buffer-create (format "*MPC %ss*" tag)))
- (mpc-proc-buffer (mpc-proc) tag buf)
- (with-current-buffer buf
- (let ((inhibit-read-only t))
- (erase-buffer)
- (if (member tag '(Directory))
- (mpc-tagbrowser-dir-mode)
- (mpc-tagbrowser-mode))
- (insert mpc-tagbrowser-all-name "\n"))
- (forward-line -1)
- (setq mpc-tag tag)
- (setq mpc-tag-name (mpc-tagbrowser-tag-name tag))
- (mpc-tagbrowser-all-select)
- (mpc-tagbrowser-refresh)
- buf))))
- (defvar tag-browser-tagtypes
- (lazy-completion-table tag-browser-tagtypes
- (lambda ()
- (append '("Playlist" "Directory")
- (mpc-cmd-tagtypes)))))
- (defun mpc-tagbrowser (tag)
- "Create a new browser for TAG."
- (interactive
- (list
- (let ((completion-ignore-case t))
- (intern
- (completing-read "Tag: " tag-browser-tagtypes nil 'require-match)))))
- (let* ((newbuf (mpc-tagbrowser-buf tag))
- (win (get-buffer-window newbuf 0)))
- (if win (select-window win)
- (if (with-current-buffer (window-buffer (selected-window))
- (derived-mode-p 'mpc-tagbrowser-mode))
- (setq win (selected-window))
-
- (let ((buffers (process-get (mpc-proc) 'buffers))
- buffer)
- (while
- (and buffers
- (not (and (buffer-live-p (setq buffer (cdr (pop buffers))))
- (with-current-buffer buffer
- (derived-mode-p 'mpc-tagbrowser-mode))
- (setq win (get-buffer-window buffer 0))))))))
- (if (not win)
- (pop-to-buffer newbuf)
- (setq win (split-window win nil 'horiz))
- (set-window-buffer win newbuf)
- (set-window-dedicated-p win 'soft)
- (select-window win)
- (balance-windows-area)))))
- (defun mpc-tagbrowser-all-select ()
- "Select the special *ALL* entry if no other is selected."
- (if mpc-select
- (delete-overlay mpc-tagbrowser-all-ol)
- (save-excursion
- (goto-char (point-min))
- (if mpc-tagbrowser-all-ol
- (move-overlay mpc-tagbrowser-all-ol
- (point) (line-beginning-position 2))
- (let ((ol (make-overlay (point) (line-beginning-position 2))))
- (overlay-put ol 'face 'region)
- (overlay-put ol 'evaporate t)
- (set (make-local-variable 'mpc-tagbrowser-all-ol) ol))))))
- (defun mpc-separator (active)
-
- (unless mpc-separator-ol
- (set (make-local-variable 'mpc-separator-ol)
- (make-overlay (point) (point)))
- (overlay-put mpc-separator-ol 'after-string
- (propertize "\n"
- 'face '(:height 0.05 :inverse-video t))))
- (goto-char (point-min))
- (forward-line 1)
- (while
- (and (member (buffer-substring-no-properties
- (line-beginning-position) (line-end-position))
- active)
- (zerop (forward-line 1))))
- (if (or (eobp) (null active))
- (delete-overlay mpc-separator-ol)
- (move-overlay mpc-separator-ol (1- (point)) (point))))
- (defun mpc-sort (active)
-
- (let ((inhibit-read-only t))
- (goto-char (point-min))
- (if (mpc-tagbrowser-all-p) (forward-line 1))
- (condition-case nil
- (sort-subr nil 'forward-line 'end-of-line
- nil nil
- (lambda (s1 s2)
- (setq s1 (buffer-substring-no-properties
- (car s1) (cdr s1)))
- (setq s2 (buffer-substring-no-properties
- (car s2) (cdr s2)))
- (cond
- ((member s1 active)
- (if (member s2 active)
- (let ((cmp (mpc-compare-strings s1 s2 t)))
- (and (numberp cmp) (< cmp 0)))
- t))
- ((member s2 active) nil)
- (t (let ((cmp (mpc-compare-strings s1 s2 t)))
- (and (numberp cmp) (< cmp 0)))))))
-
- (wrong-number-of-arguments
- (sort-subr nil 'forward-line 'end-of-line
- (lambda ()
- (let ((name (buffer-substring-no-properties
- (point) (line-end-position))))
- (cond
- ((member name active) (concat "1" name))
- (t (concat "2" "name"))))))))))
- (defvar mpc--changed-selection)
- (defun mpc-reorder (&optional nodeactivate)
- "Reorder entries based on the currently active selections.
- I.e. split the current browser buffer into a first part containing the
- entries included in the selection, then a separator, and then the entries
- not included in the selection.
- Return non-nil if a selection was deactivated."
- (mpc-select-save
- (let ((constraints (mpc-constraints-get-current (current-buffer)))
- (active 'all))
-
-
- (dolist (cst constraints)
- (let ((vals (apply 'mpc-union
- (mapcar (lambda (val)
- (mpc-cmd-list mpc-tag (car cst) val))
- (cdr cst)))))
- (setq active
- (if (listp active) (mpc-intersection active vals) vals))))
- (when (and (listp active))
-
-
- (let ((deactivate t))
- (dolist (sel selection)
- (when (member sel active) (setq deactivate nil)))
- (when deactivate
-
- (when selection
- (setq mpc--changed-selection t))
- (unless nodeactivate
- (setq selection nil)
- (mapc 'delete-overlay mpc-select)
- (setq mpc-select nil)
- (mpc-tagbrowser-all-select)))))
-
-
- (mpc-sort (if (listp active) active))
- (mpc-separator (if (listp active) active)))))
- (defun mpc-selection-refresh ()
- (let ((mpc--changed-selection t))
- (while mpc--changed-selection
- (setq mpc--changed-selection nil)
- (dolist (buf (process-get (mpc-proc) 'buffers))
- (setq buf (cdr buf))
- (when (and (buffer-local-value 'mpc-tag buf)
- (not (eq buf (current-buffer))))
- (with-current-buffer buf (mpc-reorder)))))
-
-
-
- (when mpc-tag (mpc-reorder 'nodeactivate))
-
- (if (and mpc--song-search mpc--changed-selection)
- (progn
- (setq mpc--song-search nil)
- (mpc-selection-refresh))
- (mpc-songs-refresh))))
- (defvar mpc-tagbrowser-dir-mode-map
- (let ((map (make-sparse-keymap)))
- (set-keymap-parent map mpc-tagbrowser-mode-map)
- (define-key map [?\M-\C-m] 'mpc-tagbrowser-dir-toggle)
- map))
- (define-derived-mode mpc-tagbrowser-dir-mode mpc-tagbrowser-mode '("MPC-" mpc-tag-name)
-
-
- )
- (defun mpc-tagbrowser-dir-toggle (event)
- "Open or close the element at point."
- (interactive (list last-nonmenu-event))
- (mpc-event-set-point event)
- (let ((name (buffer-substring (line-beginning-position)
- (line-end-position)))
- (prop (intern mpc-tag)))
- (if (not (member name (process-get (mpc-proc) prop)))
- (process-put (mpc-proc) prop
- (cons name (process-get (mpc-proc) prop)))
- (let ((new (delete name (process-get (mpc-proc) prop))))
- (setq name (concat name "/"))
- (process-put (mpc-proc) prop
- (delq nil
- (mapcar (lambda (x)
- (if (mpc-string-prefix-p name x)
- nil x))
- new)))))
- (mpc-tagbrowser-refresh)))
- (defvar mpc-songs-playlist nil
- "Name of the currently selected playlist, if any.
- A value of t means the main playlist.")
- (make-variable-buffer-local 'mpc-songs-playlist)
- (defun mpc-playlist-create (name)
- "Save current playlist under name NAME."
- (interactive "sPlaylist name: ")
- (mpc-proc-cmd (list "save" name))
- (let ((buf (mpc-proc-buffer (mpc-proc) 'Playlist)))
- (when (buffer-live-p buf)
- (with-current-buffer buf (mpc-tagbrowser-refresh)))))
- (defun mpc-playlist-destroy (name)
- "Delete playlist named NAME."
- (interactive
- (list (completing-read "Delete playlist: " (mpc-cmd-list 'Playlist)
- nil 'require-match)))
- (mpc-proc-cmd (list "rm" name))
- (let ((buf (mpc-proc-buffer (mpc-proc) 'Playlist)))
- (when (buffer-live-p buf)
- (with-current-buffer buf (mpc-tagbrowser-refresh)))))
- (defun mpc-playlist-rename (oldname newname)
- "Rename playlist OLDNAME to NEWNAME."
- (interactive
- (let* ((oldname (if (and (eq mpc-tag 'Playlist) (null current-prefix-arg))
- (buffer-substring (line-beginning-position)
- (line-end-position))
- (completing-read "Rename playlist: "
- (mpc-cmd-list 'Playlist)
- nil 'require-match)))
- (newname (read-string (format "Rename '%s' to: " oldname))))
- (if (zerop (length newname))
- (error "Aborted")
- (list oldname newname))))
- (mpc-proc-cmd (list "rename" oldname newname))
- (let ((buf (mpc-proc-buffer (mpc-proc) 'Playlist)))
- (if (buffer-live-p buf)
- (with-current-buffer buf (mpc-tagbrowser-refresh)))))
- (defun mpc-playlist ()
- "Show the current playlist."
- (interactive)
- (mpc-constraints-push 'noerror)
- (mpc-constraints-restore '()))
- (defun mpc-playlist-add ()
- "Add the selection to the playlist."
- (interactive)
- (let ((songs (mapcar #'car (mpc-songs-selection))))
- (mpc-cmd-add songs)
- (message "Appended %d songs" (length songs))
-
- songs))
- (defun mpc-playlist-delete ()
- "Remove the selected songs from the playlist."
- (interactive)
- (unless mpc-songs-playlist
- (error "The selected songs aren't part of a playlist"))
- (let ((song-poss (mapcar #'cdr (mpc-songs-selection))))
- (mpc-cmd-delete song-poss mpc-songs-playlist)
- (mpc-songs-refresh)
- (message "Deleted %d songs" (length song-poss))))
- (defvar mpc-volume-map
- (let ((map (make-sparse-keymap)))
- (define-key map [down-mouse-1] 'mpc-volume-mouse-set)
- (define-key map [mouse-1] 'ignore)
- (define-key map [header-line down-mouse-1] 'mpc-volume-mouse-set)
- (define-key map [header-line mouse-1] 'ignore)
- (define-key map [mode-line down-mouse-1] 'mpc-volume-mouse-set)
- (define-key map [mode-line mouse-1] 'ignore)
- map))
- (defvar mpc-volume nil) (put 'mpc-volume 'risky-local-variable t)
- (defun mpc-volume-refresh ()
-
- (setq mpc-volume
- (mpc-volume-widget
- (string-to-number (cdr (assq 'volume mpc-status))))))
- (defvar mpc-volume-step 5)
- (defun mpc-volume-mouse-set (&optional event)
- "Change volume setting."
- (interactive (list last-nonmenu-event))
- (let* ((posn (event-start event))
- (diff
- (if (memq (if (stringp (car-safe (posn-object posn)))
- (aref (car (posn-object posn)) (cdr (posn-object posn)))
- (with-current-buffer (window-buffer (posn-window posn))
- (char-after (posn-point posn))))
- '(?◁ ?<))
- (- mpc-volume-step) mpc-volume-step))
- (newvol (+ (string-to-number (cdr (assq 'volume mpc-status))) diff)))
- (mpc-proc-cmd (list "setvol" newvol) 'mpc-status-refresh)
- (message "Set MPD volume to %s%%" newvol)))
- (defun mpc-volume-widget (vol &optional size)
- (unless size (setq size 12.5))
- (let ((scaledvol (* (/ vol 100.0) size)))
-
- (list (propertize "<"
-
- 'keymap mpc-volume-map
- 'face '(:box (:line-width -2 :style pressed-button))
- 'mouse-face '(:box (:line-width -2 :style released-button)))
- " "
- (propertize "a"
- 'display (list 'space :width scaledvol)
- 'face '(:inverse-video t
- :box (:line-width -2 :style released-button)))
- (propertize "a"
- 'display (list 'space :width (- size scaledvol))
- 'face '(:box (:line-width -2 :style released-button)))
- " "
- (propertize ">"
-
- 'keymap mpc-volume-map
- 'face '(:box (:line-width -2 :style pressed-button))
- 'mouse-face '(:box (:line-width -2 :style released-button))))))
- (defvar mpc-current-song nil) (put 'mpc-current-song 'risky-local-variable t)
- (defvar mpc-current-updating nil) (put 'mpc-current-updating 'risky-local-variable t)
- (defvar mpc-songs-format-description nil) (put 'mpc-songs-format-description 'risky-local-variable t)
- (defvar mpc-previous-window-config nil)
- (defvar mpc-songs-mode-map
- (let ((map (make-sparse-keymap)))
- (set-keymap-parent map mpc-mode-map)
- (define-key map [remap mpc-select] 'mpc-songs-jump-to)
- map))
- (defvar mpc-songpointer-set-visible nil)
- (defvar mpc-songs-hashcons (make-hash-table :test 'equal :weakness t)
- "Make song file name objects unique via hash consing.
- This is used so that they can be compared with `eq', which is needed for
- `text-property-any'.")
- (defun mpc-songs-hashcons (name)
- (or (gethash name mpc-songs-hashcons) (puthash name name mpc-songs-hashcons)))
- (defcustom mpc-songs-format "%2{Disc--}%3{Track} %-5{Time} %25{Title} %20{Album} %20{Artist} %10{Date}"
- "Format used to display each song in the list of songs."
- :type 'string)
- (defvar mpc-songs-totaltime)
- (defun mpc-songs-refresh ()
- (let ((buf (mpc-proc-buffer (mpc-proc) 'songs)))
- (when (buffer-live-p buf)
- (with-current-buffer buf
- (let ((constraints (mpc-constraints-get-current (current-buffer)))
- (dontsort nil)
- (inhibit-read-only t)
- (totaltime 0)
- (curline (cons (count-lines (point-min)
- (line-beginning-position))
- (buffer-substring (line-beginning-position)
- (line-end-position))))
- active)
- (setq mpc-songs-playlist nil)
- (if (null constraints)
-
-
-
-
-
-
- (setq dontsort t
- mpc-songs-playlist t
- active (mpc-proc-buf-to-alists
- (mpc-proc-cmd "playlistinfo")))
- (dolist (cst constraints)
- (if (and (eq (car cst) 'Playlist)
- (= 1 (length (cdr cst))))
- (setq mpc-songs-playlist (cadr cst)))
-
-
- (let ((vals (apply 'mpc-union
- (mapcar (lambda (val)
- (mpc-cmd-find (car cst) val))
- (cdr cst)))))
- (setq active (cond
- ((null active)
- (if (eq (car cst) 'Playlist)
- (setq dontsort t))
- vals)
- ((or dontsort
-
-
- (not (eq (car cst) 'Playlist)))
- (mpc-intersection active vals
- (lambda (x) (assq 'file x))))
- (t
- (setq dontsort t)
- (mpc-intersection vals active
- (lambda (x)
- (assq 'file x)))))))))
- (mpc-select-save
- (erase-buffer)
-
-
-
-
-
-
- (dolist (song (if dontsort active
- (sort active
- (lambda (song1 song2)
- (let ((cmp (mpc-compare-strings
- (cdr (assq 'file song1))
- (cdr (assq 'file song2)))))
- (and (integerp cmp) (< cmp 0)))))))
- (incf totaltime (string-to-number (or (cdr (assq 'Time song)) "0")))
- (mpc-format mpc-songs-format song)
- (delete-char (- (skip-chars-backward " ")))
- (insert "\n")
- (put-text-property
- (line-beginning-position 0) (line-beginning-position)
- 'mpc-file (mpc-songs-hashcons (cdr (assq 'file song))))
- (let ((pos (assq 'Pos song)))
- (if pos
- (put-text-property
- (line-beginning-position 0) (line-beginning-position)
- 'mpc-file-pos (string-to-number (cdr pos)))))
- ))
- (goto-char (point-min))
- (forward-line (car curline))
- (if (or (search-forward (cdr curline) nil t)
- (search-backward (cdr curline) nil t))
- (beginning-of-line)
- (goto-char (point-min)))
- (set (make-local-variable 'mpc-songs-totaltime)
- (unless (zerop totaltime)
- (list " " (mpc-secs-to-time totaltime))))
- ))))
- (let ((mpc-songpointer-set-visible t))
- (mpc-songpointer-refresh)))
- (defun mpc-songs-search (string)
- "Filter songs to those who include STRING in their metadata."
- (interactive "sSearch for: ")
- (setq mpc--song-search
- (if (zerop (length string)) nil string))
- (let ((mpc--changed-selection t))
- (while mpc--changed-selection
- (setq mpc--changed-selection nil)
- (dolist (buf (process-get (mpc-proc) 'buffers))
- (setq buf (cdr buf))
- (when (buffer-local-value 'mpc-tag buf)
- (with-current-buffer buf (mpc-reorder))))
- (mpc-songs-refresh))))
- (defun mpc-songs-kill-search ()
- "Turn off the current search restriction."
- (interactive)
- (mpc-songs-search nil))
- (defun mpc-songs-selection ()
- "Return the list of songs currently selected."
- (let ((buf (mpc-proc-buffer (mpc-proc) 'songs)))
- (when (buffer-live-p buf)
- (with-current-buffer buf
- (save-excursion
- (let ((files ()))
- (if mpc-select
- (dolist (ol mpc-select)
- (push (cons
- (get-text-property (overlay-start ol) 'mpc-file)
- (get-text-property (overlay-start ol) 'mpc-file-pos))
- files))
- (goto-char (point-min))
- (while (not (eobp))
- (push (cons
- (get-text-property (point) 'mpc-file)
- (get-text-property (point) 'mpc-file-pos))
- files)
- (forward-line 1)))
- (nreverse files)))))))
- (defun mpc-songs-jump-to (song-file &optional posn)
- "Jump to song SONG-FILE; interactively, this is the song at point."
- (interactive
- (let* ((event last-nonmenu-event)
- (posn (event-end event)))
- (with-selected-window (posn-window posn)
- (goto-char (posn-point posn))
- (list (get-text-property (point) 'mpc-file)
- posn))))
- (let* ((plbuf (mpc-proc-cmd "playlist"))
- (re (if song-file
- (concat "^\\([0-9]+\\):" (regexp-quote song-file) "$")))
- (sn (with-current-buffer plbuf
- (goto-char (point-min))
- (when (and re (re-search-forward re nil t))
- (match-string 1)))))
- (cond
- ((null re) (posn-set-point posn))
- ((null sn) (error "This song is not in the playlist"))
- ((null (with-current-buffer plbuf (re-search-forward re nil t)))
-
-
- (mpc-proc-cmd (list "play" sn)))
- (t
-
-
-
-
-
- (with-selected-window (posn-window posn)
- (let* ((cur (and (markerp overlay-arrow-position)
- (marker-position overlay-arrow-position)))
- (dest (save-excursion
- (goto-char (posn-point posn))
- (line-beginning-position)))
- (lines (when cur (* (if (< cur dest) 1 -1)
- (count-lines cur dest)))))
- (with-current-buffer plbuf
- (goto-char (point-min))
-
- (forward-line (string-to-number
- (or (cdr (assq 'song mpc-status)) "0")))
-
-
- (if lines (forward-line lines))
-
- (let* ((next (save-excursion
- (when (re-search-forward re nil t)
- (cons (point) (match-string 1)))))
- (prev (save-excursion
- (when (re-search-backward re nil t)
- (cons (point) (match-string 1)))))
- (sn (cdr (if (and next prev)
- (if (< (- (car next) (point))
- (- (point) (car prev)))
- next prev)
- (or next prev)))))
- (assert sn)
- (mpc-proc-cmd (concat "play " sn))))))))))
- (define-derived-mode mpc-songs-mode mpc-mode "MPC-song"
- (setq mpc-songs-format-description
- (with-temp-buffer (mpc-format mpc-songs-format 'self) (buffer-string)))
- (set (make-local-variable 'header-line-format)
-
- (list (propertize " " 'display '(space :align-to 0))
-
- '(:eval
- (let ((hscroll (window-hscroll)))
- (with-temp-buffer
- (mpc-format mpc-songs-format 'self hscroll)
-
-
-
-
-
- (buffer-string))))))
- (set (make-local-variable 'mode-line-format)
- '("%e" mode-line-frame-identification mode-line-buffer-identification
- #(" " 0 3
- (help-echo "mouse-1: Select (drag to resize)\nmouse-2: Make current window occupy the whole frame\nmouse-3: Remove current window from display"))
- mode-line-position
- #(" " 0 2
- (help-echo "mouse-1: Select (drag to resize)\nmouse-2: Make current window occupy the whole frame\nmouse-3: Remove current window from display"))
- mpc-songs-totaltime
- mpc-current-updating
- #(" " 0 2
- (help-echo "mouse-1: Select (drag to resize)\nmouse-2: Make current window occupy the whole frame\nmouse-3: Remove current window from display"))
- (mpc--song-search
- (:propertize
- ("Search=\"" mpc--song-search "\"")
- help-echo "mouse-2: kill this search"
- follow-link t
- mouse-face mode-line-highlight
- keymap (keymap (mode-line keymap
- (mouse-2 . mpc-songs-kill-search))))
- (:propertize "NoSearch"
- help-echo "mouse-2: set a search restriction"
- follow-link t
- mouse-face mode-line-highlight
- keymap (keymap (mode-line keymap (mouse-2 . mpc-songs-search)))))))
-
-
-
-
- )
- (defun mpc-songpointer-set (pos)
- (let* ((win (get-buffer-window (current-buffer) t))
- (visible (when win
- (or mpc-songpointer-set-visible
- (and (markerp overlay-arrow-position)
- (eq (marker-buffer overlay-arrow-position)
- (current-buffer))
- (<= (window-start win) overlay-arrow-position)
- (< overlay-arrow-position (window-end win)))))))
- (unless (local-variable-p 'overlay-arrow-position)
- (set (make-local-variable 'overlay-arrow-position) (make-marker)))
- (move-marker overlay-arrow-position pos)
-
- (if (and visible pos
- (or (> (window-start win) pos) (>= pos (window-end win t))))
- (set-window-point win pos))))
- (defun mpc-songpointer-refresh ()
- (let ((buf (mpc-proc-buffer (mpc-proc) 'songs)))
- (when (buffer-live-p buf)
- (with-current-buffer buf
- (let* ((pos (text-property-any
- (point-min) (point-max)
- 'mpc-file (mpc-songs-hashcons
- (cdr (assq 'file mpc-status)))))
- (other (when pos
- (save-excursion
- (goto-char pos)
- (text-property-any
- (line-beginning-position 2) (point-max)
- 'mpc-file (mpc-songs-hashcons
- (cdr (assq 'file mpc-status))))))))
- (if other
-
-
- (mpc-proc-cmd "playlist" 'mpc-songpointer-refresh-hairy)
- (mpc-songpointer-set pos)))))))
- (defun mpc-songpointer-context (size plbuf)
- (with-current-buffer plbuf
- (goto-char (point-min))
- (forward-line (string-to-number (or (cdr (assq 'song mpc-status)) "0")))
- (let ((context-before '())
- (context-after '()))
- (save-excursion
- (dotimes (_i size)
- (when (re-search-backward "^[0-9]+:\\(.*\\)" nil t)
- (push (mpc-songs-hashcons (match-string 1)) context-before))))
-
- (forward-line 1)
- (dotimes (_i size)
- (when (re-search-forward "^[0-9]+:\\(.*\\)" nil t)
- (push (mpc-songs-hashcons (match-string 1)) context-after)))
-
- (unless (and (< (length context-before) size)
- (< (length context-after) size))
- (cons (nreverse context-before) (nreverse context-after))))))
- (defun mpc-songpointer-score (context pos)
- (let ((count 0))
- (goto-char pos)
- (dolist (song (car context))
- (and (zerop (forward-line -1))
- (eq (get-text-property (point) 'mpc-file) song)
- (incf count)))
- (goto-char pos)
- (dolist (song (cdr context))
- (and (zerop (forward-line 1))
- (eq (get-text-property (point) 'mpc-file) song)
- (incf count)))
- count))
- (defun mpc-songpointer-refresh-hairy ()
-
-
- (let ((plbuf (current-buffer))
- (buf (mpc-proc-buffer (mpc-proc) 'songs)))
- (when (buffer-live-p buf)
- (with-current-buffer buf
- (let* ((context-size 0)
- (context '(() . ()))
- (pos (text-property-any
- (point-min) (point-max)
- 'mpc-file (mpc-songs-hashcons
- (cdr (assq 'file mpc-status)))))
- (score 0)
- (other pos))
- (while
- (setq other
- (save-excursion
- (goto-char other)
- (text-property-any
- (line-beginning-position 2) (point-max)
- 'mpc-file (mpc-songs-hashcons
- (cdr (assq 'file mpc-status))))))
-
- (let ((other-score (mpc-songpointer-score context other)))
- (cond
-
- ((< other-score score) nil)
-
- ((> other-score score)
- (setq pos other)
- (setq score other-score))
-
-
-
- ((< score context-size) nil)
- (t
-
- (incf context-size)
- (let ((new-context
- (mpc-songpointer-context context-size plbuf)))
- (if (null new-context)
-
-
- (decf context-size)
- (setq context new-context)
- (setq score (mpc-songpointer-score context pos))
- (save-excursion
- (goto-char other)
-
- (setq other (line-beginning-position 0)))))))))
- (mpc-songpointer-set pos))))))
- (defun mpc-current-refresh ()
-
- (mpc-status-buffer-refresh)
- (setq mpc-current-updating
- (if (assq 'updating_db mpc-status) " Updating-DB"))
- (ignore-errors
- (setq mpc-current-song
- (when (assq 'file mpc-status)
- (concat " "
- (mpc-secs-to-time (cdr (assq 'time mpc-status)))
- " "
- (cdr (assq 'Title mpc-status))
- " ("
- (cdr (assq 'Artist mpc-status))
- " / "
- (cdr (assq 'Album mpc-status))
- ")"))))
- (force-mode-line-update t))
- (defun mpc-songs-buf ()
- (let ((buf (mpc-proc-buffer (mpc-proc) 'songs)))
- (if (buffer-live-p buf) buf
- (with-current-buffer (setq buf (get-buffer-create "*MPC-Songs*"))
- (mpc-proc-buffer (mpc-proc) 'songs buf)
- (mpc-songs-mode)
- buf))))
- (defun mpc-update ()
- "Tell MPD to refresh its database."
- (interactive)
- (mpc-cmd-update))
- (defun mpc-quit ()
- "Quit Music Player Daemon."
- (interactive)
- (let* ((proc mpc-proc)
- (bufs (mapcar 'cdr (if proc (process-get proc 'buffers))))
- (wins (mapcar (lambda (buf) (get-buffer-window buf 0)) bufs))
- (song-buf (mpc-songs-buf))
- frames)
-
- (dolist (win wins)
- (when (and win (not (memq (window-frame win) frames)))
- (push (window-frame win) frames)))
- (if (and frames song-buf
- (with-current-buffer song-buf mpc-previous-window-config))
- (progn
- (select-frame (car frames))
- (set-window-configuration
- (with-current-buffer song-buf mpc-previous-window-config)))
-
- (dolist (frame frames)
- (let ((delete t))
- (dolist (win (window-list frame))
- (unless (memq (window-buffer win) bufs) (setq delete nil)))
- (if delete (ignore-errors (delete-frame frame))))))
-
- (mapc 'kill-buffer bufs)
- (mpc-status-stop)
- (if proc (delete-process proc))))
- (defun mpc-stop ()
- "Stop playing the current queue of songs."
- (interactive)
- (mpc-cmd-stop)
- (mpc-cmd-clear)
- (mpc-status-refresh))
- (defun mpc-pause ()
- "Pause playing."
- (interactive)
- (mpc-cmd-pause "1"))
- (defun mpc-resume ()
- "Resume playing."
- (interactive)
- (mpc-cmd-pause "0"))
- (defun mpc-play ()
- "Start playing whatever is selected."
- (interactive)
- (if (member (cdr (assq 'state (mpc-cmd-status))) '("pause"))
- (mpc-resume)
-
-
-
-
-
-
-
- (mpc-cmd-clear)
- (if (mpc-playlist-add)
- (if (member (cdr (assq 'state (mpc-cmd-status))) '("stop"))
- (mpc-cmd-play))
- (error "Don't know what to play"))))
- (defun mpc-next ()
- "Jump to the next song in the queue."
- (interactive)
- (mpc-proc-cmd "next")
- (mpc-status-refresh))
- (defun mpc-prev ()
- "Jump to the beginning of the current song, or to the previous song."
- (interactive)
- (let ((time (cdr (assq 'time mpc-status))))
-
-
- (cond
-
- ((and time (> (string-to-number time) 0))
- (mpc-proc-cmd (list "seekid" (cdr (assq 'songid mpc-status)) 0)))
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- (t (mpc-proc-cmd "previous")))
- (mpc-status-refresh)))
- (defvar mpc-last-seek-time '(0 . 0))
- (defun mpc--faster (event speedup step)
- "Fast forward."
- (interactive (list last-nonmenu-event))
- (let ((repeat-delay (/ (abs (float step)) speedup)))
- (if (not (memq 'down (event-modifiers event)))
- (let* ((currenttime (float-time))
- (last-time (- currenttime (car mpc-last-seek-time))))
- (if (< last-time (* 0.9 repeat-delay))
- nil
- (let* ((status (if (< last-time 1.0)
- mpc-status (mpc-cmd-status)))
- (songid (cdr (assq 'songid status)))
- (time (if songid
- (if (< last-time 1.0)
- (cdr mpc-last-seek-time)
- (string-to-number
- (cdr (assq 'time status)))))))
- (setq mpc-last-seek-time
- (cons currenttime (setq time (+ time step))))
- (mpc-proc-cmd (list "seekid" songid time)
- 'mpc-status-refresh))))
- (let ((status (mpc-cmd-status)))
- (let* ((songid (cdr (assq 'songid status)))
- (time (if songid (string-to-number
- (cdr (assq 'time status))))))
- (let ((timer (run-with-timer
- t repeat-delay
- (lambda ()
- (mpc-proc-cmd (list "seekid" songid
- (setq time (+ time step)))
- 'mpc-status-refresh)))))
- (while (mouse-movement-p
- (event-basic-type (setq event (read-event)))))
- (cancel-timer timer)))))))
- (defvar mpc--faster-toggle-timer nil)
- (defun mpc--faster-stop ()
- (when mpc--faster-toggle-timer
- (cancel-timer mpc--faster-toggle-timer)
- (setq mpc--faster-toggle-timer nil)))
- (defun mpc--faster-toggle-refresh ()
- (if (equal (cdr (assq 'state mpc-status)) "stop")
- (mpc--faster-stop)))
- (defun mpc--songduration ()
- (string-to-number
- (let ((s (cdr (assq 'time mpc-status))))
- (if (not (string-match ":" s))
- (error "Unexpected time format %S" s)
- (substring s (match-end 0))))))
- (defvar mpc--faster-toggle-forward nil)
- (defvar mpc--faster-acceleration 0.5)
- (defun mpc--faster-toggle (speedup step)
- (setq speedup (float speedup))
- (if mpc--faster-toggle-timer
- (mpc--faster-stop)
- (mpc-status-refresh) (mpc-proc-sync)
- (let* (songid
- songduration
- songtime
- oldtime
- prevsongid)
- (let ((fun
- (lambda ()
- (let ((newsongid (cdr (assq 'songid mpc-status))))
- (if (and (equal prevsongid newsongid)
- (not (equal prevsongid songid)))
-
-
- (setq newsongid songid))
- (cond
- ((null newsongid) (mpc--faster-stop))
- ((not (equal songid newsongid))
-
- (setq songid newsongid)
- (setq songtime (string-to-number
- (cdr (assq 'time mpc-status))))
- (setq songduration (mpc--songduration))
- (setq oldtime (float-time)))
- ((and (>= songtime songduration) mpc--faster-toggle-forward)
-
- (if (not (equal (cdr (assq 'state mpc-status)) "play"))
- (mpc-proc-cmd "next" 'mpc-status-refresh)
-
-
-
-
- nil))
- ((and (<= songtime 0) (not mpc--faster-toggle-forward))
-
- (setq prevsongid songid)
- (mpc-proc-cmd "previous"
- (lambda ()
- (mpc-status-refresh
- (lambda ()
- (setq songid (cdr (assq 'songid mpc-status)))
- (setq songtime (setq songduration (mpc--songduration)))
- (setq oldtime (float-time))
- (mpc-proc-cmd (list "seekid" songid songtime)))))))
- (t
- (setq speedup (+ speedup mpc--faster-acceleration))
- (let ((newstep
- (truncate (* speedup (- (float-time) oldtime)))))
- (if (<= newstep 1) (setq newstep 1))
- (setq oldtime (+ oldtime (/ newstep speedup)))
- (if (not mpc--faster-toggle-forward)
- (setq newstep (- newstep)))
- (setq songtime (min songduration (+ songtime newstep)))
- (unless (>= songtime songduration)
- (condition-case nil
- (mpc-proc-cmd
- (list "seekid" songid songtime)
- 'mpc-status-refresh)
- (mpc-proc-error (mpc-status-refresh)))))))))))
- (setq mpc--faster-toggle-forward (> step 0))
- (funcall fun)
- (setq mpc--faster-toggle-timer
- (run-with-timer t 0.3 fun))))))
- (defvar mpc-faster-speedup 8)
- (defun mpc-ffwd (_event)
- "Fast forward."
- (interactive (list last-nonmenu-event))
-
- (mpc--faster-toggle mpc-faster-speedup 1))
- (defun mpc-rewind (_event)
- "Fast rewind."
- (interactive (list last-nonmenu-event))
-
- (mpc--faster-toggle mpc-faster-speedup -1))
- (defun mpc-play-at-point (&optional event)
- (interactive (list last-nonmenu-event))
- (mpc-select event)
- (mpc-play))
- (defun mpc-drag-n-drop (event)
- "DWIM for a drag EVENT."
- (interactive "e")
- (let* ((start (event-start event))
- (end (event-end event))
- (start-buf (window-buffer (posn-window start)))
- (end-buf (window-buffer (posn-window end)))
- (songs
- (with-current-buffer start-buf
- (goto-char (posn-point start))
- (if (get-text-property (point) 'mpc-select)
-
-
- (mpc-songs-selection)
- (cond
- ((and (derived-mode-p 'mpc-songs-mode)
- (get-text-property (point) 'mpc-file))
- (list (cons (get-text-property (point) 'mpc-file)
- (get-text-property (point) 'mpc-file-pos))))
- ((and mpc-tag (not (mpc-tagbrowser-all-p)))
- (mapcar (lambda (song)
- (list (cdr (assq 'file song))))
- (mpc-cmd-find
- mpc-tag
- (buffer-substring (line-beginning-position)
- (line-end-position)))))
- (t
- (error "Unsupported starting position for drag'n'drop gesture")))))))
- (with-current-buffer end-buf
- (goto-char (posn-point end))
- (cond
- ((eq mpc-tag 'Playlist)
-
- (let ((playlist (if (or (mpc-tagbrowser-all-p)
- (and (bolp) (eolp)))
- (error "Not a playlist")
- (buffer-substring (line-beginning-position)
- (line-end-position)))))
- (mpc-cmd-add (mapcar 'car songs) playlist)
- (message "Added %d songs to %s" (length songs) playlist)
- (if (member playlist
- (cdr (assq 'Playlist (mpc-constraints-get-current))))
- (mpc-songs-refresh))))
- ((derived-mode-p 'mpc-songs-mode)
- (cond
- ((null mpc-songs-playlist)
- (error "The songs shown do not belong to a playlist"))
- ((eq start-buf end-buf)
-
- (let ((dest-pos (get-text-property (point) 'mpc-file-pos)))
- (mpc-cmd-move (mapcar 'cdr songs) dest-pos mpc-songs-playlist)
- (message "Moved %d songs" (length songs))))
- (t
-
- (let ((dest-pos (get-text-property (point) 'mpc-file-pos))
- (pl (if (stringp mpc-songs-playlist)
- (mpc-cmd-find 'Playlist mpc-songs-playlist)
- (mpc-proc-cmd-to-alist "playlist"))))
-
-
-
- (mpc-cmd-add (mapcar 'car songs) mpc-songs-playlist)
- (mpc-cmd-move (let ((poss '()))
- (dotimes (i (length songs))
- (push (+ i (length pl)) poss))
- (nreverse poss)) dest-pos mpc-songs-playlist)
- (message "Added %d songs" (length songs)))))
- (mpc-songs-refresh))
- (t
- (error "Unsupported drag'n'drop gesture"))))))
- (defcustom mpc-frame-alist '((name . "MPC") (tool-bar-lines . 1)
- (font . "Sans"))
- "Alist of frame parameters for the MPC frame."
- :type 'alist)
- (defun mpc ()
- "Main entry point for MPC."
- (interactive
- (progn
- (if current-prefix-arg
- (setq mpc-host (read-string "MPD host and port: " nil nil mpc-host)))
- nil))
- (let* ((song-buf (mpc-songs-buf))
- (song-win (get-buffer-window song-buf 0)))
- (if song-win
- (select-window song-win)
- (if (or (window-dedicated-p (selected-window))
- (window-minibuffer-p))
- (ignore-errors (select-frame (make-frame mpc-frame-alist)))
- (with-current-buffer song-buf
- (set (make-local-variable 'mpc-previous-window-config)
- (current-window-configuration))))
- (let* ((win1 (selected-window))
- (win2 (split-window))
- (tags mpc-browser-tags))
- (unless tags (error "Need at least one entry in `mpc-browser-tags'"))
- (set-window-buffer win2 song-buf)
- (set-window-dedicated-p win2 'soft)
- (mpc-status-buffer-show)
- (while
- (progn
- (set-window-buffer win1 (mpc-tagbrowser-buf (pop tags)))
- (set-window-dedicated-p win1 'soft)
- tags)
- (setq win1 (split-window win1 nil 'horiz)))))
- (balance-windows-area))
- (mpc-songs-refresh)
- (mpc-status-refresh))
- (provide 'mpc)
|