/****************************************************************************
** DRadio - a Danmarks Radio netradio player.
**
** Copyright (C) 2009 Jess Thrysoee
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
**
*****************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include /* also defines CTRL(c) */
#include "lists.h"
#include "dradio.h"
extern sig_atomic_t got_signal;
MPLAYER *mplayer;
/** main */
int main(int argc, char **argv)
{
int i, maxfd;
MAIN_WIN main_win;
HELP_WIN help_win;
RSSCACHE_LIST *rsscache;
FILE *mplayer_outlog, *mplayer_errlog;
int exit_eventloop = 0;
sigset_t orig_mask;
fd_set watchset;
fd_set inset;
struct timespec tv, *tv_p = NULL;
int exit_status = EXIT_SUCCESS;
memset(&main_win, 0, sizeof(MAIN_WIN));
memset(&help_win, 0, sizeof(HELP_WIN));
main_win.update_xterm_title = 1;
signals_sigaction_all();
/* mask out signals that are not to be received except within the pselect() call */
signals_block_all(&orig_mask);
logo_win_toggle(&main_win);
/* command line arguments */
for (i = 1; i < argc; i++)
{
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0)
usage();
if (strcmp(argv[i], "--version") == 0)
version();
if (strcmp(argv[i], "--nologo") == 0)
logo_win_toggle(&main_win);
if (strcmp(argv[i], "--notitle") == 0)
main_win.update_xterm_title = 0;
}
setlocale(LC_ALL, "");
if (create_config_dradio_dir() == -1)
exit(EXIT_FAILURE);
if ((mplayer_outlog = open_outlog()) == NULL)
exit(EXIT_FAILURE);
if ((mplayer_errlog = open_errlog()) == NULL)
exit(EXIT_FAILURE);
if ((main_win.menu = menu_create()) == NULL) /* read conf and create menu items */
exit(EXIT_FAILURE);
/* pass all args to mplayer */
if ((mplayer = mplayer_init(argc, argv)) == NULL)
{
perror("mplayer");
exit(EXIT_FAILURE);
}
update_xterm_title(&main_win, NULL);
rsscache = rsscache_list_init();
initscr(); /* start curses mode */
nonl(); /* tell curses not to do NL->CR/NL on output */
cbreak(); /* disable line buffering */
noecho(); /* do not echo while getch() */
keypad(stdscr, TRUE); /* enable arrow keys, etc. */
curs_set(0); /* cursor invisible */
nodelay(stdscr, TRUE); /* wgetch non blocking */
wnoutrefresh(stdscr);
main_win_create(&main_win);
/* watch stdin (fd 0) for user input */
FD_ZERO(&watchset);
FD_SET(STDIN_FILENO, &watchset);
/* watch mplayer stdout for status messages */
FD_SET(fileno(mplayer->out), &watchset);
maxfd = STDIN_FILENO > fileno(mplayer->out) ? STDIN_FILENO + 1 : fileno(mplayer->out) + 1;
/* watch mplayer stdout for status messages */
FD_SET(fileno(mplayer->err), &watchset);
maxfd = maxfd > fileno(mplayer->err) ? maxfd : fileno(mplayer->err) + 1;
/* event loop */
while (!exit_eventloop)
{
int pselect_retval;
/* use copy - selects mutates the fd_set */
inset = watchset;
pselect_retval = pselect(maxfd, &inset, NULL, NULL, tv_p, &orig_mask);
/* handle pselect error */
if (pselect_retval == -1 && errno != EINTR)
{
errmsg("pselect: %s", strerror(errno));
exit_status = errno;
exit_eventloop = 1;
break;
}
/* handle signal */
if (got_signal)
{
if (got_signal == SIGWINCH)
{
logmsg("dradio: interrupted by signal %d (%s)", got_signal, strsignal(got_signal));
got_signal = 0; /* clear got_signal */
ungetch(KEY_RESIZE); /* handle resize in handle_stdin() */
}
else
{
logmsg("dradio: interrupted by signal %d (%s)", got_signal, strsignal(got_signal));
if (got_signal == SIGCHLD)
{
errmsg("dradio: mplayer not found or it exited unexpectedly, "
"please consult ~/.config/dradio/*.log for more information.");
}
exit_status = got_signal;
exit_eventloop = 1;
break;
}
}
/* handle pselect timeout */
if (pselect_retval == 0)
{
if (main_win.visible)
{
if (!mplayer->ispaused && main_win.cur_rss_item != NULL)
{
mplayer_get_time_length(mplayer);
mplayer_get_time_pos(mplayer);
}
/* query pause status */
mplayer_ispaused(mplayer);
}
tv_p = NULL;
}
/* user input */
/*if (FD_ISSET(STDIN_FILENO, &inset))*/
{
int res;
if (main_win.visible)
res = main_win_handle_stdin(&main_win, &help_win, rsscache);
else
res = help_win_handle_stdin(&help_win, &main_win);
if (res != 0)
{
if (res < 0)
exit_status = EXIT_FAILURE;
exit_eventloop = 1;
break;
}
}
/* ready mplayer stdout, unless pselect was interrupted */
if (FD_ISSET(fileno(mplayer->out), &inset) && pselect_retval != -1)
{
if (mplayer_handle_stdout(&main_win, mplayer_outlog) < 0)
{
/* something is amiss, do not read again */
FD_CLR(fileno(mplayer->out), &watchset);
}
}
/* ready mplayer stderr, unless pselect was interrupted */
if (FD_ISSET(fileno(mplayer->err), &inset) && pselect_retval != -1)
{
if (mplayer_handle_stderr(main_win.stat_win, mplayer_errlog) < 0)
{
/* something is amiss, do not read again */
FD_CLR(fileno(mplayer->err), &watchset);
}
}
if (main_win.visible)
{
if (!mplayer->ispaused && main_win.cur_rss_item != NULL)
{
/* set .1 sec new timeout */
tv.tv_sec = 0;
tv.tv_nsec = 100000000;
tv_p = &tv;
}
else
{
/* set new .5 sec timeout primarily to update pause status */
tv.tv_sec = 0;
tv.tv_nsec = 500000000;
tv_p = &tv;
}
wnoutrefresh(main_win.menu_win);
wnoutrefresh(main_win.stat_win);
}
doupdate();
}
/* out of event loop - now exit */
/* free memory */
main_win_delete(&main_win);
menu_delete(main_win.menu);
help_win_delete(&help_win);
endwin();
rsscache_list_free(rsscache);
mplayer_quit(mplayer);
if (exit_status)
errmsg(NULL);
fclose(mplayer_outlog);
fclose(mplayer_errlog);
update_xterm_title(&main_win, NULL);
return exit_status;
}
/** handle user input (stdin) on pselect interrupt. Return -1 -> ERROR, 0 -> OK, 1 -> 'q' */
int main_win_handle_stdin(MAIN_WIN *w, HELP_WIN *help_win, RSSCACHE_LIST * rsscache)
{
int c;
CONF_ITEM* cur_conf_item;
MENU *menu = w->menu; /* alias */
w->cur_item_name[0] = '\0';
/* under the ncurses implementation, handled signals never interrupt getch */
while ((c = getch()) != ERR)
{
switch(c)
{
case 'j':
case KEY_DOWN: /* menu one down */
case CTRL('n'):
menu_driver(menu, REQ_DOWN_ITEM);
break;
case 'k':
case KEY_UP: /* menu one up */
case CTRL('p'):
menu_driver(menu, REQ_UP_ITEM);
break;
case CTRL('f'): /* menu page down */
case KEY_NEXT:
case KEY_NPAGE:
if (menu_driver(menu, REQ_SCR_DPAGE) == E_REQUEST_DENIED)
menu_driver(menu, REQ_LAST_ITEM);
break;
case CTRL('b'): /* menu page up */
case KEY_PPAGE:
case KEY_PREVIOUS:
if (menu_driver(menu, REQ_SCR_UPAGE) == E_REQUEST_DENIED)
menu_driver(menu, REQ_FIRST_ITEM);
break;
case KEY_HOME: /* menu first */
menu_driver(menu, REQ_FIRST_ITEM);
break;
case KEY_END: /* menu last */
menu_driver(menu, REQ_LAST_ITEM);
break;
case KEY_ENTER: /* menu select */
case 10:
case 13:
case ' ':
w->cur_rss_item = NULL;
w->cur_item = current_item(menu);
cur_conf_item = (CONF_ITEM *)item_userptr(w->cur_item);
stat_win_update_y(w->stat_win, 4, NULL); /* clear possible errmsg */
cur_item_name_update(w);
if (cur_conf_item->srctype == rss)
{
RSS_LIST* list;
stat_win_update_y(w->stat_win, 3, "Getting RSS playlist...");
list = rss_list_lookup(rsscache, (char *)cur_conf_item->src);
if (!list)
return -1;
w->cur_rss_item = list->tail;
cur_rss_update(w);
}
stat_win_update_y(w->stat_win, 3, "Loading...");
if (cur_conf_item->srctype == playlist)
mplayer_loadlist(mplayer, (char *)cur_conf_item->src);
else if (cur_conf_item->srctype == rss)
mplayer_loadfile(mplayer, w->cur_rss_item->url);
else
mplayer_loadfile(mplayer, (char *)cur_conf_item->src);
update_xterm_title(w, w->cur_item);
break;
case '<': /* (podcast) previous in rss */
if (w->cur_rss_item && w->cur_rss_item->prev)
{
w->cur_rss_item = w->cur_rss_item->prev;
cur_item_name_update(w);
mplayer_loadfile(mplayer, w->cur_rss_item->url);
}
break;
case '>': /* (podcast) next in rss */
if (w->cur_rss_item && w->cur_rss_item->next)
{
w->cur_rss_item = w->cur_rss_item->next;
cur_item_name_update(w);
mplayer_loadfile(mplayer, w->cur_rss_item->url);
}
break;
case KEY_RIGHT: /* (podcast) seek 1 min forwards */
if (w->cur_rss_item)
mplayer_seek(mplayer, 60);
break;
case KEY_LEFT: /* (podcast) seek 1 min backwards */
if (w->cur_rss_item)
mplayer_seek(mplayer, -60);
break;
case KEY_SRIGHT: /* (podcast) seek 10 min forwards */
if (w->cur_rss_item)
mplayer_seek(mplayer, 600);
break;
case KEY_SLEFT: /* (podcast) seek 10 min backwards */
if (w->cur_rss_item)
mplayer_seek(mplayer, -600);
break;
case 'p': /* pause */
mplayer_pause(mplayer);
break;
case '*': /* volume up */
cur_item_name_update(w);
mplayer_volume_up(mplayer);
break;
case '/': /* volume down */
cur_item_name_update(w);
mplayer_volume_down(mplayer);
break;
case 't': /* toggle show logo */
logo_win_toggle(w);
main_win_resize(w);
break;
case CTRL('l'): /* refresh screen */
case KEY_RESIZE: /* window resize */
resize();
main_win_resize(w);
break;
case 'h': /* 'h' help screen */
main_win_delete(w);
help_win_create(help_win);
help_win_update(help_win, 0);
return 0;
break;
case 'q': /* 'q' quit dradio */
return 1;
break;
default:
beep();
break;
}
}
return 0;
}
/** handle mplayer stdout on pselect interrupt */
int mplayer_handle_stdout(MAIN_WIN *w, FILE *mplayer_outlog)
{
int rc;
char mplayer_outbuf[BUFSIZ];
WINDOW *stat_win = w->stat_win;
double ans_length = -1;
double ans_time_position = -1;
mplayer_outbuf[0] = 0;
/* If a read() is interrupted by a signal before it reads any
* data, it shall return -1 with errno set to [EINTR].
*
* If a read() is interrupted by a signal after it has
* successfully read some data, it shall return the number of
* bytes read.
*/
rc = read(fileno(mplayer->out), mplayer_outbuf, sizeof(mplayer_outbuf) - 1);
if (rc < 0)
{
if (errno == EINTR)
{
/* interrupted system call (e.g. xterm resized) - just try again next time around */
}
else
{
logmsg("read: (%d) %s", errno, strerror(errno));
return -1;
}
}
else if (rc == 0)
{
/* pipe closed */
return -1;
}
else
{
double f;
char *s, ans_paused[4];
mplayer_outbuf[rc] = '\0';
s = mplayer_outbuf;
while ( (s = strtok(s, "\n")) != NULL)
{
if (sscanf(s, "ANS_pause=%3s", ans_paused) == 1)
{
/* mplayer->ispaused should not be set anywhere else, otherwise
the flag may get out of sync with mplayer */
int old_ispaused = mplayer->ispaused;
if (strcmp(ans_paused, "yes") == 0)
mplayer->ispaused = 1;
else
mplayer->ispaused = 0;
if (old_ispaused != mplayer->ispaused)
cur_item_name_update(w);
}
else if (sscanf(s, "ANS_LENGTH=%lf", &ans_length) == 1)
{
seconds_to_string(ans_length, mplayer->ans_length, sizeof(mplayer->ans_length));
}
else if (sscanf(s, "ANS_TIME_POSITION=%lf", &ans_time_position) == 1)
{
seconds_to_string(ans_time_position, mplayer->ans_time_position, sizeof(mplayer->ans_time_position));
}
else if (sscanf(s, "\rCache fill: %lf%%", &f) == 1)
{
stat_win_update_y(stat_win, 3, "Caching... %.2f%%", f);
stat_win_update_y(stat_win, 4, NULL);
}
else if (strcmp(s, "Starting playback...") == 0)
{
stat_win_update_y(stat_win, 3, NULL);
stat_win_update_y(stat_win, 4, NULL);
}
else
{
fprintf(mplayer_outlog, "%s\n", s);
}
if (ans_length > 0 || ans_time_position > 0)
{
cur_ans_time_position_update(stat_win);
ans_length = ans_time_position = -1;
}
s = NULL;
}
}
return 0;
}
/** handle mplayer stderr on pselect interrupt */
int mplayer_handle_stderr(WINDOW *stat_win, FILE *mplayer_errlog)
{
int rc;
char mplayer_errbuf[BUFSIZ];
mplayer_errbuf[0] = 0;
/* If a read() is interrupted by a signal before it reads any
* data, it shall return -1 with errno set to [EINTR].
*
* If a read() is interrupted by a signal after it has
* successfully read some data, it shall return the number of
* bytes read.
*/
rc = read(fileno(mplayer->err), mplayer_errbuf, sizeof(mplayer_errbuf) - 1);
if (rc < 0)
{
if (errno == EINTR)
{
/* interrupted system call (e.g. xterm resized) - just try again next time around */
}
else
{
logmsg("read: (%d) %s", errno, strerror(errno));
return -1;
}
}
else if (rc == 0)
{
/* pipe closed */
return -1;
}
else
{
char *s;
mplayer_errbuf[rc] = '\0';
s = mplayer_errbuf;
while ( (s = strtok(s, "\n")) != NULL)
{
if (strstr(s, "Server returned 404"))
{
stat_win_update_y(stat_win, 3, "Error!");
stat_win_update_y(stat_win, 4, "View log for details.");
}
fprintf(mplayer_errlog, "%s\n", s);
fflush(mplayer_errlog);
s = NULL;
}
}
return 0;
}
/** resizeterm */
void resize()
{
struct winsize ws;
/* get new xterm size */
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
errmsg("TIOCGWINSZ: %s", strerror(errno));
resizeterm(ws.ws_row, ws.ws_col);
}
/** format seconds */
void seconds_to_string(double time, char *buf, size_t n)
{
int hours, minutes, seconds, tenthsecond;
hours = time / 3600;
minutes = ((int)time % 3600) / 60;
seconds = ((int)time % 60);
tenthsecond = ((int)(time * 10)) % 10;
if (hours > 0)
snprintf(buf, n, "%d:%02d:%02d.%d", hours, minutes, seconds, tenthsecond);
else if (minutes > 0)
snprintf(buf, n, "%02d:%02d.%d", minutes, seconds, tenthsecond);
else
snprintf(buf, n, "%02d.%d", seconds, tenthsecond);
}
/** error message */
void errmsg(const char *format, ...)
{
va_list ap;
static char buf[BUFSIZ] = {0};
if (format == NULL && *buf)
{
/* print last errmsg to stderr */
fprintf(stderr, "%s\n", buf);
}
else
{
FILE * errlog;
/* save */
va_start(ap, format);
vsnprintf(buf, sizeof(buf), format, ap);
va_end(ap);
/* print to errlog */
errlog = open_errlog();
fprintf(errlog, "%s\n", buf);
fclose(errlog);
}
}
/** log error message */
void logmsg(const char *format, ...)
{
FILE * errlog;
va_list ap;
char buf[BUFSIZ] = {0};
va_start(ap, format);
vsnprintf(buf, sizeof(buf), format, ap);
va_end(ap);
/* print to errlog */
errlog = open_errlog();
fprintf(errlog, "%s\n", buf);
fflush(errlog);
fclose(errlog);
}
/** help messages */
void usage()
{
printf("%s %s is a Danmarks Radio netradio, podcast, and TV player.\n", "dradio", VERSION);
printf("\n");
printf("Usage: %s [--nologo] [--notitle] [MPLAYER_OPTIONS]...\n", "dradio");
printf(" %s --help, -h\n", "dradio");
printf(" %s --version\n", "dradio");
printf("\n");
exit(EXIT_SUCCESS);
}
/** help messages */
void version()
{
printf("%s %s\n", "dradio", VERSION);
printf("\n");
printf("Copyright (C) 2009 Jess Thrysoee.\n");
printf("This is free software; see the source for copying conditions. There is NO\n");
printf("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n");
exit(EXIT_SUCCESS);
}