Handle streams separately in tree_add_track()
[cmus.git] / main.c
blob9d013d48e729dbaf48b6edbd0f44f38f4ad01a97
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,
193 FLAG_QUERY,
195 FLAG_LIBRARY,
196 FLAG_PLAYLIST,
197 FLAG_QUEUE,
198 FLAG_CLEAR,
200 FLAG_RAW
201 #define NR_FLAGS (FLAG_RAW + 1)
204 static struct option options[NR_FLAGS + 1] = {
205 { 0, "server", 1 },
206 { 0, "passwd", 1 },
207 { 0, "help", 0 },
208 { 0, "version", 0 },
210 { 'p', "play", 0 },
211 { 'u', "pause", 0 },
212 { 's', "stop", 0 },
213 { 'n', "next", 0 },
214 { 'r', "prev", 0 },
215 { 'f', "file", 1 },
216 { 'R', "repeat", 0 },
217 { 'S', "shuffle", 0 },
218 { 'v', "volume", 1 },
219 { 'k', "seek", 1 },
220 { 'Q', "query", 0 },
222 { 'l', "library", 0 },
223 { 'P', "playlist", 0 },
224 { 'q', "queue", 0 },
225 { 'c', "clear", 0 },
227 { 'C', "raw", 0 },
228 { 0, NULL, 0 }
231 static int flags[NR_FLAGS] = { 0, };
233 static const char *usage =
234 "Usage: %s [OPTION]... [FILE|DIR|PLAYLIST]...\n"
235 " or: %s -C COMMAND...\n"
236 " or: %s\n"
237 "Control cmus through socket.\n"
238 "\n"
239 " --server ADDR connect using ADDR instead of ~/.cmus/socket\n"
240 " ADDR is either a UNIX socket or host[:port]\n"
241 " WARNING: using TCP/IP is insecure!\n"
242 " --passwd PASSWD password to use for TCP/IP connection\n"
243 " --help display this help and exit\n"
244 " --version " VERSION "\n"
245 "\n"
246 "Cooked mode:\n"
247 " -p, --play player-play\n"
248 " -u, --pause player-pause\n"
249 " -s, --stop player-stop\n"
250 " -n, --next player-next\n"
251 " -r, --prev player-prev\n"
252 " -f, --file player-play FILE\n"
253 " -R, --repeat toggle repeat\n"
254 " -S, --shuffle toggle shuffle\n"
255 " -v, --volume VOL vol VOL\n"
256 " -k, --seek SEEK seek SEEK\n"
257 " -Q, --query get player status (same as -C status)\n"
258 "\n"
259 " -l, --library modify library instead of playlist\n"
260 " -P, --playlist modify playlist (default)\n"
261 " -q, --queue modify play queue instead of playlist\n"
262 " -c, --clear clear playlist, library (-l) or play queue (-q)\n"
263 "\n"
264 " Add FILE/DIR/PLAYLIST to playlist, library (-l) or play queue (-q).\n"
265 "\n"
266 "Raw mode:\n"
267 " -C, --raw treat arguments (instead of stdin) as raw commands\n"
268 "\n"
269 " By default cmus-remote reads raw commands from stdin (one command per line).\n"
270 "\n"
271 "Report bugs to <cmus-devel@lists.sourceforge.net>.\n";
273 int main(int argc, char *argv[])
275 char server_buf[256];
276 char *server = NULL;
277 char *play_file = NULL;
278 char *volume = NULL;
279 char *seek = NULL;
280 int query = 0;
281 int i, nr_cmds = 0;
282 int context = 'p';
284 program_name = argv[0];
285 argv++;
286 while (1) {
287 int idx;
288 char *arg;
290 idx = get_option(&argv, options, &arg);
291 if (idx < 0)
292 break;
294 flags[idx] = 1;
295 switch ((enum flags)idx) {
296 case FLAG_HELP:
297 printf(usage, program_name, program_name, program_name);
298 return 0;
299 case FLAG_VERSION:
300 printf("cmus " VERSION "\nCopyright 2004-2006 Timo Hirvonen\n");
301 return 0;
302 case FLAG_SERVER:
303 server = arg;
304 break;
305 case FLAG_PASSWD:
306 passwd = arg;
307 break;
308 case FLAG_VOLUME:
309 volume = arg;
310 nr_cmds++;
311 break;
312 case FLAG_SEEK:
313 seek = arg;
314 nr_cmds++;
315 break;
316 case FLAG_QUERY:
317 query = 1;
318 nr_cmds++;
319 break;
320 case FLAG_FILE:
321 play_file = arg;
322 nr_cmds++;
323 break;
324 case FLAG_LIBRARY:
325 context = 'l';
326 break;
327 case FLAG_PLAYLIST:
328 context = 'p';
329 break;
330 case FLAG_QUEUE:
331 context = 'q';
332 break;
333 case FLAG_PLAY:
334 case FLAG_PAUSE:
335 case FLAG_STOP:
336 case FLAG_NEXT:
337 case FLAG_PREV:
338 case FLAG_REPEAT:
339 case FLAG_SHUFFLE:
340 case FLAG_CLEAR:
341 nr_cmds++;
342 break;
343 case FLAG_RAW:
344 raw_args = 1;
345 break;
349 if (nr_cmds && raw_args)
350 die("don't mix raw and cooked stuff\n");
352 if (server == NULL) {
353 const char *config_dir = getenv("CMUS_HOME");
355 if (config_dir && config_dir[0]) {
356 snprintf(server_buf, sizeof(server_buf), "%s/socket", config_dir);
357 } else {
358 const char *home = getenv("HOME");
360 if (!home)
361 die("error: environment variable HOME not set\n");
362 snprintf(server_buf, sizeof(server_buf), "%s/.cmus/socket", home);
364 server = server_buf;
367 remote_connect(server);
369 if (raw_args) {
370 while (*argv)
371 send_cmd("%s\n", *argv++);
372 return 0;
375 if (nr_cmds == 0 && argv[0] == NULL) {
376 char line[512];
378 while (fgets(line, sizeof(line), stdin))
379 write_line(line);
380 return 0;
383 if (flags[FLAG_CLEAR])
384 send_cmd("clear -%c\n", context);
385 for (i = 0; argv[i]; i++) {
386 char *filename = file_url_absolute(argv[i]);
388 send_cmd("add -%c %s\n", context, filename);
389 free(filename);
391 if (flags[FLAG_REPEAT])
392 send_cmd("toggle repeat\n");
393 if (flags[FLAG_SHUFFLE])
394 send_cmd("toggle shuffle\n");
395 if (flags[FLAG_STOP])
396 send_cmd("player-stop\n");
397 if (flags[FLAG_NEXT])
398 send_cmd("player-next\n");
399 if (flags[FLAG_PREV])
400 send_cmd("player-prev\n");
401 if (flags[FLAG_PLAY])
402 send_cmd("player-play\n");
403 if (flags[FLAG_PAUSE])
404 send_cmd("player-pause\n");
405 if (flags[FLAG_FILE])
406 send_cmd("player-play %s\n", file_url_absolute(play_file));
407 if (volume)
408 send_cmd("vol %s\n", volume);
409 if (seek)
410 send_cmd("seek %s\n", seek);
411 if (query)
412 send_cmd("status\n");
413 return 0;