1
/*
2
 * Copyright 2008-2011 Various Authors
3
 * Copyright 2005-2006 Timo Hirvonen
4
 *
5
 * This program is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU General Public License as
7
 * published by the Free Software Foundation; either version 2 of the
8
 * License, or (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful, but
11
 * WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
 * General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17
 */
18
19
#include "prog.h"
20
#include "file.h"
21
#include "path.h"
22
#include "xmalloc.h"
23
#include "utils.h"
24
25
#include <unistd.h>
26
#include <fcntl.h>
27
#include <sys/types.h>
28
#include <sys/socket.h>
29
#include <sys/un.h>
30
#include <netinet/in.h>
31
#include <arpa/inet.h>
32
#include <netdb.h>
33
#include <stdio.h>
34
#include <stdlib.h>
35
#include <stdarg.h>
36
#include <errno.h>
37
38
static int sock;
39
static int raw_args = 0;
40
static char *passwd;
41
42
static int read_answer(void)
43
{
44
	char buf[8192];
45
	int got_nl = 0;
46
	int len = 0;
47
48
	while (1) {
49
		int rc = read(sock, buf, sizeof(buf));
50
51
		if (rc < 0) {
52
			warn_errno("read");
53
			break;
54
		}
55
		if (!rc)
56
			die("unexpected EOF\n");
57
58
		len += rc;
59
60
		// Last line should be empty (i.e. read "\n" or "...\n\n").
61
		// Write everything but the last \n to stdout.
62
		if (got_nl && buf[0] == '\n')
63
			break;
64
		if (len == 1 && buf[0] == '\n')
65
			break;
66
		if (rc > 1 && buf[rc - 1] == '\n' && buf[rc - 2] == '\n') {
67
			write_all(1, buf, rc - 1);
68
			break;
69
		}
70
		got_nl = buf[rc - 1] == '\n';
71
		write_all(1, buf, rc);
72
	}
73
	return len;
74
}
75
76
static int write_line(const char *line)
77
{
78
	if (write_all(sock, line, strlen(line)) == -1)
79
		die_errno("write");
80
81
	return read_answer();
82
}
83
84
static int send_cmd(const char *format, ...)
85
{
86
	char buf[512];
87
	va_list ap;
88
89
	va_start(ap, format);
90
	vsnprintf(buf, sizeof(buf), format, ap);
91
	va_end(ap);
92
93
	return write_line(buf);
94
}
95
96
static int remote_connect(const char *address)
97
{
98
	union {
99
		struct sockaddr sa;
100
		struct sockaddr_un un;
101
		struct sockaddr_storage sas;
102
	} addr;
103
	size_t addrlen;
104
105
	if (strchr(address, '/')) {
106
		if (passwd)
107
			warn("password ignored for unix connections\n");
108
		passwd = NULL;
109
110
		addr.sa.sa_family = AF_UNIX;
111
		strncpy(addr.un.sun_path, address, sizeof(addr.un.sun_path) - 1);
112
113
		addrlen = sizeof(addr.un);
114
	} else {
115
		const struct addrinfo hints = {
116
			.ai_socktype = SOCK_STREAM
117
		};
118
		const char *port = STRINGIZE(DEFAULT_PORT);
119
		struct addrinfo *result;
120
		char *s = strrchr(address, ':');
121
		int rc;
122
123
		if (!passwd)
124
			die("password required for tcp/ip connection\n");
125
126
		if (s) {
127
			*s++ = 0;
128
			port = s;
129
		}
130
131
		rc = getaddrinfo(address, port, &hints, &result);
132
		if (rc != 0)
133
			die("getaddrinfo: %s\n", gai_strerror(rc));
134
		memcpy(&addr.sa, result->ai_addr, result->ai_addrlen);
135
		addrlen = result->ai_addrlen;
136
		freeaddrinfo(result);
137
	}
138
139
	sock = socket(addr.sa.sa_family, SOCK_STREAM, 0);
140
	if (sock == -1)
141
		die_errno("socket");
142
143
	if (connect(sock, &addr.sa, addrlen)) {
144
		if (errno == ENOENT || errno == ECONNREFUSED) {
145
			/* "cmus-remote -C" can be used to check if cmus is running */
146
			if (!raw_args)
147
				warn("cmus is not running\n");
148
			exit(1);
149
		}
150
		die_errno("connect");
151
	}
152
153
	if (passwd)
154
		return send_cmd("passwd %s\n", passwd) == 1;
155
	return 1;
156
}
157
158
static char *file_url_absolute(const char *str)
159
{
160
	char *absolute;
161
162
	if (strncmp(str, "http://", 7) == 0)
163
		return xstrdup(str);
164
165
	absolute = path_absolute(str);
166
	if (absolute == NULL)
167
		die_errno("get_current_dir_name");
168
	return absolute;
169
}
170
171
enum flags {
172
	FLAG_SERVER,
173
	FLAG_PASSWD,
174
	FLAG_HELP,
175
	FLAG_VERSION,
176
177
	FLAG_PLAY,
178
	FLAG_PAUSE,
179
	FLAG_STOP,
180
	FLAG_NEXT,
181
	FLAG_PREV,
182
	FLAG_FILE,
183
	FLAG_REPEAT,
184
	FLAG_SHUFFLE,
185
	FLAG_VOLUME,
186
	FLAG_SEEK,
187
	FLAG_QUERY,
188
189
	FLAG_LIBRARY,
190
	FLAG_PLAYLIST,
191
	FLAG_QUEUE,
192
	FLAG_CLEAR,
193
194
	FLAG_RAW
195
#define NR_FLAGS (FLAG_RAW + 1)
196
};
197
198
static struct option options[NR_FLAGS + 1] = {
199
	{ 0, "server", 1 },
200
	{ 0, "passwd", 1 },
201
	{ 0, "help", 0 },
202
	{ 0, "version", 0 },
203
204
	{ 'p', "play", 0 },
205
	{ 'u', "pause", 0 },
206
	{ 's', "stop", 0 },
207
	{ 'n', "next", 0 },
208
	{ 'r', "prev", 0 },
209
	{ 'f', "file", 1 },
210
	{ 'R', "repeat", 0 },
211
	{ 'S', "shuffle", 0 },
212
	{ 'v', "volume", 1 },
213
	{ 'k', "seek", 1 },
214
	{ 'Q', "query", 0 },
215
216
	{ 'l', "library", 0 },
217
	{ 'P', "playlist", 0 },
218
	{ 'q', "queue", 0 },
219
	{ 'c', "clear", 0 },
220
221
	{ 'C', "raw", 0 },
222
	{ 0, NULL, 0 }
223
};
224
225
static int flags[NR_FLAGS] = { 0, };
226
227
static const char *usage =
228
"Usage: %s [OPTION]... [FILE|DIR|PLAYLIST]...\n"
229
"   or: %s -C COMMAND...\n"
230
"   or: %s\n"
231
"Control cmus through socket.\n"
232
"\n"
233
"      --server ADDR    connect using ADDR instead of ~/.cmus/socket\n"
234
"                       ADDR is either a UNIX socket or host[:port]\n"
235
"                       WARNING: using TCP/IP is insecure!\n"
236
"      --passwd PASSWD  password to use for TCP/IP connection\n"
237
"      --help           display this help and exit\n"
238
"      --version        " VERSION "\n"
239
"\n"
240
"Cooked mode:\n"
241
"  -p, --play           player-play\n"
242
"  -u, --pause          player-pause\n"
243
"  -s, --stop           player-stop\n"
244
"  -n, --next           player-next\n"
245
"  -r, --prev           player-prev\n"
246
"  -f, --file           player-play FILE\n"
247
"  -R, --repeat         toggle repeat\n"
248
"  -S, --shuffle        toggle shuffle\n"
249
"  -v, --volume VOL     vol VOL\n"
250
"  -k, --seek SEEK      seek SEEK\n"
251
"  -Q, --query          get player status (same as -C status)\n"
252
"\n"
253
"  -l, --library        modify library instead of playlist\n"
254
"  -P, --playlist       modify playlist (default)\n"
255
"  -q, --queue          modify play queue instead of playlist\n"
256
"  -c, --clear          clear playlist, library (-l) or play queue (-q)\n"
257
"\n"
258
"  Add FILE/DIR/PLAYLIST to playlist, library (-l) or play queue (-q).\n"
259
"\n"
260
"Raw mode:\n"
261
"  -C, --raw            treat arguments (instead of stdin) as raw commands\n"
262
"\n"
263
"  By default cmus-remote reads raw commands from stdin (one command per line).\n"
264
"\n"
265
"Report bugs to <cmus-devel@lists.sourceforge.net>.\n";
266
267
int main(int argc, char *argv[])
268
{
269
	char server_buf[256];
270
	char *server = NULL;
271
	char *play_file = NULL;
272
	char *volume = NULL;
273
	char *seek = NULL;
274
	int query = 0;
275
	int i, nr_cmds = 0;
276
	int context = 'p';
277
278
	program_name = argv[0];
279
	argv++;
280
	while (1) {
281
		int idx;
282
		char *arg;
283
284
		idx = get_option(&argv, options, &arg);
285
		if (idx < 0)
286
			break;
287
288
		flags[idx] = 1;
289
		switch ((enum flags)idx) {
290
		case FLAG_HELP:
291
			printf(usage, program_name, program_name, program_name);
292
			return 0;
293
		case FLAG_VERSION:
294
			printf("cmus " VERSION
295
			       "\nCopyright 2004-2006 Timo Hirvonen"
296
			       "\nCopyright 2008-2011 Various Authors\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_QUERY:
313
			query = 1;
314
			nr_cmds++;
315
			break;
316
		case FLAG_FILE:
317
			play_file = arg;
318
			nr_cmds++;
319
			break;
320
		case FLAG_LIBRARY:
321
			context = 'l';
322
			break;
323
		case FLAG_PLAYLIST:
324
			context = 'p';
325
			break;
326
		case FLAG_QUEUE:
327
			context = 'q';
328
			break;
329
		case FLAG_PLAY:
330
		case FLAG_PAUSE:
331
		case FLAG_STOP:
332
		case FLAG_NEXT:
333
		case FLAG_PREV:
334
		case FLAG_REPEAT:
335
		case FLAG_SHUFFLE:
336
		case FLAG_CLEAR:
337
			nr_cmds++;
338
			break;
339
		case FLAG_RAW:
340
			raw_args = 1;
341
			break;
342
		}
343
	}
344
345
	if (nr_cmds && raw_args)
346
		die("don't mix raw and cooked stuff\n");
347
348
	if (server == NULL) {
349
		const char *config_dir = getenv("CMUS_HOME");
350
351
		if (config_dir && config_dir[0]) {
352
			snprintf(server_buf, sizeof(server_buf), "%s/socket", config_dir);
353
		} else {
354
			const char *home = getenv("HOME");
355
356
			if (!home)
357
				die("error: environment variable HOME not set\n");
358
			snprintf(server_buf, sizeof(server_buf), "%s/.cmus/socket", home);
359
		}
360
		server = server_buf;
361
	}
362
363
	if (!remote_connect(server))
364
		return 1;
365
366
	if (raw_args) {
367
		while (*argv)
368
			send_cmd("%s\n", *argv++);
369
		return 0;
370
	}
371
372
	if (nr_cmds == 0 && argv[0] == NULL) {
373
		char line[512];
374
375
		while (fgets(line, sizeof(line), stdin))
376
			write_line(line);
377
		return 0;
378
	}
379
380
	if (flags[FLAG_CLEAR])
381
		send_cmd("clear -%c\n", context);
382
	for (i = 0; argv[i]; i++) {
383
		char *filename = file_url_absolute(argv[i]);
384
385
		send_cmd("add -%c %s\n", context, filename);
386
		free(filename);
387
	}
388
	if (flags[FLAG_REPEAT])
389
		send_cmd("toggle repeat\n");
390
	if (flags[FLAG_SHUFFLE])
391
		send_cmd("toggle shuffle\n");
392
	if (flags[FLAG_STOP])
393
		send_cmd("player-stop\n");
394
	if (flags[FLAG_NEXT])
395
		send_cmd("player-next\n");
396
	if (flags[FLAG_PREV])
397
		send_cmd("player-prev\n");
398
	if (flags[FLAG_PLAY])
399
		send_cmd("player-play\n");
400
	if (flags[FLAG_PAUSE])
401
		send_cmd("player-pause\n");
402
	if (flags[FLAG_FILE])
403
		send_cmd("player-play %s\n", file_url_absolute(play_file));
404
	if (volume)
405
		send_cmd("vol %s\n", volume);
406
	if (seek)
407
		send_cmd("seek %s\n", seek);
408
	if (query)
409
		send_cmd("status\n");
410
	return 0;
411
}