void handle_keyboard_input() { int key, result; char customkey[8] = { 0 }, * marked = NULL; fflush(stderr); if((key = fgetc(stdin)) == -1) return; if(key == 27) { int ch; while((ch = fgetc(stdin)) != -1 && !strchr("ABCDEFGHMPQRSZojmk~", ch)); return; } snprintf(customkey, sizeof(customkey), "key0x%02X", key & 0xFF); if(haskey(& rc, customkey)) run(meta(value(& rc, customkey), M_SHELLESC, & track)); switch(key) { case 'l': puts(rate("L") ? "Loved." : "Sorry, failed."); break; case 'U': puts(rate("U") ? "Unloved." : "Sorry, failed."); break; case 'B': puts(rate("B") ? "Banned." : "Sorry, failed."); fflush(stdout); enable(INTERRUPTED); kill(playfork, SIGUSR1); break; case 'n': rate("S"); break; case 'q': if(haskey(& rc, "delay-change")) { delayquit = !delayquit; if(delayquit) fputs("Going to quit soon.\n", stderr); else fputs("Delayed quit cancelled.\n", stderr); } break; case 'Q': quit(); case 'i': if(playfork) { const char * path = rcpath("i-template"); if(path && !access(path, R_OK)) { char ** template = slurp(path); if(template != NULL) {
static void cleanup(void) { rmsckif(); if(haskey(& rc, "unix") && getpid() == ppid) unlink(value(& rc, "unix")); empty(& data); empty(& rc); empty(& track); freelist(& playlist); if(current_station) { free(current_station); current_station = NULL; } if(subfork) waitpid(subfork, NULL, 0); dump_queue(); /* Clean cache. */ if(!access(rcpath("cache"), R_OK | W_OK | X_OK)) { const char * cache = rcpath("cache"); DIR * directory = opendir(cache); if(directory != NULL) { time_t expiry = 24 * 60 * 60; /* Expiration after 24h. */ struct dirent * entry; struct stat status; char path[PATH_MAX]; if(haskey(& rc, "expiry")) expiry = atoi(value(& rc, "expiry")); while((entry = readdir(directory)) != NULL) { snprintf(path, sizeof(path), "%s/%s", cache, entry->d_name); if(!stat(path, & status)) { if(status.st_mtime < (time(NULL) - expiry)) { unlink(path); } } } closedir(directory); } } if(playfork) kill(playfork, SIGUSR1); }
unsigned resolve_artist(const char * name) { unsigned artist_id = 0; char * response = fetch( makeurl("http://www.last.fm/ajax/getResource?type=artist&name=%s", name), NULL, NULL, NULL ); debug("response=<\n%s\n>\n", response); if(response != NULL) { struct hash h = { 0, NULL }; json_value * json; assert((json = json_parse(response)) != NULL); json_hash(json, & h, NULL); if(haskey(& h, "resource.id")) artist_id = atol(value(& h, "resource.id")); json_value_free(json); empty(& h); } free(response); return artist_id; }
void preview(struct playlist list) { struct tracknode * node; unsigned n = 0; if (list.track != NULL) node = list.track->next; else { puts("No tracks in queue."); return; } if(node == NULL) { puts("No tracks in queue."); } else { puts("Upcoming tracks:"); while(node != NULL) { const char * format; format = haskey(& rc, "preview-format") ? value(& rc, "preview-format") : "%a - %t"; printf("%2d %s\n", n++, meta(format, M_COLORED, & node->track)); node = node->next; } } }
/* Add a track to the scrobble queue. */ int enqueue(struct hash * track) { const char * keys [] = { "creator", "title", "album", "duration" }; unsigned i; struct hash post; char timestamp[16], lastid[8]; assert(track != NULL); memset(& post, 0, sizeof(struct hash)); for(i = 0; i < (sizeof(keys) / sizeof(char *)); ++i) if(!haskey(track, keys[i])) return 0; queue = realloc(queue, sizeof(struct hash) * (qlength + 1)); assert(queue != NULL); snprintf(timestamp, sizeof(timestamp), "%lu", (unsigned long) time(NULL)); snprintf(lastid, sizeof(lastid), "L%s", value(track, "lastfm:trackauth")); copy_track_data(track, & post); set(& post, "i", timestamp); set(& post, "r", value(track, "rating")); set(& post, "o", lastid); memcpy(& queue[qlength++], & post, sizeof(struct hash)); return !0; }
void vesa_screen_refresh(Screen* screen) { //check if there are any keys pending while (haskey()) { char ch = getchar(); if (ch == 'q') { //quit xserv gfx_teardown(screen); switch_to_text(); return; } } if (!screen->finished_drawing) return; //if no changes occured this refresh, don't bother writing the screen if (xserv_draw(screen)) { write_screen(screen); } }
void unset(struct hash * hash, const char * key) { unsigned index = haskey(hash, key); if(index > 0) { --index; if(hash->content[index].key != NULL) free(hash->content[index].key); if(hash->content[index].value != NULL) free(hash->content[index].value); memcpy( & hash->content[index], & hash->content[--hash->size], sizeof(struct pair)); hash->content = realloc(hash->content, sizeof(struct pair) * hash->size); assert(hash->content != NULL); } }
int playback(FILE * streamfd, int pipefd) { killed = 0; signal(SIGUSR1, sighand); #ifndef EXTERN_ONLY if(!haskey(& rc, "extern")) { const char * freetrack = NULL; struct stream data; struct mad_decoder dec; #ifdef LIBAO static int ao_initialized = 0; if(!ao_initialized) { ao_initialize(); ao_initialized = !0; } #else unsigned arg; int fd; #endif memset(& data, 0, sizeof(struct stream)); /* Check if there's a stream timeout configured and set it up for timed reads later. */ data.timeout = -1; if(haskey(& rc, "stream-timeout")) { const char * timeout = value(& rc, "stream-timeout"); data.timeout = atoi(timeout); if(data.timeout <= 0) { if(data.timeout < 0) fputs("Invalid stream-timeout.\n", stderr); data.timeout = -1; } } data.streamfd = streamfd; data.parent = getppid(); data.pipefd = pipefd; fcntl(pipefd, F_SETFL, O_NONBLOCK); #ifdef LIBAO data.driver_id = ao_default_driver_id(); if(-1 == data.driver_id) { fputs("Unable to find any usable output device!\n", stderr); return 0; } data.fmt.bits = 16; data.fmt.rate = 44100; data.fmt.channels = 2; data.fmt.byte_format = AO_FMT_NATIVE; data.device = ao_open_live(data.driver_id,&data.fmt,NULL); if(NULL == data.device) { fprintf(stderr, "Unable to open device. %s.\n", strerror(errno)); return 0; } #else data.audiofd = fd = open(value(& rc, "device"), O_WRONLY); if(-1 == data.audiofd) { fprintf( stderr, "Couldn't open %s! %s.\n", value(& rc, "device"), strerror(errno) ); return 0; } arg = 16; /* 16 bits */ ioctl(data.audiofd, SOUND_PCM_WRITE_BITS, & arg); #endif freetrack = value(& track, "freeTrackURL"); if(freetrack && strlen(freetrack) > 0 && haskey(& rc, "download")) { char * dnam; int rv; data.finpath = strdup(meta(value(& rc, "download"), M_RELAXPATH, & track)); assert(data.finpath != NULL); data.tmppath = strjoin("", data.finpath, ".streaming", NULL); assert(data.tmppath != NULL); dnam = strdup(data.tmppath); rv = dnam ? mkpath(dirname(dnam)) : -1; free(dnam); if(access(data.tmppath, R_OK) == -1) { data.dump = (rv == 0) ? fopen(data.tmppath, "w") : NULL; if(!data.dump) fprintf(stderr, "Can't write download to %s.\n", data.tmppath); } else { data.dump = NULL; } } mad_decoder_init(& dec, & data, input, NULL, NULL, output, NULL, NULL); mad_decoder_run(& dec, MAD_DECODER_MODE_SYNC); #ifndef LIBAO close(fd); #endif mad_decoder_finish(& dec); if(data.dump) { fclose(data.dump); if(killed) { unlink(data.tmppath); } else { int rv; #ifdef TAGLIB TagLib_File *tagme = taglib_file_new(data.tmppath); if(tagme != NULL) { TagLib_Tag *tag = taglib_file_tag(tagme); taglib_tag_set_title(tag, value(&track, "title")); taglib_tag_set_artist(tag, value(&track, "creator")); taglib_tag_set_album(tag, value(&track, "album")); taglib_file_save(tagme); taglib_file_free(tagme); } #endif if(haskey(& rc, "pp-cmd")) { const char *ppcmd = value(& rc, "pp-cmd"); size_t ppcmdlen = strlen(ppcmd); char *path = shellescape(data.tmppath); assert(path != NULL); size_t pathlen = strlen(path); char *command = malloc(ppcmdlen + pathlen + 2); assert(command != NULL); memcpy(command, ppcmd, ppcmdlen); command[ppcmdlen] = ' '; memcpy(command + ppcmdlen + 1, path, pathlen); command[ppcmdlen + 1 + pathlen] = 0; run(command); free(path); free(command); } rv = rename(data.tmppath, data.finpath); if (rv == -1) fprintf(stderr, "Can't rename %s to %s\n", data.tmppath, data.finpath); } free(data.tmppath); free(data.finpath); } } else #endif { pid_t ppid = getppid(), cpid = 0; const char * cmd = meta(value(& rc, "extern"), M_SHELLESC, & track); FILE * ext = openpipe(cmd, & cpid); unsigned char * buf; if(!ext) { fprintf(stderr, "Failed to execute external player (%s). %s.\n", cmd, strerror(errno)); return 0; } if(!(buf = calloc(BUFSIZE + 1, sizeof(unsigned char)))) { fputs("Couldn't allocate enough memory for input buffer.\n", stderr); fclose(ext); return 0; } while(!feof(streamfd)) { signed nbyte = fread(buf, sizeof(unsigned char), BUFSIZE, streamfd); if(nbyte > 0) { fwrite(buf, sizeof(unsigned char), nbyte, ext); fflush(ext); } if(kill(ppid, 0) == -1 && errno == ESRCH) break; if(killed) break; } free(buf); fclose(ext); waitpid(cpid, NULL, 0); } return !0; }
void execcmd(const char * cmd, char * reply) { char arg[1024], * ptr; unsigned ncmd; const char * known [] = { "play", "love", "ban", "skip", "quit", "info", "pause", "discovery", "tag-artist", "tag-album", "tag-track", "artist-tags", "album-tags", "track-tags", "stop", }; memset(arg, 0, sizeof(arg)); memset(reply, 0, REPLYBUFSIZE); for(ncmd = 0; ncmd < (sizeof(known) / sizeof(char *)); ++ncmd) { if(!strncmp(known[ncmd], cmd, strlen(known[ncmd]))) break; } switch(ncmd) { case (sizeof(known) / sizeof(char *)): strncpy(reply, "ERROR", REPLYBUFSIZE); break; case 0: if(sscanf(cmd, "play %128[a-zA-Z0-9:/_ %,*.-]", arg) == 1) { char * url; decode(arg, & url); station(url); free(url); } break; case 1: rate("L"); break; case 2: rate("B"); break; case 3: rate("S"); break; case 4: quit(); case 5: if(* (cmd + 5)) strncpy(reply, meta(cmd + 5, 0, & track), REPLYBUFSIZE); else if(haskey(& rc, "np-file-format")) strncpy( reply, meta(value(& rc, "np-file-format"), 0, & track), REPLYBUFSIZE ); break; case 6: if(playfork) { if(pausetime) { kill(playfork, SIGCONT); } else { time(& pausetime); kill(playfork, SIGSTOP); } } break; case 7: toggle(DISCOVERY); break; case 8: if(sscanf(cmd, "tag-artist %128s", arg) == 1) sendtag('a', arg, track); break; case 9: if(sscanf(cmd, "tag-album %128s", arg) == 1) sendtag('l', arg, track); break; case 10: if(sscanf(cmd, "tag-track %128s", arg) == 1) sendtag('t', arg, track); break; case 11: if((ptr = oldtags('a', track)) != NULL) { strncpy(reply, ptr, REPLYBUFSIZE); free(ptr); ptr = NULL; } break; case 12: if((ptr = oldtags('l', track)) != NULL) { strncpy(reply, ptr, REPLYBUFSIZE); free(ptr); ptr = NULL; } break; case 13: if((ptr = oldtags('t', track)) != NULL) { strncpy(reply, ptr, REPLYBUFSIZE); free(ptr); ptr = NULL; } break; case 14: if(playfork) { enable(STOPPED); kill(playfork, SIGUSR1); } break; } }
int main(int argc, char ** argv) { int option, nerror = 0, background = 0, have_socket = 0; time_t pauselength = 0; char * proxy; opterr = 0; /* Create directories. */ makercd(); /* Load settings from ~/.shell-fm/shell-fm.rc. */ settings(rcpath("shell-fm.rc"), !0); /* Enable discovery by default if it is set in configuration. */ if(haskey(& rc, "discovery")) enable(DISCOVERY); /* Disable RTP if option "no-rtp" is set to something. */ if(haskey(& rc, "no-rtp")) disable(RTP); /* If "daemon" is set in the configuration, enable daemon mode by default. */ if(haskey(& rc, "daemon")) background = !0; /* Get proxy environment variable. */ if((proxy = getenv("http_proxy")) != NULL) set(& rc, "proxy", proxy); /* Parse through command line options. */ while(-1 != (option = getopt(argc, argv, ":dbhi:p:D:y:"))) switch(option) { case 'd': /* Daemonize. */ background = !background; break; case 'i': /* IP to bind network interface to. */ set(& rc, "bind", optarg); break; case 'p': /* Port to listen on. */ if(atoi(optarg)) set(& rc, "port", optarg); else { fputs("Invalid port.\n", stderr); ++nerror; } break; case 'b': /* Batch mode */ batch = !0; break; case 'D': /* Path to audio device file. */ set(& rc, "device", optarg); break; case 'y': /* Proxy address. */ set(& rc, "proxy", optarg); break; case 'h': /* Print help text and exit. */ help(argv[0], 0); break; case ':': fprintf(stderr, "Missing argument for option -%c.\n\n", optopt); ++nerror; break; case '?': default: fprintf(stderr, "Unknown option -%c.\n", optopt); ++nerror; break; } /* The next argument, if present, is the lastfm:// URL we want to play. */ if(optind > 0 && optind < argc && argv[optind]) { const char * station = argv[optind]; set(& rc, "default-radio", station); } if(nerror) help(argv[0], EXIT_FAILURE); #ifdef EXTERN_ONLY /* Abort if EXTERN_ONLY is defined and no extern command is present */ if(!haskey(& rc, "extern")) { fputs("Can't continue without extern command.\n", stderr); exit(EXIT_FAILURE); } #else #ifndef LIBAO if(!haskey(& rc, "device")) set(& rc, "device", "/dev/audio"); #endif #endif if(!background) { puts("Shell.FM v" PACKAGE_VERSION ", (C) 2006-2010 by Jonas Kramer"); puts("Published under the terms of the GNU General Public License (GPL)."); #ifndef TUXBOX puts("\nPress ? for help.\n"); #else puts("Compiled for the use with Shell.FM Wrapper.\n"); #endif fflush(stdout); } /* Open a port so Shell.FM can be controlled over network. */ if(haskey(& rc, "bind")) { int port = 54311; if(haskey(& rc, "port")) port = atoi(value(& rc, "port")); if(tcpsock(value(& rc, "bind"), (unsigned short) port)) have_socket = !0; } /* Open a UNIX socket for local "remote" control. */ if(haskey(& rc, "unix") && unixsock(value(& rc, "unix"))) have_socket = !0; /* We can't daemonize if there's no possibility left to control Shell.FM. */ if(background && !have_socket) { fputs("Can't daemonize without control socket.\n", stderr); exit(EXIT_FAILURE); } /* Ask for username/password if they weren't specified in the .rc file. */ if(!haskey(& rc, "password")) { char * password; if(!haskey(& rc, "username")) { char username[256] = { 0 }; struct prompt prompt = { .prompt = "Login: "******"USER"), .history = NULL, .callback = NULL, }; strncpy(username, readline(& prompt), 255); set(& rc, "username", username); } if(!(password = getpass("Password: "******"password", password); }
int station(const char * stationURL) { char url[512] = { 0 }, * encodedURL = NULL, ** response, name[512], * completeURL; unsigned i = 0, retval = !0, regular = !0; const char * fmt; const char * types[4] = {"play", "preview", "track", "playlist"}; delayquit = 0; if(playfork && haskey(& rc, "delay-change")) { if(nextstation) { /* Cancel station change if url is empty or equal to the current station. */ free(nextstation); nextstation = NULL; if(!strlen(stationURL) || !strcmp(stationURL, current_station)) { puts("Station change cancelled."); return 0; } } /* Do nothing if the station is already played. */ else if(current_station && !strcmp(current_station, stationURL)) { return 0; } /* Do nothing if the station URL is empty. */ else if(!strlen(stationURL)) { return 0; } puts("\rDelayed."); nextstation = strdup(stationURL); return 0; } /* Do nothing if the station is already played. */ else if(current_station && !strcmp(current_station, stationURL)) { return 0; } freelist(& playlist); if(!haskey(& data, "session")) { fputs("Not authenticated, yet.\n", stderr); return 0; } if(!strncmp(HTTP_STATION_PREFIX, stationURL, strlen(HTTP_STATION_PREFIX))) { stationURL += strlen(HTTP_STATION_PREFIX); } if(!stationURL || !strlen(stationURL)) return 0; if(!strncasecmp(stationURL, "lastfm://", 9)) { completeURL = strdup(stationURL); stationURL += 9; } else { int size = strlen(stationURL) + 10; completeURL = malloc(size); snprintf(completeURL, size, "lastfm://%s", stationURL); } /* Check if it's a static playlist of tracks or track previews. */ for(i = 0; i < 4; ++i) if(!strncasecmp(types[i], stationURL, strlen(types[i]))) { regular = 0; break; } /* If this is not a special "one-time" stream, it's a regular radio station and we request it using the good old /adjust.php URL. If it's not a regular stream, the reply of this request already is a XSPF playlist we have to parse. */ if(regular) { fmt = "http://ws.audioscrobbler.com/radio/adjust.php?session=%s&url=%s"; } else { fmt = "http://ws.audioscrobbler.com/1.0/webclient/getresourceplaylist.php" "?sk=%s&url=%s&desktop=1"; } encode(completeURL, & encodedURL); snprintf(url, sizeof(url), fmt, value(& data, "session"), encodedURL); free(encodedURL); free(completeURL); if(!(response = NULL)) //fetch(url, NULL, NULL, NULL))) return 0; if(regular) { for(i = 0; response[i]; ++i) { char status[64] = { 0 }; if(sscanf(response[i], "response=%63[^\r\n]", status) > 0) if(!strncmp(status, "FAILED", 6)) retval = 0; if(sscanf(response[i], "stationname=%127[^\r\n]", name) > 0) { if(playlist.title != NULL) free(playlist.title); decode(name, & playlist.title); unhtml(playlist.title); } } purge(response); response = NULL; if(!retval) { printf("Sorry, couldn't set station to %s.\n", stationURL); return 0; } expand(& playlist); } else { char * xml = join(response, 0); response = NULL; freelist(& playlist); //if(!parsexspf(& playlist, xml)) if(NULL) retval = 0; free(xml); } enable(CHANGED); histapp(stationURL); if(current_station) free(current_station); current_station = strdup(stationURL); assert(current_station != NULL); if(retval && playfork) { enable(INTERRUPTED); kill(playfork, SIGUSR1); } return retval; }
const char * value(struct hash * hash, const char * key) { unsigned index = haskey(hash, key); return index > 0 ? hash->content[index - 1].value : ""; }
int execcmd(const char * cmd, char * reply) { char arg[1024], * ptr; unsigned ncmd; const char * known [] = { "play", "love", "ban", "skip", "quit", "info", "pause", "discovery", "tag-artist", "tag-album", "tag-track", "artist-tags", "album-tags", "track-tags", "stop", "volume-up", "volume-down", "volume", "rtp", "status", "unlove", "detach" }; memset(arg, 0, sizeof(arg)); memset(reply, 0, BUFSIZE); for(ncmd = 0; ncmd < (sizeof(known) / sizeof(char *)); ++ncmd) { if(!strncmp(known[ncmd], cmd, strlen(known[ncmd]))) break; } switch(ncmd) { case (sizeof(known) / sizeof(char *)): strncpy(reply, "ERROR", BUFSIZE); break; /* "play lastfm://station" */ case 0: if(sscanf(cmd, "play %128[a-zA-Z0-9:/_ %,*.+-]", arg) == 1) { char * url; decode(arg, & url); station(url); free(url); } break; /* Love currently played track. */ case 1: rate("L"); break; /* Ban currently played track. */ case 2: rate("B"); break; /* Skip track. */ case 3: rate("S"); break; /* Kill Shell.FM. */ case 4: quit(); /* "info FORMAT" - returns the format string with the meta data filled in. */ case 5: if(* (cmd + 5)) strncpy(reply, meta(cmd + 5, 0, & track), BUFSIZE); else if(haskey(& rc, "np-file-format")) strncpy( reply, meta(value(& rc, "np-file-format"), 0, & track), BUFSIZE ); break; /* Pause playback. */ case 6: if(playfork) { if(pausetime) { kill(playfork, SIGCONT); } else { time(& pausetime); kill(playfork, SIGSTOP); } } break; /* Toggle discovery mode. Returns "DISCOVERY <ON|OFF>" */ case 7: toggle(DISCOVERY); snprintf( reply, BUFSIZE, "DISCOVERY %s", enabled(DISCOVERY) ? "ON" : "OFF" ); break; /* "tag-artist tag1,tag2,..." - tag the artist of the current track. */ case 8: if(sscanf(cmd, "tag-artist %128s", arg) == 1) sendtag('a', arg, track); break; /* "tag-album tag1,tag2,..." - tag the album of the current track. */ case 9: if(sscanf(cmd, "tag-album %128s", arg) == 1) sendtag('l', arg, track); break; /* "tag-track tag1,tag2,..." - tag the current track. */ case 10: if(sscanf(cmd, "tag-track %128s", arg) == 1) sendtag('t', arg, track); break; /* Return comma-separated list of the current artists tags. */ case 11: if((ptr = oldtags('a', track)) != NULL) { strncpy(reply, ptr, BUFSIZE); free(ptr); ptr = NULL; } break; /* Return comma-separated list of the current albums tags. */ case 12: if((ptr = oldtags('l', track)) != NULL) { strncpy(reply, ptr, BUFSIZE); free(ptr); ptr = NULL; } break; /* Return comma-separated list of the current tracks tags. */ case 13: if((ptr = oldtags('t', track)) != NULL) { strncpy(reply, ptr, BUFSIZE); free(ptr); ptr = NULL; } break; /* Stop playback. */ case 14: if(playfork) { enable(STOPPED); kill(playfork, SIGUSR1); } break; /* Increase absolute volume (0-64) by 1. */ case 15: volume_up(); break; /* Decrease absolute volume (0-64) by 1. */ case 16: volume_down(); break; /* Set volume. "volume 32" - set absolute volume (0-64) to 32 (50%). "volume %50" - same, but using percentual volume. "volume +1" - same as "volume_up". "volume -1" - same as "volume_down". Returns absolute volume ("VOLUME 32"). */ case 17: parse_volume(cmd); snprintf(reply, BUFSIZE, "VOLUME %d", volume); break; /* Toggle RTP (report to profile, "scrobbling"). Returns "RTP <ON|OFF>". */ case 18: /* RTP on/off */ toggle(RTP); snprintf(reply, BUFSIZE, "RTP %s", enabled(RTP) ? "ON" : "OFF"); break; /* Get current status. Returns on of "PAUSE", "PLAYING" and "STOPPED". */ case 19: strncpy(reply, PLAYBACK_STATUS, BUFSIZE); break; /* Unlove currently played track. */ case 20: rate("U"); break; /* Detach from network interface. */ case 21: return 1; } return 0; }
int main(int argc, char ** argv) { int option, nerror = 0, background = 0, quiet = 0, have_socket = 0; time_t pauselength = 0; char * proxy; opterr = 0; /* Create directories. */ makercd(); /* Load settings from ~/.shell-fm/shell-fm.rc. */ settings(rcpath("shell-fm.rc"), !0); /* Enable discovery by default if it is set in configuration. */ if(haskey(& rc, "discovery")) enable(DISCOVERY); /* Disable RTP if option "no-rtp" is set to something. */ if(haskey(& rc, "no-rtp")) disable(RTP); /* If "daemon" is set in the configuration, enable daemon mode by default. */ if(haskey(& rc, "daemon")) background = !0; /* If "quiet" is set in the configuration, enable quiet mode by default. */ if(haskey(& rc, "quiet")) quiet = !0; /* Get proxy environment variable. */ if((proxy = getenv("http_proxy")) != NULL) set(& rc, "proxy", proxy); /* Parse through command line options. */ while(-1 != (option = getopt(argc, argv, ":dbhqi:p:D:y:Y:"))) switch(option) { case 'd': /* Daemonize. */ background = !background; break; case 'i': /* IP to bind network interface to. */ set(& rc, "bind", optarg); break; case 'p': /* Port to listen on. */ if(atoi(optarg)) set(& rc, "port", optarg); else { fputs("Invalid port.\n", stderr); ++nerror; } break; case 'b': /* Batch mode */ batch = !0; break; case 'D': /* Path to audio device file. */ set(& rc, "device", optarg); break; case 'y': /* Proxy address. */ set(& rc, "proxy", optarg); break; case 'Y': /* SOCKS proxy address. */ set(& rc, "socks-proxy", optarg); break; case 'q': /* Quiet mode. */ quiet = !quiet; break; case 'h': /* Print help text and exit. */ help(argv[0], 0); break; case ':': fprintf(stderr, "Missing argument for option -%c.\n\n", optopt); ++nerror; break; case '?': default: fprintf(stderr, "Unknown option -%c.\n", optopt); ++nerror; break; } /* The next argument, if present, is the lastfm:// URL we want to play. */ if(optind > 0 && optind < argc && argv[optind]) { const char * station = argv[optind]; set(& rc, "default-radio", station); } if(nerror) help(argv[0], EXIT_FAILURE); #ifdef EXTERN_ONLY /* Abort if EXTERN_ONLY is defined and no extern command is present */ if(!haskey(& rc, "extern")) { fputs("Can't continue without extern command.\n", stderr); exit(EXIT_FAILURE); } #else #ifndef LIBAO if(!haskey(& rc, "device")) set(& rc, "device", "/dev/audio"); #endif #endif if(!background && !quiet) { puts("Shell.FM v" PACKAGE_VERSION ", (C) 2006-2012 by Jonas Kramer"); puts("Published under the terms of the GNU General Public License (GPL)."); #ifndef TUXBOX puts("\nPress ? for help.\n"); #else puts("Compiled for the use with Shell.FM Wrapper.\n"); #endif fflush(stdout); } /* Open a port so Shell.FM can be controlled over network. */ if(haskey(& rc, "bind")) { int port = 54311; if(haskey(& rc, "port")) port = atoi(value(& rc, "port")); if(tcpsock(value(& rc, "bind"), (unsigned short) port)) have_socket = !0; } /* Open a UNIX socket for local "remote" control. */ if(haskey(& rc, "unix") && unixsock(value(& rc, "unix"))) have_socket = !0; /* We can't daemonize if there's no possibility left to control Shell.FM. */ if(background && !have_socket) { fputs("Can't daemonize without control socket.\n", stderr); exit(EXIT_FAILURE); } memset(& data, 0, sizeof(struct hash)); memset(& track, 0, sizeof(struct hash)); memset(& playlist, 0, sizeof(struct playlist)); /* Fork to background. */ if(background) { int null; pid_t pid = fork(); if(pid == -1) { fputs("Failed to daemonize.\n", stderr); exit(EXIT_FAILURE); } else if(pid) { exit(EXIT_SUCCESS); } enable(QUIET); /* Detach from TTY */ setsid(); pid = fork(); if(pid > 0) exit(EXIT_SUCCESS); /* Close stdin out and err */ close(0); close(1); close(2); /* Redirect stdin and out to /dev/null */ null = open("/dev/null", O_RDWR); dup(null); dup(null); } ppid = getpid(); atexit(cleanup); load_queue(); /* Set up signal handlers for communication with the playback process. */ signal(SIGINT, forcequit); /* SIGUSR2 from playfork means it detected an error. */ signal(SIGUSR2, playsig); /* Catch SIGTSTP to set pausetime when user suspends us with ^Z. */ signal(SIGTSTP, stopsig); /* Authenticate to the Last.FM server. */ create_session(); if(!background) { struct input keyboard = { 0, KEYBOARD }; register_handle(keyboard); canon(0); atexit(cleanup_term); } /* Play default radio, if specified. */ if(haskey(& rc, "default-radio")) { if(!strcmp(value(& rc, "default-radio"), "last")) { char ** history = load_history(), * last = NULL, ** p; for(p = history; * p != NULL; ++p) { last = * p; } set(& rc, "default-radio", last); purge(history); } station(value(& rc, "default-radio")); } else if(!background) radioprompt("radio url> "); /* The main loop. */ while(!0) { pid_t child; int status, playnext = 0; /* Check if anything died (submissions fork or playback fork). */ while((child = waitpid(-1, & status, WNOHANG | WUNTRACED | WCONTINUED)) > 0) { if(child == subfork) subdead(WEXITSTATUS(status)); else if(child == playfork) { if(WIFSTOPPED(status)) { /* time(& pausetime); */ } else { if(WIFCONTINUED(status)) { signal(SIGTSTP, stopsig); if(pausetime != 0) { pauselength += time(NULL) - pausetime; } } else { playnext = !0; unlinknp(); if(delayquit) { quit(); } } pausetime = 0; } } } /* Check if the playback process died. If so, submit the data of the last played track. Then check if there are tracks left in the playlist; if not, try to refresh the list and stop the stream if there are no new tracks to fetch. Also handle user stopping the stream here. We need to check for playnext != 0 before handling enabled(STOPPED) anyway, otherwise playfork would still be running. */ if(playnext) { playfork = 0; if(enabled(RTP)) { unsigned duration, played, minimum; duration = atoi(value(& track, "duration")); played = time(NULL) - change_time - pauselength; /* Allow user to specify minimum playback length (min. 50%). */ if(haskey(& rc, "minimum")) { unsigned percent = atoi(value(& rc, "minimum")); if(percent < 50) percent = 50; minimum = duration * percent / 100; } else { minimum = duration / 2; } if(duration >= 30 && (played >= 240 || played > minimum)) { debug("adding track to scrobble queue\n"); enqueue(& track); } else { debug("track too short (duration %d, played %d, minimum %d) - not scrobbling\n", duration, played, minimum); } } submit(); /* Check if the user stopped the stream. */ if(enabled(STOPPED) || error) { freelist(& playlist); empty(& track); if(error) { fputs("Playback stopped with an error.\n", stderr); error = 0; } disable(STOPPED); disable(CHANGED); continue; } shift(& playlist); } if(playnext || enabled(CHANGED)) { if(nextstation != NULL) { playnext = 0; disable(CHANGED); if(!station(nextstation)) enable(STOPPED); free(nextstation); nextstation = NULL; } if(!enabled(STOPPED) && !playlist.left) { expand(& playlist); if(!playlist.left) { puts("No tracks left."); playnext = 0; disable(CHANGED); continue; } } if(!playfork) { /* If there was a track played before, and there is a gap configured, wait that many seconds before playing the next track. */ if(playnext && !enabled(INTERRUPTED) && haskey(& rc, "gap")) { int gap = atoi(value(& rc, "gap")); if(gap > 0) sleep(gap); } disable(INTERRUPTED); if(play(& playlist)) { time(& change_time); pauselength = 0; set(& track, "stationURL", current_station); /* Print what's currently played. (Ondrej Novy) */ if(!background) { if(enabled(CHANGED) && playlist.left > 0) { puts(meta("Receiving %s.", M_COLORED, & track)); disable(CHANGED); } if(haskey(& rc, "title-format")) printf("%s\n", meta(value(& rc, "title-format"), M_COLORED, & track)); else printf("%s\n", meta("Now playing \"%t\" by %a.", M_COLORED, & track)); } if(enabled(RTP)) { notify_now_playing(& track); } /* Write track data into a file. */ if(haskey(& rc, "np-file") && haskey(& rc, "np-file-format")) { signed np; const char * file = value(& rc, "np-file"), * fmt = value(& rc, "np-file-format"); unlink(file); if(-1 != (np = open(file, O_WRONLY | O_CREAT, 0644))) { const char * output = meta(fmt, 0, & track); if(output) write(np, output, strlen(output)); close(np); } } if(haskey(& rc, "screen-format")) { const char * term = getenv("TERM"); if(term != NULL && !strncmp(term, "screen", 6)) { const char * output = meta(value(& rc, "screen-format"), 0, & track); printf("\x1Bk%s\x1B\\", output); } } if(haskey(& rc, "term-format")) { const char * output = meta(value(& rc, "term-format"), 0, & track); printf("\x1B]2;%s\a", output); } /* Run a command with our track data. */ if(haskey(& rc, "np-unescaped-cmd")) run(meta(value(& rc, "np-unescaped-cmd"), 0, & track)); if(haskey(& rc, "np-cmd")) run(meta(value(& rc, "np-cmd"), M_SHELLESC, & track)); } else change_time = 0; } if(banned(value(& track, "creator"))) { puts(meta("%a is banned.", M_COLORED, & track)); rate(RATING_BAN); fflush(stdout); } } playnext = 0; if(playfork && change_time && haskey(& track, "duration") && !pausetime) { unsigned duration; signed remain; char remstr[32]; duration = atoi(value(& track, "duration")); remain = (change_time + duration) - time(NULL) + pauselength; snprintf(remstr, sizeof(remstr), "%d", remain); set(& track, "remain", remstr); if(!background) { printf( "%s%c", meta("%r", M_COLORED, & track), // strdup(meta("%v", M_COLORED, & track)), batch ? '\n' : '\r' ); fflush(stdout); } } handle_input(1000000); } return 0; }