/* Copyright (C) Bram Avontuur (bram@avontuur.org) * * 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 2 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * If you like this program, or if you have any comments, tips for improve- * ment, money, etc, please do so on the sourceforge project page : * http://mp3blaster.sourceforge.net/ * * May 21 2000: removed all calls to attr_get() since not all [n]curses * : versions use the same paramaters (sigh) * Dec 31 2000: Applied a patch from Rob Funk to fix randomness bug * Newer dates: changes made are stored in CVS. */ /* * Added basic LIRC support. * Olgierd Pieczul */ #ifdef HAVE_CONFIG_H #include #endif #include "history.h" #include "mp3blaster.h" #include #include #include #include #include #include #include #include #include #include #include #if defined(PTHREADEDMPEG) && defined(HAVE_PTHREAD_H) #include #elif defined (LIBPTH) && defined(HAVE_PTH_H) #include #endif #include NCURSES_HEADER #ifdef HAVE_GETOPT_H #include #else #include "getopt_local.h" #endif #ifdef HAVE_ERRNO_H #include #endif #include "global.h" #include "scrollwin.h" #include "mp3win.h" #include "fileman.h" #include "exceptions.h" #include #include //load keybindings #include "keybindings.h" #include "getinput.h" #ifdef INCLUDE_LIRC #include #endif #ifdef LIBPTH #define USLEEP(x) pth_usleep(x) #else #define USLEEP(x) usleep(x) #endif /* paranoia define[s] */ #ifndef FILENAME_MAX #define FILENAME_MAX 1024 #endif #define MIN_SCREEN_WIDTH 80 #define MIN_SCREEN_HEIGHT 23 /* values for global var 'window' */ enum _action { AC_NONE, AC_REWIND, AC_FORWARD, AC_NEXT, AC_PREV, AC_STOP, AC_ONE_MP3, AC_PLAY, AC_STOP_LIST, AC_SKIPEND, AC_NEXTGRP, AC_PREVGRP } action; /* There are different types of input modes, e.g. searching for text, * inputting text, .. */ enum _input_mode { IM_DEFAULT, IM_SEARCH, IM_INPUT } input_mode; /* External functions */ extern short cf_parse_config_file(const char *); extern cf_error cf_get_error(); extern const char *cf_get_error_string(); /* Prototypes */ void cw_toggle_group_mode(); void cw_draw_group_mode(); void cw_draw_play_mode(short cleanit=0); short set_play_mode(const char*); void cw_toggle_play_mode(); void mw_clear(); void mw_settxt(const char*); short read_playlist(const char *); short write_playlist(const char *); void *play_list(void *); short start_song(short was_playing=0); void set_one_mp3(char*); void stop_song(); void stop_list(); void update_play_display(); void lock_playing_mutex(); void unlock_playing_mutex(); void update_status_file(); void write_status_file(); #ifdef PTHREADEDMPEG void change_threads(); void cw_draw_threads(short cleanit=0); short set_threads(int); #endif void add_selected_file(const char*); char *get_current_working_path(); void usage(); void show_help(); void draw_static(int file_mode=0); void draw_settings(int cleanit=0); void fw_getpath(const char *, void *); void fw_convmp3(const char *, void *); void fw_addurl(const char *, void *); void fw_search_next_char(char); void fw_start_search(int timeout=2); void fw_end_search(); void fw_delete(); void set_sound_device(const char *); void set_mixer_device(const char *); short set_fpl(int); void set_default_colours(); void set_action(_action bla); char *determine_song(short set_played=1); void set_song_info(const char *fl, struct song_info si); void set_song_status(playstatus_t); void set_song_time(int,int); void popup_win(const char **label, void (*func)(const char *, void *), void *func_args = NULL, const char *init_str = NULL, short want_input=0, int maxlen=0); int myrand(int); void reset_playlist(int); void cw_draw_repeat(); void init_helpwin(); void repaint_help(); void get_label(command_t, char *); void set_keylabel(int k, char *label); void init_playopts(); void init_globalopts(); void draw_next_song(const char*); void set_next_song(int); void reset_next_song(); mp3Win* newgroup(); short fw_markfilebad(const char *file); void get_mainwin_size(int*, int*, int*, int*); void get_mainwin_borderchars(chtype*, chtype*, chtype*, chtype*, chtype*, chtype*, chtype*, chtype*); void change_program_mode(unsigned short pm); void end_help(); void update_songinfo(playstatus_t, short); void update_ncurses(int update_all); void end_program(); const char *get_error_string(int); void fw_toggle_sort(); void fw_draw_sortmode(sortmodes_t); short fw_toggle_display(); short toggle_display(); void warning(const char *, ...); void wake_player(); void add_init_opt(struct init_opts *myopts, const char *option, void *value); void get_input( WINDOW *win, const char*defval, unsigned int size, unsigned int maxlen, int y, int x, void (*got_input)(const char *, void *), void *args = NULL ); void set_inout_opts(); void select_files_by_pattern(const char *, void *); void playlist_write(const char *playlistfile, void *args); void fw_rename_file(const char *newname, void *args); void *lirc_loop(void *); #if defined(PTHREADEDMPEG) # define LOCK_NCURSES (!pthread_mutex_lock(&ncurses_mutex)) # define UPDATE_CURSES # define UNLOCK_NCURSES pthread_mutex_unlock(&ncurses_mutex) #elif defined(LIBPTH) # define LOCK_NCURSES (pth_mutex_acquire(&ncurses_mutex, FALSE, NULL) != -1) # define UPDATE_CURSES # define UNLOCK_NCURSES pth_mutex_release(&ncurses_mutex) #endif #define OPT_LOADLIST 1 #define OPT_DEBUG 2 #define OPT_PLAYMP3 4 #define OPT_NOMIXER 8 #define OPT_AUTOLIST 16 #define OPT_QUIT 32 #define OPT_CHROOT 64 #define OPT_DOWNSAMPLE 128 #define OPT_SOUNDDEV 256 #define OPT_PLAYMODE 512 #define OPT_FPL 1024 #define OPT_8BITS 2048 #define OPT_THREADS 4096 #define OPT_MIXERDEV 8192 #define OPT_REPEAT 16384 /* global vars */ /* songinf contains info about the currently playing audio file. * Access to songinf may only occur when a lock has been acquired on the * ncurses mutex (see LOCK_NCURSES) */ struct song_t { struct song_info songinfo; playstatus_t status; int elapsed; int remain; char *path; char *next_song; char *warning; int update; // 0, don't update, 1: update songinfo, 2: update time, 3: both } songinf; struct playopts_t { short song_played; short playing_one_mp3; short allow_repeat; //true if at least 1 song in the list finished correctly short pause; short playing; char * one_mp3; Fileplayer *player; time_t tyd; time_t newtyd; short quit_program; } playopts; struct ptag_t { char tag[80]; char keywords[5][80]; char values[5][255]; }; unsigned short progmode; #ifdef PTHREADEDMPEG pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER, ncurses_mutex = PTHREAD_MUTEX_INITIALIZER, onemp3_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t playing_cond = PTHREAD_COND_INITIALIZER; pthread_t thread_playlist; //thread that handles playlist (main handles input) #ifdef INCLUDE_LIRC pthread_t thread_lirc; #endif #elif defined(LIBPTH) pth_mutex_t playing_mutex, ncurses_mutex, onemp3_mutex; pth_cond_t playing_cond; pth_t thread_playlist; #endif /* PTHREADEDMPEG */ mp3Win *mp3_curwin = NULL, /* currently displayed mp3 window */ *mp3_rootwin = NULL, /* root mp3 window */ *mp3_groupwin = NULL, /* starting group for PLAY_GROUPS mode */ *playing_group = NULL; /* in group-mode playback: group being played */ char *status_file = NULL; fileManager *file_window = NULL; /* window to select files with */ scrollWin *helpwin = NULL, *bighelpwin = NULL; NMixer *mixer = NULL; int nselfiles = 0, current_song = -1, nr_played_songs = 1, next_song = -1; short popup = 0, /* 1 when a `popup' window is onscreen */ quit_after_playlist = 0; struct _globalopts globalopts; char **selected_files = NULL, *startup_path = NULL, *fw_searchstring, **played_songs = NULL, **environment = NULL; History *history = NULL; getInput *global_input = NULL; #ifdef INCLUDE_LIRC command_t lirc_cmd = CMD_NONE; #endif /* Function Name: usage * Description : Warn user about this program's usage and then exit nicely * : Program must NOT be in ncurses-mode when this function is * : called. */ void usage() { fprintf(stderr, "Mp3blaster v%s (C)1997 - 2001 Bram Avontuur.\n%s", \ VERSION, \ "Usage:\n" \ "\tmp3blaster [options]\n"\ "\tmp3blaster [options] [file1 ...]\n"\ "\t\tPlay one or more mp3's\n"\ "\tmp3blaster [options] --list/-l \n"\ "\t\tLoad a playlist but don't start playing.\n"\ "\tmp3blaster [options] --autolist/-a \n"\ "\t\tLoad a playlist and start playing.\n"\ "\nOptions:\n"\ "\t--downsample/-2: Downsample (44->22Khz etc)\n"\ "\t--8bits/-8: 8bit audio (autodetected if your card can't handle 16 bits)\n"\ "\t--config-file/-c=file: Use other config file than the default\n"\ "\t\t~/.mp3blasterrc\n"\ "\t--debug/-d: Log debug-info in $HOME/.mp3blaster.\n"\ "\t--status-file/-f=file: Keep info on the mp3s being played, in the\n"\ "\t\tspecified file.\n"\ "\t--help/-h: This help screen.\n"\ "\t--mixer-device/-m: Mixer device to use (use 'NAS' for NAS mixer)\n"\ "\t--no-mixer/-n: Don't start the built-in mixer.\n"\ "\t--chroot/-o=: Set as mp3blaster's root dir.\n"\ "\t\tThis affects *ALL* file operations in mp3blaster!!(including\n"\ "\t\tplaylist reading&writing!) Note that only users with uid 0\n"\ "\t\tcan use this option (yet?). This feature will change soon.\n"\ "\t--playmode/-p={onegroup,allgroups,allrandom}\n"\ "\t\tDefault playing mode is resp. Play first group only, Play\n"\ "\t\tall groups, Play all songs in random order.\n"\ "\t--dont-quit/-q: Don't quit after playing mp3[s] (only makes sense\n"\ "\t\tin combination with --autolist or files from command-line)\n"\ "\t--repeat/-R: Repeat playlist.\n"\ "\t--runframes/-r=: Number of frames to decode in one loop.\n"\ "\t\tRange: 1 to 10 (default=5). A low value means that the\n"\ "\t\tinterface (while playing) reacts faster but slow CPU's might\n"\ "\t\thick. A higher number implies a slow interface but less\n"\ "\t\thicks on slow CPU's.\n"\ "\t--sound-device/-s=: Device to use to output sound.\n"\ "\t\tDefault for your system is " SOUND_DEVICE ".\n"\ "\t\tIf you want to use NAS (Network Audio System) as playback\n"\ "\t\tdevice, then enter the nasserver's address as device (e.g.\n"\ "\t\thost.name.com:0; it *must* contain a colon)\n"\ "\t--threads/-t=: Numbers of threads to use for buffering\n"\ "\t\t(only works if mp3blaster was compiled with threads). Range is \n"\ "\t\t0..500 in increments of 50 only.\n"\ "\t--version,v: Display version number.\n"\ ""); exit(1); } /* Function : calc_mainwindow_width * Description: Calculates the width of the main window (the big * : scrollable one) based on the current term size * Parameters : termWidth * : Current width of the terminal * Returns : The calculated width * SideEffects: None. */ int calc_mainwindow_width(int termWidth) { int result = termWidth - 12; if (result < 1) result = 1; return result; } /* Function : calc_mainwindow_height * Description: Calculates the height of the main window (the big * : scrollable one) based on the current term size * Parameters : termHeight * : Current height of the terminal * Returns : The calculated height * SideEffects: None. */ int calc_mainwindow_height(int termHeight) { int result = termHeight - 13; if (result < 1) result = 1; return result; } /* Function : fw_begin * Description: Enters file manager mode. Program should be in normal mode * : when this function's called. * Parameters : None. * Returns : Nothing. * SideEffects: None. */ void fw_begin() { quit_after_playlist = 0; change_program_mode(PM_FILESELECTION); fw_draw_sortmode(globalopts.fw_sortingmode); if (file_window) delete file_window; file_window = new fileManager(NULL, calc_mainwindow_height(LINES), calc_mainwindow_width(COLS), 10, 0, CP_DEFAULT, 1); file_window->drawTitleInBorder(1); file_window->setBorder(ACS_VLINE, ACS_VLINE, ACS_HLINE, ACS_HLINE, ACS_LTEE, ACS_PLUS, ACS_LTEE, ACS_PLUS); if (globalopts.want_id3names) file_window->setDisplayMode(2); //show as ID3 names else file_window->setDisplayMode(1); //show file sizes as well file_window->setWrap(globalopts.wraplist); file_window->swRefresh(0); draw_static(1); } /* fw_end closes the file_window if it's on-screen, and moves all selected * files into the currently used group. Finally, the selection-window of the * current group is put back on the screen and the title in the header-window * is set to match that of the current group. If files were selected in another * dir than the current dir, these will be added too (they're stored in * selected_files then). selected_files will be freed as well. */ void fw_end() { int i, nselected; char **selitems = NULL; mp3Win *sw = mp3_curwin; if (progmode != PM_FILESELECTION) /* or: !file_window */ return; selitems = file_window->getSelectedItems(&nselected); for ( i = 0; i < nselected; i++) { if (!is_audiofile(selitems[i]) || is_dir(selitems[i])) continue; const char *path = file_window->getPath(); char *itemname = (char *)malloc(strlen(selitems[i]) + strlen(path) + 2); if (path[strlen(path) - 1] != '/') sprintf(itemname, "%s/%s", path, selitems[i]); else sprintf(itemname, "%s%s", path, selitems[i]); add_selected_file(itemname); free(itemname); } if (nselected) { for (i = 0; i < nselected; i++) delete[] selitems[i]; delete[] selitems; } if (selected_files) { short foo[2] = { 0, 1 }; const char *bla[4]; int idx; for (i = 0; i < nselfiles; i++) { bla[0] = (const char*)selected_files[i]; if (is_httpstream(selected_files[i])) bla[1] = (const char*)selected_files[i]; else bla[1] = chop_path(selected_files[i]); bla[2] = NULL; bla[3] = NULL; idx=sw->addItem(bla, foo, CP_FILE_MP3); if (idx!=-1) sw->changeItem(idx,&id3_filename,strdup(bla[0]),2); free(selected_files[i]); } free(selected_files); selected_files = NULL; nselfiles = 0; } delete file_window; file_window = NULL; change_program_mode(PM_NORMAL); sw->swRefresh(1); draw_static(); mp3_curwin->swRefresh(2); } void fw_changedir(const char *newpath = 0) { if (progmode != PM_FILESELECTION) return; int nselected = 0; char **selitems; const char *path; if (!newpath) path = file_window->getSelectedItem(); else path = newpath; selitems = file_window->getSelectedItems(&nselected); /* if not changed to current dir and files have been selected add them to * the selection list */ if (strcmp(path, ".") && nselected) { int i; for (i = 0; i < nselected; i++) { const char *pwd = file_window->getPath(); char *file = (char *)malloc((strlen(pwd) + strlen(selitems[i]) + 2) * sizeof(char)); strcpy(file, pwd); if (pwd[strlen(pwd) - 1] != '/') strcat(file, "/"); strcat(file, selitems[i]); if (is_audiofile(file)) add_selected_file(file); free(file); delete[] selitems[i]; } delete[] selitems; } file_window->changeDir(path); refresh(); } /* PRE: Be in PM_FILESELECTION */ void fw_getpath(const char *npath, void *opts) { DIR *dir = NULL; char *newpath; if (opts); if (!npath || !(newpath = expand_path(npath))) return; if ( !(dir = opendir(newpath))) { free(newpath); return; } closedir(dir); fw_changedir(newpath); free(newpath); return; } void draw_play_key(short do_refresh) { char klabel[4]; int maxy,maxx; int r; getmaxyx(stdscr, maxy, maxx); r = maxx - 12; attrset(COLOR_PAIR(CP_LABEL)); move(maxy-9,r+4); lock_playing_mutex(); if (!playopts.playing || (playopts.playing && playopts.song_played && playopts.pause)) addstr(" |>"); else addstr(" ||"); unlock_playing_mutex(); get_label(CMD_PLAY_PLAY, klabel); move(maxy-8,r+4); addstr(klabel); attrset(COLOR_PAIR(CP_DEFAULT)|A_NORMAL); if (do_refresh) refresh(); } /* Draws static interface components (and refreshes the screen). */ void draw_static(int file_mode) { int maxy, maxx, r; char klabel[4]; getmaxyx(stdscr,maxy,maxx); r = maxx - 12; if (file_mode); //First, the border box(stdscr, ACS_VLINE, ACS_HLINE); //Now, the line under the function keys and above the status window move(5,1); hline(ACS_HLINE, maxx-2); move(5,0); addch(ACS_LTEE); move(5,maxx-1); addch(ACS_RTEE); move(maxy-4,1); hline(ACS_HLINE, maxx-2); move(maxy-4,0); addch(ACS_LTEE); move(maxy-4,maxx-1); addch(ACS_RTEE); //draw the box bottom-right from the function keys move(10,r); hline(ACS_HLINE, 11); move(6,r-1); vline(ACS_VLINE,4); move(5,r-1); addch(ACS_TTEE); move(10,maxx-1); addch(ACS_RTEE); //fix upperleft corner for playlist window move(10,0); addch(ACS_LTEE); move(11,r); addch('['); move(11,r+2); addstr("] shuffle"); move(12,r); addch('['); move(12,r+2); addstr("] repeat"); move(9,r); addstr("Threads:"); move(0,COLS-6); addstr("( )"); move(5,COLS-6); addstr("( )"); attrset(COLOR_PAIR(CP_BUTTON)); get_label(CMD_HELP_PREV, klabel); move(0,COLS-5); addnstr(klabel, 3); get_label(CMD_HELP_NEXT, klabel); move(5,COLS-5); addnstr(klabel, 3); attrset(COLOR_PAIR(CP_DEFAULT)|A_NORMAL); move(maxy-4,maxx-13); addch(ACS_PLUS); move(maxy-3,maxx-13); addch(ACS_VLINE); move(maxy-2,maxx-13); addch(ACS_VLINE); move(maxy-1,maxx-13); addch(ACS_BTEE); //draw cd-style keys draw_play_key(0); attrset(COLOR_PAIR(CP_LABEL)); move(maxy-9, r); addstr(" |<"); move(maxy-8, r); get_label(CMD_PLAY_PREVIOUS, klabel); addstr(klabel); move(maxy-9,r+8); addstr(" >|"); move(maxy-8,r+8); get_label(CMD_PLAY_NEXT, klabel); addstr(klabel); move(maxy-6, r); addstr(" <<"); move(maxy-5, r); get_label(CMD_PLAY_REWIND, klabel); addstr(klabel); move(maxy-6,r+4); addstr(" []"); move(maxy-5,r+4); get_label(CMD_PLAY_STOP, klabel); addstr(klabel); move(maxy-6,r+8); addstr(" >>"); move(maxy-5,r+8); get_label(CMD_PLAY_FORWARD, klabel); addstr(klabel); attrset(COLOR_PAIR(CP_DEFAULT)|A_NORMAL); if (!globalopts.no_mixer) { char klabel[4]; get_label(CMD_TOGGLE_MIXER, klabel); move(maxy-4,maxx-12); addstr("[ ]Mixer"); move(maxy-4, maxx-11); attrset(COLOR_PAIR(CP_BUTTON)); addnstr(klabel, 3); attrset(COLOR_PAIR(CP_DEFAULT)|A_NORMAL); } //Draw Next Song: move(7,2); addstr("Next Song :"); refresh(); return; } void refresh_screen() { wrefresh(curscr); } /* * Returns the main (scrollable) window. This windows is * Dependant on the context the program is currently in. */ scrollWin * getMainWindow() { scrollWin * mainwindow = (scrollWin *)mp3_curwin; if (progmode == PM_FILESELECTION) mainwindow = (scrollWin *)file_window; else if (progmode == PM_HELP) mainwindow = (scrollWin *)bighelpwin; return mainwindow; } /* Function : init_mixer * Description: (re)creates the mini mixer in the bottom right of the * : screen, but only if a mixer is desired. * Parameters : None. * Returns : Nothing. * SideEffects: None. */ void init_mixer() { /* initialize mixer */ if (!globalopts.no_mixer) { const int color_pairs[4] = { 13,14,15,16 }; int mixerXOffset = COLS - 12; int mixerYOffset = LINES - 3; if (mixer) { delete mixer; } //sanity check against too small screen[resize]s if (mixerXOffset < 0) mixerXOffset = 0; if (mixerYOffset < 0) mixerYOffset = 0; mixer = new NMixer(stdscr, globalopts.mixer_device, mixerXOffset, mixerYOffset, 2, color_pairs, globalopts.colours.default_bg, 1); if (!(mixer->NMixerInit())) { delete mixer; } else { //TODO: These should be customizable too.. mixer->setMixerCommandKey(MCMD_NEXTDEV, 't'); mixer->setMixerCommandKey(MCMD_PREVDEV, 'T'); mixer->setMixerCommandKey(MCMD_VOLUP, '>'); mixer->setMixerCommandKey(MCMD_VOLDN, '<'); } } } void handle_resize(int dummy = 0) { int newWindowWidth; int newWindowHeight; (void)dummy; //unused param /* re-initialize ncurses, this updates LINES & COLS */ endwin(); refresh(); clear(); newWindowWidth = calc_mainwindow_width(COLS); newWindowHeight = calc_mainwindow_height(LINES); try { //resize the window that's currently onscreen. getMainWindow()->resize(newWindowWidth, newWindowHeight); //resize all playlist windows recursively recursively. Just one of them //may be onscreen. mp3_rootwin->resize(newWindowWidth, newWindowHeight, 1); } catch (IllegalArgumentsException e) { endwin(); fprintf(stderr, "Resize too small!\n"); exit(1); } draw_settings(); init_helpwin(); init_mixer(); repaint_help(); getMainWindow()->swRefresh(2); draw_static(); //redraw current song status / info update_ncurses(1); refresh_screen(); } /* Adds a new group to mp3_curwin and returns it. */ /* Function : add_group * Description: Adds a new group to mp3_curwin and returns it. * Parameters : newgroupname: Name of the group. If no name is supplied, * : [Default] is used. If the groupname does not start and end with * : '[' and ']', these will be added. * Returns : The newly created group. * SideEffects: None. */ mp3Win * add_group(const char *newgroupname=0) { mp3Win *sw; sw = newgroup(); if (!newgroupname) newgroupname = "[Default]"; sw->setTitle(newgroupname); mp3_curwin->addGroup(sw, newgroupname); mp3_curwin->swRefresh(1); return sw; } /* Function : set_group_name * Description: Sets the group name of the currently highlighted group. * : It is called from popup_win. * Parameters : name: Name to give to group. free() after use. * : args: not used * Returns : Nothing. * SideEffects: None. */ void set_group_name(const char *name, void *args) { const char *nm; mp3Win *sw = mp3_curwin; int index; if (args); if (!sw) return; index = sw->sw_selection; if (!sw->isGroup(index) || !(nm = sw->getItem(index)) || !strcmp(nm, "[..]")) return; if (!strcmp(name, "..")) { return; } char tmpname[strlen(name)+3]; sprintf(tmpname, "[%s]", name); sw->changeItem(index, tmpname); mp3Win *gr = sw->getGroup(index); if (gr) gr->setTitle(tmpname); } void mw_clear() { int width = (COLS > 14 ? COLS - 14 : 1); char emptyline[width+1]; //clear old text memset(emptyline, ' ', width * sizeof(char)); emptyline[width] = '\0'; move(LINES-2,1); addstr(emptyline); } void mw_settxt(const char *txt) { mw_clear(); move(LINES-2,1); addnstr(txt, (COLS > 14 ? COLS-14 : 1)); refresh(); } /* PRE: f is opened * POST: if a group was successfully read from f, non-zero will be returned * and the group is added. */ int read_group_from_file(FILE *f) { return 0 || f; //gotta fix this.. #if 0 int linecount = 0; char *line = NULL; short read_ok = 1, group_ok = 0; scrollWin *sw = NULL; while (read_ok) { line = readline(f); if (!line) { read_ok = 0; continue; } if (linecount == 0) { char *tmp = (char*)malloc((strlen(line) + 1) * sizeof(char)); int success = sscanf(line, "GROUPNAME: %[^\n]", tmp); if (success < 1) /* bad group-header! */ read_ok = 0; else /* valid group - add it */ { char *title; group_ok = 1; sw = group_stack->entry(globalopts.current_group - 1); /* if there are no entries in the current group, the first * group will be read into that group. */ if (sw->getNitems()) { add_group(); sw = group_stack->entry(group_stack->stackSize() - 1); } title = (char*)malloc((strlen(tmp) + 16) * sizeof(char)); sprintf(title, "%02d:%s", globalopts.current_group, tmp); sw->setTitle(tmp); sw->swRefresh(0); free(title); } free(tmp); } else if (linecount == 1) /* read play order from file */ { char *tmp = new char[strlen(line) + 1]; int success = sscanf(line, "PLAYMODE: %[^\n]", tmp); if (success < 1) /* bad group-header! */ { read_ok = 0; group_ok = 0; } else ((mp3Win*)sw)->setPlaymode((short)(atoi(tmp) ? 1 : 0)); delete[] tmp; } else { if (!strcmp(line, "\n")) /* line is empty, great. */ read_ok = 0; else { char *songname = new char[strlen(line) + 1]; sscanf(line, "%[^\n]", songname); if (is_audiofile(songname)) { const char *bla[4]; short foo[2] = { 0, 1 }; int idx; bla[0] = (const char*)songname; bla[1] = chop_path(songname); bla[2] = NULL; bla[3] = NULL; idx=sw->addItem(bla, foo, CP_FILE_MP3); if (idx!=-1) { sw->changeItem(idx,&id3_filename,strdup(bla[0]),2); } } delete[] songname; } /* ignore non-mp3 entries.. */ } free(line); ++linecount; } if (group_ok) sw->swRefresh(1); return group_ok; #endif } /* return value = allocated using new, so delete[] it */ void popup_win ( const char **label, void (*func)(const char *, void *), void *func_args, const char *init_str, short want_input, int maxlen ) { int maxx, maxy, width, height, by, bx, i, j; popup = 1; /* global variable, indicating input status */ getmaxyx(stdscr, maxy, maxx); width = maxx - 14; height = maxy - 15; by = 11; bx = 1; if (!maxlen) maxlen = width - 2; bkgdset(COLOR_PAIR(CP_POPUP)|A_BOLD); //color the warning screen. It's really awkward, but it works.. for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { //move(by+i, bx+j); addch('x'); move(by+i, bx+j); addch(' '); } } //write text i = 0; while (label[i]) { move(by+i+2, bx+1); addstr(label[i++]); } if (want_input) { //attrset(A_BOLD|COLOR_PAIR(CP_POPUP_INPUT)); attrset(COLOR_PAIR(CP_DEFAULT)|A_NORMAL); bkgdset(COLOR_PAIR(CP_DEFAULT)); for (i = 1; i < width -1; i++) { move(by + height - 2, bx + i); addch(' '); } move(by+height-2, bx+1); echo(); leaveok(stdscr, FALSE); curs_set(1); refresh(); #if 0 text = new char[MIN(width-2,maxlen)+1]; getnstr(text, MIN(width-2,maxlen)); #else mw_settxt("Use up and down arrows to toggle insert mode"); get_input(stdscr, init_str, width - 2, maxlen, by + height -2, bx + 1, func, func_args); return; /* set_inout_opts(); */ #endif } /* TODO: IIRC want_input is always true, so this part is never called */ #if 0 attrset(COLOR_PAIR(CP_DEFAULT)|A_NORMAL); bkgdset(COLOR_PAIR(CP_DEFAULT)); popup = 0; refresh_screen(); return text; #endif } //playlist functions struct ptag_t parse_playlist_token(const char *str) { struct ptag_t restag, badtag; short status = 0; /* meaning of status (state in state machine): * 0: scanning the tagname * 1: scanning whitespace (searching for keyword) * 2: scanning keywordname * 3: scanning '=' separating keyword/value * 4: scanning '"' that starts value * 5: scanning value */ short tagcnt = 0; short current_keyword = 0; short current_value = 0; char token, last_token; short i; char *mystr = strdup(str); restag.tag[0] = badtag.tag[0] = '\0'; for (i = 0; i < 5; i++) { restag.keywords[i][0] = badtag.keywords[i][0] = '\0'; restag.values[i][0] = badtag.values[i][0] = '\0'; } if (!mystr || !strlen(mystr) || strlen(mystr) < 2 || mystr[0] != '<' || mystr[strlen(mystr)-1] != '>') return badtag; last_token = '\0'; mystr[strlen(mystr)-1] = '\0'; while(*(++mystr)) { token = *mystr; if (!status) //still completing keyword { if (token == ' ' || token == '\t') { status++; //look for keywords now restag.tag[tagcnt] = '\0'; tagcnt = 0; last_token = token; continue; } last_token = token; if (tagcnt > 78) //just chop off too long tagnames continue; restag.tag[tagcnt++] = token; } else if (status == 1 || status == 2) //looking for the start o/a keyword { if (status == 1) { if (token == ' ' || token == '\t') continue; else status = 2; //found keyword } if (token == ' ' || token == '\t' || token == '=') //end of keyword { restag.keywords[current_keyword][tagcnt] = '\0'; tagcnt = 0; current_keyword++; last_token = token; tagcnt = 0; if (token == '=') status = 4; else status = 3; continue; } last_token = token; if (tagcnt > 78) continue; restag.keywords[current_keyword][tagcnt++] = token; } else if (status == 3) //searching for = { if (token == '=') { last_token = token; status = 4; } else { last_token = token; } } else if (status == 4) //looking for " starting value; { if (token != '"') { last_token = token; continue; } else { status = 5; //start of value last_token = token; } } else if (status == 5) //scanning value { if (token == '"' && last_token != '\\') { last_token = token; status = 1; //start looking for keyword again restag.values[current_value][tagcnt] = '\0'; current_value++; tagcnt = 0; continue; } if (tagcnt > 78) { last_token = token; continue; } if (token != '\\' || last_token == '\\') restag.values[current_value][tagcnt++] = token; last_token = token; } } if (!status) //a tag with just a tagname is valid too ofcourse { restag.tag[tagcnt] = '\0'; status = 1; } if (status != 1) //not good. return badtag; debug("Scanning done\n"); { short i; debug("tag[tag]: \"%s\"\n", restag.tag); for (i = 0; i < 5; i++) { if (restag.keywords[i][0]) { debug("tag.keywords[%d] = \"%s\"\n", i, restag.keywords[i]); } if (restag.values[i][0]) { debug("tag.values[%d] = \"%s\"\n", i, restag.values[i]); } } } return restag; } /* Function : tag_keyword_value * Description: Searches for the presence of a keyword in a HTML-like tag * : and returns the value if it's present. If the keyword is not * : found, NULL is returned. Searches are case-insensitive. * Parameters : tag: the HTML-like tag, e.g. generated by parse_playlist_token * Returns : See description. * SideEffects: None. * Example : If the tag is something like * : and the requested keyword is "type", "shuffle" will be * : returned. */ const char* tag_keyword_value(struct ptag_t tag, const char *kword) { int i; for (i = 0; i < 5; i++) { if (!strcasecmp(tag.keywords[i], kword)) { return tag.values[i]; } } return NULL; } /* Function : read_playlist * Description: Reads a playlist from a file and adds the songs to the playlist * Parameters : filename: * : Path to the playlist * Returns : If succesful 1, else 0. * SideEffects: None. * TODO : Adding groups seems a bit buggy to me. If sigsegv's happen, * : this is probably the place to look for them. */ short read_playlist(const char *filename) { FILE *f; char *tmpline, *line; const char *extension; mp3Win *current_group = mp3_curwin; int is_m3u = 0; f = NULL; line = tmpline = NULL; if (!filename || !(f = fopen(filename, "r"))) return 0; if (strlen(filename)>=4) { extension = filename + (strlen(filename)-4); if (strcmp(".m3u", extension) == 0) is_m3u = 1; } while ( (tmpline = readline(f)) ) { line = crop_whitespace((const char *)tmpline); free(tmpline); if (!strlen(line)) { free(line); continue; } if (is_m3u && strncmp(line, "#EXT", 4)==0) { free(line); continue; } if (line[0] == '<') { struct ptag_t ptag = parse_playlist_token((const char *)line); const char *keyval; if (!strcasecmp(ptag.tag, "global")) { keyval = tag_keyword_value(ptag, "playmode"); if (keyval) { set_play_mode(keyval); debug("Set playmode from playlist file\n"); } keyval = tag_keyword_value(ptag, "mode"); if (keyval && !strcasecmp(keyval, "shuffle")) { mp3_rootwin->setPlaymode(1); debug("Shufflemode on!\n"); } } else if (!strcasecmp(ptag.tag, "group")) { const char *group_name = tag_keyword_value(ptag, "name"); const char *group_mode = NULL; if (group_name) { char *gname = new char[strlen(group_name) + 3]; memset(gname, '\0', (strlen(group_name) + 3) * sizeof(char)); strcat(gname, "["); strncat(gname, group_name, 48); strcat(gname, "]"); debug("GroupName: %s\n",gname); mp3Win *new_group = newgroup(); if (!new_group) continue; new_group->setTitle(gname); if ( (group_mode = tag_keyword_value(ptag, "mode")) && !strcasecmp(group_mode, "shuffle")) new_group->setPlaymode(1); current_group->addGroup(new_group, gname); current_group = new_group; } } else if (!strcasecmp(ptag.tag, "/group")) { mp3Win *prev_group = current_group->getGroup(0); //should be [..] if (prev_group) current_group = prev_group; } free(line); continue; } const char *bla[4]; short foo[2] = { 0, 1 }; short idx; if (!strchr(line, '/')) { char *currentPath = get_current_working_path(); currentPath = (char*)realloc(currentPath, (strlen(currentPath) + strlen(line) + 1) * sizeof(char)); strcat(currentPath, line); free(line); line = currentPath; //file without a path in the playlist dir } bla[0] = (const char*)line; bla[1] = chop_path((const char *)line); bla[2] = NULL; bla[3] = NULL; idx=current_group->addItem(bla, foo, CP_FILE_MP3); if (idx!=-1) current_group->changeItem(idx,&id3_filename,strdup(bla[0]),2); free(line); } //refresh_screen(); return 1; } #if 0 FILE *f; char *name; if (!filename) name = gettext("Enter filename:", 1); else { name = (char*)malloc((strlen(filename) + 1) * sizeof(char)); strcpy(name, filename); } /*append startup path if filename does not start with a slash */ if (strlen(name) && name[0] != '/') { char *newname = (char*)malloc(strlen(startup_path) + strlen(name) + 1); strcpy(newname, startup_path); strcat(newname, name); free(name); name = newname; } f = fopen(name, "r"); free(name); if (!f) { warning("Couldn't open playlist-file."); refresh_screen(); return; } short header_ok = 1; /* read global playmode from file */ { char *line = readline(f); playmode old_mode = globalopts.play_mode; if (!line) { fclose(f); return; } char tmp[strlen(line)+1]; if ( sscanf(line, "GLOBALMODE: %[^\n]", tmp) == 1) { short dum = set_play_mode((const char *)tmp); if (!dum) header_ok = 0; } else header_ok = 0; if (header_ok && old_mode != globalopts.play_mode) cw_draw_play_mode(); free(line); } if (header_ok) { while (read_group_from_file(f)) ; cw_draw_group_mode(); } else { warning("No global playmode in playlist found."); refresh_screen(); } fclose(f); //mw_settxt("Added playlist!"); #endif short write_group(mp3Win *group, FILE *f, short indent_level) { int nitems = group->getNitems(); int itemcount = 0; char *indent_string = new char[indent_level + 1]; if (indent_level > 10) return 0; memset(indent_string, '\t', indent_level); indent_string[indent_level] = '\0'; while (itemcount < nitems) { char *itemname = NULL; const char *tmpname = NULL; if (group->isGroup(itemcount)) { if (strcmp(group->getItem(itemcount), "[..]")) //skip [..] !!! { mp3Win *new_group = group->getGroup(itemcount); const char *tmp_gname1 = new_group->getTitle(); char *tmp_gname = strdup(tmp_gname1); tmp_gname[strlen(tmp_gname)-1] = '\0'; //chop off ']'; char *gname = new char[strlen(indent_string) + strlen(tmp_gname) + 20]; //TODO: escape "'s in groupname sprintf(gname, "%s\n", indent_string, (const char *)(tmp_gname + 1), (new_group->getPlaymode() ? " MODE=\"shuffle\"" : "")); debug(gname); free(tmp_gname); if (fwrite(gname, sizeof(char), strlen(gname), f) < strlen(gname) * sizeof(char)) { debug("Short write!\n"); delete[] gname; return 0; } delete[] gname; gname = NULL; write_group(new_group, f, indent_level + 1); gname = new char[strlen(indent_string) + 10]; sprintf(gname, "%s\n", indent_string); if (fwrite(gname, sizeof(char), strlen(gname), f) < strlen(gname) * sizeof(char)) { delete[] gname; return 0; } delete[] gname; } } else { tmpname = group->getItem(itemcount); itemname = new char[strlen(indent_string) + strlen(tmpname) + 2]; sprintf(itemname, "%s%s\n", indent_string, tmpname); if (fwrite(itemname, sizeof(char), strlen(itemname), f) < strlen(itemname) * sizeof(char)) { delete[] itemname; return 0; } delete[] itemname; } itemcount++; } return 1; } short write_playlist(const char *filename) { mp3Win *current_group = mp3_rootwin; FILE *f = fopen(filename, "w"); const char *pmstr = NULL; char globalstr[255]; if (!f) return 0; switch(globalopts.play_mode) { case PLAY_GROUP: pmstr = "onegroup"; break; case PLAY_GROUPS: pmstr = "allgroups"; break; //case PLAY_GROUPS_RANDOMLY: pmstr = "groupsrandom"; break; case PLAY_SONGS: pmstr = "allrandom"; break; default: pmstr = "unknown"; } //write global stuff sprintf(globalstr, "\n", pmstr, (mp3_rootwin->getPlaymode() ? " MODE=\"shuffle\"" : "")); if (fwrite(globalstr, sizeof(char), strlen(globalstr), f) != strlen(globalstr) * sizeof(char)) { fclose(f); return 0; } if (!write_group(current_group, f, 0)) { fclose(f); return 0; } fclose(f); return 1; } #if 0 char *name = NULL; name = gettext("Enter filename", 1); /*append startup path if filename does not start with a slash */ if (strlen(name) && name[0] != '/') { char *newname = (char*)malloc(strlen(startup_path) + strlen(name) + 1); strcpy(newname, startup_path); strcat(newname, name); free(name); name = newname; //fprintf(stderr, "New path: %s\n", name); } if (!(group_stack->writePlaylist(name, globalopts.play_mode))) Error("Error writing playlist!"); #if 0 f = fopen(name, "w"); free(name); if (!f) { Error("Error opening playlist for writing!"); return; } for (i = 0; i < group_window->getNitems(); i++) { if ( !((group_stack->entry(i))->writeToFile(f)) || !(fwrite("\n", sizeof(char), 1, f))) { Error("Error writing playlist!"); fclose(f); return; } } fclose(f); #endif #endif void cw_toggle_group_mode() { mp3Win *sw = mp3_curwin; sw->setPlaymode(1 - sw->getPlaymode()); cw_draw_group_mode(); refresh(); } /* draws (or clears) group mode. Does *not* refresh screen */ void cw_draw_group_mode() { mp3Win *sw = mp3_curwin; int r = COLS - 12; move(11, r+1); if (!sw->getPlaymode()) addch(' '); else addch('X'); } /* set playmode */ short set_play_mode(const char *pm) { if (!pm) return 0; if (!strcasecmp(pm, "onegroup")) globalopts.play_mode = PLAY_GROUP; else if (!strcasecmp(pm, "allgroups")) globalopts.play_mode = PLAY_GROUPS; //else if (!strcasecmp(pm, "groupsrandom")) // globalopts.play_mode = PLAY_GROUPS_RANDOMLY; else if (!strcasecmp(pm, "allrandom")) globalopts.play_mode = PLAY_SONGS; else return 0; return 1; } /* toggle & redraw playmode */ void cw_toggle_play_mode() { switch(globalopts.play_mode) { case PLAY_GROUPS: globalopts.play_mode = PLAY_GROUP; break; case PLAY_GROUP: globalopts.play_mode = PLAY_SONGS; break; case PLAY_SONGS: globalopts.play_mode = PLAY_GROUPS; break; default: globalopts.play_mode = PLAY_GROUPS; } cw_draw_play_mode(); refresh(); } /* redraw playmode, but does not update the screen. */ void cw_draw_play_mode(short cleanit) { unsigned int i; char *playmodes_desc[] = { /* PLAY_NONE */ "", // PLAY_GROUP "Global Playback: Play current group, but not its subgroups", //PLAY_GROUPS "Global Playback: Play current group, including subgroups", //PLAY_GROUPS_RANDOMLY "Global Playback: You Should Not See This", //PLAY_SONGS "Global Playback: Shuffle all songs from all groups", }; unsigned int nrchars = (COLS > 16 ? COLS - 16 : 1); for (i = 2; i < nrchars + 2; i++) { move(6, i); addch(' '); } if (!cleanit) { move(6, 2); addstr(playmodes_desc[globalopts.play_mode]); } } void * play_list(void *arg) { (void)arg; //unused #ifdef PTHREADEDMPEG pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL), pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); #endif //this loops controls the 'playing' variable. The 'action' variable is //controlled by the input thread. //playing=1: user wants to play //playing=0: user doesn't want to play while (1) { _action my_action; #ifdef PTHREADEDMPEG pthread_testcancel(); #elif defined(LIBPTH) pth_cancel_point(); #endif lock_playing_mutex(); if (!playopts.playing) { debug("Not playing, waiting to start playlist\n"); while (action != AC_PLAY && action != AC_ONE_MP3) { debug("THREAD playing going to sleep.\n"); #ifdef PTHREADEDMPEG pthread_cond_wait(&playing_cond, &playing_mutex); #elif defined(LIBPTH) pth_cond_await(&playing_cond, &playing_mutex, NULL); #endif if (playopts.quit_program) { break; //we need to die. } } debug("THREAD playing woke up!\n"); } /* are we requested to die? */ if (playopts.quit_program) { if (playopts.player) { //clean up the player debug("destroying player - we're quitting\n"); playopts.player->stop(); delete playopts.player; debug("Destroyed!\n"); } //exit loop and terminate thread break; } my_action = action; playopts.playing = 1; unlock_playing_mutex(); switch(my_action) { case AC_STOP: case AC_STOP_LIST: /* explicit user stop request */ stop_song(); //sets action to AC_NONE lock_playing_mutex(); playopts.playing = 0; unlock_playing_mutex(); update_songinfo(PS_NONE, 16); if (my_action == AC_STOP_LIST) reset_playlist(1); playopts.playing_one_mp3 = 0; break; case AC_NEXT: stop_song(); //status => AC_NONE /*29-10-2000: This isn't really necessary..the next loop will fix this. if (!start_song()) stop_list(); */ break; case AC_PREV: //previous song if (!history->atStart()) { /* skip 2 places in history; once to skip current song, once more to * jump to song before current one */ history->previous(); history->previous(); debug ("Not at end of history, selecting older song.\n"); stop_song(); //status => AC_NONE } //mw_settxt("Not implemented (yet)."); break; case AC_NONE: { if (!playopts.pause && playopts.song_played) { bool mystatus = (playopts.player)->run(globalopts.fpl); if (!mystatus) { if ((playopts.player)->ready()) { /* ugly. If run returns false, all playback should be finished! */ stop_song(); //status => AC_NONE } else { //output buffer full, wait a bit. USLEEP(10000); } } else { /* TODO: different thread for status updates. Now, this function * gets called way too often when buffering. It does prevent * updating the actual display too often, but that check is * costly as well.. */ update_play_display(); USLEEP(10000); } } if (!playopts.song_played) //let's start one then { short start_song_result; debug("No song to play, let's find one!\n"); start_song_result = start_song(); if (start_song_result == -1) //nothing more to play { short allow_repeat = playopts.allow_repeat; debug("End of playlist reached.\nI want to repeat: %d\n" \ "Am I allowed to?: %d\n", globalopts.repeat, allow_repeat); stop_list(); //end the program? if (quit_after_playlist) end_program(); //this is *so* not thread-safe! if (allow_repeat && globalopts.repeat) { debug("I may repeat the playlist.\n"); reset_playlist(1); lock_playing_mutex(); playopts.playing = 1; unlock_playing_mutex(); } } } else if (playopts.pause) USLEEP(10000); } break; case AC_ONE_MP3: //plays 1 mp3, can interrupt a playlist { short was_playing = 0; if (playopts.song_played) { stop_song(); //action => AC_NONE if (!playopts.playing_one_mp3 || playopts.playing_one_mp3 == 1) was_playing = 1; } playopts.playing_one_mp3 = 0; start_song(was_playing); //(fails: action => AC_NONE, plist continues) } break; case AC_FORWARD: if (playopts.song_played && playopts.player) { (playopts.player)->forward(globalopts.skipframes); update_play_display(); } set_action(AC_NONE); break; case AC_REWIND: if (playopts.song_played && playopts.player) { (playopts.player)->rewind(globalopts.skipframes); update_play_display(); } set_action(AC_NONE); break; case AC_SKIPEND: if (playopts.song_played) { (playopts.player)->forward(-300); update_play_display(); } set_action(AC_NONE); break; case AC_PLAY: //pause,unpause,na stop set_action(AC_NONE); if (playopts.song_played && playopts.pause) { playopts.pause = 0; update_songinfo(PS_PLAY, 16|4); if (!((playopts.player)->unpause())) stop_song(); } else if (playopts.song_played && !playopts.pause) { playopts.pause = 1; update_songinfo(PS_PAUSE, 16|4); (playopts.player)->pause(); } else if (!playopts.song_played) { playopts.allow_repeat = 0; debug("AC_PLAY: allow_repeat = 0\n"); } break; case AC_NEXTGRP: if ((globalopts.play_mode == PLAY_GROUP || globalopts.play_mode == PLAY_GROUPS) && playopts.song_played && playing_group) { stop_song(); playing_group->setAllSongsPlayed(); } break; case AC_PREVGRP: mw_settxt("Sorry, that doesn't work yet."); break; } #ifdef LIBPTH pth_yield(NULL); #endif } return NULL; } #ifdef INCLUDE_LIRC void * lirc_loop(void *arg) { struct lirc_config *config; if (arg) {} if (lirc_init("mp3blaster", 1) == -1) { LOCK_NCURSES; warning("Couldn't initialize LIRC support (no config?)"); UNLOCK_NCURSES; return NULL; } if (lirc_readconfig(NULL, &config, NULL) == 0) { char *code; char *name; int ret; while (lirc_nextcode(&code) == 0 && code) //blocks { while ((ret = lirc_code2char(config, code, &name)) == 0 && name) { if (!strcasecmp(name, "PLAY")) lirc_cmd = CMD_PLAY_PLAY; else if (!strcasecmp(name, "NEXT")) lirc_cmd = CMD_PLAY_NEXT; else if (!strcasecmp(name, "PREVIOUS")) lirc_cmd = CMD_PLAY_PREVIOUS; else if (!strcasecmp(name, "REWIND")) lirc_cmd = CMD_PLAY_REWIND; else if (!strcasecmp(name, "FORWARD")) lirc_cmd = CMD_PLAY_FORWARD; else if (!strcasecmp(name, "STOP")) lirc_cmd = CMD_PLAY_STOP; else if (!strcasecmp(name, "UP")) lirc_cmd = CMD_UP; else if (!strcasecmp(name, "DOWN")) lirc_cmd = CMD_DOWN; else if (!strcasecmp(name, "PLAYMODE")) lirc_cmd = CMD_TOGGLE_PLAYMODE; else if (!strcasecmp(name, "ENTER")) { if (progmode == PM_NORMAL) lirc_cmd = CMD_ENTER; else if (progmode == PM_FILESELECTION) lirc_cmd = CMD_FILE_ENTER; } else if (!strcasecmp(name, "FILES")) { if (progmode == PM_NORMAL) lirc_cmd = CMD_SELECT_FILES; else if (progmode == PM_FILESELECTION) lirc_cmd = CMD_FILE_ADD_FILES; } else if (!strcasecmp(name, "SELECT")) { if (progmode == PM_NORMAL) lirc_cmd = CMD_SELECT; else if (progmode == PM_FILESELECTION) lirc_cmd = CMD_FILE_SELECT; } else if (!strcasecmp(name, "QUIT")) lirc_cmd = CMD_QUIT_PROGRAM; } free(code); if (ret == -1) break; } lirc_freeconfig(config); } lirc_deinit(); return NULL; } #endif void update_playlist_history() { //TODO: implement ;-) } void update_play_display() { //update elapsed / remaining time? if (!playopts.player) return; time(&(playopts.newtyd)); if (difftime(playopts.newtyd, playopts.tyd) >= 1) { if (LOCK_NCURSES) { songinf.elapsed = (playopts.player)->elapsed_time(); songinf.remain = (playopts.player)->remaining_time(); songinf.update |= 2; UPDATE_CURSES; UNLOCK_NCURSES; } playopts.tyd = playopts.newtyd; } } void set_one_mp3(const char *mp3) { #ifdef PTHREADEDMPEG pthread_mutex_lock(&onemp3_mutex); #elif defined(LIBPTH) pth_mutex_acquire(&onemp3_mutex, FALSE, NULL); #endif if (playopts.one_mp3) delete[] playopts.one_mp3; if (mp3) { playopts.one_mp3 = new char[strlen(mp3)+1]; strcpy(playopts.one_mp3, mp3); } else playopts.one_mp3 = NULL; #ifdef PTHREADEDMPEG pthread_mutex_unlock(&onemp3_mutex); #elif defined(LIBPTH) pth_mutex_release(&onemp3_mutex); #endif } /* POST: Delete[] returned char*-pointer when you don't use it anymore!! */ /* Function : get_one_mp3 * Description: Returns a char*-pointer with a filename of a single MP3, * : if the user chose to play a single mp3 outside the usual * : playlist (which happens when you hit enter over an mp3) * Parameters : None. * Returns : A C++ allocated char*-pointer (delete[] after use), or NULL * : if no single mp3 was requested. * SideEffects: None. */ char * get_one_mp3() { char *newmp3 = NULL; if (!playopts.one_mp3) return NULL; newmp3 = new char[strlen(playopts.one_mp3)+1]; strcpy(newmp3,playopts.one_mp3); return newmp3; } void stop_list() { playopts.playing = 0; playopts.playing_one_mp3 = 0; debug("stop_list() : allow_repeat = 0\n"); playopts.allow_repeat = 0; next_song = -1; reset_playlist(1); if (LOCK_NCURSES) { songinf.update |= 32; UPDATE_CURSES; UNLOCK_NCURSES; } } const char * get_error_string(int errcode) { const char *sound_errors[21] = { "Everything's OK (You shouldnt't see this!)", "Failed to open sound device.", "Sound device is busy.", "Buffersize of sound device is wrong.", "Sound device control error.", "Failed to open file for reading.", // 5 "Failed to read file.", "Unkown proxy.", "Unkown host.", "Socket error.", "Connection failed.", // 10 "Fdopen error.", "Http request failed.", "Http write failed.", "Too many relocations", "Out of memory.", // 15 "Unexpected EOF.", "Bad sound file format.", "Can't make thread.", "Bad mpeg header.", "Unknown error." }; if (errcode < 0 || errcode > 20) return ""; return sound_errors[errcode]; } void stop_song() { int vaut; if (playopts.player) { (playopts.player)->stop(); playopts.pause = 0; vaut = (playopts.player)->geterrorcode(); } else { vaut = SOUND_ERROR_FINISH; debug("stop_song(): no playopts.player\n"); } if (vaut == SOUND_ERROR_OK || vaut == SOUND_ERROR_FINISH) { debug("Allowing playlist repeat\n"); playopts.allow_repeat = 1; } else { debug("stop_song() ended in error (allow_repeat = %d)\n", playopts.allow_repeat); } //generate warning if (LOCK_NCURSES) { songinf.update = 0; if (songinf.warning) { delete[] songinf.warning; songinf.warning = NULL; } if (vaut != SOUND_ERROR_OK && vaut != SOUND_ERROR_FINISH) { songinf.warning = new char[strlen(get_error_string(vaut))+1]; strcpy(songinf.warning, get_error_string(vaut)); songinf.update |= 8; } songinf.status = PS_STOP; songinf.update |= 4; UPDATE_CURSES; UNLOCK_NCURSES; } if (playopts.player) { delete playopts.player; playopts.player = NULL; } playopts.song_played = 0; //playopts.playing_one_mp3 = 0; (DON'T!) //set_one_mp3((const char *)NULL); (DON'T!!) set_action(AC_NONE); if (vaut == SOUND_ERROR_DEVOPENFAIL) stop_list(); } /* TODO: Implement history * PRE: This function can only be called from the playing thread, otherwise * : we'll enter a deadlock. * was_playing: if one mp3 is to be played, this determines if the playlist * continues playing after the one mp3 (is was_playing = 1, it * will) * POST: playopts.one_mp3 is always NULL * * Returns: >0 on success, <=0 on failure: * 0: general error * -1: could not find song to play */ short start_song(short was_playing) { char *song = NULL; short one_mp3 = 0; const char *sp = NULL; debug("start_song()\n"); /* If there's no standalone mp3 to be played, and a previous standalone mp3 * has just been played while there was no playlist playing, return without * choosing a new song. */ if (!(song = get_one_mp3()) && playopts.playing_one_mp3 == 2) { debug("Not going to play a new song.\n"); playopts.playing_one_mp3 = 0; return -1; } if (!song) { /* No request to play a standalone mp3 (A standalone mp3 is one that is * being started by hitting enter over a song in file- or playlist * manager; it can interrupt playlists) */ /* If we're in the playlist history, advance one position in the history */ /* if (!history->atEnd()) { debug("Advancing one entry in history\n"); history->next(); } */ /* If we're still in the playlist history, play a song from it. */ if (!history->atEnd()) { debug("Going to play song from history\n"); const char *tmpsong = history->element(); if (tmpsong) { song = new char[strlen(tmpsong) + 1]; strcpy(song, tmpsong); debug("Select song from history (%s)\n", song); } history->next(); } else //regular playlist { if ((song = determine_song())) history->add(song); //add to history. } if (!song) debug("Could not determine song in start_song()\n"); playopts.playing_one_mp3 = 0; if (!song) return -1; } else //one_mp3 { playopts.playing_one_mp3 = (was_playing ? 1 : 2); one_mp3 = 1; //let's not add standalone mp3's (those not in playlist) to the history. //it would be too confusing. //history->add(song); } set_one_mp3((const char*)NULL); void *init_args = NULL; Fileplayer::audiodriver_t audiodriver = Fileplayer::AUDIODRV_SDL; switch(globalopts.audio_driver) { case AUDIODEV_SDL: audiodriver = Fileplayer::AUDIODRV_SDL; break; case AUDIODEV_OSS: audiodriver = Fileplayer::AUDIODRV_OSS; break; case AUDIODEV_ESD: audiodriver = Fileplayer::AUDIODRV_ESD; break; case AUDIODEV_NAS: audiodriver = Fileplayer::AUDIODRV_NAS; break; //TODO: 'dump' driver which dumps output to a file! Better than current //crappy mp3->wav converter class.. } if (is_wav(song)) playopts.player = new Wavefileplayer(audiodriver); #ifdef HAVE_SIDPLAYER else if (is_sid(song)) playopts.player = new SIDfileplayer(audiodriver); #endif #ifdef INCLUDE_OGG else if (is_ogg(song)) playopts.player = new Oggplayer(audiodriver); #endif else if (is_audiofile(song)) //mp3 assumed { struct init_opts *opts = new init_opts; memset(opts->option, 0, 30); #ifdef PTHREADEDMPEG int draadjes = globalopts.threads; if (draadjes) add_init_opt(opts, "threads", (void*)&(globalopts.threads)); #endif add_init_opt(opts, "scanmp3s", (void*)&(globalopts.scan_mp3s)); //init options have been set? if (opts->option[0]) init_args = (void *)opts; playopts.player = new Mpegfileplayer(audiodriver); } if (!playopts.player) { debug("start_song: No player object could be created!\n"); stop_song(); delete[] song; return 0; } if (!(playopts.player)->openfile(song, (globalopts.sound_device ? globalopts.sound_device : NULL))) { debug("start_song: openfile failed\n"); //TODO: add warning here (geterrorcode()) warning("Couldn't open file."); stop_song(); delete[] song; return 0; } if (globalopts.downsample) (playopts.player)->setdownfrequency(1); if (globalopts.eightbits) (playopts.player)->set8bitmode(); if ( !(playopts.player)->initialize(init_args) ) { debug("start_song: player->initialize failed\n"); stop_song(); delete[] song; return 0; } playopts.song_played = 1; sp = chop_path(song); if (LOCK_NCURSES) { if (songinf.path) delete[] songinf.path; songinf.path = new char[strlen(sp)+1]; strcpy(songinf.path, sp); songinf.songinfo = (playopts.player)->getsonginfo(); if (globalopts.recode_table) { recode_string(songinf.songinfo.artist); recode_string(songinf.songinfo.songname); recode_string(songinf.songinfo.album); recode_string(songinf.songinfo.comment); } songinf.elapsed = 0; songinf.remain = songinf.songinfo.totaltime; songinf.status = PS_PLAY; songinf.update |= (1|2|4); //update time,songinfo,status UPDATE_CURSES; UNLOCK_NCURSES; } time(&(playopts.tyd)); delete[] song; if (!one_mp3) update_playlist_history(); else playopts.playing_one_mp3 = (was_playing ? 1 : 2); set_action(AC_NONE); return 1; } //Pre-condtion: // The global playmode and group modes must be set according to the wishes // of the user. // if playing_one_mp3 != NULL, then only that mp3 will be played, after which // the thread will exit. void play_one_mp3(const char *filename) { set_one_mp3(filename); set_action(AC_ONE_MP3); lock_playing_mutex(); wake_player(); unlock_playing_mutex(); } /* Function : add_selected_file * Description: Used by recsel_files() to temporarily store files until * : the recursive process has finished. * Parameters : file: file to add * Returns : Nothing. * SideEffects: adds file to selected_files array. */ void add_selected_file(const char *file) { int offset; if (!file) return; selected_files = (char **)realloc(selected_files, (nselfiles + 1) * sizeof(char*) ); offset = nselfiles; selected_files[offset] = NULL; selected_files[offset] = (char *)malloc((strlen(file) + 1) * sizeof(char)); strcpy(selected_files[offset], file); ++nselfiles; } /* Function : add_selected_files * Description: Adds an array of files to the selected_files array. * : It's much more efficient than repeatedly calling * : add_selected_file. * Parameters : files: array of char-pointers, each of which must be * : allocated with malloc() or friends. Don't free them! * : filecount: Number of pointers in 'files' array * Returns : Nothing. * SideEffects: enlarges selected_files array & increases nselfiles */ void add_selected_files(char **files, unsigned int fileCount) { unsigned int i, offset; if (!files || !fileCount) return; selected_files = (char **)realloc(selected_files, (nselfiles + fileCount) * sizeof(char*) ); offset = nselfiles; for (i = 0; i < fileCount; i++) { selected_files[i + offset] = files[i]; } nselfiles += fileCount; } int is_symlink(const char *path) { struct stat *buf = (struct stat*)malloc( 1 * sizeof(struct stat) ); int retval = 1; if (lstat(path, buf) < 0 || ((buf->st_mode) & S_IFMT) != S_IFLNK) retval = 0; /* forgot to free this. Thanks to Steve Kemp for pointing it out. */ free(buf); return retval; } /* This function adds all mp3s recursively found in ``path'' to the current * group when called by F3: recursively add mp3's. * When called by F5: Add dirs as groups (d2g), and d2g_init is not set, * all mp3's found in this dir will be added to a group with the name of * the dir. When d2f is set, 'files' and 'fileCount' should be NULL. * Only when d2g is NOT set: * 'files' must be a pointer to a char** array, in which all * found files will be stored. The char** array will be allocated with * (re/m)alloc(), as will its members. */ void recsel_files(const char *path, char ***files, unsigned int *fileCount, short d2g=0, int d2g_init=0) { struct dirent *entry = NULL; DIR *dir = NULL; short mp3_found = 0; mp3Win* d2g_groupindex = NULL; if (progmode != PM_FILESELECTION || !strcmp(path, "/dev") || is_symlink(path)) return; if ( !(dir = opendir(path)) ) return; char **dirfiles; char **dirs = NULL; int dirs_size = 0, dirfiles_size = 0; if (d2g) //don't add to files array { dirfiles = NULL; dirfiles_size = 0; } else //add to files array, so that total array can be sorted afterwards { dirfiles = *files; dirfiles_size = *fileCount; } /* first, find all files and chuck them in arrays with direntries and * audiofile entries */ while ( (entry = readdir(dir)) ) { DIR *dir2 = NULL; char *newpath = (char *)malloc((strlen(entry->d_name) + 2 + strlen(path)) * sizeof(char)); PTH_YIELD; strcpy(newpath, path); if (path[strlen(path) - 1] != '/') strcat(newpath, "/"); strcat(newpath, entry->d_name); if ( (dir2 = opendir(newpath)) ) { int i = 0; short skip_dir = 0; const char *dummy = entry->d_name; const char *baddirs[] = { ".", "..", ".AppleDouble", ".resources", //HPFS stuff ".finderinfo", NULL, }; closedir(dir2); while (baddirs[i]) { if (!strcmp(dummy, baddirs[i++])) { skip_dir = 1; break; } } if (skip_dir) { free(newpath); continue; } dirs = (char**)realloc(dirs, ++dirs_size * sizeof(char*)); dirs[dirs_size - 1] = newpath; continue; } else if (is_audiofile(newpath)) { dirfiles = (char**)realloc(dirfiles, ++dirfiles_size * sizeof(char*)); dirfiles[dirfiles_size -1] = newpath; continue; } else free(newpath); } closedir(dir); if (dirs_size) sort_files(dirs, dirs_size); PTH_YIELD; if (d2g && dirfiles_size) sort_files(dirfiles, dirfiles_size); if (!d2g) { *files = dirfiles; *fileCount = dirfiles_size; } int i; for (i = 0; i < dirs_size; i++) { PTH_YIELD; recsel_files(dirs[i], files, fileCount, d2g); free(dirs[i]); } free(dirs); dirs = NULL; if (!d2g) return; /* rest of code is only interesting for 'Add Dirs As Groups' */ for (i = 0; i < dirfiles_size; i++) { PTH_YIELD; /* Add Dirs As Groups (not first call to recsel_files) ? */ if (!d2g_init) { /* create new group if no mp3's have been found for this dir. */ if (!mp3_found) { char const *npath = 0; npath = strrchr(path, '/'); if (!npath) npath = path; else { if (strlen(npath) > 1) npath = npath + 1; else npath = path; } char rnpath[strlen(npath)+3]; sprintf(rnpath, "[%s]", npath); d2g_groupindex = newgroup(); d2g_groupindex->setTitle(rnpath); mp3_curwin->addGroup(d2g_groupindex, rnpath); /* If there are no entries in the current group, * this group will be filled instead of skipping it */ mp3_found = 1; } if (d2g_groupindex) { const char *bla[4]; short foo[2] = { 0, 1 }; int idx; bla[0] = (const char*)dirfiles[i]; bla[1] = chop_path(dirfiles[i]); bla[2] = NULL; bla[3] = NULL; idx=d2g_groupindex->addItem(bla, foo, CP_FILE_MP3); if (idx!=-1) { d2g_groupindex->changeItem(idx,&id3_filename,strdup(bla[0]),2); } } else { mw_settxt("Something's screwy with recsel_files"); refresh_screen(); } } free(dirfiles[i]); } free(dirfiles); } #ifdef PTHREADEDMPEG void change_threads() { if ( (globalopts.threads += 50) > 500) globalopts.threads = 0; cw_draw_threads(); } /* draws threads. Does not refresh screen. */ void cw_draw_threads(short cleanit) { int y, x; getmaxyx(stdscr, y,x); move(9, x - 4); if (!cleanit) printw("%03d", globalopts.threads); else addstr(" "); } #endif /* returns a malloc()'d path */ char * get_current_working_path() { char *tmppwd = (char *)malloc(65 * sizeof(char)); int size = 65; while ( !(getcwd(tmppwd, size - 1)) ) { size += 64; tmppwd = (char *)realloc(tmppwd, size * sizeof(char)); } tmppwd = (char *)realloc(tmppwd, strlen(tmppwd) + 2); if (strcmp(tmppwd, "/")) /* pwd != "/" */ strcat(tmppwd, "/"); return tmppwd; } void clean_popup() { attrset(COLOR_PAIR(CP_DEFAULT)|A_NORMAL); bkgdset(COLOR_PAIR(CP_DEFAULT)); popup = 0; mw_settxt(""); refresh_screen(); } /* Return the command corresponding to the given keycode */ command_t get_command(int kcode, unsigned short pm) { int i = 0; command_t cmd = CMD_NONE; struct keybind_t k = keys[i]; while (k.cmd != CMD_NONE) { if (k.key == kcode && (pm & k.pm)) { cmd = k.cmd; break; } k = keys[++i]; } return cmd; } /* handle_input reads input. * If no_delay = 0, then this function will block until input arrives. * Returns -1 when the program should finish, 0 or greater when everything's * okay. */ int handle_input(short no_delay) { int key = 0, retval = 0; fd_set fdsr; struct timeval tv; command_t cmd; mp3Win *sw = mp3_curwin; fileManager *fm = file_window; //get a pointer to the main window, which depends on program mode. scrollWin *mainwindow = getMainWindow(); #ifdef LIBPTH pth_yield(NULL); #endif if (no_delay) { fflush(stdin); FD_ZERO(&fdsr); FD_SET(0, &fdsr); /* stdin file descriptor */ tv.tv_sec = tv.tv_usec = 0; #ifdef INCLUDE_LIRC if (lirc_cmd == CMD_NONE) { if (select(FD_SETSIZE, &fdsr, NULL, NULL, &tv) == 1) key = getch(); else { key = ERR; } } #else if (select(FD_SETSIZE, &fdsr, NULL, NULL, &tv) == 1) key = getch(); else { key = ERR; } #endif } else { debug("THREAD main waiting for blocking input.\n"); nodelay(stdscr, FALSE); #ifdef INCLUDE_LIRC if (lirc_cmd == CMD_NONE) key = getch(); #else key = getch(); #endif debug("THREAD main woke up!\n"); } if (LOCK_NCURSES); if (songinf.update) { update_status_file(); update_ncurses(0); } UNLOCK_NCURSES; if (no_delay && key == ERR) { #ifdef LIBPTH pth_yield(NULL); USLEEP(10000); #else USLEEP(1000); #endif return 0; } if (key == KEY_RESIZE) { handle_resize(0); return retval; } #ifdef INCLUDE_LIRC if (lirc_cmd != CMD_NONE) { cmd = lirc_cmd; lirc_cmd = CMD_NONE; } else { cmd = get_command(key, progmode); } #else cmd = get_command(key, progmode); #endif if (input_mode == IM_SEARCH) { /* the user is searching for something */ if ( (key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z') || (key >= '0' && key <= '9') || strchr(" (){}[]<>,.?;:\"'=+-_!@#$%^&*", key) ) { fw_search_next_char(key); } else if (key == 13) { fw_end_search(); input_mode = IM_DEFAULT; } retval = 1; } else if (input_mode == IM_INPUT) { /* The user is entering text into a text input field. */ //TODO: make keys configurable. switch(key) { case KEY_LEFT: global_input->moveLeft(1); break; case KEY_RIGHT: global_input->moveRight(1); break; case KEY_UP: case KEY_DOWN: { short insmode = global_input->setInsertMode(global_input->getInsertMode() ^ 1); //insert_mode = 1; //TODO: This should be put in a more appriopriate place. mw_settxt((insmode ? "-- INSERT --" : "-- REPLACE --")); global_input->updateCursor(); break; } case KEY_BACKSPACE: global_input->deleteChar(); break; case KEY_DC: global_input->deleteChar(0); break; case KEY_HOME: global_input->moveToBegin(); break; case KEY_END: global_input->moveToEnd(); break; case 13: { void (*callback_fun)(const char *, void*); //global_input->getCallbackFunction(&callback_fun); clean_popup(); set_inout_opts(); callback_fun = global_input->getCallbackFunction(); callback_fun(global_input->getString(), global_input->getCallbackData()); delete global_input; global_input = NULL; input_mode = IM_DEFAULT; mainwindow->swRefresh(1); } break; default: global_input->addChar(key); } if (global_input) { global_input->updateCursor(); } //set_inout_opts(); retval = 1; } if (retval) { return retval; } switch(cmd) { case CMD_PLAY_PLAY: set_action(AC_PLAY); wake_player(); break; case CMD_PLAY_NEXT: set_action(AC_NEXT); break; case CMD_PLAY_PREVIOUS: set_action(AC_PREV); break; case CMD_PLAY_FORWARD: set_action(AC_FORWARD); break; case CMD_PLAY_REWIND: set_action(AC_REWIND); break; //case CMD_PLAY_STOP: set_action(AC_STOP); break; case CMD_PLAY_STOP: set_action(AC_STOP_LIST); break; case CMD_PLAY_SKIPEND: set_action(AC_SKIPEND); break; case CMD_PLAY_NEXTGROUP: set_action(AC_NEXTGRP); break; case CMD_PLAY_PREVGROUP: set_action(AC_PREVGRP); break; //case 'i': sw->setDisplayMode(0); sw->swRefresh(2); break; //case 'I': sw->setDisplayMode(1); sw->swRefresh(2); break; case CMD_MOVE_AFTER: sw->moveSelectedItems(sw->sw_selection); sw->swRefresh(1); break; case CMD_MOVE_BEFORE: sw->moveSelectedItems(sw->sw_selection, 1); sw->swRefresh(1); break; case CMD_QUIT_PROGRAM: //quit mp3blaster retval = -1; break; case CMD_HELP: if (progmode == PM_HELP) { end_help(); change_program_mode(PM_NORMAL); } else { change_program_mode(PM_HELP); show_help(); } break; case CMD_DOWN: if (progmode == PM_NORMAL) sw->changeSelection(1); else if (progmode == PM_FILESELECTION) fm->changeSelection(1); else if (progmode == PM_HELP) bighelpwin->changeSelection(1); break; case CMD_UP: if (progmode == PM_NORMAL) sw->changeSelection(-1); else if (progmode == PM_FILESELECTION) fm->changeSelection(-1); else if (progmode == PM_HELP) bighelpwin->changeSelection(-1); break; case CMD_DEL: case CMD_DEL_MARK: { mp3Win *tmpgroup; if (progmode != PM_NORMAL && cmd == CMD_DEL_MARK) break; if (sw->isPreviousGroup(sw->sw_selection)) break; lock_playing_mutex(); //don't delete a group that's playing right now (or contains a group //that's being played) if (sw->isGroup(sw->sw_selection) && (tmpgroup = sw->getGroup(sw->sw_selection)) && tmpgroup->isPlaying()) { if (playopts.playing) { warning("Can't delete an active group."); unlock_playing_mutex(); //don't delete a group that's playing right now (or contains a group break; } reset_playlist(1); mp3_groupwin = NULL; } /* Also mark file as bad? */ if (cmd == CMD_DEL_MARK && is_audiofile(sw->getSelectedItem())) fw_markfilebad(sw->getSelectedItem()); sw->delItem(sw->sw_selection); reset_next_song(); unlock_playing_mutex(); } break; case CMD_NEXT_PAGE: if (progmode == PM_NORMAL) sw->pageDown(); else if (progmode == PM_FILESELECTION) fm->pageDown(); else if (progmode == PM_HELP) bighelpwin->pageDown(); break; case CMD_PREV_PAGE: if (progmode == PM_NORMAL) sw->pageUp(); else if (progmode == PM_FILESELECTION) fm->pageUp(); else if (progmode == PM_HELP) bighelpwin->pageUp(); break; case CMD_SELECT: if (!sw->isGroup(sw->sw_selection)) { sw->selectItem(); sw->changeSelection(1); } break; case CMD_SELECT_ITEMS: { const char *label[] = { "Enter search pattern", NULL, }; popup_win(label, select_files_by_pattern, (void*)mainwindow, NULL, 1, 256); break; } case CMD_DESELECT_ITEMS: mainwindow->deselectItems(); mainwindow->swRefresh(0); break; case CMD_REFRESH: refresh_screen(); break; // C-l case CMD_ENTER: // play 1 mp3 or dive into a subgroup. if (sw->isGroup(sw->sw_selection)) { /* Copy display mode */ mp3_curwin = sw->getGroup(sw->sw_selection); mp3_curwin->setDisplayMode(sw->getDisplayMode()); mp3_curwin->resetPan(); mp3_curwin->swRefresh(2); cw_draw_group_mode(); refresh(); } else if (sw->getNitems() > 0) play_one_mp3(sw->getSelectedItem()); break; case CMD_TOGGLE_DISPLAY: if (progmode == PM_NORMAL) { short dispmode = sw->getDisplayMode() + 1; if (dispmode > 2) dispmode = 0; sw->setDisplayMode(dispmode); sw->swRefresh(2); } else if (progmode == PM_FILESELECTION) { fw_toggle_display(); fm->swRefresh(2); } break; case CMD_SELECT_FILES: /* file-selection */ fw_begin(); break; case CMD_ADD_GROUP: quit_after_playlist = 0; add_group(); break; //add group //TODO: rewrite read/write_playlist case CMD_WRITE_PLAYLIST: { const char *label[] = { "Playlist will be saved in: ", globalopts.playlist_dir, "(Configuration keyword to set default dir: PlaylistDir)", "Example filename: myplaylist.lst", NULL }; popup_win(label, playlist_write, NULL, NULL, 1, 40); } break; case CMD_LOAD_PLAYLIST: if (progmode == PM_NORMAL) fw_begin(); if (globalopts.playlist_dir) { char *tmpStr = expand_path(globalopts.playlist_dir); if (tmpStr) { fw_changedir(tmpStr); free(tmpStr); } else warning("Couldn't load playlist (wrong path?)"); } break; // read_playlist((const char*)NULL); break; // read playlist // write_playlist(); break; // write playlist case CMD_SET_GROUP_TITLE: if (sw->isGroup(sw->sw_selection) && !sw->isPreviousGroup(sw->sw_selection)) { const char *label[] = { "Enter Name:", NULL }; popup_win(label,set_group_name, NULL, NULL, 1,48); } break; // change groupname case CMD_TOGGLE_REPEAT: //toggle repeat //TODO: repeat's not thread-safe..low priority globalopts.repeat = 1 - globalopts.repeat; if (globalopts.repeat) quit_after_playlist = 0; cw_draw_repeat(); refresh(); break; case CMD_TOGGLE_SHUFFLE: cw_toggle_group_mode(); reset_next_song(); break; case CMD_TOGGLE_PLAYMODE: lock_playing_mutex(); if (!playopts.playing) { //TODO: is this safe? What if the users STOPS a song (!playing), and //later on continues the list? playing_group might be a dangling //pointer then...or not, because deleting a group that is being played //is caught in CMD_DEL. Or was there something else that could mess //up...have to check into this. cw_toggle_play_mode(); reset_next_song(); unlock_playing_mutex(); } else { debug("Playing, can't toggle playmode\n"); //TODO: FIX WARNING to not use sleep() in a threaded env. warning("Not possible during playback"); unlock_playing_mutex(); refresh_screen(); } break; case CMD_FILE_SELECT: fm->selectItem(); fm->changeSelection(1); break; case CMD_FILE_UP_DIR: fw_changedir(".."); break; case CMD_FILE_START_SEARCH: fw_start_search(); break; case CMD_FILE_TOGGLE_SORT: fw_toggle_sort(); fw_changedir("."); break; case CMD_FILE_MARK_BAD: { char *fullpath = NULL; const char *halfpath = NULL; char **selitems = NULL; int itemcnt = 0, i = 0, moved_items_count = 0; if (progmode != PM_FILESELECTION) break; debug("Mark file(s) as bad\n"); selitems = fm->getSelectedItems(&itemcnt); if (!itemcnt) { selitems = new char*[1]; selitems[0] = new char[strlen(fm->getSelectedItem())+1]; strcpy(selitems[0], fm->getSelectedItem()); itemcnt = 1; debug("Used highlighted file to mark as bad.\n"); } halfpath = fm->getPath(); for (i = 0; i < itemcnt; i++) { fullpath = new char[strlen(halfpath) + strlen(selitems[i]) + 1]; strcpy(fullpath, halfpath); strcat(fullpath, selitems[i]); debug(fullpath); if (is_audiofile(fullpath)) { if (fw_markfilebad((const char *)fullpath)) moved_items_count++; } delete[] fullpath; delete[] selitems[i]; fullpath = NULL; } delete[] selitems; if (moved_items_count) fw_changedir("."); } case CMD_FILE_DELETE: fw_delete(); break; case CMD_FILE_ENTER: /* change into dir, play soundfile, load playlist, * end search */ if (fm->isDir(fm->sw_selection)) fw_changedir(); else if (is_playlist(fm->getSelectedItem())) { char bla[strlen(fm->getPath()) + strlen(fm->getSelectedItem()) + 2]; sprintf(bla, "%s/%s", fm->getPath(), fm->getSelectedItem()); fw_end(); read_playlist(bla); mp3_curwin->swRefresh(2); break; } else if (fm->getNitems() > 0) play_one_mp3(fm->getSelectedItem()); break; case CMD_FILE_ADD_FILES: fw_end(); break; case CMD_FILE_INV_SELECTION: fm->invertSelection(); break; case CMD_FILE_RECURSIVE_SELECT: { char *tmppwd = get_current_working_path(), **files = NULL; unsigned int fileCount = 0; mw_settxt("Recursively selecting files.."); recsel_files(tmppwd, &files, &fileCount); //TODO: move this to fileManager class? free(tmppwd); if (fileCount) { sort_files(files, fileCount); add_selected_files(files, fileCount); //copies allocated pointers free(files); } fw_end(); mw_settxt("Added all mp3's in this dir and all subdirs " \ "to current group."); } break; case CMD_FILE_SET_PATH: { const char *label[] = { "Enter path below", NULL, }; popup_win(label, fw_getpath, NULL, NULL, 1, 1024); break; } case CMD_FILE_DIRS_AS_GROUPS: { char *tmppwd = get_current_working_path(); mw_settxt("Adding groups.."); recsel_files(tmppwd, NULL, NULL, 1, 1); free(tmppwd); fw_end(); mw_settxt("Added dirs as groups."); } break; case CMD_FILE_MP3_TO_WAV: /* Convert mp3 to wav */ { const char *label[] = { "Dir to put wavefiles:", startup_path, NULL }; popup_win(label, fw_convmp3, NULL, NULL, 1, 1024); } break; case CMD_FILE_ADD_URL: /* Add HTTP url to play */ { const char *label[] = { "URL:", NULL }; popup_win(label, fw_addurl, NULL, NULL, 1, 2048); } break; case CMD_HELP_PREV: helpwin->pageUp(); repaint_help(); break; case CMD_HELP_NEXT: helpwin->pageDown(); repaint_help(); break; #ifdef PTHREADEDMPEG case CMD_CHANGE_THREAD: change_threads(); break; #endif case CMD_CLEAR_PLAYLIST: lock_playing_mutex(); if (!playopts.playing) { mp3_rootwin->delItems(); mp3_curwin = mp3_rootwin; mp3_rootwin->swRefresh(2); } unlock_playing_mutex(); break; case CMD_FILE_RENAME: { const char *label[] = { "Filename:", NULL }; if (!fm->isDir(fm->sw_selection) && fm->getSelectedItem(0)) { popup_win(label, fw_rename_file, NULL, chop_path(fm->getSelectedItem(0)), 1, 2048); } } break; case CMD_LEFT: if (progmode == PM_NORMAL) sw->pan(-globalopts.pan_size); else if (progmode == PM_FILESELECTION) fm->pan(-globalopts.pan_size); break; case CMD_RIGHT: if (progmode == PM_NORMAL) sw->pan(globalopts.pan_size); else if (progmode == PM_FILESELECTION) fm->pan(globalopts.pan_size); break; case CMD_TOGGLE_WRAP: globalopts.wraplist = !globalopts.wraplist; break; case CMD_JUMP_TOP: if (progmode == PM_NORMAL) sw->jumpTop(); else if (progmode == PM_FILESELECTION) fm->jumpTop(); else if (progmode == PM_HELP) bighelpwin->jumpTop(); break; case CMD_JUMP_BOT: if (progmode == PM_NORMAL) sw->jumpBottom(); else if (progmode == PM_FILESELECTION) fm->jumpBottom(); else if (progmode == PM_HELP) bighelpwin->jumpBottom(); break; case CMD_NONE: break; default: if (mixer) mixer->ProcessKey(key); } return retval; } /* Function : show_help * Description: Displays a help screen in the main window. "progmode" should * : be in PM_HELP. * Parameters : None. * Returns : Nothing. * SideEffects: None. */ void show_help() { if (!bighelpwin) { int height, width, y, x; chtype b1, b2, b3, b4, b5, b6, b7, b8; char **lines = NULL; char *configfile = NULL; int linecount = 0; //short h=(COLS/2)+1; get_mainwin_size(&height, &width, &y, &x); get_mainwin_borderchars(&b1, &b2, &b3, &b4, &b5, &b6, &b7, &b8); configfile = new char[strlen(MP3BLASTER_DOCDIR) + 20]; sprintf(configfile, "%s/%s", MP3BLASTER_DOCDIR, "commands.txt"); if (read_file(configfile, &lines, &linecount)) { int i; bighelpwin = new scrollWin(height, width, y, x, NULL, 0, CP_DEFAULT, 1); bighelpwin->setWrap(globalopts.wraplist); for (i = 0; i < linecount; i++) { char *newline = strrchr(lines[i], '\n'); if (newline) newline[0] = '\0'; bighelpwin->addItem(lines[i]); free(lines[i]); } free(lines); } else { bighelpwin = new scrollWin(height, width, y, x, lines, linecount, CP_DEFAULT, 1); bighelpwin->setWrap(globalopts.wraplist); bighelpwin->addItem("Couldn't read mp3blaster helpfile:"); bighelpwin->addItem(configfile); } bighelpwin->setBorder(b1, b2, b3, b4, b5, b6, b7, b8); delete[] configfile; } mw_settxt("Press '?' to exit help screen."); bighelpwin->swRefresh(2); } void end_help() { //simpy 'hide' bighelpwin.. mp3_curwin->swRefresh(2); mw_settxt("Visit http://mp3blaster.sourceforge.net/"); } void cw_draw_filesort(short cleanit) { if (!cleanit) { if (progmode == PM_FILESELECTION) fw_draw_sortmode(globalopts.fw_sortingmode); } else { //clear 'Sorting mode' line. move(6, 2); int stextlen = (COLS > 16 ? COLS - 16 : 1); char emptyline[stextlen + 1]; memset(emptyline, ' ', stextlen * sizeof(char)); emptyline[stextlen] = '\0'; addstr(emptyline); } } void draw_settings(int cleanit) { cw_draw_group_mode(); if (progmode == PM_FILESELECTION) cw_draw_filesort(cleanit); else if (progmode == PM_NORMAL) cw_draw_play_mode(cleanit); cw_draw_repeat(); #ifdef PTHREADEDMPEG cw_draw_threads(cleanit); #endif refresh(); } void fw_convmp3(const char *tmp, void *args) { char **selitems; int nselected; char *dir2write; if (args); if (tmp && !strlen(tmp)) { const char *tmp2 = file_window->getPath(); dir2write = strdup(tmp2); } else if (tmp) dir2write = expand_path(tmp); else { warning("Invalid dir entered."); refresh(); return; } if (!dir2write || !is_dir(dir2write)) { warning("Invalid path entered."); refresh(); free(dir2write); return; } if (progmode != PM_FILESELECTION) { free(dir2write); return; } selitems = file_window->getSelectedItems(&nselected); if (!nselected && file_window->getSelectedItem()) { selitems = new char*[1]; selitems[0] = new char[strlen(file_window->getSelectedItem()) + 1]; strcpy(selitems[0], file_window->getSelectedItem()); nselected = 1; } else if (!nselected) { warning("No item[s] selected."); refresh(); free(dir2write); return; } for (int i = 0; i < nselected; i++) { const char *pwd = file_window->getPath(); char *file = new char[(strlen(pwd) + strlen(selitems[i]) + 2) * sizeof(char)], *file2write = new char[(strlen(dir2write) + strlen(selitems[i]) + 6)]; int len; strcpy(file, pwd); if (pwd[strlen(pwd) - 1] != '/') strcat(file, "/"); strcat(file, selitems[i]); strcpy(file2write, dir2write); if (pwd[strlen(dir2write) - 1] != '/') strcat(file2write, "/"); strcat(file2write, selitems[i]); if ((len = strlen(file2write)) > 4 && !fnmatch(".[mm][pP][23]", (file2write + (len - 4)), 0)) bcopy(".wav", (void*)(file2write + (len - 4)), 4); else strcat(file2write, ".wav"); if (is_audiofile(file)) { char bla[strlen(file)+80]; Mpegfileplayer *decoder = NULL; if (!(decoder = new Mpegfileplayer(Fileplayer::AUDIODRV_OSS)) || !decoder->openfile(file, file2write, WAV) || !decoder->initialize(NULL)) { sprintf(bla, "Decoding of %s failed.", selitems[i]); warning(bla); refresh(); if (decoder) delete decoder; } else { sprintf(bla, "Converting '%s' to wavefile, please wait.", selitems[i]); mw_settxt(bla); while (decoder->run(10)); int decode_error = decoder->geterrorcode(); decoder->stop(); if (decode_error != SOUND_ERROR_OK && decode_error != SOUND_ERROR_FINISH) { warning("Decode error: %s", get_error_string(decode_error)); } else mw_settxt("Conversion(s) finished without errors."); delete decoder; } } else { warning("Skipping non-audio file"); refresh(); } delete[] selitems[i]; delete[] file; delete[] file2write; } if (selitems) delete[] selitems; free(dir2write); } void fw_addurl(const char *urlname, void *args) { mp3Win *sw; if (args); if (!urlname || progmode != PM_FILESELECTION) { return; } sw = mp3_curwin; add_selected_file(urlname); } void fw_set_search_timeout(int timeout=2) { struct itimerval tijd, dummy; tijd.it_value.tv_sec = tijd.it_interval.tv_sec = timeout; tijd.it_value.tv_usec = tijd.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &tijd, &dummy); } void fw_end_search() { if (fw_searchstring) delete[] fw_searchstring; fw_searchstring = NULL; //signal(SIGALRM, SIG_IGN); mw_settxt(""); } /* called when sigalarm is triggered (i.e. when, in searchmode in filemanager, * a key has not been pressed for a few seconds (default: 2). This will * disable the searchmode. */ void fw_search_timeout(int blub) { if (blub); //no warning this way ;) if (input_mode != IM_SEARCH) return; fw_end_search(); input_mode = IM_DEFAULT; } /* called when someone presses [a-zA-Z0-9] in searchmode in filemanager * or in playlist */ void fw_search_next_char(char nxt) { char *tmp; short foundmatch = 0; scrollWin *sw = file_window; if (progmode == PM_NORMAL) sw = mp3_curwin; if (!fw_searchstring) { tmp = new char[2]; tmp[0] = nxt; tmp[1] = '\0'; } else { tmp = new char[strlen(fw_searchstring)+2]; strcpy(tmp, fw_searchstring); strncat(tmp, &nxt, 1); } for (int i = 0; i < sw->getNitems(); i++) { const char *item = sw->getItem(i); if (progmode == PM_NORMAL) item = chop_path(item); if (!strncmp(item, tmp, strlen(tmp))) { sw->setItem(i); foundmatch = 1; break; } } fw_set_search_timeout(2); if (!foundmatch) beep(); else { if (fw_searchstring) delete[] fw_searchstring; fw_searchstring = new char[strlen(tmp) + 1]; strcpy(fw_searchstring, tmp); char bla[strlen(fw_searchstring)+20]; sprintf(bla, "Search: %s", fw_searchstring); mw_settxt(bla); } } /* called when someone presses '/' in filemanager */ void fw_start_search(int timeout) { signal(SIGALRM, &fw_search_timeout); fw_set_search_timeout(timeout); mw_settxt("Search:"); input_mode = IM_SEARCH; } void set_sound_device(const char *devname) { if (!devname) return; if (globalopts.sound_device) delete[] globalopts.sound_device; globalopts.sound_device = new char[strlen(devname)+1]; strcpy(globalopts.sound_device, devname); } void set_mixer_device(const char *devname) { if (!devname) return; if (globalopts.mixer_device) delete[] globalopts.mixer_device; globalopts.mixer_device = new char[strlen(devname)+1]; strcpy(globalopts.mixer_device, devname); } short set_fpl(int fpl) { if (fpl < 1 || fpl > 10) return 0; globalopts.fpl = fpl; return 1; } #ifdef PTHREADEDMPEG short set_threads(int t) { if (t < 0 || t > 500 || t%50 != 0) return 0; else globalopts.threads = t; //t = 0; //prevent compile warning return 1; } #endif short set_audiofile_matching(const char **matches, int nmatches) { if (!matches || nmatches < 1) return 0; if (globalopts.extensions) { int i=0; while (globalopts.extensions[i]) { delete[] globalopts.extensions[i]; i++; } delete[] globalopts.extensions; } globalopts.extensions = new char*[nmatches+1]; for (int i=0; i < nmatches; i++) { globalopts.extensions[i] = new char[strlen(matches[i])+1]; strcpy(globalopts.extensions[i], matches[i]); } globalopts.extensions[nmatches] = NULL; return 1; } short set_playlist_matching(const char **matches, int nmatches) { if (!matches || nmatches < 1) return 0; if (globalopts.plist_exts) { int i=0; while (globalopts.plist_exts[i]) { delete[] globalopts.plist_exts[i]; i++; } delete[] globalopts.plist_exts; } globalopts.plist_exts = new char*[nmatches+1]; for (int i=0; i < nmatches; i++) { globalopts.plist_exts[i] = new char[strlen(matches[i])+1]; strcpy(globalopts.plist_exts[i], matches[i]); } globalopts.plist_exts[nmatches] = NULL; return 1; } short set_warn_delay(unsigned int seconds) { if (seconds > 60) return 0; globalopts.warndelay = seconds; return 1; } short set_skip_frames(unsigned int frames) { if (frames > 1000 || frames < 1) /* get real */ return 0; globalopts.skipframes = frames; return 1; } void set_default_colours() { globalopts.colours.default_fg = COLOR_WHITE; globalopts.colours.default_bg = COLOR_BLACK; globalopts.colours.popup_fg = COLOR_WHITE; globalopts.colours.popup_bg = COLOR_BLUE; globalopts.colours.popup_input_fg = COLOR_WHITE; globalopts.colours.popup_input_bg = COLOR_MAGENTA; globalopts.colours.error_fg = COLOR_WHITE; globalopts.colours.error_bg = COLOR_RED; globalopts.colours.button_fg = COLOR_MAGENTA; globalopts.colours.button_bg = COLOR_BLACK; globalopts.colours.shortcut_fg = COLOR_MAGENTA; globalopts.colours.shortcut_bg = COLOR_BLUE; globalopts.colours.label_fg = COLOR_BLACK; globalopts.colours.label_bg = COLOR_YELLOW; globalopts.colours.number_fg = COLOR_YELLOW; globalopts.colours.number_bg = COLOR_BLACK; globalopts.colours.file_mp3_fg = COLOR_GREEN; globalopts.colours.file_dir_fg = COLOR_BLUE; globalopts.colours.file_lst_fg = COLOR_YELLOW; globalopts.colours.file_win_fg = COLOR_MAGENTA; } mp3Win * newgroup() { mp3Win *tmp; tmp = new mp3Win(calc_mainwindow_height(LINES), calc_mainwindow_width(COLS), 10, 0, NULL, 0, CP_DEFAULT, 1); tmp->drawTitleInBorder(1); tmp->setBorder(ACS_VLINE,ACS_VLINE,ACS_HLINE,ACS_HLINE, ACS_LTEE, ACS_PLUS, ACS_LTEE, ACS_PLUS); tmp->setDisplayMode(globalopts.display_mode); tmp->setWrap(globalopts.wraplist); return tmp; } void set_action(_action bla) { lock_playing_mutex(); action = bla; if (bla == AC_STOP) quit_after_playlist = 0; unlock_playing_mutex(); } /* determine_song returns a pointer to the song to play (or NULL if there is * none). delete[] it after use. * Argument set_played = 0 => don't set it to 'played' status! */ char* determine_song(short set_played) { const char *mysong = NULL; char *song; int total_songs; total_songs = mp3_rootwin->getUnplayedSongs(); if (!total_songs) return NULL; switch(globalopts.play_mode) { case PLAY_GROUPS_RANDOMLY: return NULL; break; case PLAY_NONE: break; case PLAY_SONGS: //all songs in random order if (next_song > -1) mysong = mp3_rootwin->getUnplayedSong(next_song, set_played); else mysong = mp3_rootwin->getUnplayedSong(myrand(total_songs), set_played); next_song = (total_songs - 1 ? myrand(total_songs - 1) : -1); break; case PLAY_GROUP: //current group: case PLAY_GROUPS: { if (globalopts.play_mode == PLAY_GROUPS && !mp3_groupwin) { mp3_groupwin = mp3_curwin; playing_group = NULL; } while (!playing_group || !playing_group->getUnplayedSongs()) { if (playing_group) //this means that there are no unplayed songs in it { playing_group->setNotPlaying(); playing_group = NULL; if (globalopts.play_mode == PLAY_GROUP) //finished playing group break; } if (globalopts.play_mode == PLAY_GROUPS) { if (!mp3_groupwin) //shouldn't happen... { reset_playlist(0); break; } //getUnplayedGroups counts the group itself as well if it contains //unplayed songs. Just so you know. I forgot this myself ;-) int tg = mp3_groupwin->getUnplayedGroups(); if (!tg) //no more groups left to play! { reset_playlist(0); //mp3_groupwin = NULL; break; } //determine which group to play. if (mp3_groupwin->getPlaymode()) playing_group = mp3_groupwin->getUnplayedGroup(myrand(tg)); else playing_group = mp3_groupwin->getUnplayedGroup(0); if (playing_group->getUnplayedSongs(0)) { playing_group->setPlayed(); playing_group->setPlaying(); } else { playing_group = NULL; mp3_groupwin = NULL; } } else { playing_group = mp3_curwin; playing_group->setPlaying(); } } if (!playing_group) //no valid group could be found. return NULL; if ((total_songs = playing_group->getUnplayedSongs(0))) { if (!playing_group->getPlaymode()) //normal playback { if (next_song > -1) mysong = playing_group->getUnplayedSong(next_song, set_played, 0); else mysong = playing_group->getUnplayedSong(0, set_played, 0); next_song = 0; } else //random playback { if (next_song > -1) mysong = playing_group->getUnplayedSong(next_song, set_played, 0); else mysong = playing_group->getUnplayedSong(myrand(total_songs), set_played, 0); if (total_songs - 1) next_song = myrand(total_songs - 1); else next_song = -1; } } else reset_playlist(1); //only reset playing group } break; } if (mysong) { if (next_song > -1) set_next_song(next_song); song = new char[strlen(mysong)+1]; strcpy(song, mysong); return song; } return NULL; } void update_status_file() { if (status_file == NULL) return; if (songinf.update & (1 | 4 | 16 | 32)) { // song info, song status, song status too (?), song change write_status_file(); } } void write_status_file() { FILE *file; struct song_info &si = songinf.songinfo; // mpeg internal song info file = fopen(status_file, "w"); if (file == NULL) return; if (songinf.path != NULL) fprintf(file, "path %s\n", songinf.path); switch (songinf.status) { case PS_PLAY: fprintf(file, "status playing\n"); break; case PS_PAUSE: fprintf(file, "status paused\n"); break; case PS_REWIND: fprintf(file, "status rewinding\n"); break; case PS_FORWARD: fprintf(file, "status fast forwarding\n"); break; case PS_PREV: fprintf(file, "status selecting previous song\n"); break; case PS_NEXT: fprintf(file, "status selecting next song\n"); break; case PS_STOP: fprintf(file, "status stopped\n"); break; case PS_RECORD: fprintf(file, "status recording\n"); break; case PS_NONE: break; } if (si.songname[0] != '\0') fprintf(file, "title %s\n", si.songname); if (si.artist[0] != '\0') fprintf(file, "artist %s\n", si.artist); if (si.album[0] != '\0') fprintf(file, "album %s\n", si.album); if (si.year[0] != '\0') fprintf(file, "year %s\n", si.year); if (si.comment[0] != '\0') fprintf(file, "comment %s\n", si.comment); if (si.mode[0] != '\0') fprintf(file, "mode %s\n", si.mode); fprintf(file, "format MPEG %d layer %d\n", si.mp3_version + 1, si.mp3_layer); fprintf(file, "bitrate %d\n", si.bitrate); fprintf(file, "samplerate %d\n", si.samplerate); fprintf(file, "length %d\n", si.totaltime); if (songinf.next_song) fprintf(file, "next %s\n", songinf.next_song); fclose(file); } void set_next_song(int next_song) { const char *ns = NULL; switch(globalopts.play_mode) { case PLAY_SONGS: ns = mp3_rootwin->getUnplayedSong(next_song, 0); break; case PLAY_GROUP: case PLAY_GROUPS: if (!playing_group) break; ns = playing_group->getUnplayedSong(next_song, 0, 0); if (globalopts.play_mode == PLAY_GROUPS && !ns) ns = "??? (can't know yet)"; break; case PLAY_NONE: break; case PLAY_GROUPS_RANDOMLY: ns = "You shouldn't see this!"; } //don't draw next_song on the screen yet, because this function can be //called from different threads. if (!ns) return; if (LOCK_NCURSES) { songinf.update = 32; if (songinf.next_song) free(songinf.next_song); songinf.next_song = strdup(chop_path(ns)); UPDATE_CURSES; UNLOCK_NCURSES; } } void set_song_info(const char *fl, struct song_info si) { int maxy, maxx, r, i; bool show_hours = (si.totaltime >= 3600); char bla[64]; int hours = si.totaltime / 3600, minutes = (si.totaltime % 3600) / 60, seconds = si.totaltime % 60; draw_static(); getmaxyx(stdscr, maxy, maxx); r = maxx - 12; //total time move(13,r+8);addch(':'); move(14,r+8);addch(':'); if (show_hours) { move(14, r+5); addch(':'); } move(6,r); addstr("Mpg Layer"); move(7,r); addstr(" Khz kb"); attrset(COLOR_PAIR(CP_NUMBER)|A_BOLD); if (si.totaltime >= 3600) { move(14, r+3); printw("%2d", hours); move(14,r+6); printw("%02d", minutes); } else { move(14, r+3); addstr(" "); move(14,r+6); printw("%2d", minutes); } move(14,r+9); printw("%02d", seconds); attroff(A_BOLD); //mp3 info move(8,r); addstr(" "); move(8,r); addnstr(si.mode,11); move(6,r+3); printw("%1d", si.mp3_version + 1); move(6,r+10); printw("%1d", si.mp3_layer); move(7,r); printw("%2d", (si.samplerate > 1000 ? si.samplerate / 1000 : si.samplerate)); move(7,r+6); printw("%-3d", si.bitrate); attrset(COLOR_PAIR(CP_DEFAULT)|A_NORMAL); //clear songname for (i = 4; i < maxx - 14; i++) { move(maxy-3, i); addch('x'); move(maxy-3, i); addch(' '); } move(maxy-3,4); if (!strlen(si.artist)) addnstr(fl, maxx - 18); else printw("%s - %s", (strlen(si.artist) ? si.artist : ""), (strlen(si.songname) ? si.songname : "") ); sprintf(bla, "%s (%s)", (si.album && strlen(si.album) ? si.album : ""), (si.comment && strlen(si.comment) ? si.comment : "no comments")); mw_settxt(bla); refresh(); } void set_song_status(playstatus_t s) { int maxy, maxx; getmaxyx(stdscr,maxy,maxx); move(maxy-3,1); switch(s) { case PS_PLAY: addstr("|>");break; case PS_PAUSE: addstr("||");break; case PS_REWIND: addstr("<<");break; case PS_FORWARD: addstr(">>");break; case PS_PREV: addstr("|<");break; case PS_NEXT: addstr(">|");break; case PS_STOP: addstr("[]"); mw_clear(); break; case PS_RECORD: addstr("()");break; default: break; } refresh(); } void set_song_time(int elapsed, int remaining) { int maxy, maxx, r; bool show_hours = (elapsed >= 3600); int seconds = elapsed %60, minutes = (elapsed % 3600) / 60, hours = (elapsed / 3600); if (remaining); //suppress warning getmaxyx(stdscr, maxy, maxx); r = maxx - 12; //print elapsed time attrset(COLOR_PAIR(CP_NUMBER)|A_BOLD); if (show_hours) { move(13,r+3); printw("%2d", hours); move(13,r+6); printw("%02d", minutes); } else { move(13,r+3); addstr(" "); move(13,r+6); printw("%2d", minutes); } move(13,r+9); printw("%02d", seconds); attrset(COLOR_PAIR(CP_DEFAULT)|A_NORMAL); move(13,r+8); addch(':'); if (show_hours) { move(13, r+5); addch(':'); } refresh(); } int myrand(int max_nr) { return (int)(((max_nr * 1.0) * rand()) / (RAND_MAX + 1.0)); } void reset_playlist(int full) { debug("reset_playlist(%d)\n", full); if (!full) //only reset currently played group { if (playing_group) { playing_group->resetSongs(0); //reset SONGS in played group playing_group->setNotPlaying(); //reset playing status of played group playing_group->setNotPlayed(); //reset PLAYLIST status of played group playing_group = NULL; } } else { if (playing_group) playing_group->setNotPlaying(); playing_group = NULL; mp3_groupwin = NULL; mp3_rootwin->resetSongs(); mp3_rootwin->resetGroups(); } //reset history as well if (history) { delete history; history = new History(); } } void cw_draw_repeat() { move(12,COLS-11); if (globalopts.repeat) addch('X'); else addch(' '); } /* Function : init_helpwin * Description: sets up the helpwin that's on top of the ncurses window. * : Do this whenever you change program mode (normal mode to * : file manager mode e.g.) * Parameters : None. * Returns : Nothing. * SideEffects: None. */ void init_helpwin() { int i = 0, item_count = 0; char line[80], desc[27], keylabel[4]; struct keybind_t k = keys[i]; if (helpwin) { delete helpwin; helpwin = NULL; } memset(line, 0, sizeof(line)); //TODO: scrollwin never uses first&last line if no border's used!! helpwin = new scrollWin(4, COLS > 2 ? COLS-2 : 1,1,1,NULL,0,CP_DEFAULT,0); helpwin->setWrap(globalopts.wraplist); helpwin->hideScrollbar(); //content is afhankelijk van program-mode, dus bij verandereing van //program mode moet ook de commandset meeveranderen. while (k.cmd != CMD_NONE) { if (k.pm & progmode) { set_keylabel(k.key, keylabel); sprintf(desc, "[%3s] %-19s ", keylabel, k.desc); if (!(item_count%3)) strcpy(line, desc); else strcat(line, desc); if (item_count%3 == 2) { helpwin->addItem(line); memset(line, 0, sizeof(line)); } item_count++; } k = keys[++i]; } if (item_count%3) helpwin->addItem(line); helpwin->swRefresh(2); } void bindkey(command_t cmd, int kcode) { int i = 0; struct keybind_t k = keys[i]; while (k.cmd != CMD_NONE) { if (k.cmd == cmd) { keys[i].key = kcode; break; } k = keys[++i]; } } /* puts the label corresponding to a command in 'label', which must have at * least size of 4. */ void get_label(command_t cmd, char *label) { int i = 0; struct keybind_t k = keys[i]; while (k.cmd != cmd) { k = keys[i++]; } set_keylabel(k.key, label); } /* label must be alloc'd with size 4. * Produces a 3-character label + terminating 0 that corresponds to the given * keycode 'k' */ void set_keylabel(int k, char *label) { int i; struct keylabel_t kl; label[3] = '\0'; if (k > 32 && k < 177) { sprintf(label, " %c ", k); return; } for (i = 0; i < 64; i++) { if (k == KEY_F(i)) { sprintf(label, "F%2d", i); return; } } i = 0; kl = klabels[i]; while (kl.key != ERR) { if (kl.key == k) { sprintf(label, "%s", kl.desc); return; } kl = klabels[++i]; } sprintf(label, "s%02x", (unsigned int)k); return; } /* Draws the purple colour over the keylabels. * It's ugly, but there are (currently) no means to have scrollWin colour * only parts of a string. TODO: implement that! */ void repaint_help() { int i, j; /* Redhat 7.2 ships with a buggy ncurses version, which will crash on * chgat. Therefore, they will not get funky colours in the help menu. */ #if defined(NCURSES_VERSION_MAJOR) && (!defined(NCURSES_VERSION_PATCH) || !(NCURSES_VERSION_MAJOR == 5 && NCURSES_VERSION_MINOR == 2 && NCURSES_VERSION_PATCH == 20010714)) for (i = 1; i < 5; i++) { for (j = 2; j < 55; j += 26) { move (i, j); chgat(3, A_NORMAL, CP_BUTTON, NULL); } } #endif touchline(stdscr, 1, 4); refresh(); //refresh_screen(); } /* mode == 0 (default): next song * mode == 1: jump to previous song * mode == 2: generate new 'next' song without jumping to another song * returns NULL if no next song to play could be determined.. char * determine(short mode) { if (!mode) { if (!(played_songs[++current_song])) //no next song. played_songs[current_song] = determine_song(); //there's a need to realloc the played_songs array? if (current_song == nr_played_songs - 1) { played_songs = (char**)realloc(played_songs, ++nr_played_songs * sizeof(char*)); played_songs[current_song + 1] = NULL; } if (!played_songs[current_song]) //end of playlist probably return NULL; //determine next song to play if (!played_songs[current_song +1 ]) played_songs[current_song + 1] = determine_song(); return played_songs[current_song]; } else if (mode == 1) // jump to previous song { if (current_song == 0) // this is the first song in the list return played_songs[current_song]; else return played_songs[--current_song]; } else if (mode == 2) // determine new 'next' song { if (current_song == nr_played_songs - 1) { played_songs = (char**)realloc(played_songs, ++nr_played_songs * sizeof(char*)); played_songs[current_song + 1] = NULL; } if (played_songs[current_song + 1]) { delete[] played_songs[current_song + 1]; played_songs[current_song + 1] = NULL; } } } */ void init_playopts() { playopts.song_played = 0; debug("init_playopts(): allow_repeat = 0\n"); playopts.allow_repeat = 0; playopts.playing_one_mp3 = 0; playopts.pause = 0; playopts.playing = 0; playopts.one_mp3 = NULL; playopts.player = NULL; playopts.quit_program = 0; } void init_globalopts() { globalopts.no_mixer = 0; /* mixer on by default */ globalopts.fpl = 10; /* 5 frames are played between input handling */ globalopts.sound_device = NULL; /* default sound device */ globalopts.mixer_device = NULL; /* default mixer device */ globalopts.downsample = 0; /* no downsampling */ globalopts.eightbits = 0; /* 8bits audio off by default */ globalopts.fw_sortingmode = FW_SORT_ALPHA; /* case insensitive dirlist */ globalopts.fw_hideothers = 0; /* show all files */ globalopts.play_mode = PLAY_GROUPS; globalopts.warndelay = 2; /* wait 2 seconds for a warning to disappear */ globalopts.skipframes=10; /* skip 100 frames during music search */ globalopts.pan_size = 5; /* pan left/right 5 chars at once */ globalopts.debug = 0; /* no debugging info per default */ globalopts.display_mode = 1; /* file display mode (1=filename, 2=id3, * not finished yet.. */ globalopts.repeat = 0; globalopts.extensions = NULL; globalopts.plist_exts = NULL; globalopts.playlist_dir = get_homedir(NULL); globalopts.recode_table = NULL; #ifdef PTHREADEDMPEG #if !defined(__FreeBSD__) globalopts.threads = 100; #else /* FreeBSD playback using threads is much less efficient for some reason */ globalopts.threads = 0; #endif #endif globalopts.want_id3names = 0; globalopts.selectitems_unselectfirst = 0; globalopts.selectitems_searchusingregexp = 0; globalopts.selectitems_caseinsensitive = 1; //only works for regexp search globalopts.scan_mp3s = 0; //scan mp3's to calculate correct total time. globalopts.wraplist = true; #if WANT_SDL globalopts.audio_driver = AUDIODEV_SDL; //recommended for hick-free playback #elif WANT_OSS globalopts.audio_driver = AUDIODEV_OSS; #else globalopts.audio_driver - AUDIODEV_ESD; //hicks too much, don't use or fix. #endif } void reset_next_song() { int width = (COLS > 33 ? COLS - 33 : 1); char emptyline[width+1]; next_song = -1; move(7,19); //clear old text memset(emptyline, ' ', width * sizeof(char)); emptyline[width] = '\0'; addstr(emptyline); refresh(); } /* Draws next song to be played on the screen */ void draw_next_song(const char *ns) { int mywidth = (COLS > 33 ? COLS - 33 : 1); char empty[mywidth+1]; if (!ns) return; memset((void*)empty, ' ', mywidth * sizeof(char)); empty[mywidth] = '\0'; move(7,19); addstr(empty); move(7,19); addnstr(ns, mywidth); refresh(); } short set_playlist_dir(const char *dirname) { if (!dirname || !strlen(dirname)) return 0; if (globalopts.playlist_dir) { free(globalopts.playlist_dir); globalopts.playlist_dir = NULL; } globalopts.playlist_dir = strdup(dirname); if (globalopts.playlist_dir) return 1; return 0; } /* Function : fw_markfilebad * Description: Moves the given file to a subdir called 'baditems' in the same * : dir as this file is in. If the subdir does not exist, one will * : be created. * Parameters : file: The file to mark as bad. * Returns : If the move was successful, 1, else 0. * SideEffects: If called from within PM_FILEMANAGER mode, you'll likely want * : to refresh the current directory (fw_changedir(".")). */ short fw_markfilebad(const char *file) { //extract path from filename. //check/create subdir baditems //move file to subdir const char *baditem, *app_path = "baditems/"; char *baddir = NULL, *oldpath = NULL; struct stat bdstat; short result = 0; if (file){ debug("FILE: ");debug(file);debug("\n");} baditem = chop_path(file); oldpath = chop_file(file); //must be delete[]'d baddir = new char[strlen(oldpath) + strlen(app_path) + strlen(baditem) + 1]; strcpy(baddir, oldpath); strcat(baddir, app_path); debug("baddir: "); debug(baddir); debug("\n"); if (stat(baddir, &bdstat) < 0) { int trymkdir = mkdir(baddir, 0755); if (trymkdir < 0) { //warning("Could not make directory 'baditems' for bad audiofile\n"); delete[] baddir; delete[] oldpath; return 0; } } else if (!is_dir(baddir)) { /* warning("Can't create a dir 'baditems' because a file with that "\ "name already exists\n"); */ delete[] baddir; delete[] oldpath; return 0; } strcat(baddir, baditem); debug("baddir(2): "); debug(baddir); debug("\n"); int rsuccess = rename(file, (const char*)baddir); if (rsuccess < 0) result = 0; else result = 1; //warning("Failed to mark file as bad.\n"); delete[] baddir; delete[] oldpath; return result; } /* Function : get_mainwin_size * Description: Stores the main window (the biggest square window on the screen * : size and position in the provided int pointers. * Parameters : y,x: top-left coordinate of window on stdscr * : height,width: You'll figure this out.. * Returns : Nothing. The values are stored in the provided pointers. * SideEffects: None. */ void get_mainwin_size(int *height, int *width, int *y, int *x) { *height = calc_mainwindow_height(LINES); *width = calc_mainwindow_width(COLS); *y = 10; *x = 0; } void get_mainwin_borderchars(chtype *ls, chtype *rs, chtype *ts, chtype *bs, chtype *tl, chtype *tr, chtype *bl, chtype *br) { *ls = ACS_VLINE; *rs = ACS_VLINE; *ts = ACS_HLINE; *bs = ACS_HLINE; *tl = ACS_LTEE; *tr = ACS_PLUS; *bl = ACS_LTEE; *br = ACS_PLUS; } void change_program_mode(unsigned short pm) { progmode = pm; if (pm != PM_NORMAL) draw_settings(1); else draw_settings(); init_helpwin(); repaint_help(); }; /* PRE: ncurses_mutex *must* be locked */ void update_ncurses(int update_all) { if (update_all || songinf.update & 1) set_song_info((const char *)songinf.path, songinf.songinfo); if (update_all || songinf.update & 2) set_song_time(songinf.elapsed, songinf.remain); if (update_all || songinf.update & 4) set_song_status(songinf.status); if ((songinf.update & 8 && songinf.warning)) { mw_settxt((const char *)songinf.warning); delete[] songinf.warning; songinf.warning = NULL; refresh_screen(); } if (update_all || songinf.update & 16) draw_play_key(1); if (update_all || songinf.update & 32) //draw next_song on screen. { if (songinf.next_song) { draw_next_song((const char*)(songinf.next_song)); //free(songinf.next_song); } else reset_next_song(); //songinf.next_song = NULL; } songinf.update = 0; } void update_songinfo(playstatus_t status, short update) { if (LOCK_NCURSES) { songinf.update = update; songinf.status = status; UPDATE_CURSES; UNLOCK_NCURSES; } } void end_program() { quit_after_playlist = 0; if (playopts.playing) stop_song(); //prevent ncurses clash if (LOCK_NCURSES); endwin(); UNLOCK_NCURSES; exit(0); }; void fw_toggle_sort() { sortmodes_t newmode = globalopts.fw_sortingmode; switch(globalopts.fw_sortingmode) { case FW_SORT_ALPHA: newmode = FW_SORT_ALPHA_CASE; break; case FW_SORT_ALPHA_CASE: newmode = FW_SORT_MODIFY_NEW; break; case FW_SORT_MODIFY_NEW: newmode = FW_SORT_MODIFY_OLD; break; case FW_SORT_MODIFY_OLD: newmode = FW_SORT_SIZE_SMALL; break; case FW_SORT_SIZE_SMALL: newmode = FW_SORT_SIZE_BIG; break; case FW_SORT_SIZE_BIG: newmode = FW_SORT_NONE; break; case FW_SORT_NONE: newmode = FW_SORT_ALPHA; break; } globalopts.fw_sortingmode = newmode; fw_draw_sortmode(newmode); } void fw_draw_sortmode(sortmodes_t sm) { const char *dsc[] = { "Sort alphabetically, case-insensitive", "Sort alphabetically, case-sensitive", "Sort by modification date, newest first", "Sort by modification date, oldest first", "Sort by file size, smallest first", "Sort by file size, biggest first", "Don't sort", NULL, }; short idx = 0; int maxlen = (COLS > 16 ? COLS - 16 : 1); char emptyline[maxlen + 1]; memset(emptyline, ' ', maxlen * sizeof(char)); emptyline[maxlen] = '\0'; switch(sm) { case FW_SORT_ALPHA: idx = 0; break; case FW_SORT_ALPHA_CASE: idx = 1; break; case FW_SORT_MODIFY_NEW: idx = 2; break; case FW_SORT_MODIFY_OLD: idx = 3; break; case FW_SORT_SIZE_SMALL: idx = 4; break; case FW_SORT_SIZE_BIG: idx = 5; break; case FW_SORT_NONE: idx = 6; break; } move(6, 2); addstr(emptyline); move(6, 2); printw("Sorting mode : %s", dsc[idx]); } /* Function : set_sort_mode * Description: Sets the sort mode for the file manager * Parameters : mstring: canonical name for sortmode * Returns : -1 on failure, >=0 on success * SideEffects: None. */ short set_sort_mode(const char *mstring) { //TODO: make a nice struct with canonical name, enum, indexNR const char *dsc[] = { "alpha", "alpha-case", "modify-new", "modify-old", "size-small", "size-big", "none", NULL }; unsigned int i = 0; short found_it = -1; while (dsc[i]) { if (!strcasecmp(mstring, dsc[i])) { found_it = i; break; } i++; } if (found_it < 0) return -1; sortmodes_t smode = globalopts.fw_sortingmode; switch(found_it) { case 0: smode = FW_SORT_ALPHA; break; case 1: smode = FW_SORT_ALPHA_CASE; break; case 2: smode = FW_SORT_MODIFY_NEW; break; case 3: smode = FW_SORT_MODIFY_OLD; break; case 4: smode = FW_SORT_SIZE_SMALL; break; case 5: smode = FW_SORT_SIZE_BIG; break; case 6: smode = FW_SORT_NONE; break; } globalopts.fw_sortingmode = smode; if (progmode == PM_FILESELECTION) fw_draw_sortmode(smode); return found_it; } short fw_toggle_display() { if (!file_window) return 0; short dispmode = file_window->getDisplayMode() + 1; if (dispmode > 2) dispmode = 0; file_window->setDisplayMode(dispmode); //refresh fm after this function has been called. return 1; } /* delete currently selected file */ void fw_delete() { const char *file = file_window->getSelectedItem(); if (file && !is_dir(file)) { if (unlink(file) < 0) warning("Could not delete file."); else { fw_changedir("."); mw_settxt("File deleted."); } } } void warning(const char *txt, ... ) { va_list ap; char buf[1025]; mw_clear(); move(LINES-2,1); attrset(COLOR_PAIR(CP_ERROR)|A_BOLD); va_start(ap, txt); vsnprintf(buf, 1024, txt, ap); va_end(ap); addnstr(buf, (COLS > 14 ? COLS - 14 : 1)); attrset(COLOR_PAIR(CP_DEFAULT)|A_NORMAL); refresh(); } void unlock_playing_mutex() { #ifdef PTHREADEDMPEG pthread_mutex_unlock(&playing_mutex); #elif defined(LIBPTH) pth_mutex_release(&playing_mutex); #endif } void lock_playing_mutex() { #ifdef PTHREADEDMPEG pthread_mutex_lock(&playing_mutex); #elif defined(LIBPTH) pth_mutex_acquire(&playing_mutex, FALSE, NULL); #endif } void wake_player() { debug("wake_player\n"); #ifdef PTHREADEDMPEG pthread_cond_signal(&playing_cond); #elif defined(LIBPTH) pth_cond_notify(&playing_cond, TRUE); #endif debug("/wake_player\n"); } void add_init_opt(struct init_opts *myopts, const char *option, void *value) { struct init_opts *tmp; //find the last init_opts, and add a new one. Unless the first one's //empty (!tmp->option[0]) tmp = myopts; if (tmp->option[0]) { while (tmp->next) tmp = tmp->next; tmp->next = new init_opts; tmp = tmp->next; } tmp->value = value; memset(tmp->option, 0, 30); strncpy(tmp->option, option, 29); tmp->next = NULL; } void get_input( WINDOW *win, const char*defval, unsigned int size, unsigned int maxlen, int y, int x, void (*got_input)(const char *, void *), void *args ) { if (global_input) { delete global_input; global_input = NULL; } global_input = new getInput(win, defval, size, maxlen, y, x); input_mode = IM_INPUT; global_input->setCallbackFunction(got_input, args); mw_settxt("Use up and down arrow keys to toggle insert mode"); } void set_inout_opts() { cbreak(); noecho(); nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE); leaveok(stdscr, FALSE); curs_set(0); } /* Function : select_files_by_pattern * Description: Select a bunch of files by giving a pattern. Function is called * : from popup_win * Parameters : pattern: Pattern to search files for. * : args: pointer to scrollWin instance to search in * Returns : Nothing. * SideEffects: Selects files in the scrollWin instance. */ void select_files_by_pattern(const char *pattern, void *args) { scrollWin *mainwindow = (scrollWin*)args; if (!pattern || !strlen(pattern)) { warning("No pattern given."); return; } if (globalopts.selectitems_unselectfirst) mainwindow->deselectItems(); mainwindow->selectItems(pattern, (globalopts.selectitems_searchusingregexp ? "regex" : "global"), (globalopts.selectitems_caseinsensitive ? SW_SEARCH_CASE_INSENSITIVE : SW_SEARCH_NONE), mainwindow->getDisplayMode()); mainwindow->swRefresh(0); } /* Function : playlist_write * Description: called from popup_win, this function will write the current * : playlist to a file given by the user. * Parameters : playlistfile * : name of the playlist file to write to. free() after use * : args: unused * Returns : Nothing. * SideEffects: None. */ void playlist_write(const char *playlistfile, void *args) { const char *plistfile = NULL; char *org_path = NULL; char *tmp_pfile = NULL; if (args); if (!playlistfile || !strlen(playlistfile)) return; if (!is_playlist(playlistfile)) { tmp_pfile = (char*)malloc((strlen(playlistfile) + 5) * sizeof(char)); sprintf(tmp_pfile, "%s.lst", playlistfile); plistfile = chop_path(tmp_pfile); } else { plistfile = chop_path(playlistfile); } if (!plistfile || !strlen(plistfile)) { free(tmp_pfile); return; } org_path = get_current_working_path(); char *tmpDir = expand_path(globalopts.playlist_dir); do { if (!tmpDir) { warning("Failed to change to playlist dir."); break; } if (chdir(tmpDir) == -1) { warning("Failed to change to playlist dir."); break; } if (!write_playlist(plistfile)) warning("Failed to write playlist."); else mw_settxt("Playlist written."); } while (0); if (tmpDir) free(tmpDir); chdir(org_path); free(org_path); free(tmp_pfile); } void fw_rename_file(const char *newname, void *args) { fileManager *fm = file_window; if (args); if (progmode != PM_FILESELECTION || !fm) return; const char *base_path = fm->getPath(); const char *oldname = fm->getSelectedItem(0); if (!oldname || !newname || !strlen(newname)) { warning("Nothing to rename (to)."); return; } char *new_path = new char[strlen(base_path) + 1 + strlen(newname) + 1]; sprintf(new_path, "%s/%s", base_path, newname); struct stat tmpbuf; if (stat(new_path, &tmpbuf) == 0) //it exists! { warning("Cowardly refusing to overwrite existing file."); return; } int rename_result = rename(oldname, new_path); delete[] new_path; if (rename_result == -1) { const char *errormsg = (const char *)strerror(errno); if (!errormsg) errormsg = "Unknown error occurred"; char *warningmsg = new char[strlen(errormsg) + 50]; sprintf(warningmsg, "Rename failed: %s", errormsg); warning(warningmsg); debug(warningmsg); delete[] warningmsg; } else { fw_changedir("."); //refresh dir mw_settxt("File renamed."); } return; } int ctoi(char *s) { char *foo; int res = strtoul(s, &foo, 0); if (*foo) /* parse error */ return 0; return res; } short read_recode_table(const char *charMapFileName) { FILE *fp; unsigned char buf[512],*p,*q; int in,on,count; int line; int i; short result = 1; if ((fp=fopen((char *)charMapFileName,"r")) == NULL) { fprintf(stderr,"readRecodeTable: cannot open char map file \"%s\"\n", charMapFileName); return 0; } count=0; line = 0; if (globalopts.recode_table) free(globalopts.recode_table); globalopts.recode_table = (unsigned char *) malloc(sizeof(unsigned char) * 256); for (i = 0; i < 256; i++) globalopts.recode_table[i] = i; while (fgets((char*)buf,sizeof(buf),fp)) { line++; p=(unsigned char *)strtok((char*)buf," \t\n#"); q=(unsigned char *)strtok(NULL," \t\n#"); if (p && q) { in = ctoi((char *)p); if (in > 255) { fprintf(stderr, "readRecodeTable: %s: line %d: char val too big\n", charMapFileName, line); result = 0; break; } on=ctoi((char *)q); if (in && on) { if ( count++ < 256 ) globalopts.recode_table[in]=(char)on; else { fprintf(stderr,"readRecodeTable: char map table \"%s\" is big\n",charMapFileName); result = 0; break; } } } } fclose(fp); return result; } short set_charset_table(const char *tabName) { if (!strlen(tabName)) return 0; return read_recode_table(tabName); } short set_audio_driver(const char *driverName) { #ifdef WANT_OSS if (!strcasecmp(driverName, "oss")) { globalopts.audio_driver = AUDIODEV_OSS; return 1; } #endif #ifdef WANT_ESD if (!strcasecmp(driverName, "esd")) { globalopts.audio_driver = AUDIODEV_ESD; return 1; } #endif #ifdef WANT_SDL if (!strcasecmp(driverName, "sdl")) { globalopts.audio_driver = AUDIODEV_SDL; return 1; } #endif #ifdef WANT_NAS if (!strcasecmp(driverName, "nas")) { globalopts.audio_driver = AUDIODEV_NAS; return 1; } #endif return 0; //bad value or unsupported audio driver } short set_pan_size(int psize) { if (psize < 1 || psize > 40) return 0; globalopts.pan_size = psize; return 1; }; int main(int argc, char *argv[], char *envp[]) { int c, long_index, key, options = 0, playmp3s_nr = 0; time_t t; char **playmp3s = NULL, *init_playlist = NULL, *chroot_dir = NULL, *config_file = NULL; struct _tmp { char *play_mode; char *sound_device; char *mixer_device; int fpl; #ifdef PTHREADEDMPEG int threads; #endif } tmp; #ifdef LIBPTH pth_init(); #endif tmp.sound_device = NULL; tmp.mixer_device = NULL; tmp.play_mode = NULL; tmp.fpl = 5; tmp.threads = 10; environment = envp; songinf.update = 0; songinf.path = NULL; songinf.next_song = NULL; songinf.warning = NULL; playing_group = NULL; status_file = NULL; mixer = NULL; helpwin = NULL; bighelpwin = NULL; mp3_groupwin = NULL; history = new History(); long_index = 0; played_songs = (char**)malloc(sizeof(char*)); played_songs[0] = NULL; nr_played_songs = 1; current_song = -1; srand((unsigned int)time(&t)); init_globalopts(); init_playopts(); set_default_colours(); // fill globalopts.colours with default values. /* parse arguments */ while (1) { static struct option long_options[] = { { "downsample", 0, 0, '2'}, { "8bits", 0, 0, '8'}, { "autolist", 1, 0, 'a'}, { "config-file", 1, 0, 'c'}, { "debug", 0, 0, 'd'}, { "help", 0, 0, 'h'}, { "list", 1, 0, 'l'}, { "mixer-device", 1, 0, 'm' }, { "no-mixer", 0, 0, 'n'}, { "chroot", 1, 0, 'o'}, { "playmode", 1, 0, 'p'}, { "dont-quit", 0, 0, 'q'}, { "repeat", 0, 0, 'R'}, { "runframes", 1, 0, 'r'}, { "sound-device", 1, 0, 's'}, { "status-file", 1, 0, 'f'}, { "threads", 1, 0, 't'}, { "version", 0, 0, 'v'}, { 0, 0, 0, 0} }; c = getopt_long_only(argc, argv, "28a:c:df:hl:m:no:p:qRr:s:t:v", long_options, &long_index); if (c == EOF) break; switch(c) { case ':': case '?': usage(); break; case '2': /* downsample */ options |= OPT_DOWNSAMPLE; break; case '8': /* 8bit audio */ options |= OPT_8BITS; break; case 'a': /* load playlist and play */ options |= OPT_AUTOLIST; if (init_playlist) delete[] init_playlist; init_playlist = new char[strlen(optarg)+1]; strcpy(init_playlist, optarg); break; case 'c': /* configuration file */ if (config_file) free(config_file); config_file = strdup(optarg); break; case 'd': options |= OPT_DEBUG; break; case 'f': /* status file */ if (status_file) free(status_file); status_file = strdup(optarg); break; case 'h': /* help */ usage(); break; case 'l': /* load playlist */ options |= OPT_LOADLIST; if (init_playlist) delete[] init_playlist; init_playlist = new char[strlen(optarg)+1]; strcpy(init_playlist, optarg); break; case 'm': /* mixer device */ options |= OPT_MIXERDEV; if (tmp.mixer_device) free(tmp.mixer_device); tmp.mixer_device = strdup(optarg); break; case 'n': /* disable mixer */ options |= OPT_NOMIXER; break; case 'o': /* chroot */ options |= OPT_CHROOT; if (chroot_dir) delete[] chroot_dir; chroot_dir = new char[strlen(optarg)+1]; strcpy(chroot_dir, optarg); break; case 'p': /* set initial playmode */ options |= OPT_PLAYMODE; tmp.play_mode = new char[strlen(optarg)+1]; strcpy(tmp.play_mode, optarg); break; case 'q': /* don't quit after playing 1 mp3/playlist */ options |= OPT_QUIT; break; case 'R': /* playlist repeat */ options |= OPT_REPEAT; break; case 'r': /* numbers of frames to decode in 1 loop 1<=fpl<=10 */ options |= OPT_FPL; tmp.fpl = atoi(optarg); break; case 's': /* sound-device other than the default /dev/{dsp,audio} ? */ options |= OPT_SOUNDDEV; if (tmp.sound_device) free(tmp.sound_device); tmp.sound_device = strdup(optarg); break; case 't': /* threads */ options |= OPT_THREADS; #ifdef PTHREADEDMPEG tmp.threads = atoi(optarg); #endif break; case 'v': /* version info */ printf("%s version %s - http://mp3blaster.sourceforge.net/\n", argv[0], VERSION); printf("Supported audio formats: " BUILDOPTS_AUDIOFORMATS "\n"); printf("Supported audio output drivers: " BUILDOPTS_AUDIODRIVERS "\n"); printf("Build features: " BUILDOPTS_FEATURES "\n"); exit(0); break; default: usage(); } } if (options & OPT_DEBUG) { globalopts.debug = 1; debug("Debugging of mp3blaster started.\n"); } //read .mp3blasterrc if (!cf_parse_config_file(config_file) && (config_file || cf_get_error() != NOSUCHFILE)) { fprintf(stderr, "%s\n", cf_get_error_string()); exit(1); } if (config_file) free(config_file); if (optind < argc) /* assume all other arguments are mp3's */ { /* this is not valid if a playlist is loaded from commandline */ if ( (options&OPT_LOADLIST) || (options&OPT_AUTOLIST) ) usage(); if (!(playmp3s = (char**)malloc((argc - optind) * sizeof(char*)))) { fprintf(stderr, "Cannot allocate enough memory.\n"); exit(1); } /* Build a playlist from the remaining arguments */ for (int i = optind ; i < argc; i++) { if (!is_audiofile(argv[i])) { fprintf(stderr, "%s is not a valid audio file!\n", argv[i]); usage(); } playmp3s[i - optind] = (char*)malloc((strlen(argv[i])+1) * sizeof(char)); strcpy(playmp3s[i - optind], argv[i]); } options |= OPT_PLAYMP3; } //signal(SIGALRM, SIG_IGN); //Initialize NCURSES initscr(); start_color(); set_inout_opts(); startup_path = get_current_working_path(); if (false && (LINES < MIN_SCREEN_HEIGHT || COLS < MIN_SCREEN_WIDTH)) { mvaddstr(0, 0, "You'll need at least an 80x23 screen, sorry.\n"); getch(); endwin(); exit(1); } /* setup colours */ init_pair(CP_DEFAULT, globalopts.colours.default_fg, globalopts.colours.default_bg); init_pair(CP_POPUP, globalopts.colours.popup_fg, globalopts.colours.popup_bg); init_pair(CP_POPUP_INPUT, globalopts.colours.popup_input_fg, globalopts.colours.popup_input_bg); init_pair(CP_ERROR, globalopts.colours.error_fg, globalopts.colours.error_bg); init_pair(CP_BUTTON, globalopts.colours.button_fg, globalopts.colours.button_bg); init_pair(CP_SHORTCUTS, globalopts.colours.shortcut_fg, globalopts.colours.shortcut_bg); init_pair(CP_LABEL, globalopts.colours.label_fg, globalopts.colours.label_bg); init_pair(CP_NUMBER, globalopts.colours.number_fg, globalopts.colours.number_bg); init_pair(CP_FILE_MP3, globalopts.colours.file_mp3_fg, globalopts.colours.default_bg); init_pair(CP_FILE_DIR, globalopts.colours.file_dir_fg, globalopts.colours.default_bg); init_pair(CP_FILE_LST, globalopts.colours.file_lst_fg, globalopts.colours.default_bg); init_pair(CP_FILE_WIN, globalopts.colours.file_win_fg, globalopts.colours.default_bg); /* initialize selection window */ mp3_rootwin = newgroup(); mp3_rootwin->setTitle("[Default]"); mp3_rootwin->swRefresh(0); mp3_rootwin->drawBorder(); //to get the title mp3_curwin = mp3_rootwin; progmode = PM_NORMAL; /* initialize helpwin */ init_helpwin(); repaint_help(); draw_static(); mw_settxt("Press '?' to get help on available commands"); refresh(); if (options & OPT_NOMIXER) { globalopts.no_mixer = 1; } if (options & OPT_SOUNDDEV) { set_sound_device(tmp.sound_device); free(tmp.sound_device); } if (options & OPT_MIXERDEV) { set_mixer_device(tmp.mixer_device); free(tmp.mixer_device); } if (options & OPT_REPEAT) { globalopts.repeat = 1; } if (options & OPT_PLAYMODE) { if (!set_play_mode(tmp.play_mode)) { endwin(); usage(); } delete[] tmp.play_mode; } if (options & OPT_FPL) { if (!set_fpl(tmp.fpl)) { endwin(); usage(); } } if (options & OPT_THREADS) { #ifdef PTHREADEDMPEG if (!set_threads(tmp.threads)) #else if (1) //threading is not enabled.. #endif { endwin(); usage(); } } if (options & OPT_CHROOT) { if (chroot(chroot_dir) < 0) { endwin(); perror("chroot"); fprintf(stderr, "Could not chroot to %s! (are you root?)\n", chroot_dir); exit(1); } chdir("/"); delete[] chroot_dir; } if (options & OPT_DOWNSAMPLE) globalopts.downsample = 1; if (options & OPT_8BITS) globalopts.eightbits = 1; /* create nmixer interface */ init_mixer(); /* All options from configfile and commandline have been read. */ draw_settings(); /* Accept signal for redrawing the interface */ signal(SIGWINCH, handle_resize); /*\ |*| very rude hack caused by nasplayer lib that outputs rubbish to stderr \*/ //fclose(stderr); /*\ |*| EORH \*/ action = AC_NONE; if (options & OPT_PLAYMP3) { const char *bla[4]; short idx; short foo[2] = { 0, 1 }; for (int i = 0; i < (argc - optind); i++) { bla[0] = (const char*)playmp3s[i]; if (is_httpstream(bla[0])) bla[1] = bla[0]; else bla[1] = chop_path(playmp3s[i]); bla[2] = NULL; bla[3] = NULL; idx=mp3_curwin->addItem(bla, foo, CP_FILE_MP3); if (idx!=-1) mp3_curwin->changeItem(idx,&id3_filename,strdup(bla[0]),2); delete[] playmp3s[i]; } delete[] playmp3s; playmp3s_nr = 0; mp3_curwin->swRefresh(0); set_action(AC_PLAY); if (!(options & OPT_QUIT)) quit_after_playlist = 1; } else if ((options & OPT_LOADLIST) || (options & OPT_AUTOLIST)) { read_playlist(init_playlist); mp3_curwin->swRefresh(1); delete[] init_playlist; if (options & OPT_AUTOLIST) set_action(AC_PLAY); } #ifdef PTHREADEDMPEG pthread_create(&thread_playlist, NULL, play_list, NULL); # ifdef INCLUDE_LIRC pthread_create(&thread_lirc, NULL, lirc_loop, NULL); # endif #elif defined(LIBPTH) warning("Setting up PTH thread"); pth_mutex_init(&playing_mutex); pth_mutex_init(&ncurses_mutex); pth_mutex_init(&onemp3_mutex); pth_cond_init(&playing_cond); thread_playlist = pth_spawn(PTH_ATTR_DEFAULT, play_list, NULL); pth_yield(NULL); #endif /* PTHREADEDMPEG */ /* read input */ while ( (key = handle_input(1)) >= 0) { } /* signal running threads to terminate themselves */ playopts.quit_program = 1; wake_player(); #ifdef PTHREADEDMPEG pthread_join(thread_playlist, NULL); //the lirc thread blocks..sod it. #elif defined (LIBPTH) pth_join(thread_playlist, NULL); #endif endwin(); return 0; }