Christophe Weblog Wiki Code Publications Music
e669b0c9edadcc945c8c40717c0e99cde5104c03
[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-display-players)
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   (dolist (line (split-string string "\n"))
34     (squeeze-update-state-from-line line))
35   string)
36
37 (defun squeeze-update-state-from-line (string)
38   (cond
39    ((string-match "^players 0" string)
40     (setq squeeze-players (squeeze-parse-players-line string)))
41    ((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)
42     (let ((state (match-string 2 string))
43           (id (url-unhex-string (match-string 1 string)))
44           player)
45       (dolist (p squeeze-players)
46         (when (string= id (squeeze-player-playerid p))
47           (setq player p)
48           (return)))
49       (setf (squeeze-player-power player) state)))))
50
51 (defface squeeze-player-face
52   '((t))
53   "Face for displaying players")
54
55 (defface squeeze-player-on-face
56   '((t :weight bold))
57   "Face for displaying players which are on")
58 (defface squeeze-player-off-face
59   '((t :weight light))
60   "Face for displaying players which are on")
61
62 (defvar squeeze-players ())
63
64 (defun squeeze-control-toggle-power (&optional id)
65   (interactive)
66   (unless id
67     (setq id (get-text-property (point) 'squeeze-playerid)))
68   (comint-send-string (get-buffer-process "*squeeze*") (format "%s power\n" id)))
69
70 (defun squeeze-control-query-power (&optional id)
71   (interactive)
72   (unless id
73     (setq id (get-text-property (point) 'squeeze-playerid)))
74   (comint-send-string (get-buffer-process "*squeeze*") (format "%s power ?\n" id))
75   (accept-process-output (get-buffer-process "*squeeze*")))
76
77 (defun squeeze-control-player-face (player)
78   (let ((power (squeeze-player-power player)))
79     (cond ((string= power "1") 'squeeze-player-on-face)
80           ((string= power "0") 'squeeze-player-off-face)
81           (t 'squeeze-player-face))))
82
83 (defun squeeze-control-display-players ()
84   (interactive)
85   (dolist (player squeeze-players)
86     (squeeze-control-query-power (squeeze-player-playerid player)))
87   (let ((players squeeze-players))
88     (with-current-buffer (get-buffer-create "*squeeze-control*")
89       (squeeze-control-mode)
90       (read-only-mode -1)
91       (erase-buffer)
92       (dolist (player players)
93         (insert (propertize (squeeze-player-name player)
94                             'face (squeeze-control-player-face player)
95                             'squeeze-playerid (squeeze-player-playerid player))
96                 "\n"))
97       (read-only-mode 1))))
98
99 (cl-defstruct (squeeze-player (:constructor squeeze-make-player))
100   playerindex playerid uuid ip name model isplayer displaytype canpoweroff connected power)
101
102 (defun squeeze-string-plistify (string start end)
103   (save-match-data
104     (let (result)
105       (loop
106        (message "start: %d" start)
107        (if (string-match "\\([a-z]+\\)%3A\\([^ \n]+\\)" string start)
108            (let ((mend (match-end 0)))
109              (when (> mend end)
110                (return))
111              (push (intern (format ":%s" (substring string (match-beginning 1) (match-end 1)))) result)
112              (push (url-unhex-string (substring string (match-beginning 2) (match-end 2))) result)
113              (setq start mend))
114          (return)))
115       (nreverse result))))
116
117 (defun squeeze-parse-players-line (string)
118   (let ((countpos (string-match " count%3A\\([0-9]\\) " string))
119         (startpos (match-end 0)))
120     (unless countpos
121       (message "no count found in players line"))
122     (let ((count (parse-integer string (match-beginning 1) (match-end 1)))
123           result endpos)
124       (dotimes (i (1- count))
125         (setq endpos (progn (string-match " connected%3A[0-1] " string startpos)
126                             (match-end 0)))
127         (push (apply 'squeeze-make-player (squeeze-string-plistify string startpos endpos)) result)
128         (setq startpos endpos))
129       (push (apply 'squeeze-make-player (squeeze-string-plistify string startpos (length string))) result)
130       result)))
131
132 (defun squeeze-complete-command-at-point ()
133   (save-excursion
134     (list (progn (backward-word) (point))
135           (progn (forward-word) (point))
136           '(;; General commands and queries
137             "login" "can" "version" "listen" "subscribe" "pref"
138             "logging" "getstring" "setsncredentials" "debug"
139             "exit" "shutdown"
140
141             ;; Player commands and queries
142             "player" "count" "id" "uuid" "name" "ip" "model" "isplayer"
143             "displaytype" "canpoweroff" "?" "signalstrength" "connected"
144             "sleep" "sync" "syncgroups" "power" "mixer" "volume" "muting"
145             "bass" "treble" "pitch" "show" "display" "linesperscreen"
146             "displaynow" "playerpref" "button" "ir" "irenable"
147             "connect" "client" "forget" "disconnect" "players"
148             
149             ;; Database commands and queries
150             "rescan" "rescanprogress" "abortscan" "wipecache" "info"
151             "total" "genres" "artists" "albums" "songs" "years"
152             "musicfolder" "playlists" "tracks" "new" "rename" "delete"
153             "edit" "songinfo" "titles" "search" "pragma"
154
155             ;; Playlist commands and queries
156             "play" "stop" "pause" "mode" "time" "genre" "artist" "album"
157             "title" "duration" "remote" "current_title" "path" "playlist"
158             "add" "insert" "deleteitem" "move" "delete" "preview" "resume"
159             "save" "loadalbum" "addalbum" "loadtracks" "addtracks"
160             "insertalbum" "deletealbum" "clear" "zap" "name" "url"
161             "modified" "playlistsinfo" "index" "shuffle" "repeat"
162             "playlistcontrol"
163
164             ;; Compound queries
165             "serverstatus" "status" "displaystatus" "readdirectory"
166
167             ;; Notifications
168             
169             ;; Alarm commands and queries
170             "alarm" "alarms"
171
172             ;; Plugins commands and queries
173             "favorites"
174             ))))
175
176 (defun squeeze ()
177   (interactive)
178   (let ((buffer (make-comint-in-buffer "squeeze" nil
179                                        (cons squeeze-server-address
180                                              squeeze-server-port))))
181     (switch-to-buffer buffer)
182     (squeeze-mode)))
183
184 (defun squeeze-control ()
185   (interactive)
186   (squeeze)
187   (let ((buffer (get-buffer-create "*squeeze-control*")))
188     (switch-to-buffer buffer)
189     (squeeze-control-display-players)))