1 /*
2 * Copyright (c) 2010, 2011 Ryan Flannery <ryan.flannery@gmail.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include "vitunes.h"
18 #include "config.h" /* NOTE: must be after vitunes.h */
19 #include "socket.h"
20
21 /*****************************************************************************
22 * GLOBALS, EXPORTED
23 ****************************************************************************/
24
25 /* key playlists to keep track of */
26 playlist *viewing_playlist;
27 playlist *playing_playlist;
28
29 /* visual mode start position */
30 int visual_mode_start = -1;
31
32 /* signal flags */
33 volatile sig_atomic_t VSIG_QUIT = 0; /* 1 = quit vitunes */
34 volatile sig_atomic_t VSIG_RESIZE = 0; /* 1 = resize display */
35 volatile sig_atomic_t VSIG_SIGCHLD = 0; /* 1 = got sigchld */
36 volatile sig_atomic_t VSIG_PLAYER_MONITOR = 0; /* 1 = update player stats */
37
38 /*
39 * enum used for QUIT_CAUSE values. Currently only one is used, but might add
40 * more. see the end of the main function and the signal_handler for how/why
41 * this is used.
42 */
43 enum { EXIT_NORMAL, BAD_PLAYER };
44 volatile sig_atomic_t QUIT_CAUSE = EXIT_NORMAL;
45
46 /* used with -DDEBUG */
47 FILE *debug_log;
48
49
50 /*****************************************************************************
51 * GLOBALS, LOCAL
52 ****************************************************************************/
53
54 /* location patterns of various things (%s is user's home directory) */
55 const char *VITUNES_DIR_FMT = "%s/.vitunes";
56 const char *CONF_FILE_FMT = "%s/.vitunes/vitunes.conf";
57 const char *DB_FILE_FMT = "%s/.vitunes/vitunes.db";
58 const char *PLAYLIST_DIR_FMT = "%s/.vitunes/playlists";
59
60 /* absolute paths of key stuff */
61 char *vitunes_dir;
62 char *conf_file;
63 char *db_file;
64 char *playlist_dir;
65 char *player_backend;
66
67
68 /*****************************************************************************
69 * local functions
70 ****************************************************************************/
71
72 /* misc. functions */
73 int handle_switches(int argc, char *argv[]);
74 void usage(const char *);
75 void signal_handler(int);
76 void setup_timer();
77
78
79 int
main(int argc,char * argv[])80 main(int argc, char *argv[])
81 {
82 char *home;
83 int previous_command;
84 int input;
85 int sock = -1;
86 fd_set fds;
87
88 #ifdef DEBUG
89 if ((debug_log = fopen("vitunes-debug.log", "w")) == NULL)
90 err(1, "failed to open debug log");
91 #endif
92
93 /*------------------------------------------------------------------------
94 * build paths names needed by vitunes & handle switches
95 *----------------------------------------------------------------------*/
96
97 /* get home dir */
98 if ((home = getenv("HOME")) == NULL)
99 errx(1, "$HOME not set. Can't find my config files.");
100
101 /* build paths & other needed strings */
102 asprintf(&vitunes_dir, VITUNES_DIR_FMT, home);
103 asprintf(&conf_file, CONF_FILE_FMT, home);
104 asprintf(&db_file, DB_FILE_FMT, home);
105 asprintf(&playlist_dir, PLAYLIST_DIR_FMT, home);
106 asprintf(&player_backend, "%s", DEFAULT_PLAYER_BACKEND);
107 if (vitunes_dir == NULL || conf_file == NULL
108 || db_file == NULL || playlist_dir == NULL
109 || player_backend == NULL)
110 err(1, "failed to create needed file names");
111
112 /* handle command-line switches & e-commands */
113 handle_switches(argc, argv);
114
115 if(sock_send_msg(VITUNES_RUNNING) != -1) {
116 printf("Vitunes appears to be running already. Won't open socket.");
117 } else {
118 if((sock = sock_listen()) == -1)
119 errx(1, "failed to open socket.");
120 }
121
122
123 /*
124 * IF we've reached here, then there were no e-commands.
125 * start vitunes normally...
126 */
127
128
129 /*------------------------------------------------------------------------
130 * initialize stuff
131 *--------------------------------------------------------------------- */
132
133 /* setup signal handlers (XXX must be before player_init) */
134 signal(SIGPIPE, SIG_IGN); /* broken pipe with child (ignore) */
135 signal(SIGCHLD, signal_handler); /* child died */
136 signal(SIGHUP, signal_handler); /* quit */
137 signal(SIGINT, signal_handler); /* quit */
138 signal(SIGQUIT, signal_handler); /* quit */
139 signal(SIGTERM, signal_handler); /* quit */
140 signal(SIGWINCH, signal_handler); /* resize */
141 setup_timer(); /* periodic timer to update player */
142
143 /* init small stuff (XXX some must be done before medialib_load) */
144 mi_query_init(); /* global query description */
145 mi_sort_init(); /* global sort description */
146 mi_display_init(); /* global display description */
147 ybuffer_init(); /* global yank/copy buffer */
148 toggleset_init(); /* global toggleset (list of toggle-lists) */
149
150 /* load media library (database and all playlists) & sort */
151 medialib_load(db_file, playlist_dir);
152 if (mdb.library->nfiles == 0) {
153 printf("The vitunes database is currently empty.\n");
154 printf("See 'vitunes -e help add' for how to add files.\n");
155 return 0;
156 }
157
158 /* apply default sort to library */
159 qsort(mdb.library->files, mdb.library->nfiles, sizeof(meta_info*), mi_compare);
160
161 /* setup user interface and default colors */
162 kb_init();
163 ui_init(DEFAULT_LIBRARY_WINDOW_WIDTH);
164 paint_setup_colors();
165
166 /* basic ui setup to get ui started */
167 setup_viewing_playlist(mdb.library);
168 ui.library->nrows = mdb.nplaylists;
169 playing_playlist = NULL;
170
171 /* load config file and run commands in it now */
172 load_config();
173
174 /* start media player child */
175 player_init(player_backend);
176 player_info.mode = DEFAULT_PLAYER_MODE;
177 atexit(player_destroy);
178
179 /* initial painting of the display */
180 paint_all();
181
182 /* -----------------------------------------------------------------------
183 * begin input loop
184 * -------------------------------------------------------------------- */
185
186 previous_command = -1;
187 while (!VSIG_QUIT) {
188 struct timeval tv;
189
190 /* handle any signal flags */
191 process_signals();
192
193 tv.tv_sec = 1;
194 tv.tv_usec = 0;
195
196 FD_ZERO(&fds);
197 FD_SET(0, &fds);
198 if(sock > 0)
199 FD_SET(sock, &fds);
200 errno = 0;
201 if(select((sock > 0 ? sock : 0) + 1, &fds, NULL, NULL, &tv) == -1) {
202 if(errno == 0 || errno == EINTR)
203 continue;
204 break;
205 }
206
207 if(sock > 0) {
208 if(FD_ISSET(sock, &fds))
209 sock_recv_and_exec(sock);
210 }
211
212 if(FD_ISSET(0, &fds)) {
213 /* handle any available input */
214 if ((input = getch()) && input != ERR) {
215 if (isdigit(input) && (input != '0' || gnum_get() > 0))
216 gnum_add(input - '0');
217 else if (input == '\n' && gnum_get() > 0 && previous_command >= 0)
218 kb_execute(previous_command);
219 else
220 kb_execute(input);
221 }
222 }
223 }
224
225 /* -----------------------------------------------------------------------
226 * cleanup
227 * -------------------------------------------------------------------- */
228
229 ui_destroy();
230 player_destroy();
231 medialib_destroy();
232
233 mi_query_clear();
234 ybuffer_free();
235 toggleset_free();
236
237 /* do we have any odd cause for quitting? */
238 if (QUIT_CAUSE != EXIT_NORMAL) {
239 switch (QUIT_CAUSE) {
240 case BAD_PLAYER:
241 warnx("It appears the media player is misbehaving. Apologies.");
242 break;
243 }
244 }
245
246 return 0;
247 }
248
249 /* print proper usage */
250 void
usage(const char * pname)251 usage(const char *pname)
252 {
253 fprintf(stderr,"\
254 usage: %s [-f config-file] [-d database-file] [-p playlist-dir] [-m player-path] [-e COMMAND ...]\n\
255 See \"%s -e help\" for information about what e-commands are available.\n\
256 ",
257 pname, pname);
258 exit(1);
259 }
260
261 /* actual signal handler */
262 void
signal_handler(int sig)263 signal_handler(int sig)
264 {
265 switch (sig) {
266 case SIGHUP:
267 case SIGINT:
268 case SIGQUIT:
269 case SIGTERM:
270 VSIG_QUIT = 1;
271 break;
272 case SIGALRM:
273 VSIG_PLAYER_MONITOR = 1;
274 break;
275 case SIGWINCH:
276 VSIG_RESIZE = 1;
277 break;
278 case SIGCHLD:
279 VSIG_SIGCHLD = 1;
280 break;
281 }
282 }
283
284 /* handle any signal flags */
285 void
process_signals()286 process_signals()
287 {
288 static playlist *prev_queue = NULL;
289 static int prev_qidx = -1;
290 static bool prev_is_playing = false;
291 static float prev_volume = -1;
292
293 /* handle resize event */
294 if (VSIG_RESIZE) {
295 ui_resize();
296 ui_clear();
297 paint_all();
298 VSIG_RESIZE = 0;
299 }
300
301 /* monitor player */
302 if (VSIG_PLAYER_MONITOR) {
303 player_monitor();
304
305 if (prev_is_playing || player.playing())
306 paint_player();
307
308 /* need to repaint anything else? */
309 if (prev_is_playing != player.playing()) {
310 paint_library();
311 paint_playlist();
312 } else if (prev_queue != player_info.queue) {
313 paint_library();
314 if (prev_queue == viewing_playlist) {
315 paint_playlist();
316 }
317 }
318 if (player_info.queue == viewing_playlist
319 && prev_qidx != player_info.qidx) {
320 paint_playlist();
321 }
322 if (prev_volume != player.volume()) {
323 paint_message("volume: %3.0f%%", player.volume());
324 prev_volume = player.volume();
325 }
326
327 prev_queue = player_info.queue;
328 prev_qidx = player_info.qidx;
329 prev_is_playing = player.playing();
330 VSIG_PLAYER_MONITOR = 0;
331 }
332
333 /* restart player if needed */
334 if (VSIG_SIGCHLD) {
335 if (player.sigchld != NULL) player.sigchld();
336 VSIG_SIGCHLD = 0;
337 }
338 }
339
340 /* setup timer signal handler above */
341 void
setup_timer()342 setup_timer()
343 {
344 struct sigaction sig_act;
345 struct itimerval timer;
346
347 /* create timer signal handler */
348 if (sigemptyset(&sig_act.sa_mask) < 0)
349 err(1, "setup_timer: sigemptyset failed");
350
351 sig_act.sa_flags = 0;
352 sig_act.sa_handler = signal_handler;
353 if (sigaction(SIGALRM, &sig_act, NULL) < 0)
354 err(1, "setup_timer: sigaction failed");
355
356 /* setup timer details */
357 timer.it_value.tv_sec = 0;
358 timer.it_value.tv_usec = 500000; /* 1 half-second */
359 timer.it_interval.tv_sec = 0;
360 timer.it_interval.tv_usec = 500000; /* 1 half-second */
361
362 /* register timer */
363 if (setitimer(ITIMER_REAL, &timer, NULL) < 0)
364 err(1, "setup_timer: setitimer failed");
365 }
366
367 /*
368 * load config file and execute all command-mode commands within.
369 * XXX note that this requires mdb, ui, and player to all be loaded/setup
370 */
371 void
load_config()372 load_config()
373 {
374 const char *errmsg = NULL;
375 size_t length, linenum;
376 FILE *fin;
377 char *line;
378 char *copy;
379 char **argv;
380 int argc;
381 bool found;
382 int found_idx = 0;
383 int num_matches;
384 int i, ret;
385
386 if ((fin = fopen(conf_file, "r")) == NULL)
387 return;
388
389 linenum = 0;
390 while (!feof(fin)) {
391
392 /* get next line */
393 if ((line = fparseln(fin, &length, &linenum, NULL, 0)) == NULL) {
394 if (ferror(fin))
395 err(1, "error reading config file '%s'", conf_file);
396 else
397 break;
398 }
399
400 /* skip whitespace */
401 copy = line;
402 copy += strspn(copy, " \t\n");
403 if (copy[0] == '\0') {
404 free(line);
405 continue;
406 }
407
408 /* parse line into argc/argv */
409 if (str2argv(copy, &argc, &argv, &errmsg) != 0) {
410 endwin();
411 errx(1, "%s line %zd: parse error: %s", conf_file, linenum, errmsg);
412 }
413
414 /* run command */
415 found = false;
416 num_matches = 0;
417 for (i = 0; i < CommandPathSize; i++) {
418 if (match_command_name(argv[0], CommandPath[i].name)) {
419 found = true;
420 found_idx = i;
421 num_matches++;
422 }
423 }
424
425 if (found && num_matches == 1) {
426 if ((ret = (CommandPath[found_idx].func)(argc, argv)) != 0) {
427 endwin();
428 errx(1, "%s line %zd: error with command '%s' [%i]",
429 conf_file, linenum, argv[0], ret);
430 }
431 } else if (num_matches > 1) {
432 endwin();
433 errx(1, "%s line %zd: ambiguous abbreviation '%s'",
434 conf_file, linenum, argv[0]);
435 } else {
436 endwin();
437 errx(1, "%s line %zd: unknown command'%s'",
438 conf_file, linenum, argv[0]);
439 }
440
441 argv_free(&argc, &argv);
442 free(line);
443 }
444
445 fclose(fin);
446 }
447
448 /*
449 * parse the command line and handle all switches.
450 * this also handles all of the e-commands.
451 */
452 int
handle_switches(int argc,char * argv[])453 handle_switches(int argc, char *argv[])
454 {
455 int ch;
456 int i;
457 int had_c_commands = 0;
458
459 while ((ch = getopt(argc, argv, "he:f:d:p:m:c:")) != -1) {
460 switch (ch) {
461 case 'c':
462 if(sock_send_msg(optarg) == -1)
463 errx(1, "Failed to send message. Vitunes not running?");
464 had_c_commands = 1;
465 break;
466
467 case 'd':
468 if ((db_file = strdup(optarg)) == NULL)
469 err(1, "handle_switches: strdup db_file failed");
470 break;
471
472 case 'e':
473 /* an e-command */
474 argc -= optind - 1;
475 argv += optind - 1;
476
477 for (i = 0; i < ECMD_PATH_SIZE; i++) {
478 if (strcmp(optarg, ECMD_PATH[i].name) == 0)
479 exit((ECMD_PATH[i].func)(argc, argv));
480 }
481
482 errx(1, "Unknown e-command '%s'. See 'vitunes -e help' for list.",
483 optarg);
484 break;
485
486 case 'f':
487 if ((conf_file = strdup(optarg)) == NULL)
488 err(1, "handle_switches: strdup conf_file failed");
489 break;
490
491 case 'm':
492 if ((player_backend = strdup(optarg)) == NULL)
493 err(1, "handle_switches: strdup player_backend failed");
494 break;
495
496 case 'p':
497 if ((playlist_dir = strdup(optarg)) == NULL)
498 err(1, "handle_switches: strdup playlist_dir failed");
499 break;
500
501 case 'h':
502 case '?':
503 default:
504 usage(argv[0]);
505 /* NOT REACHED */
506 }
507 }
508
509 if(had_c_commands)
510 exit(0);
511
512 return 0;
513 }
514