-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathxmms2.clj
209 lines (184 loc) · 6.71 KB
/
xmms2.clj
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
; Albert Cardona 20090129
; Released under the General Public License in its latest version.
;
; Assumes xmms2 is installed and in the $PATH
; and that there is already a playlist loaded into it.
;
; Shows the list of tracks and enables play on double-click
; by calling "xmms2 jump <playlist-index>"
;
; From a clojure prompt, run xmms2 commands like:
; (in-ns 'xmms2.music)
; (xmms2 "info")
; (xmms2 "pause")
; (xmms2 "jump 10")
;
; Do NOT run (xmms2 "status")
(ns xmms2.gui
(:import (javax.swing JTable JFrame JScrollPane JPanel JLabel BoxLayout)
(javax.swing.table AbstractTableModel)
(java.io BufferedReader InputStreamReader)
(java.awt.event MouseAdapter WindowAdapter KeyAdapter KeyEvent)))
(defn- assoc-track
"[0/1] Some title here --> {0 \"Some title here\"})"
[tracks line]
(let [i1 (.indexOf line (int \[))
i2 (.indexOf line (int \/) i1)
i3 (.indexOf line (int \]) i2)]
(if (some #{-1} [i1 i2 i3])
tracks
(assoc tracks (Integer/parseInt (.substring line (inc i1) i2)) (.substring line (+ 2 i3))))))
(defmacro exec
"Execute a command on the shell, passing to the given function the lazy sequence of lines read as output, and the rest of arguments.
Errors printing to stderr are println'ed."
[cmd pred & args]
`(let [proc# (.exec (Runtime/getRuntime) ~cmd)
to-br# (fn [s#] (BufferedReader. (InputStreamReader. s#)))]
(with-open [in# (to-br# (.getInputStream proc#))
err# (to-br# (.getErrorStream proc#))]
(doseq [l# (line-seq err#)] (println l#))
(~pred (line-seq in#) ~@args))))
(defn exec-pr
"Execute a command on the shell and print its output."
[cmd]
(exec cmd
#(doseq [line %1] (println line))))
(defn find-tracks
"Query xmms2 list, returns a map of track number vs. title."
[]
(exec "xmms2 list"
(fn [lines] (reduce assoc-track {} lines))))
(defn text-width
"Measure the width, in pixels, of the String text, by the Font and FontMetrics of component."
[#^String text #^java.awt.Component component]
; Get the int[] of widths of the first 256 chars
(let [#^ints ws (.getWidths (.getFontMetrics component (.getFont component)))]
(reduce (fn [sum c]
(if (< (int c) (int 256))
(+ sum (aget ws (int c)))
sum))
0
text)))
(defn create-table []
(let [col-titles {0 "Track" 1 "Title"}
tracks (find-tracks)
model (proxy [AbstractTableModel] []
(getColumnName [col]
(if-let [title (col-titles col)]
title
""))
(getRowCount []
(count tracks))
(getColumnCount []
(count col-titles))
(getValueAt [row col]
(if (== 0 col)
(inc row)
(tracks row)))
(isCellEditable [row col]
false)
(setValueAt [ob row col] nil))
table (JTable. model)]
;(doseq [[k v] tracks]
; (println k v))
; The last track number is the largest number:
; adjust the cell width to it (plus a space to avoid '...')
(let [col-model (.. table getColumnModel (getColumn 0))
last-row (dec (count tracks))
cell (.. table (getDefaultRenderer (.getColumnClass model 0))
(getTableCellRendererComponent table last-row false false last-row 0))]
(.setMaxWidth col-model (text-width (str (.getText cell) \space) cell))
table)))
(defn xmms2
"Execute an XMMS2 command+args, and print its output."
[cmd]
(if (= (.trim cmd) "status")
(println "Can't execute 'status'!")
(exec-pr (str "xmms2 " cmd))))
(let [status-thread (ref nil)]
(defn- monitor
"Returns a function that prints the continuous output of 'xmms2 status' by repeatedly calling setText to the label."
[label]
(fn []
(with-open [br (BufferedReader.
(InputStreamReader.
(.getInputStream
(.exec (Runtime/getRuntime) "xmms2 status"))))]
; Could use (doseq [line (take-while .... but then I'd hold onto the head
; of the potentially infinite line-seq!
(loop [lines (line-seq br)]
(if (not (.isInterrupted (Thread/currentThread)))
(do
(.setText label (first lines))
(recur (rest lines))))))))
(defn monitor-status
"Monitors current song status in a separate thread
printing the status to the given label.
If called more than once, it interrupts the previous thread
and starts a new one with the given label."
[label]
(dosync (alter status-thread
(fn [old]
(if old
(.interrupt old))
(let [thread (Thread. (monitor label) "XMMS2 Status Monitor")]
(.start thread)
thread))))
(defn debug-status
[]
(println "Status thread: " @status-thread))
(defn quit-status
"Stop monitoring status."
[]
(dosync (alter status-thread
(fn [old]
(if old
(.interrupt old))
nil))))))
(defn play
"Play a song given its index in the playlist."
[row]
(xmms2 (str "jump " row)))
(defn get-id
"Return the id of the row"
[row]
(println "TODO! Returning row for now")
row)
(defn queue
"Queue a song given its index in the playlist."
[row]
(xmms2 (str "addid queue " (get-id row))))
(defn make-gui []
(let [table (create-table)
jsp (JScrollPane. table)
label (JLabel. "Not playing.")
panel (JPanel.)
frame (JFrame. "XMMS2")]
(.addMouseListener table
(proxy [MouseAdapter] []
(mousePressed [evt]
(if (= 2 (.getClickCount evt))
(let [row (.rowAtPoint table (.getPoint evt))]
(play row))))))
(doto panel
(.setLayout (BoxLayout. panel BoxLayout/Y_AXIS))
(.add label)
(.add jsp))
(doto frame
(.add panel)
(.pack)
(.setVisible true)
(.addWindowListener (proxy (WindowAdapter) []
(windowClosing [evt]
(quit-status)))))
(monitor-status label)
(let [kl (proxy (KeyAdapter) []
(keyPressed [evt]
(let [key-code (.getKeyCode evt)]
(cond
(= key-code KeyEvent/VK_SPACE) (xmms2 "togglePlay")
(= key-code KeyEvent/VK_Q) (println "Q: queuing not implemented yet.")))
(.consume evt)))]
(.addKeyListener table kl)
(.addKeyListener frame kl))))
(make-gui)