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