Christophe Weblog Wiki Code Publications Music
improve squeeze-control startup
[squeeze-el.git] / squeeze.el
1 (defgroup squeeze nil
2   "Interact with Squeezebox media servers"
3   :prefix "squeeze-" 
4   :group 'applications)
5
6 (defcustom squeeze-server-address "localhost"
7   "Address for the Squeezebox server"
8   :group 'squeeze)
9 (defcustom squeeze-server-port 9090
10   "Port number for the Squeezebox server"
11   :group 'squeeze)
12
13 (defvar squeeze-mode-map
14   (let ((map (make-sparse-keymap)))
15     (define-key map (kbd "TAB") 'completion-at-point)
16     map))
17
18 (define-derived-mode squeeze-mode comint-mode "Squeeze"
19   "Major mode for interacting with the Squeezebox Server CLI.\\<squeeze-mode-map>"
20   (add-hook 'comint-preoutput-filter-functions 'url-unhex-string nil t)
21   (add-hook 'comint-preoutput-filter-functions 'squeeze-update-state nil t))
22
23 (defvar squeeze-control-mode-map
24   (let ((map (make-sparse-keymap)))
25     (define-key map (kbd "SPC") 'squeeze-control-toggle-power)
26     (define-key map (kbd "g") 'squeeze-control-refresh)
27     map))
28
29 (define-derived-mode squeeze-control-mode special-mode "SqueezeControl"
30   "Major mode for controlling Squeezebox Servers.\\<squeeze-control-mode-map>")
31
32 (defun squeeze-update-state (string)
33   (let (done-something)
34     (dolist (line (split-string string "\n"))
35       (when (squeeze-update-state-from-line line)
36         (setq done-something t))))
37   (squeeze-control-display-players)
38   string)
39
40 (defun squeeze-update-state-from-line (string)
41   (cond
42    ((string-match "^players 0" string)
43     (setq squeeze-players (squeeze-parse-players-line string))
44     t)
45    ((string-match "^\\([0-9a-f][0-9a-f]%3A[0-9a-f][0-9a-f]%3A[0-9a-f][0-9a-f]%3A[0-9a-f][0-9a-f]%3A[0-9a-f][0-9a-f]%3A[0-9a-f][0-9a-f]\\) power\\( \\([01]\\)\\)?" string)
46     (let ((state (match-string 3 string))
47           (id (url-unhex-string (match-string 1 string)))
48           player)
49       (dolist (p squeeze-players)
50         (when (string= id (squeeze-player-playerid p))
51           (setq player p)
52           (return)))
53       (if state
54           (setf (squeeze-player-power player) state)
55         (let ((current (squeeze-player-power player)))
56           (setf (squeeze-player-power player)
57                 (cond ((string= current "0") "1")
58                       ((string= current "1") "0"))))))
59     t)))
60
61 (defface squeeze-player-face
62   '((t))
63   "Face for displaying players"
64   :group 'squeeze)
65 (defface squeeze-player-on-face
66   '((t :weight bold))
67   "Face for displaying players which are on"
68   :inherit 'squeeze-player-face
69   :group 'squeeze)
70 (defface squeeze-player-off-face
71   '((t :weight light))
72   "Face for displaying players which are off"
73   :inherit 'squeeze-player-face
74   :group 'squeeze)
75
76 (defvar squeeze-players ())
77
78 (defun squeeze-control-query-players ()
79   (interactive)
80   (comint-send-string (get-buffer-process "*squeeze*") (format "players ?\n")))
81
82 (defun squeeze-control-toggle-power (&optional id)
83   (interactive)
84   (unless id
85     (setq id (get-text-property (point) 'squeeze-playerid)))
86   (comint-send-string (get-buffer-process "*squeeze*") (format "%s power\n" id)))
87
88 (defun squeeze-control-query-power (&optional id)
89   (interactive)
90   (unless id
91     (setq id (get-text-property (point) 'squeeze-playerid)))
92   (comint-send-string (get-buffer-process "*squeeze*") (format "%s power ?\n" id)))
93
94 (defun squeeze-control-player-face (player)
95   (let ((power (squeeze-player-power player)))
96     (cond ((string= power "1") 'squeeze-player-on-face)
97           ((string= power "0") 'squeeze-player-off-face)
98           (t 'squeeze-player-face))))
99
100 (defun squeeze-control-listen ()
101   (comint-send-string (get-buffer-process "*squeeze*") (format "listen 1\n")))
102
103 (defun squeeze-control-refresh ()
104   (interactive)
105   (squeeze-control-query-players)
106   (dolist (player squeeze-players)
107     (squeeze-control-query-power (squeeze-player-playerid player))))
108
109 (defun squeeze-control-display-players ()
110   (interactive)
111   (let ((players squeeze-players))
112     (with-current-buffer (get-buffer-create "*squeeze-control*")
113       (squeeze-control-mode)
114       (read-only-mode -1)
115       (erase-buffer)
116       (dolist (player players)
117         (insert (propertize (squeeze-player-name player)
118                             'face (squeeze-control-player-face player)
119                             'squeeze-playerid (squeeze-player-playerid player))
120                 "\n"))
121       (read-only-mode 1))))
122
123 (cl-defstruct (squeeze-player (:constructor squeeze-make-player))
124   playerindex playerid uuid ip name model isplayer displaytype canpoweroff connected power)
125
126 (defun squeeze-string-plistify (string start end)
127   (save-match-data
128     (let (result)
129       (loop
130        (message "start: %d" start)
131        (if (string-match "\\([a-z]+\\)%3A\\([^ \n]+\\)" string start)
132            (let ((mend (match-end 0)))
133              (when (> mend end)
134                (return))
135              (push (intern (format ":%s" (substring string (match-beginning 1) (match-end 1)))) result)
136              (push (url-unhex-string (substring string (match-beginning 2) (match-end 2))) result)
137              (setq start mend))
138          (return)))
139       (nreverse result))))
140
141 (defun squeeze-parse-players-line (string)
142   (let ((countpos (string-match " count%3A\\([0-9]\\) " string))
143         (startpos (match-end 0)))
144     (unless countpos
145       (message "no count found in players line"))
146     (let ((count (parse-integer string (match-beginning 1) (match-end 1)))
147           result endpos)
148       (dotimes (i (1- count))
149         (setq endpos (progn (string-match " connected%3A[0-1] " string startpos)
150                             (match-end 0)))
151         (push (apply 'squeeze-make-player (squeeze-string-plistify string startpos endpos)) result)
152         (setq startpos endpos))
153       (push (apply 'squeeze-make-player (squeeze-string-plistify string startpos (length string))) result)
154       result)))
155
156 (defun squeeze-complete-command-at-point ()
157   (save-excursion
158     (list (progn (backward-word) (point))
159           (progn (forward-word) (point))
160           '(;; General commands and queries
161             "login" "can" "version" "listen" "subscribe" "pref"
162             "logging" "getstring" "setsncredentials" "debug"
163             "exit" "shutdown"
164
165             ;; Player commands and queries
166             "player" "count" "id" "uuid" "name" "ip" "model" "isplayer"
167             "displaytype" "canpoweroff" "?" "signalstrength" "connected"
168             "sleep" "sync" "syncgroups" "power" "mixer" "volume" "muting"
169             "bass" "treble" "pitch" "show" "display" "linesperscreen"
170             "displaynow" "playerpref" "button" "ir" "irenable"
171             "connect" "client" "forget" "disconnect" "players"
172             
173             ;; Database commands and queries
174             "rescan" "rescanprogress" "abortscan" "wipecache" "info"
175             "total" "genres" "artists" "albums" "songs" "years"
176             "musicfolder" "playlists" "tracks" "new" "rename" "delete"
177             "edit" "songinfo" "titles" "search" "pragma"
178
179             ;; Playlist commands and queries
180             "play" "stop" "pause" "mode" "time" "genre" "artist" "album"
181             "title" "duration" "remote" "current_title" "path" "playlist"
182             "add" "insert" "deleteitem" "move" "delete" "preview" "resume"
183             "save" "loadalbum" "addalbum" "loadtracks" "addtracks"
184             "insertalbum" "deletealbum" "clear" "zap" "name" "url"
185             "modified" "playlistsinfo" "index" "shuffle" "repeat"
186             "playlistcontrol"
187
188             ;; Compound queries
189             "serverstatus" "status" "displaystatus" "readdirectory"
190
191             ;; Notifications
192             
193             ;; Alarm commands and queries
194             "alarm" "alarms"
195
196             ;; Plugins commands and queries
197             "favorites"
198             ))))
199
200 (defun squeeze ()
201   (interactive)
202   (let ((buffer (make-comint-in-buffer "squeeze" nil
203                                        (cons squeeze-server-address
204                                              squeeze-server-port))))
205     (switch-to-buffer buffer)
206     (squeeze-mode)))
207
208 (defun squeeze-control ()
209   (interactive)
210   (squeeze)
211   (let ((buffer (get-buffer-create "*squeeze-control*")))
212     (switch-to-buffer buffer)
213     (squeeze-control-listen)
214     (squeeze-control-refresh)
215     (squeeze-control-display-players)))