-
Notifications
You must be signed in to change notification settings - Fork 22
/
bongo.el
10908 lines (9870 loc) · 436 KB
/
bongo.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; bongo.el --- play music with Emacs
;; Copyright (C) 2005-2010, 2014 Daniel Brockman <daniel@brockman.se>
;; Copyright (C) 2006-2007 Daniel Jensen <daniel@bigwalter.net>
;; Copyright (C) 2005 Lars Öhrman <larohr@gmail.com>
;; Copyright (C) 2011 Jürgen Hötzel <juergen@archlinux.org>
;; Copyright (C) 1998, 2000-2005 Free Software Foundation, Inc.
;; Version: 1.1
;; Package-Requires: ((cl-lib "0.5") (emacs "24.1"))
;; This file is part of Bongo.
;; Bongo is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 2 of
;; the License, or (at your option) any later version.
;; Bongo 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 Bongo (see the file `COPYING'); if not,
;; write to the Free Software Foundation, 51 Franklin Street,
;; Fifth Floor, Boston, MA 02110-1301, USA.
;;; Commentary:
;; Bongo is a flexible and usable media player for GNU Emacs.
;; For detailed documentation see the projects README file at
;; https://github.com/dbrock/bongo/
;;; Code:
(eval-when-compile
(require 'rx))
(require 'dired) ; Required for dired integration
(require 'volume nil 'no-error) ; Required for adjusting volume
(require 'pcase)
(require 'cl-lib)
;; We try to load this library so that we can later decide
;; whether to enable Bongo Last.fm mode by default.
(require 'lastfm-submit nil 'no-error)
(declare-function w32-get-clipboard-data "w32select.c")
(defgroup bongo nil
"Buffer-oriented media player."
:prefix "bongo-"
:group 'multimedia
:group 'applications)
;;;; Macro definitions
(defmacro with-bongo-buffer (&rest body)
"Execute the forms in BODY in some Bongo buffer.
The value returned is the value of the last form in BODY.
If the current buffer is not a Bongo buffer, switch to
the buffer returned by the function `bongo-buffer'."
(declare (indent 0) (debug t))
`(with-current-buffer
(if (bongo-buffer-p)
(current-buffer)
(bongo-buffer))
,@body))
(defmacro with-temp-bongo-playlist-buffer (&rest body)
"Execute the forms in BODY in a temporary Bongo playlist buffer.
The value returned is the value of the last form in BODY."
(declare (indent 0) (debug t))
`(with-temp-buffer
(bongo-playlist-mode)
,@body))
(defmacro with-temp-bongo-library-buffer (&rest body)
"Execute the forms in BODY in a temporary Bongo library buffer.
The value returned is the value of the last form in BODY."
(declare (indent 0) (debug t))
`(with-temp-buffer
(bongo-library-mode)
,@body))
(defmacro with-bongo-library-buffer (&rest body)
"Execute the forms in BODY in some Bongo library buffer.
The value returned is the value of the last form in BODY.
If the current buffer is not a library buffer, switch to
the buffer returned by the function `bongo-library-buffer'."
(declare (indent 0) (debug t))
`(with-current-buffer
(if (bongo-library-buffer-p)
(current-buffer)
(bongo-library-buffer))
,@body))
(defmacro with-bongo-playlist-buffer (&rest body)
"Execute the forms in BODY in some Bongo playlist buffer.
The value returned is the value of the last form in BODY.
If the current buffer is not a playlist buffer, switch to
the buffer returned by the function `bongo-playlist-buffer'."
(declare (indent 0) (debug t))
`(with-current-buffer
(if (bongo-playlist-buffer-p)
(current-buffer)
(bongo-playlist-buffer))
,@body))
(defmacro with-point-at-bongo-track (point &rest body)
"Execute BODY with point at the Bongo track line at POINT.
If there is no track at POINT, use the next track line.
If there is no next track line, signal an error."
(declare (indent 1) (debug t))
`(progn
(push-mark)
(bongo-goto-point ,point)
(when line-move-ignore-invisible
(bongo-skip-invisible))
(let ((line-move-ignore-invisible nil))
(when (not (bongo-track-line-p))
(bongo-goto-point (or (bongo-point-at-next-track-line)
(error "No track at point"))))
,@body)))
(defvar bongo-player-start-imminent nil
"If non-nil, Bongo is just about to start playing some track.
For example, this will be non-nil while switching tracks.
See `with-imminent-bongo-player-start'.")
(defvar bongo-deferred-status-indicator-updates nil
"List of deferred Bongo status indicator updates.
Entries are of the form (FUNCTION . ARGUMENTS).
When `bongo-player-start-imminent' is non-nil, all status
indicator updates should be deferred to prevent flicker.")
(defmacro with-imminent-bongo-player-start (&rest body)
"Execute BODY with `bongo-player-start-imminent' bound to t.
Afterwards, unless `bongo-player-start-imminent' was already bound
to a non-nil value, perform all deferred status indicator updates.
See `bongo-deferred-status-indicator-updates'."
(declare (indent 0) (debug t))
`(progn
(let ((bongo-player-start-imminent t))
,@body)
(unless bongo-player-start-imminent
(dolist (entry (nreverse bongo-deferred-status-indicator-updates))
(apply (car entry) (cdr entry)))
(setq bongo-deferred-status-indicator-updates nil))))
(defmacro bongo-ignore-movement-errors (&rest body)
"Execute BODY; if a Bongo movement error occurs, return nil.
Otherwise, return the value of the last form in BODY."
(declare (indent 0) (debug t))
`(condition-case nil
(progn ,@body)
(bongo-movement-error nil)))
(defmacro bongo-until (test &rest body)
"If TEST yields nil, evaluate BODY... and repeat.
The order of execution is thus TEST, BODY..., TEST, BODY..., TEST,
and so on, until TEST returns non-nil.
Return the final value of TEST.
\(fn TEST BODY...)"
(declare (indent 1) (debug t))
(let ((result (cl-gensym)))
`(let (,result)
(while (unless (setq ,result ,test)
(prog1 t
,@body)))
,result)))
;;;; Global variables
(defvar bongo-title nil
"Bound dynamically to the formatted album title or nil.")
(defvar bongo-year nil
"Bound dynamically to the formatted album year or nil.")
(defvar bongo-album nil
"Bound dynamically to the contents of album field or nil.")
(defvar bongo-infoset nil
"Bound dynamically to the whole infoset or nil.")
(defvar bongo-target nil
"Short for `bongo-infoset-formatting-target'.")
(defvar bongo-line nil
"Short for `bongo-infoset-formatting-target-line'.")
(defvar bongo-index nil
"Bound dynamically is bound to the formatted track index or nil.")
(defvar bongo-length nil
"Bound dynamically is bound to the formatted track length or nil.")
(defvar bongo-track nil
"Bound dynamically is bound to the contents of the `track' field.")
(defvar bongo-sprinkle-mode nil
"Non-nil if `bongo-sprinke-mode' is on.
Declaring here so that it can be used in function appearing before the
minor mode definition.")
;;;; Commonly-used variables
(defvar bongo-backends '()
"List of symbols naming available Bongo player backends.
The backend data for each entry is stored in the `bongo-backend'
property of the backend name symbol.")
(defvar bongo-backend-matchers '()
"List of Bongo player backend matchers.
See `bongo-custom-backend-matchers' for more information.")
(defvar bongo-player nil
"The currently active player for this buffer, or nil.
This variable is only used in Bongo mode buffers.")
(make-variable-buffer-local 'bongo-player)
(defvar bongo-seek-buffer nil
"The current interactive Bongo Seek buffer, or nil.")
(defvar bongo-library-buffer nil
"The default Bongo library buffer, or nil.
Bongo library commands will operate on this buffer when
executed from buffers that are not in Bongo Library mode.
This variable overrides `bongo-default-library-buffer-name'.
See the function `bongo-library-buffer'.")
(defvar bongo-playlist-buffer nil
"The default Bongo playlist buffer, or nil.
Bongo playlist commands will operate on this buffer when
executed from buffers that are not in Bongo Playlist mode.
This variable overrides `bongo-default-playlist-buffer-name'.
See the function `bongo-playlist-buffer'.")
;;;; Customization variables
(defcustom bongo-enabled-backends nil
"Dummy declaration."
:group 'bongo)
(defcustom bongo-custom-backend-matchers nil
"Dummy declaration."
:group 'bongo)
(defun bongo-evaluate-backend-defcustoms ()
"Define `bongo-enabled-backends' and `bongo-custom-backend-matchers'.
This should be done whenever `bongo-backends' changes, so that
the `defcustom' options can be updated."
(custom-declare-variable 'bongo-enabled-backends
`',(apply 'nconc
(mapcar (lambda (backend-name)
(when (executable-find
(bongo-backend-program-name
(bongo-backend backend-name)))
(list backend-name)))
bongo-backends))
"List of names of enabled Bongo player backends.
See `bongo-backends' for a list of available backends."
:type `(list (set :inline t :format "%v"
,@(mapcar (lambda (backend-name)
`(const :tag ,(bongo-backend-pretty-name
backend-name)
,backend-name))
bongo-backends)))
:set (lambda (name value)
(set-default name value)
(when (fboundp 'bongo-buffer-p)
(dolist (buffer (if custom-local-buffer
(list (current-buffer))
(buffer-list)))
(when (bongo-buffer-p buffer)
(with-current-buffer buffer
(bongo-update-enabled-backends-list))))))
:group 'bongo)
(custom-reevaluate-setting 'bongo-enabled-backends)
(custom-declare-variable 'bongo-custom-backend-matchers nil
"List of custom Bongo player backend matchers.
Entries are rules of the form (BACKEND-NAME . MATCHER).
BACKEND-NAME is either `ignore' (which tells Bongo to ignore
the matched files), or a symbol naming the backend to use.
MATCHER specifies which files the rule applies to;
it is given to `bongo-file-name-matches-p'.
This option overrides `bongo-enabled-backends' in that disabled
backends will still be used if these rules say so. In addition,
it always takes precedence over `bongo-backend-matchers'.
For example, let's say that you want to use VLC instead of
mpg123 to play MP3 files, use speexdec to play \".speex\" files
in addition to \".spx\" files, and ignore WAV files altogether.
Then you could use the following setting:
(setq bongo-custom-backend-matchers
'((vlc local-file \"mp3\")
(speexdec local-file \"speex\")
(ignore local-file \"wav\")))"
:type
`(repeat
(cons :format "%v"
(choice :tag "Backend"
(const :tag "Ignore matching files" ignore)
,@(mapcar (lambda (backend-name)
(list 'const
:tag (bongo-backend-pretty-name
(bongo-backend backend-name))
backend-name))
bongo-backends)
(symbol :tag "Other backend"))
(cons :format "%v"
(repeat :tag "File types"
:value (local-file)
(choice :tag "Type"
(const :tag "Local file" local-file)
(string :tag "\
URI (specify scheme followed by a colon)")))
(choice :tag "Matcher"
(repeat :tag "File extensions" string)
(regexp :tag "File name (regexp)")
(function :tag "File name (predicate)")
(const :tag "All files" t)))))
:group 'bongo))
(bongo-evaluate-backend-defcustoms)
(defcustom bongo-default-directory nil
"Default directory for Bongo buffers, or nil.
If nil, use the value of `default-directory' when the buffer is created."
:type '(choice (const :tag "None in particular" nil)
directory)
:group 'bongo
:group 'bongo-file-names)
(defcustom bongo-prefer-library-buffers t
"If non-nil, prefer library buffers over playlist buffers.
This affects what kind of buffer is created by `\\[bongo]' when there
are no existing Bongo buffers.
Regardless of this setting, you can switch to a specific type of Bongo
buffer using the commands `\\[bongo-library]' and `\\[bongo-playlist]'.
To create a new library or playlist, create a new buffer and then switch to
the right mode using `\\[bongo-library-mode]' or `\\[bongo-playlist-mode]'.
If you set this variable to nil, you can happily use Bongo without ever
seeing a library buffer (unless you create one yourself, of course)."
:type 'boolean
:group 'bongo)
(defcustom bongo-display-playlist-after-enqueue t
"Whether to display the playlist after enqueuing a track."
:type 'boolean
:group 'bongo)
(defcustom bongo-mark-played-tracks nil
"Whether to mark all tracks that have been played.
Tracks marked as played are not selected for random playback.
Enabling Bongo Sprinkle mode sets this variable locally;
see `bongo-sprinkle-mode' for more about that.
Played tracks are displayed in the face `bongo-played-track'.
To delete all played tracks, use `\\[bongo-flush-playlist]'.
To clear the mark for all tracks, use `\\[bongo-reset-playlist]'."
:type 'boolean
:group 'bongo)
(defcustom bongo-confirm-flush-playlist t
"If non-nil, ask for confirmation before flushing the playlist.
This affects only the command `\\[bongo-flush-playlist]'.
In particular, `\\[bongo-erase-buffer]' and `\\[bongo-delete-played-tracks]'
never ask for confirmation, regardless of the value of this variable."
:type 'boolean
:group 'bongo)
(defcustom bongo-default-playlist-buffer-name "*Bongo Playlist*"
"The name of the default Bongo playlist buffer."
:type 'string
:group 'bongo)
(defcustom bongo-default-library-buffer-name "*Bongo Library*"
"The name of the default Bongo library buffer."
:type 'string
:group 'bongo)
(defcustom bongo-next-action 'bongo-play-next-or-stop
"The function to call after the current track finishes playing."
:type '(choice
(function-item :tag "Stop playback"
bongo-stop)
(function-item :tag "Play the next track"
bongo-play-next-or-stop)
(function-item :tag "Play the same track again"
bongo-replay-current)
(function-item :tag "Play the previous track"
bongo-play-previous-or-stop)
(function-item :tag "Play a random track"
bongo-play-random-or-stop))
:group 'bongo)
(make-variable-buffer-local 'bongo-next-action)
(defvar bongo-stored-next-action nil
"The old value of `bongo-next-action'.
This variable is used by `bongo-play-queued'.")
(defgroup bongo-file-names nil
"File names and file name parsing in Bongo.
If your files do not have nice names but do have nice tags, then you
can use the `tree-from-tags.rb' tool (shipped with Bongo) to create a
hierarchy of nicely-named links to your files."
:group 'bongo)
(defcustom bongo-file-name-field-separator " - "
"String used to split track file names into fields.
For example, if your tracks are named like this,
Frank Morton - 2004 - Frank Morton - 01 - Pojken på Tallbacksvägen.ogg
and your file name field separator is \" - \" (which is the default),
then the fields are \"Frank Morton\", \"2004\", \"Frank Morton\", \"01\",
and \"Pojken på Tallbacksvägen\".
When the the fields of a track's file name have been extracted,
they are used to build an infoset.
This is used by `bongo-default-infoset-from-file-name'."
:type 'string
:group 'bongo-file-names)
(defcustom bongo-file-name-album-year-regexp
"\\`\\([0-9]\\{4\\}\\|'?[0-9]\\{2\\}\\)\\'"
"Regexp matching album years.
This is used by `bongo-default-infoset-from-file-name'."
:type 'regexp
:group 'bongo-file-names)
(defcustom bongo-file-name-track-index-regexp "\\`[0-9]+\\'"
"Regexp matching track indices.
This is used by `bongo-default-infoset-from-file-name'."
:type 'regexp
:group 'bongo-file-names)
(defcustom bongo-album-cover-file-names
'("cover.jpg" "cover.jpeg" "cover.png"
"front.jpg" "front.jpeg" "front.png"
"album.jpg" "album.jpeg" "album.png")
"File names of images that should be considered album covers.
See also `bongo-insert-album-covers'."
:type '(repeat string)
:group 'bongo-file-names)
(defcustom bongo-update-references-to-renamed-files 'ask
"Whether to search all Bongo buffers after renaming a file.
If nil, never search through any buffers after renaming a file.
If `ask', prompt the user every time.
If any other value, always perform the search.
You can rename a file from Bongo using `bongo-rename-line'."
:type '(choice (const :tag "Never" nil)
(const :tag "Ask" ask)
(other :tag "Always" t))
:group 'bongo
:group 'bongo-file-names)
(defun bongo-format-string (format)
"Short for (apply 'concat (mapcar 'eval FORMAT))."
(apply 'concat (mapcar 'eval format)))
(defgroup bongo-display nil
"Display of Bongo playlist and library buffers."
:group 'bongo)
(defcustom bongo-field-separator
(if (and (fboundp 'char-displayable-p)
(char-displayable-p ?—))
" —— " " -- ")
"String used to separate field values in track descriptions.
This is used by the function `bongo-default-format-field'."
:type '(choice (const :tag " —— (Unicode dashes)" " —— ")
(const :tag " -- (ASCII dashes)" " -- ")
string)
:group 'bongo-display)
(defcustom bongo-insert-album-covers nil
"Whether to put album cover images into Bongo buffers.
See also `bongo-album-cover-file-names'."
:type 'boolean
:link '(custom-group-link bongo-file-names)
:group 'bongo-display)
(defcustom bongo-album-cover-size 200
"Size in pixels to which album cover art should be scaled."
:type 'integer
:group 'bongo-display)
(defcustom bongo-join-inserted-tracks t
"Whether to automatically join newly-inserted tracks.
This is done by repeatedly running `bongo-join'."
:type 'boolean
:group 'bongo-display)
(defcustom bongo-insert-intermediate-headers nil
"Whether to automatically insert intermediate headers.
This is best explained by an example. Say you have the
following section,
[Frank Morton —— Frank Morton (2004)]
01. Pojken på Tallbacksvägen
02. Kanske det blir så att jag måste gå
and you insert the following section immediately afterwards.
[Frank Morton —— Jag såg en film om en gammal man (2005)]
01. Det är så mysigt att vara två
02. Labyrinten
If this variable is nil, the result will be as follows:
[Frank Morton —— Frank Morton (2004)]
01. Pojken på Tallbacksvägen
02. Kanske det blir så att jag måste gå
[Frank Morton —— Jag såg en film om en gammal man (2005)]
01. Det är så mysigt att vara två
02. Labyrinten
On the other hand, if it is non-nil, the result will be as follows:
[Frank Morton]
[Frank Morton (2004)]
01. Pojken på Tallbacksvägen
02. Kanske det blir så att jag måste gå
[Jag såg en film om en gammal man (2005)]
01. Det är så mysigt att vara två
02. Labyrinten
Notice that an intermediate header ``[Frank Morton]'' was inserted."
:type 'boolean
:group 'bongo-display)
(defcustom bongo-album-function
'bongo-default-album
"Function for displaying albums in Bongo.
Value is a funcion, evaluating to a string or nil.
When the function is evaluated,
- `bongo-title' is bound to the formatted album title;
- `bongo-year' is bound to the formatted album year or nil;
- `bongo-album' is bound to the contents of the `album' field;
- `bongo-infoset' is bound to the whole infoset;
- `bongo-target' is short for `bongo-infoset-formatting-target';
- `bongo-line' is short for `bongo-infoset-formatting-target-line'.
This variable is used by the function `bongo-default-format-field'."
:type 'function
:options '(bongo-default-album)
:group 'bongo-display)
(defun bongo-default-album ()
(concat
bongo-title
(when bongo-year
(concat " (" bongo-year ")"))))
(defcustom bongo-display-track-lengths t
"Whether to display track lengths in Bongo playlist buffers.
See also `bongo-track-length-column'."
:type 'boolean
:group 'bongo-display)
(defcustom bongo-track-length-column 60
"Column at which to align track lengths in Bongo playlist buffers.
See also `bongo-display-track-lengths'."
:type 'integer
:group 'bongo-display)
(defcustom bongo-track-function
'bongo-default-track
"Function for displaying tracks in Bongo.
Value is a function, evaluating to a string or nil.
When the function is evaluated,
- `bongo-title' is bound to the formatted track title;
- `bongo-index' is bound to the formatted track index or nil;
- `bongo-length' is bound to the formatted track length or nil;
- `bongo-track' is bound to the contents of the `track' field;
- `bongo-infoset' is bound to the whole infoset;
- `bongo-target' is short for `bongo-infoset-formatting-target';
- `bongo-line' is short for `bongo-infoset-formatting-target-line'.
This variable is used by the function `bongo-default-format-field'."
:type 'function
:options '()
:group 'bongo-display)
(defcustom bongo-indentation-string " "
"String prefixed to lines once for each level of indentation."
:type 'string
:group 'bongo-display)
(defun bongo-default-track ()
(concat
(when bongo-index
(concat bongo-index ". "))
bongo-title
(when (and bongo-display-track-lengths bongo-length
(bongo-playlist-buffer-p bongo-target))
(concat (let ((other-fields-width
(with-temp-buffer
(insert (bongo-format-infoset
`((track (length . nil)
,@bongo-track)
,@bongo-infoset)))
(current-column)))
(indentation-width
(* (length bongo-indentation-string)
(bongo-line-indentation bongo-line))))
(make-string (max 0 (- bongo-track-length-column
indentation-width
other-fields-width)) 32))
" " bongo-length))))
(defcustom bongo-track-length-function
'bongo-default-track-length
"Function for displaying track lengths in Bongo.
Value is a Function, evaluating to a string or nil.
When the function is evaluated,
- `bongo-length' is bound to the length of the track in seconds;
- `bongo-track' is bound to the contents of the `track' field;
- `bongo-infoset' is bound to the whole infoset;
- `bongo-target' is short for `bongo-infoset-formatting-target';
- `bongo-line' is short for `bongo-infoset-formatting-target-line'."
:type 'function
:options '(bongo-default-track-length)
:group 'bongo-display)
(defun bongo-default-track-length ()
(concat "[" (bongo-format-seconds bongo-length) "]"))
(defcustom bongo-action-format
'("Action: " bongo-action-description)
"Template for displaying action tracks in Bongo.
Value is a list of expressions, each evaluating to a string or nil.
The values of the expressions are concatenated.
When the expressions are evaluated,
- `bongo-action-description' is bound to the action description;
- `bongo-action-expression' is bound to the action expression;
- `bongo-target' is short for `bongo-infoset-formatting-target';
- `bongo-line' is short for `bongo-infoset-formatting-target-line'."
:type '(repeat sexp)
:group 'bongo-display)
(defcustom bongo-stream-format
'((or bongo-uri-title bongo-stream-name bongo-uri)
(when bongo-stream-genre
(concat " (" bongo-stream-genre ")"))
(when bongo-stream-part-title
(concat ": " bongo-stream-part-title)))
"Template for displaying stream tracks in Bongo.
Value is a list of expressions, each evaluating to a string or nil.
The values of the expressions are concatenated.
When the expressions are evaluated,
- `bongo-uri' is bound to the URI of the stream;
- `bongo-uri-title' is bound to the URI title or nil;
- `bongo-stream-name' is bound to the stream name or nil;
- `bongo-stream-genre' is bound to the stream genre or nil;
- `bongo-stream-part-title' is bound to the stream part title;
- `bongo-target' is short for `bongo-infoset-formatting-target';
- `bongo-line' is short for `bongo-infoset-formatting-target-line'."
:type '(repeat sexp)
:group 'bongo-display)
(defcustom bongo-track-line-format
'((bongo-format-infoset bongo-internal-infoset))
"Template for displaying track lines in Bongo.
Value is a list of expressions, each evaluating to a string or nil.
The values of the expressions are concatenated.
When the expressions are evaluated,
- `bongo-internal-infoset' is bound to the internal infoset;
- `bongo-infoset' is bound to the whole infoset;
- `bongo-target' is short for `bongo-infoset-formatting-target';
- `bongo-line' is short for `bongo-infoset-formatting-target-line'."
:type '(repeat sexp)
:group 'bongo-display)
(defcustom bongo-display-header-icons t
"Whether to display icons for header lines in Bongo buffers."
:type 'boolean
:group 'bongo-display)
(defcustom bongo-expanded-header-icon "expanded-header-icon.png"
"File name of icon to use for header lines of expanded sections.
If nil, do not use any icon."
:type '(choice file (const :tag "None" nil))
:group 'bongo-display)
(defcustom bongo-collapsed-header-icon "collapsed-header-icon.png"
"File name of icon to use for header lines of collapsed sections.
If nil, do not use any icon."
:type '(choice file (const :tag "None" nil))
:group 'bongo-display)
(defcustom bongo-expanded-header-format "[%s]"
"Template for displaying header lines for expanded sections.
%s means the header line content.
This variable is only used when not displaying header icons."
:type 'string
:group 'bongo-display)
(defcustom bongo-collapsed-header-format "[%s ...]"
"Template for displaying header lines for collapsed sections.
%s means the header line content.
This variable is only used when not displaying header icons."
:type 'string
:group 'bongo-display)
(defcustom bongo-section-header-line-format
'((if (and bongo-display-header-icons (display-images-p))
(bongo-format-infoset bongo-internal-infoset)
(format (if bongo-collapsed
bongo-collapsed-header-format
bongo-expanded-header-format)
(bongo-format-infoset bongo-internal-infoset))))
"Template for displaying header lines in Bongo.
Value is a list of expressions, each evaluating to a string or nil.
The values of the expressions are concatenated.
When the expressions are evaluated,
- `bongo-internal-infoset' is bound to the internal infoset;
- `bongo-infoset' is bound to the whole infoset;
- `bongo-collapsed' is non-nil if the section is collapsed;
- `bongo-target' is short for `bongo-infoset-formatting-target';
- `bongo-line' is short for `bongo-infoset-formatting-target-line'.
The values of the expressions are concatenated."
:type '(repeat sexp)
:group 'bongo-display)
(defgroup bongo-track-icons nil
"Display of track icons in Bongo buffers."
:group 'bongo-display)
(defcustom bongo-display-track-icons t
"Whether to display icons for track lines in Bongo buffers."
:type 'boolean
:group 'bongo-track-icons
:group 'bongo-display)
(defcustom bongo-unknown-local-file-track-icon
"unknown-local-file-track-icon.png"
"File name of icon to use for unknown local file tracks."
:type '(choice file (const :tag "None" nil))
:group 'bongo-track-icons)
(defcustom bongo-local-audio-file-track-icon
"local-audio-file-track-icon.png"
"File name of icon to use for local audio file tracks.
If nil, use the same icon as for unknown local file tracks."
:type '(choice file (const :tag "\
Same as for unknown local file tracks" nil))
:group 'bongo-track-icons)
(defcustom bongo-local-video-file-track-icon
"local-video-file-track-icon.png"
"File name of icon to use for video file tracks.
If nil, use the same icon as for unknown file tracks."
:type '(choice file (const :tag "\
Same as for unknown local file tracks" nil))
:group 'bongo-track-icons)
(defcustom bongo-audio-cd-track-icon "audio-cd-track-icon.png"
"File name of icon to use for audio CD tracks.
If nil, use the same icon as for local audio file tracks."
:type '(choice file (const :tag "\
Same as for local audio file tracks" nil))
:group 'bongo-track-icons)
(defcustom bongo-uri-track-icon "uri-track-icon.png"
"File name of icon to use for URI tracks.
If nil, do not use any icon at all."
:type '(choice file (const :tag "None" nil))
:group 'bongo-track-icons)
(defcustom bongo-action-track-icon "action-track-icon.png"
"File name of icon to use for action tracks.
If nil, do not use any icon at all."
:type '(choice file (const :tag "None" nil))
:group 'bongo-track-icons)
(defcustom bongo-currently-playing-track-icon nil
"File name of icon to use for currently playing tracks.
If nil, use the same icon as for other tracks."
:type '(choice file (const :tag "Same as for other tracks" nil))
:group 'bongo-track-icons)
(defcustom bongo-played-track-icon nil
"File name of icon to use for played track lines.
If nil, use the same icon as for unplayed tracks."
:type '(choice file (const :tag "Same as for other tracks" nil))
:group 'bongo-track-icons)
(defconst bongo-images-directory
(or (expand-file-name "images" (file-name-directory load-file-name))
(error "Please use `load-file' to load bongo.el")))
(defun bongo-find-image (file-name &optional face)
(let ((image-load-path (cons bongo-images-directory
(and (boundp 'image-load-path)
image-load-path))))
(find-image
(list (list :ascent 'center
:file file-name
:type (image-type-from-file-name file-name)
:foreground (face-foreground
(or face 'default) nil 'default)
:background (face-background
(or face 'default) nil 'default))))))
(defun bongo-make-image-string (image)
"Return a string with IMAGE in its `display' property."
(propertize " " 'display image))
(defun bongo-make-image-placeholder-string (image)
"Return a blank string taking up as much space as IMAGE would."
(let ((size (image-size image t)))
(propertize " " 'display `(space :width (,(car size))
:height (,(cdr size))))))
(defun bongo-line-icon-string ()
"Return the string to use as an icon for the current line."
(let ((file-name
(cond ((and (bongo-track-line-p) bongo-display-track-icons)
(cond ((and (bongo-currently-playing-track-line-p)
bongo-currently-playing-track-icon)
bongo-currently-playing-track-icon)
((and (bongo-played-track-line-p)
bongo-played-track-icon)
bongo-played-track-icon)
((bongo-audio-cd-track-line-p)
bongo-audio-cd-track-icon)
((bongo-uri-track-line-p)
bongo-uri-track-icon)
((bongo-action-track-line-p)
bongo-action-track-icon)
((bongo-local-audio-file-track-line-p)
(or bongo-local-audio-file-track-icon
bongo-unknown-local-file-track-icon))
((bongo-local-video-file-track-line-p)
(or bongo-local-video-file-track-icon
bongo-unknown-local-file-track-icon))
((bongo-local-file-track-line-p)
bongo-unknown-local-file-track-icon)))
((and (bongo-header-line-p) bongo-display-header-icons)
(if (bongo-collapsed-header-line-p)
bongo-collapsed-header-icon
bongo-expanded-header-icon)))))
(when file-name
(bongo-make-image-string (bongo-find-image file-name)))))
(defgroup bongo-header-line nil
"Display of header lines in Bongo playlist buffers."
:group 'bongo
:group 'bongo-display)
(defcustom bongo-header-line-mode t
"Whether to display header lines in Bongo playlist buffers."
:type 'boolean
:initialize 'custom-initialize-default
:set 'custom-set-minor-mode
:group 'bongo-header-line)
(defcustom bongo-header-line-playing-string "Playing:"
"String to display in the header line when a track is playing."
:type 'string
:group 'bongo-header-line)
(defcustom bongo-header-line-paused-string "Paused: "
"String to display in the header line when a track is paused."
:type 'string
:group 'bongo-header-line)
(defun bongo-header-line-playback-status ()
"Return the string to use for header line playback status."
(when (bongo-playing-p)
(if (bongo-paused-p)
bongo-header-line-paused-string
bongo-header-line-playing-string)))
(defcustom bongo-header-line-function
'bongo-default-header-line-function
"Function for Bongo playlist header lines."
:type 'function
:options '(bongo-default-header-line-function)
:group 'bongo-header-line)
(defun bongo-default-header-line-function ()
"Return String of Playback status and Track description"
(concat (bongo-header-line-playback-status) " "
(bongo-formatted-infoset)))
(defvar bongo-header-line-string nil
"Bongo header line string.
Value is derived from `bongo-header-line-function'.
The name of this variable should go in `header-line-format'.")
(make-variable-buffer-local 'bongo-header-line-string)
(put 'bongo-header-line-string 'risky-local-variable t)
(defun bongo-update-header-line-string (&rest dummy)
"Update `bongo-header-line-string' using `bongo-header-line-format'.
If Bongo is not playing anything, set the header line string to nil.
Accept DUMMY arguments to ease hook usage."
(when (bongo-buffer-p)
(let ((new-header-line-format header-line-format)
(new-bongo-header-line-string bongo-header-line-string))
(when (null new-header-line-format)
(setq new-header-line-format '("")))
(if bongo-header-line-mode
(add-to-list 'new-header-line-format
'bongo-header-line-string t)
(setq new-header-line-format
(remq 'bongo-header-line-string new-header-line-format)))
(setq new-bongo-header-line-string
(when (bongo-playing-p)
(funcall bongo-header-line-function)))
(when (or (equal new-header-line-format '(""))
(and (equal new-header-line-format
'("" bongo-header-line-string))
(null new-bongo-header-line-string)))
(setq new-header-line-format nil))
(if (not bongo-player-start-imminent)
(setq header-line-format new-header-line-format
bongo-header-line-string new-bongo-header-line-string)
(add-to-list 'bongo-deferred-status-indicator-updates
(list 'set 'header-line-format
new-header-line-format))
(add-to-list 'bongo-deferred-status-indicator-updates
(list 'set 'bongo-header-line-string
new-bongo-header-line-string))))))
(defun bongo-header-line-mode (argument &optional called-interactively-p)
"Toggle display of Bongo mode line indicator on or off.
With ARGUMENT equal to `toggle', or interactively
with no prefix argument, toggle the mode.
With zero or negative ARGUMENT, turn the mode off.
With any other ARGUMENT, turn the mode on.
When called interactively, CALLED-INTERACTIVELY-P is non-nil."
;; Use `toggle' rather than (if mode 0 1) so that using
;; `repeat-command' still does the toggling correctly.
(interactive (list (or current-prefix-arg 'toggle)
'called-interactively-p))
(setq bongo-header-line-mode
(if (eq argument 'toggle)
(not bongo-header-line-mode)
(> (prefix-numeric-value argument) 0)))
(when called-interactively-p
(customize-mark-as-set 'bongo-header-line-mode))
(when (called-interactively-p 'interactive)
(message "Bongo header line mode %s."
(if bongo-header-line-mode
"enabled" "disabled")))
bongo-header-line-mode)
(defgroup bongo-mode-line nil
"Display of Bongo mode line indicators."
:group 'bongo
:group 'bongo-display)
(defcustom bongo-display-playback-mode-indicator t
"Display playback mode indicators in playlist buffer mode lines.
These indicate which playback mode is in effect.
However, nothing is shown for normal in-order playback.
To change playback mode, try (for example) `\\[bongo-random-playback-mode]'."
:type 'boolean
:group 'bongo-mode-line
:group 'bongo-display)
(defcustom bongo-mode-line-indicator-mode t
"Display a Bongo playback status indicator in the global mode line.
See `bongo-mode-line-indicator-format'."
:type 'boolean
:initialize 'custom-initialize-default
:set 'custom-set-minor-mode
:group 'bongo-mode-line)
(defvar bongo-player-times-last-updated 0
"Time in seconds when player output was last updated because of
player times changed")
(defun bongo-hyphen-padded-mode-line-p ()