cmus-remote: Read command results
[cmus.git] / main.c
blob3ed0165ac372e8b5bea12bee62b91fbb2980e10a
1 /*
2 * Copyright 2005-2006 Timo Hirvonen
3 */
5 #include "prog.h"
6 #include "file.h"
7 #include "path.h"
8 #include "xmalloc.h"
10 #include <unistd.h>
11 #include <fcntl.h>
12 #include <sys/types.h>
13 #include <sys/socket.h>
14 #include <sys/un.h>
15 #include <netinet/in.h>
16 #include <arpa/inet.h>
17 #include <netdb.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <stdarg.h>
21 #include <errno.h>
23 static int sock;
24 static int raw_args = 0;
25 static char *passwd;
27 static void gethostbyname_failed(void)
29 const char *error = "Unknown error.";
31 switch (h_errno) {
32 case HOST_NOT_FOUND:
33 case NO_ADDRESS:
34 error = "Host not found.";
35 break;
36 case NO_RECOVERY:
37 error = "A non-recoverable name server error.";
38 break;
39 case TRY_AGAIN:
40 error = "A temporary error occurred on an authoritative name server.";
41 break;
43 die("gethostbyname: %s\n", error);
46 static void read_answer(void)
48 char buf[8192];
49 int got_nl = 0;
50 int len = 0;
52 while (1) {
53 int rc = read(sock, buf, sizeof(buf));
55 if (rc < 0) {
56 warn_errno("read");
57 return;
59 if (!rc)
60 die("unexpected EOF\n");
62 len += rc;
64 // Last line should be empty (i.e. read "\n" or "...\n\n").
65 // Write everything but the last \n to stdout.
66 if (got_nl && buf[0] == '\n')
67 return;
68 if (len == 1 && buf[0] == '\n')
69 return;
70 if (rc > 1 && buf[rc - 1] == '\n' && buf[rc - 2] == '\n') {
71 write_all(1, buf, rc - 1);
72 return;
74 got_nl = buf[rc - 1] == '\n';
75 write_all(1, buf, rc);
79 static void write_line(const char *line)
81 if (write_all(sock, line, strlen(line)) == -1)
82 die_errno("write");
84 read_answer();
87 static void send_cmd(const char *format, ...)
89 char buf[512];
90 va_list ap;
92 va_start(ap, format);
93 vsnprintf(buf, sizeof(buf), format, ap);
94 va_end(ap);
96 write_line(buf);
99 static void remote_connect(const char *address)
101 union {
102 struct sockaddr sa;
103 struct sockaddr_un un;
104 struct sockaddr_in in;
105 } addr;
106 int addrlen;
108 if (strchr(address, '/')) {
109 if (passwd)
110 warn("password ignored for unix connections\n");
111 passwd = NULL;
113 addr.sa.sa_family = AF_UNIX;
114 strncpy(addr.un.sun_path, address, sizeof(addr.un.sun_path) - 1);
116 addrlen = sizeof(addr.un);
117 } else {
118 char *s = strchr(address, ':');
119 int port = DEFAULT_PORT;
120 struct hostent *hent;
122 if (!passwd)
123 die("password required for tcp/ip connection\n");
125 if (s) {
126 *s++ = 0;
127 port = atoi(s);
129 hent = gethostbyname(address);
130 if (!hent)
131 gethostbyname_failed();
133 addr.sa.sa_family = hent->h_addrtype;
134 switch (addr.sa.sa_family) {
135 case AF_INET:
136 memcpy(&addr.in.sin_addr, hent->h_addr_list[0], hent->h_length);
137 addr.in.sin_port = htons(port);
139 addrlen = sizeof(addr.in);
140 break;
141 default:
142 die("unsupported address type\n");
146 sock = socket(addr.sa.sa_family, SOCK_STREAM, 0);
147 if (sock == -1)
148 die_errno("socket");
150 if (connect(sock, &addr.sa, addrlen)) {
151 if (errno == ENOENT || errno == ECONNREFUSED) {
152 /* "cmus-remote -C" can be used to check if cmus is running */
153 if (!raw_args)
154 warn("cmus is not running\n");
155 exit(1);
157 die_errno("connect");
160 if (passwd)
161 send_cmd("%s\n", passwd);
164 static char *file_url_absolute(const char *str)
166 char *absolute;
168 if (strncmp(str, "http://", 7) == 0)
169 return xstrdup(str);
171 absolute = path_absolute(str);
172 if (absolute == NULL)
173 die_errno("get_current_dir_name");
174 return absolute;
177 enum flags {
178 FLAG_SERVER,
179 FLAG_PASSWD,
180 FLAG_HELP,
181 FLAG_VERSION,
183 FLAG_PLAY,
184 FLAG_PAUSE,
185 FLAG_STOP,
186 FLAG_NEXT,
187 FLAG_PREV,
188 FLAG_FILE,
189 FLAG_REPEAT,
190 FLAG_SHUFFLE,
191 FLAG_VOLUME,
192 FLAG_SEEK,
194 FLAG_LIBRARY,
195 FLAG_PLAYLIST,
196 FLAG_QUEUE,
197 FLAG_CLEAR,
199 FLAG_RAW
200 #define NR_FLAGS (FLAG_RAW + 1)
203 static struct option options[NR_FLAGS + 1] = {
204 { 0, "server", 1 },
205 { 0, "passwd", 1 },
206 { 0, "help", 0 },
207 { 0, "version", 0 },
209 { 'p', "play", 0 },
210 { 'u', "pause", 0 },
211 { 's', "stop", 0 },
212 { 'n', "next", 0 },
213 { 'r', "prev", 0 },
214 { 'f', "file", 1 },
215 { 'R', "repeat", 0 },
216 { 'S', "shuffle", 0 },
217 { 'v', "volume", 1 },
218 { 'k', "seek", 1 },
220 { 'l', "library", 0 },
221 { 'P', "playlist", 0 },
222 { 'q', "queue", 0 },
223 { 'c', "clear", 0 },
225 { 'C', "raw", 0 },
226 { 0, NULL, 0 }
229 static int flags[NR_FLAGS] = { 0, };
231 static const char *usage =
232 "Usage: %s [OPTION]... [FILE|DIR|PLAYLIST]...\n"
233 " or: %s -C COMMAND...\n"
234 " or: %s\n"
235 "Control cmus through socket.\n"
236 "\n"
237 " --server ADDR connect using ADDR instead of ~/.cmus/socket\n"
238 " ADDR is either a UNIX socket or host[:port]\n"
239 " WARNING: using TCP/IP is insecure!\n"
240 " --passwd PASSWD password to use for TCP/IP connection\n"
241 " --help display this help and exit\n"
242 " --version " VERSION "\n"
243 "\n"
244 "Cooked mode:\n"
245 " -p, --play player-play\n"
246 " -u, --pause player-pause\n"
247 " -s, --stop player-stop\n"
248 " -n, --next player-next\n"
249 " -r, --prev player-prev\n"
250 " -f, --file player-play FILE\n"
251 " -R, --repeat toggle repeat\n"
252 " -S, --shuffle toggle shuffle\n"
253 " -v, --volume VOL vol VOL\n"
254 " -k, --seek SEEK seek SEEK\n"
255 "\n"
256 " -l, --library modify library instead of playlist\n"
257 " -P, --playlist modify playlist (default)\n"
258 " -q, --queue modify play queue instead of playlist\n"
259 " -c, --clear clear playlist, library (-l) or play queue (-q)\n"
260 "\n"
261 " Add FILE/DIR/PLAYLIST to playlist, library (-l) or play queue (-q).\n"
262 "\n"
263 "Raw mode:\n"
264 " -C, --raw treat arguments (instead of stdin) as raw commands\n"
265 "\n"
266 " By default cmus-remote reads raw commands from stdin (one command per line).\n"
267 "\n"
268 "Report bugs to <cmus-devel@lists.sourceforge.net>.\n";
270 int main(int argc, char *argv[])
272 char server_buf[256];
273 char *server = NULL;
274 char *play_file = NULL;
275 char *volume = NULL;
276 char *seek = NULL;
277 int i, nr_cmds = 0;
278 int context = 'p';
280 program_name = argv[0];
281 argv++;
282 while (1) {
283 int idx;
284 char *arg;
286 idx = get_option(&argv, options, &arg);
287 if (idx < 0)
288 break;
290 flags[idx] = 1;
291 switch ((enum flags)idx) {
292 case FLAG_HELP:
293 printf(usage, program_name, program_name, program_name);
294 return 0;
295 case FLAG_VERSION:
296 printf("cmus " VERSION "\nCopyright 2004-2006 Timo Hirvonen\n");
297 return 0;
298 case FLAG_SERVER:
299 server = arg;
300 break;
301 case FLAG_PASSWD:
302 passwd = arg;
303 break;
304 case FLAG_VOLUME:
305 volume = arg;
306 nr_cmds++;
307 break;
308 case FLAG_SEEK:
309 seek = arg;
310 nr_cmds++;
311 break;
312 case FLAG_FILE:
313 play_file = arg;
314 nr_cmds++;
315 break;
316 case FLAG_LIBRARY:
317 context = 'l';
318 break;
319 case FLAG_PLAYLIST:
320 context = 'p';
321 break;
322 case FLAG_QUEUE:
323 context = 'q';
324 break;
325 case FLAG_PLAY:
326 case FLAG_PAUSE:
327 case FLAG_STOP:
328 case FLAG_NEXT:
329 case FLAG_PREV:
330 case FLAG_REPEAT:
331 case FLAG_SHUFFLE:
332 case FLAG_CLEAR:
333 nr_cmds++;
334 break;
335 case FLAG_RAW:
336 raw_args = 1;
337 break;
341 if (nr_cmds && raw_args)
342 die("don't mix raw and cooked stuff\n");
344 if (server == NULL) {
345 const char *config_dir = getenv("CMUS_HOME");
347 if (config_dir && config_dir[0]) {
348 snprintf(server_buf, sizeof(server_buf), "%s/socket", config_dir);
349 } else {
350 const char *home = getenv("HOME");
352 if (!home)
353 die("error: environment variable HOME not set\n");
354 snprintf(server_buf, sizeof(server_buf), "%s/.cmus/socket", home);
356 server = server_buf;
359 remote_connect(server);
361 if (raw_args) {
362 while (*argv)
363 send_cmd("%s\n", *argv++);
364 return 0;
367 if (nr_cmds == 0 && argv[0] == NULL) {
368 char line[512];
370 while (fgets(line, sizeof(line), stdin))
371 write_line(line);
372 return 0;
375 if (flags[FLAG_CLEAR])
376 send_cmd("clear -%c\n", context);
377 for (i = 0; argv[i]; i++) {
378 char *filename = file_url_absolute(argv[i]);
380 send_cmd("add -%c %s\n", context, filename);
381 free(filename);
383 if (flags[FLAG_REPEAT])
384 send_cmd("toggle repeat\n");
385 if (flags[FLAG_SHUFFLE])
386 send_cmd("toggle shuffle\n");
387 if (flags[FLAG_STOP])
388 send_cmd("player-stop\n");
389 if (flags[FLAG_NEXT])
390 send_cmd("player-next\n");
391 if (flags[FLAG_PREV])
392 send_cmd("player-prev\n");
393 if (flags[FLAG_PLAY])
394 send_cmd("player-play\n");
395 if (flags[FLAG_PAUSE])
396 send_cmd("player-pause\n");
397 if (flags[FLAG_FILE])
398 send_cmd("player-play %s\n", file_url_absolute(play_file));
399 if (volume)
400 send_cmd("vol %s\n", volume);
401 if (seek)
402 send_cmd("seek %s\n", seek);
403 return 0;