diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 20 | ||||
-rw-r--r-- | src/colors.c | 336 | ||||
-rw-r--r-- | src/colors.h | 27 | ||||
-rw-r--r-- | src/command.c | 429 | ||||
-rw-r--r-- | src/command.h | 76 | ||||
-rw-r--r-- | src/conf.c | 594 | ||||
-rw-r--r-- | src/conf.h | 7 | ||||
-rw-r--r-- | src/libmpdclient.c | 1211 | ||||
-rw-r--r-- | src/libmpdclient.h | 399 | ||||
-rw-r--r-- | src/list_window.c | 296 | ||||
-rw-r--r-- | src/list_window.h | 67 | ||||
-rw-r--r-- | src/main.c | 199 | ||||
-rw-r--r-- | src/mpc.c | 359 | ||||
-rw-r--r-- | src/mpc.h | 50 | ||||
-rw-r--r-- | src/options.c | 185 | ||||
-rw-r--r-- | src/options.h | 28 | ||||
-rw-r--r-- | src/screen.c | 869 | ||||
-rw-r--r-- | src/screen.h | 113 | ||||
-rw-r--r-- | src/screen_file.c | 503 | ||||
-rw-r--r-- | src/screen_file.h | 7 | ||||
-rw-r--r-- | src/screen_help.c | 254 | ||||
-rw-r--r-- | src/screen_help.h | 2 | ||||
-rw-r--r-- | src/screen_keydef.c | 393 | ||||
-rw-r--r-- | src/screen_play.c | 390 | ||||
-rw-r--r-- | src/screen_play.h | 9 | ||||
-rw-r--r-- | src/screen_search.c | 12 | ||||
-rw-r--r-- | src/screen_search.h | 0 | ||||
-rw-r--r-- | src/screen_utils.c | 153 | ||||
-rw-r--r-- | src/screen_utils.h | 18 | ||||
-rw-r--r-- | src/support.c | 216 | ||||
-rw-r--r-- | src/support.h | 24 |
31 files changed, 7246 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 000000000..abf121cb2 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,20 @@ +# +# $Id$ +# + +bin_PROGRAMS = ncmpc + +ncmpc_headers = libmpdclient.h mpc.h options.h conf.h command.h screen.h \ + screen_utils.h screen_play.h screen_file.h screen_search.h \ + screen_help.h list_window.h colors.h support.h + +ncmpc_SOURCES = libmpdclient.c main.c mpc.c options.c conf.c command.c \ + screen.c screen_utils.c screen_play.c screen_file.c \ + screen_search.c screen_help.c screen_keydef.c \ + list_window.c colors.c support.c $(ncmpc_headers) + + + + + + diff --git a/src/colors.c b/src/colors.c new file mode 100644 index 000000000..f70969390 --- /dev/null +++ b/src/colors.c @@ -0,0 +1,336 @@ +/* + * (c) 2004 by Kalle Wallin (kaw@linux.se) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ncurses.h> +#include <glib.h> + +#include "config.h" +#include "options.h" +#include "support.h" +#include "colors.h" + +#ifdef DEBUG +#define D(x) x +#else +#define D(x) +#endif + +#define COLOR_BRIGHT_MASK (1<<7) + +#define COLOR_BRIGHT_BLACK (COLOR_BLACK | COLOR_BRIGHT_MASK) +#define COLOR_BRIGHT_RED (COLOR_RED | COLOR_BRIGHT_MASK) +#define COLOR_BRIGHT_GREEN (COLOR_GREEN | COLOR_BRIGHT_MASK) +#define COLOR_BRIGHT_YELLOW (COLOR_YELLOW | COLOR_BRIGHT_MASK) +#define COLOR_BRIGHT_BLUE (COLOR_BLUE | COLOR_BRIGHT_MASK) +#define COLOR_BRIGHT_MAGENTA (COLOR_MAGENTA | COLOR_BRIGHT_MASK) +#define COLOR_BRIGHT_CYAN (COLOR_CYAN | COLOR_BRIGHT_MASK) +#define COLOR_BRIGHT_WHITE (COLOR_WHITE | COLOR_BRIGHT_MASK) + +#define IS_BRIGHT(color) (color & COLOR_BRIGHT_MASK) + +/* name of the color fields */ +#define NAME_TITLE "title" +#define NAME_TITLE_BOLD "title-bold" +#define NAME_LINE "line" +#define NAME_LINE_BOLD "line-flags" +#define NAME_LIST "list" +#define NAME_LIST_BOLD "list-bold" +#define NAME_PROGRESS "progressbar" +#define NAME_STATUS "status-song" +#define NAME_STATUS_BOLD "status-state" +#define NAME_STATUS_TIME "status-time" +#define NAME_ALERT "alert" +#define NAME_BGCOLOR "background" + +typedef struct { + short color; + short r,g,b; +} color_definition_entry_t; + +typedef struct { + int id; + char *name; + short fg; + attr_t attrs; +} color_entry_t; + +static color_entry_t colors[] = { + + /* color pair, field name, color, mono attribute */ + /*-------------------------------------------------------------------------*/ + { COLOR_TITLE, NAME_TITLE, COLOR_YELLOW, A_NORMAL }, + { COLOR_TITLE_BOLD, NAME_TITLE_BOLD, COLOR_BRIGHT_YELLOW, A_BOLD }, + { COLOR_LINE, NAME_LINE, COLOR_WHITE, A_NORMAL }, + { COLOR_LINE_BOLD, NAME_LINE_BOLD, COLOR_BRIGHT_WHITE, A_BOLD }, + { COLOR_LIST, NAME_LIST, COLOR_GREEN, A_NORMAL }, + { COLOR_LIST_BOLD, NAME_LIST_BOLD, COLOR_BRIGHT_GREEN, A_BOLD }, + { COLOR_PROGRESSBAR, NAME_PROGRESS, COLOR_WHITE, A_NORMAL }, + { COLOR_STATUS, NAME_STATUS, COLOR_YELLOW, A_NORMAL }, + { COLOR_STATUS_BOLD, NAME_STATUS_BOLD, COLOR_BRIGHT_YELLOW, A_BOLD }, + { COLOR_STATUS_TIME, NAME_STATUS_TIME, COLOR_RED, A_NORMAL }, + { COLOR_STATUS_ALERT, NAME_ALERT, COLOR_BRIGHT_RED, A_BOLD }, + { 0, NULL, 0, 0 } +}; + +/* background color */ +static short bg = COLOR_BLACK; + +static GList *color_definition_list = NULL; + +static color_entry_t * +colors_lookup(int id) +{ + int i; + + i=0; + while( colors[i].name != NULL ) + { + if( colors[i].id == id ) + return &colors[i]; + i++; + } + return NULL; +} + +static color_entry_t * +colors_lookup_by_name(char *name) +{ + int i; + + i=0; + while( colors[i].name != NULL ) + { + if( !strcasecmp(colors[i].name, name) ) + return &colors[i]; + i++; + } + return NULL; +} + +static int +colors_update_pair(int id) +{ + color_entry_t *entry = colors_lookup(id); + short fg = -1; + + if( !entry ) + return -1; + + if( IS_BRIGHT(entry->fg) ) + { + entry->attrs = A_BOLD; + fg = entry->fg & ~COLOR_BRIGHT_MASK; + } + else + { + entry->attrs = A_NORMAL; + fg = entry->fg; + } + + init_pair(entry->id, fg, bg); + + return 0; +} + +short +colors_str2color(char *str) +{ + if( !strcasecmp(str,"black") ) + return COLOR_BLACK; + else if( !strcasecmp(str,"red") ) + return COLOR_RED; + else if( !strcasecmp(str,"green") ) + return COLOR_GREEN; + else if( !strcasecmp(str,"yellow") ) + return COLOR_YELLOW; + else if( !strcasecmp(str,"blue") ) + return COLOR_BLUE; + else if( !strcasecmp(str,"magenta") ) + return COLOR_MAGENTA; + else if( !strcasecmp(str,"cyan") ) + return COLOR_CYAN; + else if( !strcasecmp(str,"white") ) + return COLOR_WHITE; + else if( !strcasecmp(str,"brightred") ) + return COLOR_BRIGHT_RED; + else if( !strcasecmp(str,"brightgreen") ) + return COLOR_BRIGHT_GREEN; + else if( !strcasecmp(str,"brightyellow") ) + return COLOR_BRIGHT_YELLOW; + else if( !strcasecmp(str,"brightblue") ) + return COLOR_BRIGHT_BLUE; + else if( !strcasecmp(str,"brightmagenta") ) + return COLOR_BRIGHT_MAGENTA; + else if( !strcasecmp(str,"brightcyan") ) + return COLOR_BRIGHT_CYAN; + else if( !strcasecmp(str,"brightwhite") ) + return COLOR_BRIGHT_WHITE; + else if( !strcasecmp(str,"grey") || !strcasecmp(str,"gray") ) + return COLOR_BRIGHT_BLACK; + else if( !strcasecmp(str,"none") ) + return -1; + fprintf(stderr,"Warning: Unknown color - %s\n", str); + return -2; +} + +/* This function is called from conf.c before curses have been started, + * it adds the definition to the color_definition_list and init_color() is + * done in colors_start() */ +int +colors_define(char *name, short r, short g, short b) +{ + color_definition_entry_t *entry; + short color = colors_str2color(name); + + if( color<0 ) + return -1; + + entry = g_malloc(sizeof(color_definition_entry_t)); + entry->color = color; + entry->r = r; + entry->g = g; + entry->b = b; + + color_definition_list = g_list_append(color_definition_list, entry); + + return 0; +} + + +int +colors_assign(char *name, char *value) +{ + color_entry_t *entry = colors_lookup_by_name(name); + short color; + + if( !entry ) + { + if( !strcasecmp(NAME_BGCOLOR, name) ) + { + if( (color=colors_str2color(value)) < -1 ) + return -1; + if( color > COLORS ) + color = color & ~COLOR_BRIGHT_MASK; + bg = color; + return 0; + } + fprintf(stderr,"Warning: Unknown color field - %s\n", name); + return -1; + } + + if( (color=colors_str2color(value)) < -1 ) + return -1; + entry->fg = color; + + return 0; +} + + +int +colors_start(void) +{ + if( has_colors() ) + { + /* initialize color support */ + start_color(); + use_default_colors(); + /* define any custom colors defined in the configuration file */ + if( color_definition_list && can_change_color() ) + { + GList *list = color_definition_list; + + while( list ) + { + color_definition_entry_t *entry = list->data; + + if( entry->color <= COLORS ) + init_color(entry->color, entry->r, entry->g, entry->b); + list=list->next; + } + } + else if( !can_change_color() ) + fprintf(stderr, "Terminal lacks support for changing colors!\n"); + + if( options.enable_colors ) + { + int i = 0; + + while(colors[i].name) + { + /* update the color pairs */ + colors_update_pair(colors[i].id); + i++; + } + + } + } + else if( options.enable_colors ) + { + fprintf(stderr, "Terminal lacks color capabilities!\n"); + options.enable_colors = 0; + } + + /* free the color_definition_list */ + if( color_definition_list ) + { + GList *list = color_definition_list; + + while( list ) + { + g_free(list->data); + list=list->next; + } + g_list_free(color_definition_list); + color_definition_list = NULL; + } + + return 0; +} + + +int +colors_use(WINDOW *w, int id) +{ + color_entry_t *entry = colors_lookup(id); + short pair; + attr_t attrs; + + if( !entry ) + return -1; + + wattr_get(w, &attrs, &pair, NULL); + + if( options.enable_colors ) + { + /* color mode */ + if( attrs != entry->attrs || id!=pair ) + wattr_set(w, entry->attrs, id, NULL); + } + else + { + /* mono mode */ + if( attrs != entry->attrs ) + wattrset(w, entry->attrs); + } + + return 0; +} + diff --git a/src/colors.h b/src/colors.h new file mode 100644 index 000000000..a66d693cc --- /dev/null +++ b/src/colors.h @@ -0,0 +1,27 @@ +#ifndef COLORS_H +#define COLORS_H + +#define COLOR_TITLE 1 +#define COLOR_TITLE_BOLD 2 +#define COLOR_LINE 3 +#define COLOR_LINE_BOLD 4 +#define COLOR_LIST 5 +#define COLOR_LIST_BOLD 6 +#define COLOR_PROGRESSBAR 7 +#define COLOR_STATUS 8 +#define COLOR_STATUS_BOLD 9 +#define COLOR_STATUS_TIME 10 +#define COLOR_STATUS_ALERT 11 + +short colors_str2color(char *str); + +int colors_assign(char *name, char *value); +int colors_define(char *name, short r, short g, short b); +int colors_start(void); +int colors_use(WINDOW *w, int id); + + +#endif /* COLORS_H */ + + + diff --git a/src/command.c b/src/command.c new file mode 100644 index 000000000..1e9edcd0d --- /dev/null +++ b/src/command.c @@ -0,0 +1,429 @@ +/* + * (c) 2004 by Kalle Wallin (kaw@linux.se) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <glib.h> +#include <ncurses.h> + +#include "config.h" +#include "command.h" + +#undef DEBUG_KEYS + +#ifdef DEBUG_KEYS +#define DK(x) x +#else +#define DK(x) +#endif + +extern void screen_resize(void); + +#define BS KEY_BACKSPACE +#define DEL KEY_DC +#define UP KEY_UP +#define DWN KEY_DOWN +#define LEFT KEY_LEFT +#define RGHT KEY_RIGHT +#define HOME KEY_HOME +#define END KEY_END +#define PGDN KEY_NPAGE +#define PGUP KEY_PPAGE +#define TAB 0x09 +#define STAB 0x161 +#define ESC 0x1B +#define F1 KEY_F(1) +#define F2 KEY_F(2) +#define F3 KEY_F(3) +#define F4 KEY_F(4) +#define F5 KEY_F(5) +#define F6 KEY_F(6) + + +static command_definition_t cmds[] = +{ + { { 13, 0, 0 }, CMD_PLAY, "play", + "Play/Enter directory" }, + { { 'P', 0, 0 }, CMD_PAUSE,"pause", + "Pause" }, + { { 's', BS, 0 }, CMD_STOP, "stop", + "Stop" }, + { { '>', 0, 0 }, CMD_TRACK_NEXT, "next", + "Next track" }, + { { '<', 0, 0 }, CMD_TRACK_PREVIOUS, "prev", + "Previous track" }, + { { 'f', 0, 0 }, CMD_SEEK_FORWARD, "seek-forward", + "Seek forward" }, + { { 'b', 0, 0 }, CMD_SEEK_BACKWARD, "seek-backward", + "Seek backward" }, + + { { '+', RGHT, 0 }, CMD_VOLUME_UP, "volume-up", + "Increase volume" }, + { { '-', LEFT, 0 }, CMD_VOLUME_DOWN, "volume-down", + "Decrease volume" }, + + { { 'w', 0, 0 }, CMD_TOGGLE_FIND_WRAP, "wrap-mode", + "Toggle find mode" }, + { { 'U', 0, 0 }, CMD_TOGGLE_AUTOCENTER, "autocenter-mode", + "Toggle auto center mode" }, + + { { ' ', 'a', 0 }, CMD_SELECT, "select", + "Select/deselect song in playlist" }, + { { DEL, 'd', 0 }, CMD_DELETE, "delete", + "Delete song from playlist" }, + { { 'Z', 0, 0 }, CMD_SHUFFLE, "shuffle", + "Shuffle playlist" }, + { { 'c', 0, 0 }, CMD_CLEAR, "clear", + "Clear playlist" }, + { { 'r', 0, 0 }, CMD_REPEAT, "repeat", + "Toggle repeat mode" }, + { { 'z', 0, 0 }, CMD_RANDOM, "random", + "Toggle random mode" }, + { { 'x', 0, 0 }, CMD_CROSSFADE, "crossfade", + "Toggle crossfade mode" }, + { { 'S', 0, 0 }, CMD_SAVE_PLAYLIST, "save", + "Save playlist" }, + + { { 0, 0, 0 }, CMD_LIST_MOVE_UP, "move-up", + "Move item up" }, + { { 0, 0, 0 }, CMD_LIST_MOVE_DOWN, "move-down", + "Move item down" }, + + { { UP, ',', 0 }, CMD_LIST_PREVIOUS, "up", + "Move cursor up" }, + { { DWN, '.', 0 }, CMD_LIST_NEXT, "down", + "Move cursor down" }, + { { HOME, 0x01, 0 }, CMD_LIST_FIRST, "home", + "Home " }, + { { END, 0x05, 0 }, CMD_LIST_LAST, "end", + "End " }, + { { PGUP, 'A', 0 }, CMD_LIST_PREVIOUS_PAGE, "pgup", + "Page up" }, + { { PGDN, 'B', 0 }, CMD_LIST_NEXT_PAGE, "pgdn", + "Page down" }, + { { '/', 0, 0 }, CMD_LIST_FIND, "find", + "Forward find" }, + { { 'n', 0, 0 }, CMD_LIST_FIND_NEXT, "find-next", + "Forward find next" }, + { { '?', 0, 0 }, CMD_LIST_RFIND, "rfind", + "Backward find" }, + { { 'p', 0, 0 }, CMD_LIST_RFIND_NEXT, "rfind-next", + "Backward find previous" }, + + + { { TAB, 0, 0 }, CMD_SCREEN_NEXT, "screen-next", + "Next screen" }, + + { { STAB, 0, 0 }, CMD_SCREEN_PREVIOUS, "screen-prev", + "Previous screen" }, + + { { '1', F1, 'h' }, CMD_SCREEN_HELP, "screen-help", + "Help screen" }, + { { '2', F2, 0 }, CMD_SCREEN_PLAY, "screen-playlist", + "Playlist screen" }, + { { '3', F3, 0 }, CMD_SCREEN_FILE, "screen-browse", + "Browse screen" }, + { {'u', 0, 0 }, CMD_SCREEN_UPDATE, "update", + "Update screen" }, +#ifdef ENABLE_KEYDEF_SCREEN + { {'K', 0, 0 }, CMD_SCREEN_KEYDEF, "screen-keyedit", + "Key configuration screen" }, +#endif + + { { 'q', 0, 0 }, CMD_QUIT, "quit", + "Quit " PACKAGE }, + + { { -1, -1, -1 }, CMD_NONE, NULL, NULL } +}; + +command_definition_t * +get_command_definitions(void) +{ + return cmds; +} + +char * +key2str(int key) +{ + static char buf[32]; + int i; + + buf[0] = 0; + switch(key) + { + case 0: + return "Undefined"; + case ' ': + return "Space"; + case 13: + return "Enter"; + case BS: + return "Backspace"; + case DEL: + return "Delete"; + case UP: + return "Up"; + case DWN: + return "Down"; + case LEFT: + return "Left"; + case RGHT: + return "Right"; + case HOME: + return "Home"; + case END: + return "End"; + case PGDN: + return "PageDown"; + case PGUP: + return "PageUp"; + case TAB: + return "Tab"; + case STAB: + return "Shift+Tab"; + case ESC: + return "Esc"; + case KEY_IC: + return "Insert"; + default: + for(i=0; i<=63; i++) + if( key==KEY_F(i) ) + { + snprintf(buf, 32, "F%d", i ); + return buf; + } + if( !(key & ~037) ) + snprintf(buf, 32, "Ctrl-%c", 'A'+(key & 037)-1 ); + else if( (key & ~037) == 224 ) + snprintf(buf, 32, "Alt-%c", 'A'+(key & 037)-1 ); + else if( key>32 && key<256 ) + snprintf(buf, 32, "%c", key); + else + snprintf(buf, 32, "0x%03X", key); + } + return buf; +} + +void +command_dump_keys(void) +{ + int i; + + i=0; + while( cmds[i].description ) + { + if( cmds[i].command != CMD_NONE ) + printf(" %20s : %s\n", get_key_names(cmds[i].command,1),cmds[i].name); + i++; + } +} + +char * +get_key_names(command_t command, int all) +{ + int i; + + i=0; + while( cmds[i].description ) + { + if( cmds[i].command == command ) + { + int j; + static char keystr[80]; + + strncpy(keystr, key2str(cmds[i].keys[0]), 80); + if( !all ) + return keystr; + j=1; + while( j<MAX_COMMAND_KEYS && cmds[i].keys[j]>0 ) + { + strcat(keystr, " "); + strcat(keystr, key2str(cmds[i].keys[j])); + j++; + } + return keystr; + } + i++; + } + return NULL; +} + +char * +get_key_description(command_t command) +{ + int i; + + i=0; + while( cmds[i].description ) + { + if( cmds[i].command == command ) + return cmds[i].description; + i++; + } + return NULL; +} + +char * +get_key_command_name(command_t command) +{ + int i; + + i=0; + while( cmds[i].name ) + { + if( cmds[i].command == command ) + return cmds[i].name; + i++; + } + return NULL; +} + +command_t +get_key_command_from_name(char *name) +{ + int i; + + i=0; + while( cmds[i].name ) + { + if( strcmp(name, cmds[i].name) == 0 ) + return cmds[i].command; + i++; + } + return CMD_NONE; +} + + +command_t +find_key_command(int key, command_definition_t *cmds) +{ + int i; + + i=0; + while( cmds && cmds[i].name ) + { + if( cmds[i].keys[0] == key || + cmds[i].keys[1] == key || + cmds[i].keys[2] == key ) + return cmds[i].command; + i++; + } + return CMD_NONE; +} + +command_t +get_key_command(int key) +{ + return find_key_command(key, cmds); +} + + +command_t +get_keyboard_command(void) +{ + int key; + + key = wgetch(stdscr); + + if( key==KEY_RESIZE ) + screen_resize(); + + if( key==ERR ) + return CMD_NONE; + + DK(fprintf(stderr, "key = 0x%02X\t", key)); + + return get_key_command(key); +} + +int +assign_keys(command_t command, int keys[MAX_COMMAND_KEYS]) +{ + int i; + + i=0; + while( cmds[i].name ) + { + if( cmds[i].command == command ) + { + memcpy(cmds[i].keys, keys, sizeof(int)*MAX_COMMAND_KEYS); + return 0; + } + i++; + } + return -1; +} + +int +check_key_bindings(void) +{ + int i; + int retval = 0; + + i=0; + while( cmds[i].name ) + { + int j; + command_t cmd; + + for(j=0; j<MAX_COMMAND_KEYS; j++) + if( cmds[i].keys[j] && + (cmd=get_key_command(cmds[i].keys[j])) != cmds[i].command ) + { + fprintf(stderr, "Error: Key %s assigned to %s and %s !!!\n", + key2str(cmds[i].keys[j]), + get_key_command_name(cmds[i].command), + get_key_command_name(cmd)); + retval = -1; + } + i++; + } + return retval; +} + +int +write_key_bindings(FILE *f) +{ + int i,j; + + i=0; + while( cmds[i].name && !ferror(f) ) + { + fprintf(f, "# %s\n", cmds[i].description); + fprintf(f, "key %s = ", cmds[i].name); + for(j=0; j<MAX_COMMAND_KEYS; j++) + { + if( j && cmds[i].keys[j] ) + fprintf(f, ", "); + if( !j || cmds[i].keys[j] ) + { + if( cmds[i].keys[j]<256 && (isalpha(cmds[i].keys[j]) || + isdigit(cmds[i].keys[j])) ) + fprintf(f, "\'%c\'", cmds[i].keys[j]); + else + fprintf(f, "%d", cmds[i].keys[j]); + } + } + fprintf(f,"\n\n"); + i++; + } + return ferror(f); +} diff --git a/src/command.h b/src/command.h new file mode 100644 index 000000000..1ce67e894 --- /dev/null +++ b/src/command.h @@ -0,0 +1,76 @@ +#ifndef COMMAND_H +#define COMMAND_H + +#define MAX_COMMAND_KEYS 3 + +typedef enum +{ + CMD_NONE = 0, + CMD_PLAY, + CMD_SELECT, + CMD_PAUSE, + CMD_STOP, + CMD_TRACK_NEXT, + CMD_TRACK_PREVIOUS, + CMD_SEEK_FORWARD, + CMD_SEEK_BACKWARD, + CMD_SHUFFLE, + CMD_RANDOM, + CMD_CLEAR, + CMD_DELETE, + CMD_REPEAT, + CMD_CROSSFADE, + CMD_VOLUME_UP, + CMD_VOLUME_DOWN, + CMD_SAVE_PLAYLIST, + CMD_TOGGLE_FIND_WRAP, + CMD_TOGGLE_AUTOCENTER, + CMD_LIST_PREVIOUS, + CMD_LIST_NEXT, + CMD_LIST_FIRST, + CMD_LIST_LAST, + CMD_LIST_NEXT_PAGE, + CMD_LIST_PREVIOUS_PAGE, + CMD_LIST_FIND, + CMD_LIST_FIND_NEXT, + CMD_LIST_RFIND, + CMD_LIST_RFIND_NEXT, + CMD_LIST_MOVE_UP, + CMD_LIST_MOVE_DOWN, + CMD_SCREEN_UPDATE, + CMD_SCREEN_PREVIOUS, + CMD_SCREEN_NEXT, + CMD_SCREEN_PLAY, + CMD_SCREEN_FILE, + CMD_SCREEN_SEARCH, + CMD_SCREEN_KEYDEF, + CMD_SCREEN_HELP, + CMD_QUIT +} command_t; + +typedef struct +{ + int keys[MAX_COMMAND_KEYS]; + command_t command; + char *name; + char *description; +} command_definition_t; + +command_definition_t *get_command_definitions(void); +command_t find_key_command(int key, command_definition_t *cmds); + +void command_dump_keys(void); +int check_key_bindings(void); +int write_key_bindings(FILE *f); + +char *key2str(int key); +char *get_key_description(command_t command); +char *get_key_command_name(command_t command); +char *get_key_names(command_t command, int all); +command_t get_key_command(int key); +command_t get_key_command_from_name(char *name); +int assign_keys(command_t command, int keys[MAX_COMMAND_KEYS]); + +command_t get_keyboard_command(void); + +#endif diff --git a/src/conf.c b/src/conf.c new file mode 100644 index 000000000..06866c13e --- /dev/null +++ b/src/conf.c @@ -0,0 +1,594 @@ +/* + * (c) 2004 by Kalle Wallin (kaw@linux.se) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <ctype.h> +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <glib.h> +#include <ncurses.h> + +#include "config.h" +#include "options.h" +#include "support.h" +#include "command.h" +#include "colors.h" +#include "conf.h" + +#ifdef DEBUG +#define D(x) x +#else +#define D(x) +#endif + +#define ENABLE_OLD_COLOR_SYNTAX + +#define MAX_LINE_LENGTH 1024 +#define COMMENT_TOKEN '#' + +/* configuration field names */ +#define CONF_ENABLE_COLORS "enable_colors" +#define CONF_AUTO_CENTER "auto_center" +#define CONF_WIDE_CURSOR "wide_cursor" +#define CONF_KEY_DEFINITION "key" +#define CONF_COLOR "color" +#define CONF_COLOR_DEFINITION "colordef" + +/* Deprecated - configuration field names */ +#define CONF_COLOR_BACKGROUND "background_color" +#define CONF_COLOR_TITLE "title_color" +#define CONF_COLOR_LINE "line_color" +#define CONF_COLOR_LIST "list_color" +#define CONF_COLOR_PROGRESS "progress_color" +#define CONF_COLOR_STATUS "status_color" +#define CONF_COLOR_ALERT "alert_color" + + +typedef enum { + KEY_PARSER_UNKNOWN, + KEY_PARSER_CHAR, + KEY_PARSER_DEC, + KEY_PARSER_HEX, + KEY_PARSER_DONE +} key_parser_state_t; + +static int +str2bool(char *str) +{ + if( !strcasecmp(str,"no") || !strcasecmp(str,"false") || + !strcasecmp(str,"off") || !strcasecmp(str,"0") ) + return 0; + return 1; +} + +static int +parse_key_value(char *str, size_t len, char **end) +{ + int i, value; + key_parser_state_t state; + + i=0; + value=0; + state=KEY_PARSER_UNKNOWN; + *end = str; + + while( i<len && state!=KEY_PARSER_DONE ) + { + int next = 0; + int c = str[i]; + + if( i+1<len ) + next = str[i+1]; + + switch(state) + { + case KEY_PARSER_UNKNOWN: + if( c=='\'' ) + state = KEY_PARSER_CHAR; + else if( c=='0' && next=='x' ) + state = KEY_PARSER_HEX; + else if( isdigit(c) ) + state = KEY_PARSER_DEC; + else { + fprintf(stderr, "Error: Unsupported key definition - %s\n", str); + return -1; + } + break; + case KEY_PARSER_CHAR: + if( next!='\'' ) + { + fprintf(stderr, "Error: Unsupported key definition - %s\n", str); + return -1; + } + value = c; + *end = str+i+2; + state = KEY_PARSER_DONE; + break; + case KEY_PARSER_DEC: + value = (int) strtol(str+(i-1), end, 10); + state = KEY_PARSER_DONE; + break; + case KEY_PARSER_HEX: + if( !isdigit(next) ) + { + fprintf(stderr, "Error: Digit expexted after 0x - %s\n", str); + return -1; + } + value = (int) strtol(str+(i+1), end, 16); + state = KEY_PARSER_DONE; + break; + case KEY_PARSER_DONE: + break; + } + i++; + } + + if( *end> str+len ) + *end = str+len; + + return value; +} + +static int +parse_key_definition(char *str) +{ + char buf[MAX_LINE_LENGTH]; + char *p, *end; + size_t len = strlen(str); + int i,j,key; + int keys[MAX_COMMAND_KEYS]; + command_t cmd; + + /* get the command name */ + i=0; + j=0; + memset(buf, 0, MAX_LINE_LENGTH); + while( i<len && str[i]!='=' && !IS_WHITESPACE(str[i]) ) + buf[j++] = str[i++]; + if( (cmd=get_key_command_from_name(buf)) == CMD_NONE ) + { + fprintf(stderr, "Error: Unknown key command %s\n", buf); + return -1; + } + + /* skip whitespace */ + while( i<len && (str[i]=='=' || IS_WHITESPACE(str[i])) ) + i++; + + /* get the value part */ + memset(buf, 0, MAX_LINE_LENGTH); + strncpy(buf, str+i, len-i); + len = strlen(buf); + if( len==0 ) + { + fprintf(stderr,"Error: Incomplete key definition - %s\n", str); + return -1; + } + + /* parse key values */ + i = 0; + key = 0; + len = strlen(buf); + p = buf; + end = buf+len; + memset(keys, 0, sizeof(int)*MAX_COMMAND_KEYS); + while( i<MAX_COMMAND_KEYS && p<end && (key=parse_key_value(p,len+1,&p))>=0 ) + { + keys[i++] = key; + while( p<end && (*p==',' || *p==' ' || *p=='\t') ) + p++; + len = strlen(p); + } + if( key<0 ) + { + fprintf(stderr,"Error: Bad key definition - %s\n", str); + return -1; + } + + return assign_keys(cmd, keys); +} + +static int +parse_color(char *str) +{ + char *name = str; + char *value = NULL; + int len,i; + + i=0; + len=strlen(str); + /* get the color name */ + while( i<len && str[i]!='=' && !IS_WHITESPACE(str[i]) ) + i++; + + /* skip whitespace */ + while( i<len && (str[i]=='=' || IS_WHITESPACE(str[i])) ) + { + str[i]='\0'; + i++; + } + + if( i<len ) + value = str+i; + + return colors_assign(name, value); +} + + +static int +parse_color_definition(char *str) +{ + char buf[MAX_LINE_LENGTH]; + char *p, *end, *name; + size_t len = strlen(str); + int i,j,value; + short color, rgb[3]; + + /* get the command name */ + i=0; + j=0; + memset(buf, 0, MAX_LINE_LENGTH); + while( i<len && str[i]!='=' && !IS_WHITESPACE(str[i]) ) + buf[j++] = str[i++]; + color=colors_str2color(buf); + if( color<0 ) + { + fprintf(stderr, "Error: Bad color %s [%d]\n", buf, color); + return -1; + } + name = g_strdup(buf); + + /* skip whitespace */ + while( i<len && (str[i]=='=' || IS_WHITESPACE(str[i])) ) + i++; + + /* get the value part */ + memset(buf, 0, MAX_LINE_LENGTH); + strncpy(buf, str+i, len-i); + len = strlen(buf); + if( len==0 ) + { + fprintf(stderr,"Error: Incomplete color definition - %s\n", str); + g_free(name); + return -1; + } + + /* parse r,g.b values with the key definition parser */ + i = 0; + value = 0; + len = strlen(buf); + p = buf; + end = buf+len; + memset(rgb, 0, sizeof(short)*3); + while( i<3 && p<end && (value=parse_key_value(p,len+1,&p))>=0 ) + { + rgb[i++] = value; + while( p<end && (*p==',' || *p==' ' || *p=='\t') ) + p++; + len = strlen(p); + } + if( value<0 || i!=3) + { + fprintf(stderr,"Error: Bad color definition - %s\n", str); + g_free(name); + return -1; + } + value = colors_define(name, rgb[0], rgb[1], rgb[2]); + g_free(name); + return value; +} + + +static int +read_rc_file(char *filename, options_t *options) +{ + int fd; + int quit = 0; + int free_filename = 0; + + if( filename==NULL ) + return -1; + + D(printf("Reading configuration file %s\n", filename)); + if( (fd=open(filename,O_RDONLY)) <0 ) + { + perror(filename); + if( free_filename ) + g_free(filename); + return -1; + } + + while( !quit ) + { + int i,j; + int len; + int match_found; + char line[MAX_LINE_LENGTH]; + char name[MAX_LINE_LENGTH]; + char value[MAX_LINE_LENGTH]; + + line[0] = '\0'; + value[0] = '\0'; + + i = 0; + /* read a line ending with '\n' */ + do { + len = read( fd, &line[i], 1 ); + if( len == 1 ) + i++; + else + quit = 1; + } while( !quit && i<MAX_LINE_LENGTH && line[i-1]!='\n' ); + + + /* remove trailing whitespace */ + line[i] = '\0'; + i--; + while( i>=0 && IS_WHITESPACE(line[i]) ) + { + line[i] = '\0'; + i--; + } + len = i+1; + + if( len>0 ) + { + i = 0; + /* skip whitespace */ + while( i<len && IS_WHITESPACE(line[i]) ) + i++; + + /* continue if this line is not a comment */ + if( line[i] != COMMENT_TOKEN ) + { + /* get the name part */ + j=0; + while( i<len && line[i]!='=' && !IS_WHITESPACE(line[i]) ) + { + name[j++] = line[i++]; + } + name[j] = '\0'; + + /* skip '=' and whitespace */ + while( i<len && (line[i]=='=' || IS_WHITESPACE(line[i])) ) + i++; + + /* get the value part */ + j=0; + while( i<len ) + { + value[j++] = line[i++]; + } + value[j] = '\0'; + + match_found = 1; + + /* key definition */ + if( !strcasecmp(CONF_KEY_DEFINITION, name) ) + { + parse_key_definition(value); + } + /* enable colors */ + else if( !strcasecmp(CONF_ENABLE_COLORS, name) ) + { + options->enable_colors = str2bool(value); + } + /* auto center */ + else if( !strcasecmp(CONF_AUTO_CENTER, name) ) + { + options->auto_center = str2bool(value); + } + /* color assignment */ + else if( !strcasecmp(CONF_COLOR, name) ) + { + parse_color(value); + } +#ifdef ENABLE_OLD_COLOR_SYNTAX + /* background color */ + else if( !strcasecmp(CONF_COLOR_BACKGROUND, name) ) + { + fprintf(stderr,"%s: %s - deprecated!\n", filename,name); + colors_assign("background", value); + } + /* color - top (title) window */ + else if( !strcasecmp(CONF_COLOR_TITLE, name) ) + { + fprintf(stderr,"%s: %s - deprecated!\n", filename,name); + colors_assign("title", value); + colors_assign("title2", value); + } + /* color - line (title) window */ + else if( !strcasecmp(CONF_COLOR_LINE, name) ) + { + fprintf(stderr,"%s: %s - deprecated!\n", filename,name); + colors_assign("line", value); + colors_assign("line2", value); + } + /* color - list window */ + else if( !strcasecmp(CONF_COLOR_LIST, name) ) + { + fprintf(stderr,"%s: %s - deprecated!\n", filename,name); + colors_assign("list", value); + } + /* color - progress bar */ + else if( !strcasecmp(CONF_COLOR_PROGRESS, name) ) + { + fprintf(stderr,"%s: %s - deprecated!\n", filename,name); + colors_assign("progressbar", value); + } + /* color - status window */ + else if( !strcasecmp(CONF_COLOR_STATUS, name) ) + { + fprintf(stderr,"%s: %s - deprecated!\n", filename,name); + colors_assign("status", value); + colors_assign("status2", value); + } + /* color - alerts */ + else if( !strcasecmp(CONF_COLOR_ALERT, name) ) + { + fprintf(stderr,"%s: %s - deprecated!\n", filename,name); + colors_assign("alert", value); + } +#endif + /* wide cursor */ + else if( !strcasecmp(CONF_WIDE_CURSOR, name) ) + { + options->wide_cursor = str2bool(value); + } + /* color definition */ + else if( !strcasecmp(CONF_COLOR_DEFINITION, name) ) + { + parse_color_definition(value); + } + else + { + match_found = 0; + } + + + if( !match_found ) + fprintf(stderr, + "Unknown configuration parameter: %s\n", + name); +#ifdef DEBUG + printf( " %s = %s %s\n", + name, + value, + match_found ? "" : "- UNKNOWN SETTING!" ); +#endif + + } + } + } + + D(printf( "--\n\n" )); + + if( free_filename ) + g_free(filename); + + return 0; +} + +int +check_user_conf_dir(void) +{ + int retval; + char *dirname = g_build_filename(g_get_home_dir(), "." PACKAGE, NULL); + + if( g_file_test(dirname, G_FILE_TEST_IS_DIR) ) + { + g_free(dirname); + return 0; + } + retval = mkdir(dirname, 0755); + g_free(dirname); + return retval; +} + +char * +get_user_key_binding_filename(void) +{ + return g_build_filename(g_get_home_dir(), "." PACKAGE, "keys", NULL); +} + + +int +read_configuration(options_t *options) +{ + char *filename = NULL; + + /* check for command line configuration file */ + if( options->config_file ) + filename = g_strdup(options->config_file); + + /* check for user configuration ~/.ncmpc/config */ + if( filename == NULL ) + { + filename = g_build_filename(g_get_home_dir(), + "." PACKAGE, "config", NULL); + if( !g_file_test(filename, G_FILE_TEST_IS_REGULAR) ) + { + g_free(filename); + filename = NULL; + } + } + + /* check for global configuration SYSCONFDIR/ncmpc/config */ + if( filename == NULL ) + { + filename = g_build_filename(SYSCONFDIR, PACKAGE, "config", NULL); + if( !g_file_test(filename, G_FILE_TEST_IS_REGULAR) ) + { + g_free(filename); + filename = NULL; + } + } + + /* load configuration */ + if( filename ) + { + read_rc_file(filename, options); + g_free(filename); + filename = NULL; + } + + /* check for command line key binding file */ + if( options->key_file ) + filename = g_strdup(options->key_file); + + /* check for user key bindings ~/.ncmpc/keys */ + if( filename == NULL ) + { + filename = get_user_key_binding_filename(); + if( !g_file_test(filename, G_FILE_TEST_IS_REGULAR) ) + { + g_free(filename); + filename = NULL; + } + } + + /* check for global key bindings SYSCONFDIR/ncmpc/keys */ + if( filename == NULL ) + { + filename = g_build_filename(SYSCONFDIR, PACKAGE, "keys", NULL); + if( !g_file_test(filename, G_FILE_TEST_IS_REGULAR) ) + { + g_free(filename); + filename = NULL; + } + } + + /* load key bindings */ + if( filename ) + { + read_rc_file(filename, options); + g_free(filename); + filename = NULL; + } + + return 0; +} + + + diff --git a/src/conf.h b/src/conf.h new file mode 100644 index 000000000..20b4f5eca --- /dev/null +++ b/src/conf.h @@ -0,0 +1,7 @@ + +int check_user_conf_dir(void); + +char *get_user_key_binding_filename(void); + +int read_configuration(options_t *options); + diff --git a/src/libmpdclient.c b/src/libmpdclient.c new file mode 100644 index 000000000..2e8b579da --- /dev/null +++ b/src/libmpdclient.c @@ -0,0 +1,1211 @@ +/* libmpdclient + * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu) + * This project's homepage is: http://www.musicpd.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "libmpdclient.h" + +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <stdio.h> +#include <sys/param.h> +#include <string.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <stdlib.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#ifndef HAVE_SOCKLEN_T +typedef SOCKLEN_T socklen_t; +#endif +#endif + + +#ifndef MPD_NO_IPV6 +#ifdef AF_INET6 +#define MPD_HAVE_IPV6 +#endif +#endif + +#ifdef MPD_HAVE_IPV6 +int mpd_ipv6Supported() { + int s; + s = socket(AF_INET6,SOCK_STREAM,0); + if(s == -1) return 0; + close(s); + return 1; +} +#endif + + +char * mpd_sanitizeArg(const char * arg) { + size_t i; + int count=0; + char * ret; + + for(i=0;i<strlen(arg);i++) { + if(arg[i]=='"' || arg[i]=='\\') count++; + } + + ret = malloc(strlen(arg)+count+1); + + count = 0; + for(i=0;i<strlen(arg)+1;i++) { + if(arg[i]=='"' || arg[i]=='\\') { + ret[i+count] = '\\'; + count++; + } + ret[i+count] = arg[i]; + } + + return ret; +} + +mpd_ReturnElement * mpd_newReturnElement(const char * name, const char * value) +{ + mpd_ReturnElement * ret = malloc(sizeof(mpd_ReturnElement)); + + ret->name = strdup(name); + ret->value = strdup(value); + + return ret; +} + +void mpd_freeReturnElement(mpd_ReturnElement * re) { + free(re->name); + free(re->value); + free(re); +} + +void mpd_setConnectionTimeout(mpd_Connection * connection, float timeout) { + connection->timeout.tv_sec = (int)timeout; + connection->timeout.tv_usec = (int)(timeout*1e6 - + connection->timeout.tv_sec*1000000+0.5); +} + +mpd_Connection * mpd_newConnection(const char * host, int port, float timeout) { + int err; + struct hostent * he; + struct sockaddr * dest; + socklen_t destlen; + struct sockaddr_in sin; + char * rt; + char * output; + mpd_Connection * connection = malloc(sizeof(mpd_Connection)); + struct timeval tv; + fd_set fds; +#ifdef MPD_HAVE_IPV6 + struct sockaddr_in6 sin6; +#endif + strcpy(connection->buffer,""); + connection->buflen = 0; + connection->bufstart = 0; + strcpy(connection->errorStr,""); + connection->error = 0; + connection->doneProcessing = 0; + connection->commandList = 0; + connection->returnElement = NULL; + + if(!(he=gethostbyname(host))) { + snprintf(connection->errorStr,MPD_BUFFER_MAX_LENGTH, + "host \"%s\" not found",host); + connection->error = MPD_ERROR_UNKHOST; + return connection; + } + + memset(&sin,0,sizeof(struct sockaddr_in)); + /*dest.sin_family = he->h_addrtype;*/ + sin.sin_family = AF_INET; + sin.sin_port = htons(port); +#ifdef MPD_HAVE_IPV6 + memset(&sin6,0,sizeof(struct sockaddr_in6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(port); +#endif + switch(he->h_addrtype) { + case AF_INET: + memcpy((char *)&sin.sin_addr.s_addr,(char *)he->h_addr, + he->h_length); + dest = (struct sockaddr *)&sin; + destlen = sizeof(struct sockaddr_in); + break; +#ifdef MPD_HAVE_IPV6 + case AF_INET6: + if(!mpd_ipv6Supported()) { + strcpy(connection->errorStr,"no IPv6 suuport but a " + "IPv6 address found\n"); + connection->error = MPD_ERROR_SYSTEM; + return connection; + } + memcpy((char *)&sin6.sin6_addr.s6_addr,(char *)he->h_addr, + he->h_length); + dest = (struct sockaddr *)&sin6; + destlen = sizeof(struct sockaddr_in6); + break; +#endif + default: + strcpy(connection->errorStr,"address type is not IPv4 or " + "IPv6\n"); + connection->error = MPD_ERROR_SYSTEM; + return connection; + break; + } + + if((connection->sock = socket(dest->sa_family,SOCK_STREAM,0))<0) { + strcpy(connection->errorStr,"problems creating socket"); + connection->error = MPD_ERROR_SYSTEM; + return connection; + } + + /* connect stuff */ + { +#ifdef SO_RCVTIMEO + struct timeval rcvoldto; + struct timeval sndoldto; + socklen_t oldlen = sizeof(struct timeval); + + mpd_setConnectionTimeout(connection,timeout); + + tv.tv_sec = connection->timeout.tv_sec; + tv.tv_usec = connection->timeout.tv_usec; + + if(getsockopt(connection->sock,SOL_SOCKET,SO_RCVTIMEO,&rcvoldto, + &oldlen)<0 || + getsockopt(connection->sock,SOL_SOCKET, + SO_SNDTIMEO,&sndoldto,&oldlen)<0) + { + strcpy(connection->errorStr,"problems getting socket " + "timeout\n"); + connection->error = MPD_ERROR_SYSTEM; + return connection; + } + if(setsockopt(connection->sock,SOL_SOCKET,SO_RCVTIMEO,&tv, + sizeof(struct timeval))<0 || + setsockopt(connection->sock,SOL_SOCKET, + SO_SNDTIMEO,&tv, + sizeof(struct timeval))<0) + { + strcpy(connection->errorStr,"problems setting socket " + "timeout\n"); + connection->error = MPD_ERROR_SYSTEM; + return connection; + } +#endif + if(connect(connection->sock,dest,destlen)<0) { + snprintf(connection->errorStr,MPD_BUFFER_MAX_LENGTH, + "problems connecting to \"%s\" on port" + " %i",host,port); + connection->error = MPD_ERROR_CONNPORT; + return connection; + } +#ifdef SO_RCVTIMEO + if(setsockopt(connection->sock,SOL_SOCKET,SO_SNDTIMEO,&rcvoldto, + sizeof(struct timeval))<0 || + setsockopt(connection->sock,SOL_SOCKET, + SO_SNDTIMEO,&sndoldto, + sizeof(struct timeval))<0) + { + strcpy(connection->errorStr,"problems setting socket " + "timeout\n"); + connection->error = MPD_ERROR_SYSTEM; + return connection; + } +#endif + } + + while(!(rt = strstr(connection->buffer,"\n"))) { + tv.tv_sec = connection->timeout.tv_sec; + tv.tv_usec = connection->timeout.tv_usec; + FD_ZERO(&fds); + FD_SET(connection->sock,&fds); + if((err = select(connection->sock+1,&fds,NULL,NULL,&tv)) == 1) { + int readed; + readed = recv(connection->sock, + &(connection->buffer[connection->buflen]), + MPD_BUFFER_MAX_LENGTH-connection->buflen,0); + if(readed<=0) { + snprintf(connection->errorStr,MPD_BUFFER_MAX_LENGTH, + "problems getting a response from" + " \"%s\" on port %i",host, + port); + connection->error = MPD_ERROR_NORESPONSE; + return connection; + } + connection->buflen+=readed; + connection->buffer[connection->buflen] = '\0'; + tv.tv_sec = connection->timeout.tv_sec; + tv.tv_usec = connection->timeout.tv_usec; + } + else if(err<0 && errno==EINTR) continue; + else { + snprintf(connection->errorStr,MPD_BUFFER_MAX_LENGTH, + "timeout in attempting to get a response from" + " \"%s\" on port %i",host,port); + connection->error = MPD_ERROR_NORESPONSE; + return connection; + } + } + + *rt = '\0'; + output = strdup(connection->buffer); + strcpy(connection->buffer,rt+1); + connection->buflen = strlen(connection->buffer); + + if(strncmp(output,MPD_WELCOME_MESSAGE,strlen(MPD_WELCOME_MESSAGE))) { + free(output); + snprintf(connection->errorStr,MPD_BUFFER_MAX_LENGTH, + "mpd not running on port %i on host \"%s\"", + port,host); + connection->error = MPD_ERROR_NOTMPD; + return connection; + } + + { + char * test; + char * version[3]; + char * tmp = &output[strlen(MPD_WELCOME_MESSAGE)]; + char * search = "."; + int i; + + for(i=0;i<3;i++) { + char * tok; + if(i==3) search = " "; + version[i] = strtok_r(tmp,search,&tok); + if(!version[i]) { + free(output); + snprintf(connection->errorStr, + MPD_BUFFER_MAX_LENGTH, + "error parsing version number at " + "\"%s\"", + &output[strlen(MPD_WELCOME_MESSAGE)]); + connection->error = MPD_ERROR_NOTMPD; + return connection; + } + connection->version[i] = strtol(version[i],&test,10); + if(version[i]==test || *test!='\0') { + free(output); + snprintf(connection->errorStr, + MPD_BUFFER_MAX_LENGTH, + "error parsing version number at " + "\"%s\"", + &output[strlen(MPD_WELCOME_MESSAGE)]); + connection->error = MPD_ERROR_NOTMPD; + return connection; + } + tmp = NULL; + } + } + + free(output); + + connection->doneProcessing = 1; + + return connection; +} + +void mpd_clearError(mpd_Connection * connection) { + connection->error = 0; + connection->errorStr[0] = '\0'; +} + +void mpd_closeConnection(mpd_Connection * connection) { + close(connection->sock); + if(connection->returnElement) free(connection->returnElement); + free(connection); +} + +void mpd_executeCommand(mpd_Connection * connection, char * command) { + int ret; + struct timeval tv; + fd_set fds; + char * commandPtr = command; + int commandLen = strlen(command); + + if(!connection->doneProcessing && !connection->commandList) { + strcpy(connection->errorStr,"not done processing current command"); + connection->error = 1; + return; + } + + mpd_clearError(connection); + + FD_ZERO(&fds); + FD_SET(connection->sock,&fds); + tv.tv_sec = connection->timeout.tv_sec; + tv.tv_usec = connection->timeout.tv_usec; + + while((ret = select(connection->sock+1,NULL,&fds,NULL,&tv)==1) || + (ret==-1 && errno==EINTR)) { + ret = send(connection->sock,commandPtr,commandLen, + MSG_DONTWAIT); + if(ret<=0) + { + if(ret==EAGAIN || ret==EINTR) continue; + snprintf(connection->errorStr,MPD_BUFFER_MAX_LENGTH, + "problems giving command \"%s\"",command); + connection->error = MPD_ERROR_SENDING; + return; + } + else { + commandPtr+=ret; + commandLen-=ret; + } + + if(commandLen<=0) break; + } + + if(commandLen>0) { + perror(""); + snprintf(connection->errorStr,MPD_BUFFER_MAX_LENGTH, + "timeout sending command \"%s\"",command); + connection->error = MPD_ERROR_TIMEOUT; + return; + } + + if(!connection->commandList) connection->doneProcessing = 0; +} + +void mpd_getNextReturnElement(mpd_Connection * connection) { + char * output = NULL; + char * rt = NULL; + char * name; + char * value; + fd_set fds; + struct timeval tv; + char * tok; + int readed; + char * bufferCheck; + int err; + + if(connection->returnElement) mpd_freeReturnElement(connection->returnElement); + connection->returnElement = NULL; + + if(connection->doneProcessing) { + strcpy(connection->errorStr,"already done processing current command"); + connection->error = 1; + return; + } + + bufferCheck = connection->buffer+connection->bufstart; + while(connection->bufstart>=connection->buflen || + !(rt = strstr(bufferCheck,"\n"))) { + if(connection->buflen>=MPD_BUFFER_MAX_LENGTH) { + memmove(connection->buffer, + connection->buffer+ + connection->bufstart, + connection->buflen- + connection->bufstart+1); + bufferCheck-=connection->bufstart; + connection->buflen-=connection->bufstart; + connection->bufstart = 0; + } + if(connection->buflen>=MPD_BUFFER_MAX_LENGTH) { + strcpy(connection->errorStr,"buffer overrun"); + connection->error = MPD_ERROR_BUFFEROVERRUN; + connection->doneProcessing = 1; + return; + } + bufferCheck+=connection->buflen-connection->bufstart; + tv.tv_sec = connection->timeout.tv_sec; + tv.tv_usec = connection->timeout.tv_usec; + FD_ZERO(&fds); + FD_SET(connection->sock,&fds); + if((err = select(connection->sock+1,&fds,NULL,NULL,&tv) == 1)) { + readed = recv(connection->sock, + connection->buffer+connection->buflen, + MPD_BUFFER_MAX_LENGTH-connection->buflen, + MSG_DONTWAIT); + if(readed<0 && (errno==EAGAIN || errno==EINTR)) { + continue; + } + if(readed<=0) { + strcpy(connection->errorStr,"connection" + " closed"); + connection->error = MPD_ERROR_CONNCLOSED; + connection->doneProcessing = 1; + return; + } + connection->buflen+=readed; + connection->buffer[connection->buflen] = '\0'; + } + else if(err<0 && errno==EINTR) continue; + else { + strcpy(connection->errorStr,"connection timeout"); + connection->error = MPD_ERROR_TIMEOUT; + connection->doneProcessing = 1; + return; + } + } + + *rt = '\0'; + output = connection->buffer+connection->bufstart; + connection->bufstart = rt - connection->buffer + 1; + + if(strcmp(output,"OK")==0) { + connection->doneProcessing = 1; + return; + } + if(strncmp(output,"ACK",strlen("ACK"))==0) { + strcpy(connection->errorStr,output); + connection->error = MPD_ERROR_ACK; + connection->doneProcessing = 1; + return; + } + + name = strtok_r(output,":",&tok); + if(name && (value = strtok_r(NULL,"",&tok)) && value[0]==' ') { + connection->returnElement = mpd_newReturnElement(name,&(value[1])); + } + else { + if(!name || !value) { + snprintf(connection->errorStr,MPD_BUFFER_MAX_LENGTH, + "error parsing: %s",output); + } + else { + snprintf(connection->errorStr,MPD_BUFFER_MAX_LENGTH, + "error parsing: %s:%s",name,value); + } + connection->errorStr[MPD_BUFFER_MAX_LENGTH] = '\0'; + connection->error = 1; + } +} + +void mpd_finishCommand(mpd_Connection * connection) { + while(!connection->doneProcessing) mpd_getNextReturnElement(connection); +} + + +mpd_Status * mpd_getStatus(mpd_Connection * connection) { + mpd_Status * status; + + mpd_executeCommand(connection,"status\n"); + + if(connection->error) return NULL; + + status = malloc(sizeof(mpd_Status)); + status->volume = -1; + status->repeat = 0; + status->random = 0; + status->playlist = -1; + status->playlistLength = -1; + status->state = -1; + status->song = 0; + status->elapsedTime = 0; + status->totalTime = 0; + status->bitRate = 0; + status->sampleRate = 0; + status->bits = 0; + status->channels = 0; + status->crossfade = -1; + status->error = NULL; + status->updatingDb = 0; + + mpd_getNextReturnElement(connection); + if(connection->error) { + free(status); + return NULL; + } + while(connection->returnElement) { + mpd_ReturnElement * re = connection->returnElement; + if(strcmp(re->name,"volume")==0) { + status->volume = atoi(re->value); + } + else if(strcmp(re->name,"repeat")==0) { + status->repeat = atoi(re->value); + } + else if(strcmp(re->name,"random")==0) { + status->random = atoi(re->value); + } + else if(strcmp(re->name,"playlist")==0) { + status->playlist = strtol(re->value,NULL,10); + } + else if(strcmp(re->name,"playlistlength")==0) { + status->playlistLength = atoi(re->value); + } + else if(strcmp(re->name,"bitrate")==0) { + status->bitRate = atoi(re->value); + } + else if(strcmp(re->name,"state")==0) { + if(strcmp(re->value,"play")==0) { + status->state = MPD_STATUS_STATE_PLAY; + } + else if(strcmp(re->value,"stop")==0) { + status->state = MPD_STATUS_STATE_STOP; + } + else if(strcmp(re->value,"pause")==0) { + status->state = MPD_STATUS_STATE_PAUSE; + } + else { + status->state = MPD_STATUS_STATE_UNKNOWN; + } + } + else if(strcmp(re->name,"song")==0) { + status->song = atoi(re->value); + } + else if(strcmp(re->name,"time")==0) { + char * tok; + char * copy; + char * temp; + copy = strdup(re->value); + temp = strtok_r(copy,":",&tok); + if(temp) { + status->elapsedTime = atoi(temp); + temp = strtok_r(NULL,"",&tok); + if(temp) status->totalTime = atoi(temp); + } + free(copy); + } + else if(strcmp(re->name,"error")==0) { + status->error = strdup(re->value); + } + else if(strcmp(re->name,"xfade")==0) { + status->crossfade = atoi(re->value); + } + else if(strcmp(re->name,"updating_db")==0) { + status->updatingDb = atoi(re->value); + } + else if(strcmp(re->name,"audio")==0) { + char * tok; + char * copy; + char * temp; + copy = strdup(re->value); + temp = strtok_r(copy,":",&tok); + if(temp) { + status->sampleRate = atoi(temp); + temp = strtok_r(NULL,":",&tok); + if(temp) { + status->bits = atoi(temp); + temp = strtok_r(NULL,"",&tok); + if(temp) status->channels = atoi(temp); + } + } + free(copy); + } + + mpd_getNextReturnElement(connection); + if(connection->error) { + free(status); + return NULL; + } + } + + if(connection->error) { + free(status); + return NULL; + } + else if(status->state<0) { + strcpy(connection->errorStr,"state not found"); + connection->error = 1; + free(status); + return NULL; + } + + return status; +} + +void mpd_freeStatus(mpd_Status * status) { + if(status->error) free(status->error); + free(status); +} + +mpd_Stats * mpd_getStats(mpd_Connection * connection) { + mpd_Stats * stats; + + mpd_executeCommand(connection,"stats\n"); + + if(connection->error) return NULL; + + stats = malloc(sizeof(mpd_Stats)); + stats->numberOfArtists = 0; + stats->numberOfAlbums = 0; + stats->numberOfSongs = 0; + stats->uptime = 0; + stats->dbUpdateTime = 0; + stats->playTime = 0; + stats->dbPlayTime = 0; + + mpd_getNextReturnElement(connection); + if(connection->error) { + free(stats); + return NULL; + } + while(connection->returnElement) { + mpd_ReturnElement * re = connection->returnElement; + if(strcmp(re->name,"artists")==0) { + stats->numberOfArtists = atoi(re->value); + } + else if(strcmp(re->name,"albums")==0) { + stats->numberOfAlbums = atoi(re->value); + } + else if(strcmp(re->name,"songs")==0) { + stats->numberOfSongs = atoi(re->value); + } + else if(strcmp(re->name,"uptime")==0) { + stats->uptime = strtol(re->value,NULL,10); + } + else if(strcmp(re->name,"db_update")==0) { + stats->dbUpdateTime = strtol(re->value,NULL,10); + } + else if(strcmp(re->name,"playtime")==0) { + stats->playTime = strtol(re->value,NULL,10); + } + else if(strcmp(re->name,"db_playtime")==0) { + stats->dbPlayTime = strtol(re->value,NULL,10); + } + + mpd_getNextReturnElement(connection); + if(connection->error) { + free(stats); + return NULL; + } + } + + if(connection->error) { + free(stats); + return NULL; + } + + return stats; +} + +void mpd_freeStats(mpd_Stats * stats) { + free(stats); +} + +void mpd_initSong(mpd_Song * song) { + song->file = NULL; + song->artist = NULL; + song->album = NULL; + song->track = NULL; + song->title = NULL; + song->time = MPD_SONG_NO_TIME; +} + +void mpd_finishSong(mpd_Song * song) { + if(song->file) free(song->file); + if(song->artist) free(song->artist); + if(song->album) free(song->album); + if(song->title) free(song->title); + if(song->track) free(song->track); +} + +mpd_Song * mpd_newSong() { + mpd_Song * ret = malloc(sizeof(mpd_Song)); + + mpd_initSong(ret); + + return ret; +} + +void mpd_freeSong(mpd_Song * song) { + mpd_finishSong(song); + free(song); +} + +mpd_Song * mpd_songDup(mpd_Song * song) { + mpd_Song * ret = mpd_newSong(); + + if(song->file) ret->file = strdup(song->file); + if(song->artist) ret->artist = strdup(song->artist); + if(song->album) ret->album = strdup(song->album); + if(song->title) ret->title = strdup(song->title); + if(song->track) ret->track = strdup(song->track); + ret->time = song->time; + + return ret; +} + +void mpd_initDirectory(mpd_Directory * directory) { + directory->path = NULL; +} + +void mpd_finishDirectory(mpd_Directory * directory) { + if(directory->path) free(directory->path); +} + +mpd_Directory * mpd_newDirectory () { + mpd_Directory * directory = malloc(sizeof(mpd_Directory));; + + mpd_initDirectory(directory); + + return directory; +} + +void mpd_freeDirectory(mpd_Directory * directory) { + mpd_finishDirectory(directory); + + free(directory); +} + +mpd_Directory * mpd_directoryDup(mpd_Directory * directory) { + mpd_Directory * ret = mpd_newDirectory(); + + if(directory->path) ret->path = strdup(directory->path); + + return ret; +} + +void mpd_initPlaylistFile(mpd_PlaylistFile * playlist) { + playlist->path = NULL; +} + +void mpd_finishPlaylistFile(mpd_PlaylistFile * playlist) { + if(playlist->path) free(playlist->path); +} + +mpd_PlaylistFile * mpd_newPlaylistFile() { + mpd_PlaylistFile * playlist = malloc(sizeof(mpd_PlaylistFile)); + + mpd_initPlaylistFile(playlist); + + return playlist; +} + +void mpd_freePlaylistFile(mpd_PlaylistFile * playlist) { + mpd_finishPlaylistFile(playlist); + free(playlist); +} + +mpd_PlaylistFile * mpd_playlistFileDup(mpd_PlaylistFile * playlist) { + mpd_PlaylistFile * ret = mpd_newPlaylistFile(); + + if(playlist->path) ret->path = strdup(playlist->path); + + return ret; +} + +void mpd_initInfoEntity(mpd_InfoEntity * entity) { + entity->info.directory = NULL; +} + +void mpd_finishInfoEntity(mpd_InfoEntity * entity) { + if(entity->info.directory) { + if(entity->type == MPD_INFO_ENTITY_TYPE_DIRECTORY) { + mpd_freeDirectory(entity->info.directory); + } + else if(entity->type == MPD_INFO_ENTITY_TYPE_SONG) { + mpd_freeSong(entity->info.song); + } + else if(entity->type == MPD_INFO_ENTITY_TYPE_PLAYLISTFILE) { + mpd_freePlaylistFile(entity->info.playlistFile); + } + } +} + +mpd_InfoEntity * mpd_newInfoEntity() { + mpd_InfoEntity * entity = malloc(sizeof(mpd_InfoEntity)); + + mpd_initInfoEntity(entity); + + return entity; +} + +void mpd_freeInfoEntity(mpd_InfoEntity * entity) { + mpd_finishInfoEntity(entity); + free(entity); +} + +void mpd_sendInfoCommand(mpd_Connection * connection, char * command) { + mpd_executeCommand(connection,command); +} + +mpd_InfoEntity * mpd_getNextInfoEntity(mpd_Connection * connection) { + mpd_InfoEntity * entity = NULL; + + if(connection->doneProcessing) return NULL; + + if(!connection->returnElement) mpd_getNextReturnElement(connection); + + if(connection->returnElement) { + if(strcmp(connection->returnElement->name,"file")==0) { + entity = mpd_newInfoEntity(); + entity->type = MPD_INFO_ENTITY_TYPE_SONG; + entity->info.song = mpd_newSong(); + entity->info.song->file = + strdup(connection->returnElement->value); + } + else if(strcmp(connection->returnElement->name, + "directory")==0) { + entity = mpd_newInfoEntity(); + entity->type = MPD_INFO_ENTITY_TYPE_DIRECTORY; + entity->info.directory = mpd_newDirectory(); + entity->info.directory->path = + strdup(connection->returnElement->value); + } + else if(strcmp(connection->returnElement->name,"playlist")==0) { + entity = mpd_newInfoEntity(); + entity->type = MPD_INFO_ENTITY_TYPE_PLAYLISTFILE; + entity->info.playlistFile = mpd_newPlaylistFile(); + entity->info.playlistFile->path = + strdup(connection->returnElement->value); + } + else { + connection->error = 1; + strcpy(connection->errorStr,"problem parsing song info"); + return NULL; + } + } + else return NULL; + + mpd_getNextReturnElement(connection); + while(connection->returnElement) { + mpd_ReturnElement * re = connection->returnElement; + + if(strcmp(re->name,"file")==0) return entity; + else if(strcmp(re->name,"directory")==0) return entity; + else if(strcmp(re->name,"playlist")==0) return entity; + + if(entity->type == MPD_INFO_ENTITY_TYPE_SONG && + strlen(re->value)) { + if(!entity->info.song->artist && + strcmp(re->name,"Artist")==0) { + entity->info.song->artist = strdup(re->value); + } + else if(!entity->info.song->album && + strcmp(re->name,"Album")==0) { + entity->info.song->album = strdup(re->value); + } + else if(!entity->info.song->title && + strcmp(re->name,"Title")==0) { + entity->info.song->title = strdup(re->value); + } + else if(!entity->info.song->track && + strcmp(re->name,"Track")==0) { + entity->info.song->track = strdup(re->value); + } + else if(entity->info.song->time==MPD_SONG_NO_TIME && + strcmp(re->name,"Time")==0) { + entity->info.song->time = atoi(re->value); + } + } + else if(entity->type == MPD_INFO_ENTITY_TYPE_DIRECTORY) { + } + else if(entity->type == MPD_INFO_ENTITY_TYPE_PLAYLISTFILE) { + } + + mpd_getNextReturnElement(connection); + } + + return entity; +} + +char * mpd_getNextReturnElementNamed(mpd_Connection * connection, + const char * name) +{ + if(connection->doneProcessing) return NULL; + + mpd_getNextReturnElement(connection); + while(connection->returnElement) { + mpd_ReturnElement * re = connection->returnElement; + + if(strcmp(re->name,name)==0) return strdup(re->value); + mpd_getNextReturnElement(connection); + } + + return NULL; +} + +char * mpd_getNextArtist(mpd_Connection * connection) { + return mpd_getNextReturnElementNamed(connection,"Artist"); +} + +char * mpd_getNextAlbum(mpd_Connection * connection) { + return mpd_getNextReturnElementNamed(connection,"Album"); +} + +void mpd_sendPlaylistInfoCommand(mpd_Connection * connection, int songNum) { + char * string = malloc(strlen("playlistinfo")+25); + sprintf(string,"playlistinfo \"%i\"\n",songNum); + mpd_sendInfoCommand(connection,string); + free(string); +} + +void mpd_sendListallCommand(mpd_Connection * connection, const char * dir) { + char * sDir = mpd_sanitizeArg(dir); + char * string = malloc(strlen("listall")+strlen(sDir)+5); + sprintf(string,"listall \"%s\"\n",sDir); + mpd_sendInfoCommand(connection,string); + free(string); + free(sDir); +} + +void mpd_sendListallInfoCommand(mpd_Connection * connection, const char * dir) { + char * sDir = mpd_sanitizeArg(dir); + char * string = malloc(strlen("listallinfo")+strlen(sDir)+5); + sprintf(string,"listallinfo \"%s\"\n",sDir); + mpd_sendInfoCommand(connection,string); + free(string); + free(sDir); +} + +void mpd_sendLsInfoCommand(mpd_Connection * connection, const char * dir) { + char * sDir = mpd_sanitizeArg(dir); + char * string = malloc(strlen("lsinfo")+strlen(sDir)+5); + sprintf(string,"lsinfo \"%s\"\n",sDir); + mpd_sendInfoCommand(connection,string); + free(string); + free(sDir); +} + +void mpd_sendSearchCommand(mpd_Connection * connection, int table, + const char * str) +{ + char st[10]; + char * string; + char * sanitStr = mpd_sanitizeArg(str); + if(table == MPD_TABLE_ARTIST) strcpy(st,"artist"); + else if(table == MPD_TABLE_ALBUM) strcpy(st,"album"); + else if(table == MPD_TABLE_TITLE) strcpy(st,"title"); + else if(table == MPD_TABLE_FILENAME) strcpy(st,"filename"); + else { + connection->error = 1; + strcpy(connection->errorStr,"unknown table for search"); + return; + } + string = malloc(strlen("search")+strlen(sanitStr)+strlen(st)+6); + sprintf(string,"search %s \"%s\"\n",st,sanitStr); + mpd_sendInfoCommand(connection,string); + free(string); + free(sanitStr); +} + +void mpd_sendFindCommand(mpd_Connection * connection, int table, + const char * str) +{ + char st[10]; + char * string; + char * sanitStr = mpd_sanitizeArg(str); + if(table == MPD_TABLE_ARTIST) strcpy(st,"artist"); + else if(table == MPD_TABLE_ALBUM) strcpy(st,"album"); + else if(table == MPD_TABLE_TITLE) strcpy(st,"title"); + else { + connection->error = 1; + strcpy(connection->errorStr,"unknown table for find"); + return; + } + string = malloc(strlen("find")+strlen(sanitStr)+strlen(st)+6); + sprintf(string,"find %s \"%s\"\n",st,sanitStr); + mpd_sendInfoCommand(connection,string); + free(string); + free(sanitStr); +} + +void mpd_sendListCommand(mpd_Connection * connection, int table, + const char * arg1) +{ + char st[10]; + char * string; + if(table == MPD_TABLE_ARTIST) strcpy(st,"artist"); + else if(table == MPD_TABLE_ALBUM) strcpy(st,"album"); + else { + connection->error = 1; + strcpy(connection->errorStr,"unknown table for list"); + return; + } + if(arg1) { + char * sanitArg1 = mpd_sanitizeArg(arg1); + string = malloc(strlen("list")+strlen(sanitArg1)+strlen(st)+6); + sprintf(string,"list %s \"%s\"\n",st,sanitArg1); + free(sanitArg1); + } + else { + string = malloc(strlen("list")+strlen(st)+3); + sprintf(string,"list %s\n",st); + } + mpd_sendInfoCommand(connection,string); + free(string); +} + +void mpd_sendAddCommand(mpd_Connection * connection, const char * file) { + char * sFile = mpd_sanitizeArg(file); + char * string = malloc(strlen("add")+strlen(sFile)+5); + sprintf(string,"add \"%s\"\n",sFile); + mpd_executeCommand(connection,string); + free(string); + free(sFile); +} + +void mpd_sendDeleteCommand(mpd_Connection * connection, int songNum) { + char * string = malloc(strlen("delete")+25); + sprintf(string,"delete \"%i\"\n",songNum); + mpd_sendInfoCommand(connection,string); + free(string); +} + +void mpd_sendSaveCommand(mpd_Connection * connection, const char * name) { + char * sName = mpd_sanitizeArg(name); + char * string = malloc(strlen("save")+strlen(sName)+5); + sprintf(string,"save \"%s\"\n",sName); + mpd_executeCommand(connection,string); + free(string); + free(sName); +} + +void mpd_sendLoadCommand(mpd_Connection * connection, const char * name) { + char * sName = mpd_sanitizeArg(name); + char * string = malloc(strlen("load")+strlen(sName)+5); + sprintf(string,"load \"%s\"\n",sName); + mpd_executeCommand(connection,string); + free(string); + free(sName); +} + +void mpd_sendRmCommand(mpd_Connection * connection, const char * name) { + char * sName = mpd_sanitizeArg(name); + char * string = malloc(strlen("rm")+strlen(sName)+5); + sprintf(string,"rm \"%s\"\n",sName); + mpd_executeCommand(connection,string); + free(string); + free(sName); +} + +void mpd_sendShuffleCommand(mpd_Connection * connection) { + mpd_executeCommand(connection,"shuffle\n"); +} + +void mpd_sendClearCommand(mpd_Connection * connection) { + mpd_executeCommand(connection,"clear\n"); +} + +void mpd_sendPlayCommand(mpd_Connection * connection, int songNum) { + char * string = malloc(strlen("play")+25); + sprintf(string,"play \"%i\"\n",songNum); + mpd_sendInfoCommand(connection,string); + free(string); +} + +void mpd_sendStopCommand(mpd_Connection * connection) { + mpd_executeCommand(connection,"stop\n"); +} + +void mpd_sendPauseCommand(mpd_Connection * connection) { + mpd_executeCommand(connection,"pause\n"); +} + +void mpd_sendNextCommand(mpd_Connection * connection) { + mpd_executeCommand(connection,"next\n"); +} + +void mpd_sendMoveCommand(mpd_Connection * connection, int from, int to) { + char * string = malloc(strlen("move")+25); + sprintf(string,"move \"%i\" \"%i\"\n",from,to); + mpd_sendInfoCommand(connection,string); + free(string); +} + +void mpd_sendSwapCommand(mpd_Connection * connection, int song1, int song2) { + char * string = malloc(strlen("swap")+25); + sprintf(string,"swap \"%i\" \"%i\"\n",song1,song2); + mpd_sendInfoCommand(connection,string); + free(string); +} + +void mpd_sendSeekCommand(mpd_Connection * connection, int song, int time) { + char * string = malloc(strlen("seek")+25); + sprintf(string,"seek \"%i\" \"%i\"\n",song,time); + mpd_sendInfoCommand(connection,string); + free(string); +} + +void mpd_sendUpdateCommand(mpd_Connection * connection) { + mpd_executeCommand(connection,"update\n"); +} + +int mpd_getUpdateId(mpd_Connection * connection) { + char * jobid; + int ret = 0; + + jobid = mpd_getNextReturnElementNamed(connection,"updating_db"); + if(jobid) { + ret = atoi(jobid); + free(jobid); + } + + return ret; +} + +void mpd_sendPrevCommand(mpd_Connection * connection) { + mpd_executeCommand(connection,"previous\n"); +} + +void mpd_sendRepeatCommand(mpd_Connection * connection, int repeatMode) { + char * string = malloc(strlen("repeat")+25); + sprintf(string,"repeat \"%i\"\n",repeatMode); + mpd_executeCommand(connection,string); + free(string); +} + +void mpd_sendRandomCommand(mpd_Connection * connection, int randomMode) { + char * string = malloc(strlen("random")+25); + sprintf(string,"random \"%i\"\n",randomMode); + mpd_executeCommand(connection,string); + free(string); +} + +void mpd_sendSetvolCommand(mpd_Connection * connection, int volumeChange) { + char * string = malloc(strlen("setvol")+25); + sprintf(string,"setvol \"%i\"\n",volumeChange); + mpd_executeCommand(connection,string); + free(string); +} + +void mpd_sendVolumeCommand(mpd_Connection * connection, int volumeChange) { + char * string = malloc(strlen("volume")+25); + sprintf(string,"volume \"%i\"\n",volumeChange); + mpd_executeCommand(connection,string); + free(string); +} + +void mpd_sendCrossfadeCommand(mpd_Connection * connection, int seconds) { + char * string = malloc(strlen("crossfade")+25); + sprintf(string,"crossfade \"%i\"\n",seconds); + mpd_executeCommand(connection,string); + free(string); +} + +void mpd_sendPasswordCommand(mpd_Connection * connection, const char * pass) { + char * sPass = mpd_sanitizeArg(pass); + char * string = malloc(strlen("password")+strlen(sPass)+5); + sprintf(string,"password \"%s\"\n",sPass); + mpd_executeCommand(connection,string); + free(string); + free(sPass); +} + +void mpd_sendCommandListBegin(mpd_Connection * connection) { + if(connection->commandList) { + strcpy(connection->errorStr,"already in command list mode"); + connection->error = 1; + return; + } + connection->commandList = 1; + mpd_executeCommand(connection,"command_list_begin\n"); +} + +void mpd_sendCommandListEnd(mpd_Connection * connection) { + if(!connection->commandList) { + strcpy(connection->errorStr,"not in command list mode"); + connection->error = 1; + return; + } + connection->commandList = 0; + mpd_executeCommand(connection,"command_list_end\n"); +} diff --git a/src/libmpdclient.h b/src/libmpdclient.h new file mode 100644 index 000000000..e5e58791c --- /dev/null +++ b/src/libmpdclient.h @@ -0,0 +1,399 @@ +/* libmpdclient + * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu) + * This project's homepage is: http://www.musicpd.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef LIBMPDCLIENT_H +#define LIBMPDCLIENT_H + +#include <sys/time.h> + +#define MPD_BUFFER_MAX_LENGTH 50000 +#define MPD_WELCOME_MESSAGE "OK MPD " + +#define MPD_ERROR_TIMEOUT 10 /* timeout trying to talk to mpd */ +#define MPD_ERROR_SYSTEM 11 /* system error */ +#define MPD_ERROR_UNKHOST 12 /* unknown host */ +#define MPD_ERROR_CONNPORT 13 /* problems connecting to port on host */ +#define MPD_ERROR_NOTMPD 14 /* mpd not running on port at host */ +#define MPD_ERROR_NORESPONSE 15 /* no response on attempting to connect */ +#define MPD_ERROR_SENDING 16 /* error sending command */ +#define MPD_ERROR_CONNCLOSED 17 /* connection closed by mpd */ +#define MPD_ERROR_ACK 18 /* ACK returned! */ +#define MPD_ERROR_BUFFEROVERRUN 19 /* Buffer was overrun! */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* internal stuff don't touch this struct */ +typedef struct _mpd_ReturnElement { + char * name; + char * value; +} mpd_ReturnElement; + +/* mpd_Connection + * holds info about connection to mpd + * use error, and errorStr to detect errors + */ +typedef struct _mpd_Connection { + /* use this to check the version of mpd */ + int version[3]; + /* IMPORTANT, you want to get the error messages from here */ + char errorStr[MPD_BUFFER_MAX_LENGTH+1]; + /* this will be set to 1 if there is an error, 0 if not */ + int error; + /* DON'T TOUCH any of the rest of this stuff */ + int sock; + char buffer[MPD_BUFFER_MAX_LENGTH+1]; + int buflen; + int bufstart; + int doneProcessing; + int commandList; + mpd_ReturnElement * returnElement; + struct timeval timeout; +} mpd_Connection; + +/* mpd_newConnection + * use this to open a new connection + * you should use mpd_closeConnection, when your done with the connection, + * even if an error has occurred + * _timeout_ is the connection timeout period in seconds + */ +mpd_Connection * mpd_newConnection(const char * host, int port, float timeout); + +void mpd_setConnectionTimeout(mpd_Connection * connection, float timeout); + +/* mpd_closeConnection + * use this to close a connection and free'ing subsequent memory + */ +void mpd_closeConnection(mpd_Connection * connection); + +/* mpd_clearError + * clears error + */ +void mpd_clearError(mpd_Connection * connection); + +/* STATUS STUFF */ + +/* use these with status.state to determine what state the player is in */ +#define MPD_STATUS_STATE_UNKNOWN 0 +#define MPD_STATUS_STATE_STOP 1 +#define MPD_STATUS_STATE_PLAY 2 +#define MPD_STATUS_STATE_PAUSE 3 + +/* us this with status.volume to determine if mpd has volume support */ +#define MPD_STATUS_NO_VOLUME -1 + +/* mpd_Status + * holds info return from status command + */ +typedef struct mpd_Status { + /* 0-100, or MPD_STATUS_NO_VOLUME when there is no volume support */ + int volume; + /* 1 if repeat is on, 0 otherwise */ + int repeat; + /* 1 if random is on, 0 otherwise */ + int random; + /* playlist length */ + int playlistLength; + /* playlist, use this to determine when the playlist has changed */ + long long playlist; + /* use with MPD_STATUS_STATE_* to determine state of player */ + int state; + /* crossfade setting in seconds */ + int crossfade; + /* if in PLAY or PAUSE state, this is the number of the currently + * playing song in the playlist, beginning with 0 + */ + int song; + /* time in seconds that have elapsed in the currently playing/paused + * song + */ + int elapsedTime; + /* length in seconds of the currently playing/paused song */ + int totalTime; + /* current bit rate in kbs */ + int bitRate; + /* audio sample rate */ + unsigned int sampleRate; + /* audio bits */ + int bits; + /* audio channels */ + int channels; + /* 1 if mpd is updating, 0 otherwise */ + int updatingDb; + /* error */ + char * error; +} mpd_Status; + +/* mpd_getStatus + * returns status info, be sure to free it with mpd_freeStatus() + */ +mpd_Status * mpd_getStatus(mpd_Connection * connection); + +/* mpd_freeStatus + * free's status info malloc'd and returned by mpd_getStatus + */ +void mpd_freeStatus(mpd_Status * status); + +typedef struct _mpd_Stats { + int numberOfArtists; + int numberOfAlbums; + int numberOfSongs; + unsigned long uptime; + unsigned long dbUpdateTime; + unsigned long playTime; + unsigned long dbPlayTime; +} mpd_Stats; + +mpd_Stats * mpd_getStats(mpd_Connection * connection); + +void mpd_freeStats(mpd_Stats * stats); + +/* SONG STUFF */ + +#define MPD_SONG_NO_TIME -1 + +/* mpd_Song + * for storing song info returned by mpd + */ +typedef struct _mpd_Song { + /* filename of song */ + char * file; + /* artist, maybe NULL if there is no tag */ + char * artist; + /* title, maybe NULL if there is no tag */ + char * title; + /* album, maybe NULL if there is no tag */ + char * album; + /* track, maybe NULL if there is no tag */ + char * track; + /* length of song in seconds, check that it is not MPD_SONG_NO_TIME */ + int time; +} mpd_Song; + +/* mpd_newSong + * use to allocate memory for a new mpd_Song + * file, artist, etc all initialized to NULL + * if your going to assign values to file, artist, etc + * be sure to malloc or strdup the memory + * use mpd_freeSong to free the memory for the mpd_Song, it will also + * free memory for file, artist, etc, so don't do it yourself + */ +mpd_Song * mpd_newSong(); + +/* mpd_freeSong + * use to free memory allocated by mpd_newSong + * also it will free memory pointed to by file, artist, etc, so be careful + */ +void mpd_freeSong(mpd_Song * song); + +/* mpd_songDup + * works like strDup, but for a mpd_Song + */ +mpd_Song * mpd_songDup(mpd_Song * song); + +/* DIRECTORY STUFF */ + +/* mpd_Directory + * used to store info fro directory (right now that just the path) + */ +typedef struct _mpd_Directory { + char * path; +} mpd_Directory; + +/* mpd_newDirectory + * allocates memory for a new directory + * use mpd_freeDirectory to free this memory + */ +mpd_Directory * mpd_newDirectory (); + +/* mpd_freeDirectory + * used to free memory allocated with mpd_newDirectory, and it frees + * path of mpd_Directory, so be careful + */ +void mpd_freeDirectory(mpd_Directory * directory); + +/* mpd_directoryDup + * works like strdup, but for mpd_Directory + */ +mpd_Directory * mpd_directoryDup(mpd_Directory * directory); + +/* PLAYLISTFILE STUFF */ + +/* mpd_PlaylistFile + * stores info about playlist file returned by lsinfo + */ +typedef struct _mpd_PlaylistFile { + char * path; +} mpd_PlaylistFile; + +/* mpd_newPlaylistFile + * allocates memory for new mpd_PlaylistFile, path is set to NULL + * free this memory with mpd_freePlaylistFile + */ +mpd_PlaylistFile * mpd_newPlaylistFile(); + +/* mpd_freePlaylist + * free memory allocated for freePlaylistFile, will also free + * path, so be careful + */ +void mpd_freePlaylistFile(mpd_PlaylistFile * playlist); + +/* mpd_playlistFileDup + * works like strdup, but for mpd_PlaylistFile + */ +mpd_PlaylistFile * mpd_playlistFileDup(mpd_PlaylistFile * playlist); + +/* INFO ENTITY STUFF */ + +/* the type of entity returned from one of the commands that generates info + * use in conjunction with mpd_InfoEntity.type + */ +#define MPD_INFO_ENTITY_TYPE_DIRECTORY 0 +#define MPD_INFO_ENTITY_TYPE_SONG 1 +#define MPD_INFO_ENTITY_TYPE_PLAYLISTFILE 2 + +/* mpd_InfoEntity + * stores info on stuff returned info commands + */ +typedef struct mpd_InfoEntity { + /* the type of entity, use with MPD_INFO_ENTITY_TYPE_* to determine + * what this entity is (song, directory, etc...) + */ + int type; + /* the actual data you want, mpd_Song, mpd_Directory, etc */ + union { + mpd_Directory * directory; + mpd_Song * song; + mpd_PlaylistFile * playlistFile; + } info; +} mpd_InfoEntity; + +mpd_InfoEntity * mpd_newInfoEntity(); + +void mpd_freeInfoEntity(mpd_InfoEntity * entity); + +/* INFO COMMANDS AND STUFF */ + +/* use this function to loop over after calling Info/Listall functions */ +mpd_InfoEntity * mpd_getNextInfoEntity(mpd_Connection * connection); + +/* songNum of -1, means to display the whole list */ +void mpd_sendPlaylistInfoCommand(mpd_Connection * connection, int songNum); + +void mpd_sendListallCommand(mpd_Connection * connection, const char * dir); + +void mpd_sendListallInfoCommand(mpd_Connection * connection, const char * dir); + +void mpd_sendLsInfoCommand(mpd_Connection * connection, const char * dir); + +#define MPD_TABLE_ARTIST 0 +#define MPD_TABLE_ALBUM 1 +#define MPD_TABLE_TITLE 2 +#define MPD_TABLE_FILENAME 3 + +void mpd_sendSearchCommand(mpd_Connection * connection, int table, + const char * str); + +void mpd_sendFindCommand(mpd_Connection * connection, int table, + const char * str); + +/* LIST TAG COMMANDS */ + +/* use this function fetch next artist entry, be sure to free the returned + * string. NULL means there are no more. Best used with sendListArtists + */ +char * mpd_getNextArtist(mpd_Connection * connection); + +char * mpd_getNextAlbum(mpd_Connection * connection); + +/* list artist or albums by artist, arg1 should be set to the artist if + * listing albums by a artist, otherwise NULL for listing all artists or albums + */ +void mpd_sendListCommand(mpd_Connection * connection, int table, + const char * arg1); + +/* SIMPLE COMMANDS */ + +void mpd_sendAddCommand(mpd_Connection * connection, const char * file); + +void mpd_sendDeleteCommand(mpd_Connection * connection, int songNum); + +void mpd_sendSaveCommand(mpd_Connection * connection, const char * name); + +void mpd_sendLoadCommand(mpd_Connection * connection, const char * name); + +void mpd_sendRmCommand(mpd_Connection * connection, const char * name); + +void mpd_sendShuffleCommand(mpd_Connection * connection); + +void mpd_sendClearCommand(mpd_Connection * connection); + +/* use this to start playing at the beginning, useful when in random mode */ +#define MPD_PLAY_AT_BEGINNING -1 + +void mpd_sendPlayCommand(mpd_Connection * connection, int songNum); + +void mpd_sendStopCommand(mpd_Connection * connection); + +void mpd_sendPauseCommand(mpd_Connection * connection); + +void mpd_sendNextCommand(mpd_Connection * connection); + +void mpd_sendPrevCommand(mpd_Connection * connection); + +void mpd_sendMoveCommand(mpd_Connection * connection, int from, int to); + +void mpd_sendSwapCommand(mpd_Connection * connection, int song1, int song2); + +void mpd_sendSeekCommand(mpd_Connection * connection, int song, int time); + +void mpd_sendRepeatCommand(mpd_Connection * connection, int repeatMode); + +void mpd_sendRandomCommand(mpd_Connection * connection, int randomMode); + +void mpd_sendSetvolCommand(mpd_Connection * connection, int volumeChange); + +/* WARNING: don't use volume command, its depreacted */ +void mpd_sendVolumeCommand(mpd_Connection * connection, int volumeChange); + +void mpd_sendCrossfadeCommand(mpd_Connection * connection, int seconds); + +void mpd_sendUpdateCommand(mpd_Connection * connection); + +/* returns the update job id, call this after a update command*/ +int mpd_getUpdateId(mpd_Connection * connection); + +void mpd_sendPasswordCommand(mpd_Connection * connection, const char * pass); + +/* after executing a command, when your done with it to get its status + * (you want to check connection->error for an error) + */ +void mpd_finishCommand(mpd_Connection * connection); + +/* command list stuff, use this to do things like add files very quickly */ +void mpd_sendCommandListBegin(mpd_Connection * connection); + +void mpd_sendCommandListEnd(mpd_Connection * connection); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/list_window.c b/src/list_window.c new file mode 100644 index 000000000..6b4a41f0c --- /dev/null +++ b/src/list_window.c @@ -0,0 +1,296 @@ +/* + * (c) 2004 by Kalle Wallin (kaw@linux.se) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <glib.h> +#include <ncurses.h> + +#include "config.h" +#include "options.h" +#include "support.h" +#include "command.h" +#include "colors.h" +#include "list_window.h" + +list_window_t * +list_window_init(WINDOW *w, int width, int height) +{ + list_window_t *lw; + + lw = g_malloc(sizeof(list_window_t)); + memset(lw, 0, sizeof(list_window_t)); + lw->w = w; + lw->cols = width; + lw->rows = height; + lw->clear = 1; + return lw; +} + +list_window_t * +list_window_free(list_window_t *lw) +{ + if( lw ) + { + memset(lw, 0, sizeof(list_window_t)); + g_free(lw); + } + return NULL; +} + +void +list_window_reset(list_window_t *lw) +{ + lw->selected = 0; + lw->start = 0; + lw->clear = 1; +} + +void +list_window_check_selected(list_window_t *lw, int length) +{ + while( lw->start && lw->start+lw->rows>length) + lw->start--; + + if( lw->selected<0 ) + lw->selected=0; + + while( lw->selected<lw->start ) + lw->selected++; + + while( lw->selected>0 && length>0 && lw->selected>=length ) + lw->selected--; +} + +void +list_window_set_selected(list_window_t *lw, int n) +{ + lw->selected=n; +} + +void +list_window_next(list_window_t *lw, int length) +{ + if( lw->selected < length-1 ) + lw->selected++; +} + +void +list_window_previous(list_window_t *lw) +{ + if( lw->selected > 0 ) + lw->selected--; +} + +void +list_window_first(list_window_t *lw) +{ + lw->selected = 0; +} + +void +list_window_last(list_window_t *lw, int length) +{ + lw->selected = length-1; +} + +void +list_window_next_page(list_window_t *lw, int length) +{ + int step = lw->rows-1; + if( step<= 0 ) + return; + if( lw->selected+step < length-1 ) + lw->selected+=step; + else + return list_window_last(lw,length); +} + +void +list_window_previous_page(list_window_t *lw) +{ + int step = lw->rows-1; + if( step<= 0 ) + return; + if( lw->selected-step > 0 ) + lw->selected-=step; + else + list_window_first(lw); +} + + +void +list_window_paint(list_window_t *lw, + list_window_callback_fn_t callback, + void *callback_data) +{ + int i; + int fill = options.wide_cursor; + + while( lw->selected < lw->start ) + { + lw->start--; + lw->clear=1; + } + while( lw->selected >= lw->start+lw->rows ) + { + lw->start++; + lw->clear=1; + } + + for(i=0; i<lw->rows; i++) + { + int highlight = 0; + char *label; + + label = (callback) (lw->start+i, &highlight, callback_data); + wmove(lw->w, i, 0); + if( lw->clear && (!fill || !label) ) + wclrtoeol(lw->w); + if( label ) + { + int selected = lw->start+i == lw->selected; + + if( highlight ) + colors_use(lw->w, COLOR_LIST_BOLD); + else + colors_use(lw->w, COLOR_LIST); + + if( selected ) + wattron(lw->w, A_REVERSE); + + waddnstr(lw->w, label, lw->cols-1); + if( fill ) + mvwhline(lw->w, i, strlen(label), ' ', lw->cols-1); + + if( selected ) + wattroff(lw->w, A_REVERSE); + } + + } + lw->clear=0; +} + + +int +list_window_find(list_window_t *lw, + list_window_callback_fn_t callback, + void *callback_data, + char *str, + int wrap) +{ + int h; + int i = lw->selected+1; + char *label; + + while( wrap || i==lw->selected+1 ) + { + while( (label=(callback) (i,&h,callback_data)) ) + { + if( str && label && strcasestr(label, str) ) + { + lw->selected = i; + return 0; + } + if( wrap && i==lw->selected ) + return 1; + i++; + } + if( wrap ) + { + i=0; /* first item */ + beep(); + } + } + return 1; +} + + +int +list_window_rfind(list_window_t *lw, + list_window_callback_fn_t callback, + void *callback_data, + char *str, + int wrap, + int rows) +{ + int h; + int i = lw->selected-1; + char *label; + + while( wrap || i==lw->selected-1 ) + { + while( i>=0 && (label=(callback) (i,&h,callback_data)) ) + { + if( str && label && strcasestr(label, str) ) + { + lw->selected = i; + return 0; + } + if( wrap && i==lw->selected ) + return 1; + i--; + } + if( wrap ) + { + i=rows-1; /* last item */ + beep(); + } + } + return 1; +} + + +/* perform basic list window commands (movement) */ +int +list_window_cmd(list_window_t *lw, int rows, command_t cmd) +{ + switch(cmd) + { + case CMD_LIST_PREVIOUS: + list_window_previous(lw); + lw->repaint=1; + break; + case CMD_LIST_NEXT: + list_window_next(lw, rows); + lw->repaint=1; + break; + case CMD_LIST_FIRST: + list_window_first(lw); + lw->repaint = 1; + break; + case CMD_LIST_LAST: + list_window_last(lw, rows); + lw->repaint = 1; + break; + case CMD_LIST_NEXT_PAGE: + list_window_next_page(lw, rows); + lw->repaint = 1; + break; + case CMD_LIST_PREVIOUS_PAGE: + list_window_previous_page(lw); + lw->repaint = 1; + break; + default: + return 0; + } + return 1; +} + + diff --git a/src/list_window.h b/src/list_window.h new file mode 100644 index 000000000..9e9170382 --- /dev/null +++ b/src/list_window.h @@ -0,0 +1,67 @@ +#ifndef LIST_WINDOW_H +#define LIST_WINDOW_H + +#define LW_ROW(lw) (lw ? lw->selected-lw->start : 0) + +typedef char * (*list_window_callback_fn_t) (int index, + int *highlight, + void *data); + +typedef struct +{ + WINDOW *w; + int rows, cols; + + int start; + int selected; + int clear; + int repaint; + +} list_window_t; + + +/* create a new list window */ +list_window_t *list_window_init(WINDOW *w, int width, int height); + +/* destroy a list window (returns NULL) */ +list_window_t *list_window_free(list_window_t *lw); + +/* reset a list window (selected=0, start=0, clear=1) */ +void list_window_reset(list_window_t *lw); + +/* paint a list window */ +void list_window_paint(list_window_t *lw, + list_window_callback_fn_t callback, + void *callback_data); + +/* perform basic list window commands (movement) */ +int list_window_cmd(list_window_t *lw, int rows, command_t cmd); + + +/* select functions */ +void list_window_set_selected(list_window_t *lw, int n); +void list_window_previous(list_window_t *lw); +void list_window_next(list_window_t *lw, int length); +void list_window_first(list_window_t *lw); +void list_window_last(list_window_t *lw, int length); +void list_window_previous_page(list_window_t *lw); +void list_window_next_page(list_window_t *lw, int length); +void list_window_check_selected(list_window_t *lw, int length); + +/* find a string in a list window */ +int list_window_find(list_window_t *lw, + list_window_callback_fn_t callback, + void *callback_data, + char *str, + int wrap); + +/* find a string in a list window (reversed) */ +int +list_window_rfind(list_window_t *lw, + list_window_callback_fn_t callback, + void *callback_data, + char *str, + int wrap, + int rows); + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 000000000..bb3740b62 --- /dev/null +++ b/src/main.c @@ -0,0 +1,199 @@ +/* + * (c) 2004 by Kalle Wallin (kaw@linux.se) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <glib.h> + +#include "config.h" +#include "libmpdclient.h" +#include "support.h" +#include "mpc.h" +#include "options.h" +#include "command.h" +#include "screen.h" +#include "conf.h" + +/* time in seconds between mpd updates (double) */ +#define MPD_UPDATE_TIME 0.5 + +/* timout in seconds before trying to reconnect (int) */ +#define MPD_RECONNECT_TIMEOUT 3 + + +static mpd_client_t *mpc = NULL; +static GTimer *timer = NULL; + +void +exit_and_cleanup(void) +{ + screen_exit(); + printf("\n"); + charset_close(); + if( mpc ) + { + if( mpc_error(mpc) ) + fprintf(stderr,"Error: %s\n", mpc_error_str(mpc)); + mpc_close(mpc); + } + g_free(options.host); + g_free(options.password); + if( timer ) + g_timer_destroy(timer); +} + +void +catch_sigint( int sig ) +{ + printf( "\nExiting...\n"); + exit(EXIT_SUCCESS); +} + +int +main(int argc, const char *argv[]) +{ + options_t *options; + struct sigaction act; + gboolean connected; + + /* initialize options */ + options = options_init(); + + /* parse command line options - 1 pass get configuration files */ + options_parse(argc, argv); + + /* read configuration */ + read_configuration(options); + + /* check key bindings */ + if( check_key_bindings() ) + { + fprintf(stderr, "Confusing key bindings - exiting!\n"); + exit(EXIT_FAILURE); + } + + /* parse command line options - 2 pass */ + options_parse(argc, argv); + + /* initialize local charset */ + if( charset_init() ) + exit(EXIT_FAILURE); + + /* setup signal behavior - SIGINT */ + sigemptyset( &act.sa_mask ); + act.sa_flags = 0; + act.sa_handler = catch_sigint; + if( sigaction( SIGINT, &act, NULL )<0 ) + { + perror("signal"); + exit(EXIT_FAILURE); + } + /* setup signal behavior - SIGTERM */ + sigemptyset( &act.sa_mask ); + act.sa_flags = 0; + act.sa_handler = catch_sigint; + if( sigaction( SIGTERM, &act, NULL )<0 ) + { + perror("sigaction()"); + exit(EXIT_FAILURE); + } + + /* set xterm title */ + if( g_getenv("DISPLAY") ) + printf("%c]0;%s%c", '\033', PACKAGE " version " VERSION, '\007'); + + /* install exit function */ + atexit(exit_and_cleanup); + + /* connect to our music player daemon */ + mpc = mpc_connect(options->host, options->port, options->password); + if( mpc_error(mpc) ) + exit(EXIT_FAILURE); + + /* initialize curses */ + screen_init(); + + /* initialize timer */ + timer = g_timer_new(); + + connected = TRUE; + while( connected || options->reconnect ) + { + static gdouble t = G_MAXDOUBLE; + + if( connected && t>=MPD_UPDATE_TIME ) + { + mpc_update(mpc); + if( mpc_error(mpc) == MPD_ERROR_ACK ) + { + screen_status_printf("%s", mpc_error_str(mpc)); + mpd_clearError(mpc->connection); + mpd_finishCommand(mpc->connection); + } + else if( mpc_error(mpc) ) + { + screen_status_printf("Lost connection to %s", options->host); + connected = FALSE; + doupdate(); + mpd_clearError(mpc->connection); + mpd_closeConnection(mpc->connection); + mpc->connection = NULL; + } + else + mpd_finishCommand(mpc->connection); + g_timer_start(timer); + } + + if( connected ) + { + command_t cmd; + + screen_update(mpc); + if( (cmd=get_keyboard_command()) != CMD_NONE ) + { + screen_cmd(mpc, cmd); + if( cmd==CMD_VOLUME_UP || cmd==CMD_VOLUME_DOWN) + /* make shure we dont update the volume yet */ + g_timer_start(timer); + } + else + screen_idle(mpc); + } + else if( options->reconnect ) + { + sleep(MPD_RECONNECT_TIMEOUT); + screen_status_printf("Connecting to %s... [Press Ctrl-C to abort]", + options->host); + if( mpc_reconnect(mpc, + options->host, + options->port, + options->password) == 0 ) + { + screen_status_printf("Connected to %s!", options->host); + connected = TRUE; + } + doupdate(); + } + + t = g_timer_elapsed(timer, NULL); + } + + exit(EXIT_FAILURE); +} diff --git a/src/mpc.c b/src/mpc.c new file mode 100644 index 000000000..0a80af4dd --- /dev/null +++ b/src/mpc.c @@ -0,0 +1,359 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> +#include <string.h> +#include <glib.h> + +#include "config.h" +#include "support.h" +#include "libmpdclient.h" +#include "mpc.h" +#include "options.h" + +#define MAX_SONG_LENGTH 1024 + +#ifdef DEBUG +#define D(x) x +#else +#define D(x) +#endif + +int +mpc_close(mpd_client_t *c) +{ + if( c->connection ) + mpd_closeConnection(c->connection); + if( c->cwd ) + g_free( c->cwd ); + + return 0; +} + +mpd_client_t * +mpc_connect(char *host, int port, char *password) +{ + mpd_Connection *connection; + mpd_client_t *c; + + connection = mpd_newConnection(host, port, 10); + if( connection==NULL ) + { + fprintf(stderr, "mpd_newConnection to %s:%d failed!\n", host, port); + exit(EXIT_FAILURE); + } + + c = g_malloc(sizeof(mpd_client_t)); + memset(c, 0, sizeof(mpd_client_t)); + c->connection = connection; + c->cwd = g_strdup(""); + + if( password ) + { + mpd_sendPasswordCommand(connection, password); + mpd_finishCommand(connection); + } + + return c; +} + +int +mpc_reconnect(mpd_client_t *c, char *host, int port, char *password) +{ + mpd_Connection *connection; + + connection = mpd_newConnection(host, port, 10); + if( connection==NULL ) + return -1; + if( connection->error ) + { + mpd_closeConnection(connection); + return -1; + } + + c->connection = connection; + + if( password ) + { + mpd_sendPasswordCommand(connection, password); + mpd_finishCommand(connection); + } + + return 0; +} + + +int +mpc_error(mpd_client_t *c) +{ + if( c == NULL || c->connection == NULL ) + return 1; + if( c->connection->error ) + return c->connection->error; + + return 0; +} + +char * +mpc_error_str(mpd_client_t *c) +{ + if( c == NULL || c->connection == NULL ) + return "Not connected"; + + if( c->connection && c->connection->errorStr ) + return c->connection->errorStr; + + return NULL; +} + + + +int +mpc_free_playlist(mpd_client_t *c) +{ + GList *list; + + if( c==NULL || c->playlist==NULL ) + return -1; + + list=g_list_first(c->playlist); + + while( list!=NULL ) + { + mpd_Song *song = (mpd_Song *) list->data; + + mpd_freeSong(song); + list=list->next; + } + g_list_free(c->playlist); + c->playlist=NULL; + c->playlist_length=0; + + c->song_id = -1; + c->song = NULL; + + return 0; +} + +int +mpc_update_playlist(mpd_client_t *c) +{ + mpd_InfoEntity *entity; + + D(fprintf(stderr, "mpc_update_playlist() [%d]\n", c->status->playlist)); + + if( mpc_error(c) ) + return -1; + + if( c->playlist ) + mpc_free_playlist(c); + + c->playlist_length=0; + mpd_sendPlaylistInfoCommand(c->connection,-1); + if( mpc_error(c) ) + return -1; + while( (entity=mpd_getNextInfoEntity(c->connection)) ) + { + if(entity->type==MPD_INFO_ENTITY_TYPE_SONG) + { + mpd_Song *song = mpd_songDup(entity->info.song); + + c->playlist = g_list_append(c->playlist, (gpointer) song); + c->playlist_length++; + } + mpd_freeInfoEntity(entity); + } + c->playlist_id = c->status->playlist; + c->playlist_updated = 1; + c->song_id = -1; + c->song = NULL; + + mpc_filelist_set_selected(c); + + return 0; +} + +int +mpc_playlist_get_song_index(mpd_client_t *c, char *filename) +{ + GList *list = c->playlist; + int i=0; + + while( list ) + { + mpd_Song *song = (mpd_Song *) list->data; + if( strcmp(song->file, filename ) == 0 ) + return i; + list=list->next; + i++; + } + return -1; +} + +mpd_Song * +mpc_playlist_get_song(mpd_client_t *c, int n) +{ + return (mpd_Song *) g_list_nth_data(c->playlist, n); +} + + +char * +mpc_get_song_name(mpd_Song *song) +{ + static char buf[MAX_SONG_LENGTH]; + char *name; + + if( song->title ) + { + if( song->artist ) + { + snprintf(buf, MAX_SONG_LENGTH, "%s - %s", song->artist, song->title); + name = utf8_to_locale(buf); + strncpy(buf, name, MAX_SONG_LENGTH); + g_free(name); + return buf; + } + else + { + name = utf8_to_locale(song->title); + strncpy(buf, name, MAX_SONG_LENGTH); + g_free(name); + return buf; + } + } + name = utf8_to_locale(basename(song->file)); + strncpy(buf, name, MAX_SONG_LENGTH); + g_free(name); + return buf; +} + +int +mpc_update(mpd_client_t *c) +{ + if( mpc_error(c) ) + return -1; + + if( c->status ) + { + mpd_freeStatus(c->status); + } + + c->status = mpd_getStatus(c->connection); + if( mpc_error(c) ) + return -1; + + if( c->playlist_id!=c->status->playlist ) + mpc_update_playlist(c); + + if( !c->song || c->status->song != c->song_id ) + { + c->song = mpc_playlist_get_song(c, c->status->song); + c->song_id = c->status->song; + c->song_updated = 1; + } + + return 0; +} + + + + + + +int +mpc_free_filelist(mpd_client_t *c) +{ + GList *list; + + if( c==NULL || c->filelist==NULL ) + return -1; + + list=g_list_first(c->filelist); + + while( list!=NULL ) + { + filelist_entry_t *entry = list->data; + + if( entry->entity ) + mpd_freeInfoEntity(entry->entity); + g_free(entry); + list=list->next; + } + g_list_free(c->filelist); + c->filelist=NULL; + c->filelist_length=0; + + return 0; +} + + + +int +mpc_update_filelist(mpd_client_t *c) +{ + mpd_InfoEntity *entity; + + if( mpc_error(c) ) + return -1; + + if( c->filelist ) + mpc_free_filelist(c); + + c->filelist_length=0; + + // mpd_sendListallCommand(conn,""); + mpd_sendLsInfoCommand(c->connection, c->cwd); + + if( c->cwd && c->cwd[0] ) + { + /* add a dummy entry for ./.. */ + filelist_entry_t *entry = g_malloc(sizeof(filelist_entry_t)); + memset(entry, 0, sizeof(filelist_entry_t)); + entry->entity = NULL; + c->filelist = g_list_append(c->filelist, (gpointer) entry); + c->filelist_length++; + } + + while( (entity=mpd_getNextInfoEntity(c->connection)) ) + { + filelist_entry_t *entry = g_malloc(sizeof(filelist_entry_t)); + + memset(entry, 0, sizeof(filelist_entry_t)); + entry->entity = entity; + c->filelist = g_list_append(c->filelist, (gpointer) entry); + c->filelist_length++; + } + + c->filelist_updated = 1; + + mpd_finishCommand(c->connection); + + mpc_filelist_set_selected(c); + + return 0; +} + +int +mpc_filelist_set_selected(mpd_client_t *c) +{ + GList *list = c->filelist; + + while( list ) + { + filelist_entry_t *entry = list->data; + mpd_InfoEntity *entity = entry->entity ; + + if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG ) + { + mpd_Song *song = entity->info.song; + + if( mpc_playlist_get_song_index(c, song->file) >= 0 ) + entry->selected = 1; + else + entry->selected = 0; + } + + list=list->next; + } + return 0; +} diff --git a/src/mpc.h b/src/mpc.h new file mode 100644 index 000000000..a09aeeebe --- /dev/null +++ b/src/mpc.h @@ -0,0 +1,50 @@ + +typedef struct +{ + char selected; + mpd_InfoEntity *entity; +} filelist_entry_t; + +typedef struct +{ + mpd_Connection *connection; + mpd_Status *status; + + mpd_Song *song; + int song_id; + int song_updated; + + int seek_song_id; + int seek_target_time; + + GList *playlist; + int playlist_length; + long long playlist_id; + int playlist_updated; + + char *cwd; + GList *filelist; + int filelist_length; + int filelist_updated; + +} mpd_client_t; + + +int mpc_close(mpd_client_t *c); + +mpd_client_t *mpc_connect(char *host, int port, char *passwd); +int mpc_reconnect(mpd_client_t *c, char *host, int port, char *passwd); + +int mpc_update(mpd_client_t *c); +int mpc_update_playlist(mpd_client_t *c); + +int mpc_update_filelist(mpd_client_t *c); +int mpc_filelist_set_selected(mpd_client_t *c); +int mpc_set_cwd(mpd_client_t *c, char *dir); + +mpd_Song *mpc_playlist_get_song(mpd_client_t *c, int n); +char *mpc_get_song_name(mpd_Song *song); +int mpc_playlist_get_song_index(mpd_client_t *c, char *filename); + +int mpc_error(mpd_client_t *c); +char *mpc_error_str(mpd_client_t *c); diff --git a/src/options.c b/src/options.c new file mode 100644 index 000000000..416727ad3 --- /dev/null +++ b/src/options.c @@ -0,0 +1,185 @@ +/* + * (c) 2004 by Kalle Wallin (kaw@linux.se) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ncurses.h> +#include <glib.h> +#include <popt.h> + +#include "config.h" +#include "options.h" +#include "command.h" +#include "support.h" + +options_t options; + +static char *mpd_host = NULL; +static char *mpd_password = NULL; +static char *config_file = NULL; +static char *key_file = NULL; + +static struct poptOption optionsTable[] = { +#ifdef DEBUG + { "debug", 'D', 0, 0, 'D', "Enable debug output." }, +#endif + { "version", 'V', 0, 0, 'V', "Display version information." }, + { "colors", 'c', 0, 0, 'c', "Enable colors." }, + { "no-colors", 'C', 0, 0, 'C', "Disable colors." }, + { "exit", 'e', 0, 0, 'e', "Exit on connection errors." }, + { "port", 'p', POPT_ARG_INT, &options.port, 0, + "Connect to server on port [" DEFAULT_PORT_STR "].", "PORT" }, + { "host", 'h', POPT_ARG_STRING, &mpd_host, 0, + "Connect to server [" DEFAULT_HOST "].", "HOSTNAME" }, + { "password", 'P', POPT_ARG_STRING, &mpd_password, 0, + "Connect with password.", "PASSWORD" }, + { "config", 'f', POPT_ARG_STRING, &config_file, 0, + "Read config from FILE." , "FILE" }, + { "key-file", 'k', POPT_ARG_STRING, &key_file, 0, + "Read key bindings from FILE." , "FILE" }, + + POPT_AUTOHELP + { NULL, 0, 0, NULL, 0 } +}; + +static void +usage(poptContext optCon, int exitcode, char *error, char *addl) +{ + poptPrintUsage(optCon, stderr, 0); + if (error) + fprintf(stderr, "%s: %s0", error, addl); + exit(exitcode); +} + +options_t * +options_parse( int argc, const char **argv) +{ + int c; + poptContext optCon; /* context for parsing command-line options */ + + mpd_host = NULL; + mpd_password = NULL; + config_file = NULL; + key_file = NULL; + optCon = poptGetContext(NULL, argc, argv, optionsTable, 0); + while ((c = poptGetNextOpt(optCon)) >= 0) + { + switch (c) + { +#ifdef DEBUG + case 'D': + options.debug = 1; + break; +#endif + case 'c': + options.enable_colors = 1; + break; + case 'C': + options.enable_colors = 0; + break; + case 'V': + printf("Version " VERSION "\n"); + exit(EXIT_SUCCESS); + case 'e': + options.reconnect = 0; + break; + default: + fprintf(stderr, "%s: %s\n", + poptBadOption(optCon, POPT_BADOPTION_NOALIAS), + poptStrerror(c)); + poptFreeContext(optCon); + exit(EXIT_FAILURE); + break; + } + } + if (c < -1) + { + /* an error occurred during option processing */ + fprintf(stderr, "%s: %s\n", + poptBadOption(optCon, POPT_BADOPTION_NOALIAS), + poptStrerror(c)); + poptFreeContext(optCon); + exit(EXIT_FAILURE); + } + + if( mpd_host ) + { + g_free(options.host); + options.host = mpd_host; + } + if( mpd_password ) + { + g_free(options.password); + options.password = mpd_password; + } + if( config_file ) + { + g_free(options.config_file); + options.config_file = config_file; + } + if( key_file ) + { + g_free(options.key_file); + options.key_file = key_file; + } + + poptFreeContext(optCon); + return &options; +} + +options_t * +options_init( void ) +{ + const char *value; + char *tmp; + + memset(&options, 0, sizeof(options_t)); + + if( (value=g_getenv(MPD_HOST_ENV)) ) + options.host = g_strdup(value); + else + options.host = g_strdup(DEFAULT_HOST); + if( (tmp=g_strstr_len(options.host, strlen(options.host), "@")) ) + { + char *oldhost = options.host; + *tmp = '\0'; + options.password = locale_to_utf8(oldhost); + options.host = g_strdup(tmp+1); + g_free(oldhost); + } + + if( (value=g_getenv(MPD_PORT_ENV)) ) + options.port = atoi(value); + else + options.port = DEFAULT_PORT; + + options.reconnect = 1; + options.find_wrap = 1; + options.wide_cursor = 1; + + return &options; +} + + +options_t * +options_get(void) +{ + return &options; +} diff --git a/src/options.h b/src/options.h new file mode 100644 index 000000000..2a6681e2a --- /dev/null +++ b/src/options.h @@ -0,0 +1,28 @@ + +#define MPD_HOST_ENV "MPD_HOST" +#define MPD_PORT_ENV "MPD_PORT" + +typedef struct +{ + char *host; + char *username; + char *password; + char *config_file; + char *key_file; + int port; + int reconnect; + int debug; + int find_wrap; + int auto_center; + int wide_cursor; + int enable_colors; + +} options_t; + +extern options_t options; + +options_t *options_init(void); +options_t *options_parse(int argc, const char **argv); + + + diff --git a/src/screen.c b/src/screen.c new file mode 100644 index 000000000..d846f3df5 --- /dev/null +++ b/src/screen.c @@ -0,0 +1,869 @@ +/* + * (c) 2004 by Kalle Wallin (kaw@linux.se) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <stdarg.h> +#include <string.h> +#include <time.h> +//#include <signal.h> +#include <locale.h> +#include <glib.h> +#include <ncurses.h> + +#include "config.h" +#include "libmpdclient.h" +#include "mpc.h" +#include "command.h" +#include "options.h" +#include "colors.h" +#include "screen.h" +#include "screen_play.h" +#include "screen_file.h" +#include "screen_help.h" +#include "screen_search.h" +#include "screen_utils.h" + +#undef ENABLE_STATUS_LINE_CLOCK +#define ENABLE_SCROLLING + +#define DEFAULT_CROSSFADE_TIME 10 + +#define STATUS_MESSAGE_TIMEOUT 3 +#define STATUS_LINE_MAX_SIZE 512 + +#ifdef ENABLE_KEYDEF_SCREEN +extern screen_functions_t *get_screen_keydef(void); +#endif + +static screen_t *screen = NULL; +static screen_functions_t *mode_fn = NULL; + +static void +switch_screen_mode(screen_mode_t new_mode, mpd_client_t *c) +{ + if( new_mode == screen->mode ) + return; + + /* close the old mode */ + if( mode_fn && mode_fn->close ) + mode_fn->close(); + + /* get functions for the new mode */ + switch(new_mode) + { + case SCREEN_PLAY_WINDOW: + mode_fn = get_screen_playlist(); + break; + case SCREEN_FILE_WINDOW: + mode_fn = get_screen_file(); + break; + case SCREEN_HELP_WINDOW: + mode_fn = get_screen_help(); + break; +#ifdef ENABLE_KEYDEF_SCREEN + case SCREEN_KEYDEF_WINDOW: + mode_fn = get_screen_keydef(); + break; +#endif + default: + break; + } + + screen->mode = new_mode; + screen->painted = 0; + + /* open the new mode */ + if( mode_fn && mode_fn->open ) + mode_fn->open(screen, c); + +} + +static void +paint_top_window(char *header, mpd_client_t *c, int clear) +{ + char flags[4]; + static int prev_volume = -1; + static int prev_header_len = -1; + WINDOW *w = screen->top_window.w; + + if(prev_header_len!=strlen(header)) + { + prev_header_len = strlen(header); + clear = 1; + } + + if(clear) + { + wmove(w, 0, 0); + wclrtoeol(w); + } + + if(prev_volume!=c->status->volume || clear) + { + char buf[32]; + + if( header[0] ) + { + colors_use(w, COLOR_TITLE_BOLD); + mvwaddstr(w, 0, 0, header); + } + else + { + colors_use(w, COLOR_TITLE_BOLD); + waddstr(w, get_key_names(CMD_SCREEN_HELP, FALSE)); + colors_use(w, COLOR_TITLE); + waddstr(w, ":Help "); + colors_use(w, COLOR_TITLE_BOLD); + waddstr(w, get_key_names(CMD_SCREEN_PLAY, FALSE)); + colors_use(w, COLOR_TITLE); + waddstr(w, ":Playlist "); + colors_use(w, COLOR_TITLE_BOLD); + waddstr(w, get_key_names(CMD_SCREEN_FILE, FALSE)); + colors_use(w, COLOR_TITLE); + waddstr(w, ":Browse"); + } + if( c->status->volume==MPD_STATUS_NO_VOLUME ) + { + snprintf(buf, 32, "Volume n/a "); + } + else + { + snprintf(buf, 32, " Volume %d%%", c->status->volume); + } + colors_use(w, COLOR_TITLE); + mvwaddstr(w, 0, screen->top_window.cols-strlen(buf), buf); + + flags[0] = 0; + if( c->status->repeat ) + strcat(flags, "r"); + if( c->status->random ) + strcat(flags, "z"); + if( c->status->crossfade ) + strcat(flags, "x"); + if( c->status->updatingDb ) + strcat(flags, "U"); + colors_use(w, COLOR_LINE); + mvwhline(w, 1, 0, ACS_HLINE, screen->top_window.cols); + if( flags[0] ) + { + wmove(w,1,screen->top_window.cols-strlen(flags)-3); + waddch(w, '['); + colors_use(w, COLOR_LINE_BOLD); + waddstr(w, flags); + colors_use(w, COLOR_LINE); + waddch(w, ']'); + } + wnoutrefresh(w); + } +} + +static void +paint_progress_window(mpd_client_t *c) +{ + double p; + int width; + int elapsedTime = c->status->elapsedTime; + + if( c->status==NULL || IS_STOPPED(c->status->state) ) + { + mvwhline(screen->progress_window.w, 0, 0, ACS_HLINE, + screen->progress_window.cols); + wnoutrefresh(screen->progress_window.w); + return; + } + + if( c->seek_song_id == c->song_id ) + elapsedTime = c->seek_target_time; + + p = ((double) elapsedTime) / ((double) c->status->totalTime); + + width = (int) (p * (double) screen->progress_window.cols); + mvwhline(screen->progress_window.w, + 0, 0, + ACS_HLINE, + screen->progress_window.cols); + whline(screen->progress_window.w, '=', width-1); + mvwaddch(screen->progress_window.w, 0, width-1, 'O'); + wnoutrefresh(screen->progress_window.w); +} + +#ifdef ENABLE_SCROLLING +static char * +scroll_string(char *str, char *sep, int width) +{ + static int offset = 0; + static time_t t = 0; + char *tmp, *buf; + size_t len; + + if( offset==0 ) + { + offset++; + return g_strdup(str); + } + + /* create a buffer containing the string and the separator */ + tmp = g_malloc(strlen(str)+strlen(sep)+1); + strcpy(tmp, str); + strcat(tmp, sep); + len = strlen(tmp); + + if( offset >= len ) + offset = 0; + + /* create the new scrolled string */ + buf = g_malloc(width+1); + strncpy(buf, tmp+offset, width); + if( strlen(buf) < width ) + strncat(buf, tmp, width-strlen(buf)); + + if( time(NULL)-t >= 1 ) + { + t = time(NULL); + offset++; + } + g_free(tmp); + return buf; +} +#endif + +static void +paint_status_window(mpd_client_t *c) +{ + WINDOW *w = screen->status_window.w; + mpd_Status *status = c->status; + mpd_Song *song = c->song; + int elapsedTime = c->status->elapsedTime; + int x = 0; + + if( time(NULL) - screen->status_timestamp <= STATUS_MESSAGE_TIMEOUT ) + return; + + + wmove(w, 0, 0); + wclrtoeol(w); + colors_use(w, COLOR_STATUS_BOLD); + + switch(status->state) + { + case MPD_STATUS_STATE_STOP: + waddstr(w, "Stopped! "); + break; + case MPD_STATUS_STATE_PLAY: + waddstr(w, "Playing:"); + break; + case MPD_STATUS_STATE_PAUSE: + waddstr(w, "[Paused]"); + break; + default: + break; + } + x += 9; + + /* create time string */ + memset(screen->buf, 0, screen->buf_size); + if( IS_PLAYING(status->state) || IS_PAUSED(status->state) ) + { + if( status->totalTime > 0 ) + { + if( c->seek_song_id == c->song_id ) + elapsedTime = c->seek_target_time; + snprintf(screen->buf, screen->buf_size, + " [%i:%02i/%i:%02i]", + elapsedTime/60, elapsedTime%60, + status->totalTime/60, status->totalTime%60 ); + } + else + { + snprintf(screen->buf, screen->buf_size, " [%d kbps]", status->bitRate ); + } + } + else + { + time_t timep; + + time(&timep); + /* Note: setlocale(LC_TIME,"") should be used first */ + //strftime(screen->buf, screen->buf_size, "%X ", localtime(&timep)); + strftime(screen->buf, screen->buf_size, " %k:%M", localtime(&timep)); + } + + /* display song */ + if( (IS_PLAYING(status->state) || IS_PAUSED(status->state)) && song ) + { + char *songname = mpc_get_song_name(song); + int width = COLS-x-strlen(screen->buf); + + colors_use(w, COLOR_STATUS); +#ifdef ENABLE_SCROLLING + if( strlen(songname) > width ) + { + char *tmp = scroll_string(songname, " *** ", width); + strcpy(songname, tmp); + g_free(tmp); + } +#endif + mvwaddnstr(w, 0, x, songname, width); + } + + /* distplay time string */ + if( screen->buf[0] ) + { + x = screen->status_window.cols - strlen(screen->buf); + colors_use(w, COLOR_STATUS_TIME); + mvwaddstr(w, 0, x, screen->buf); + } + +#ifdef ENABLE_STATUS_LINE_CLOCK + else if( c->status->state == MPD_STATUS_STATE_STOP ) + { + time_t timep; + + /* Note: setlocale(LC_TIME,"") should be used first */ + time(&timep); + strftime(screen->buf, screen->buf_size, "%X ", localtime(&timep)); + x = screen->status_window.cols - strlen(screen->buf) ; + colors_use(w, COLOR_STATUS_TIME); + mvwaddstr(w, 0, x, screen->buf); + } +#endif + + wnoutrefresh(w); +} + + + +int +screen_exit(void) +{ + endwin(); + if( screen ) + { + GList *list = g_list_first(screen->screen_list); + + /* close and exit all screens (playlist,browse,help...) */ + while( list ) + { + screen_functions_t *mode_fn = list->data; + + if( mode_fn && mode_fn->close ) + mode_fn->close(); + if( mode_fn && mode_fn->exit ) + mode_fn->exit(); + list->data = NULL; + list=list->next; + } + g_list_free(screen->screen_list); + + g_free(screen->buf); + g_free(screen->findbuf); + g_free(screen); + screen = NULL; + } + return 0; +} + +void +screen_resize(void) +{ + GList *list; + +#ifdef DEBUG + fprintf(stderr, "Resize rows %d->%d, cols %d->%d\n", + screen->rows, LINES, + screen->cols, COLS); +#endif + + if( COLS<SCREEN_MIN_COLS || LINES<SCREEN_MIN_ROWS ) + { + screen_exit(); + fprintf(stderr, "Error: Screen to small!\n"); + exit(EXIT_FAILURE); + } + + resizeterm(LINES, COLS); + + screen->cols = COLS; + screen->rows = LINES; + + /* top window */ + screen->top_window.cols = screen->cols; + wresize(screen->top_window.w, 2, screen->cols); + + /* main window */ + screen->main_window.cols = screen->cols; + screen->main_window.rows = screen->rows-4; + wresize(screen->main_window.w, screen->main_window.rows, screen->cols); + wclear(screen->main_window.w); + + /* progress window */ + screen->progress_window.cols = screen->cols; + wresize(screen->progress_window.w, 1, screen->cols); + mvwin(screen->progress_window.w, screen->rows-2, 0); + + /* status window */ + screen->status_window.cols = screen->cols; + wresize(screen->status_window.w, 1, screen->cols); + mvwin(screen->status_window.w, screen->rows-1, 0); + + screen->buf_size = screen->cols; + g_free(screen->buf); + screen->buf = g_malloc(screen->cols); + + list = g_list_first(screen->screen_list); + while( list ) + { + screen_functions_t *mode_fn = list->data; + + if( mode_fn && mode_fn->resize ) + mode_fn->resize(screen->main_window.cols, screen->main_window.rows); + + list=list->next; + } + + /* ? - without this the cursor becomes visible with aterm & Eterm */ + curs_set(1); + curs_set(0); + + screen->painted = 0; +} + +void +screen_status_message(char *msg) +{ + WINDOW *w = screen->status_window.w; + + wmove(w, 0, 0); + wclrtoeol(w); + colors_use(w, COLOR_STATUS_ALERT); + waddstr(w, msg); + wnoutrefresh(w); + screen->status_timestamp = time(NULL); +} + +void +screen_status_printf(char *format, ...) +{ + char buffer[STATUS_LINE_MAX_SIZE]; + va_list ap; + + va_start(ap,format); + vsnprintf(buffer,sizeof(buffer),format,ap); + va_end(ap); + screen_status_message(buffer); +} + +int +screen_init(void) +{ + GList *list; + + /* initialize the curses library */ + initscr(); + /* initialize color support */ + colors_start(); + /* tell curses not to do NL->CR/NL on output */ + nonl(); + /* take input chars one at a time, no wait for \n */ + cbreak(); + /* don't echo input */ + noecho(); + /* set cursor invisible */ + curs_set(0); + /* return from getch() without blocking */ + // nodelay(stdscr, TRUE); + keypad(stdscr, TRUE); + timeout(SCREEN_TIMEOUT); + + if( COLS<SCREEN_MIN_COLS || LINES<SCREEN_MIN_ROWS ) + { + fprintf(stderr, "Error: Screen to small!\n"); + exit(EXIT_FAILURE); + } + + screen = g_malloc(sizeof(screen_t)); + memset(screen, 0, sizeof(screen_t)); + screen->mode = SCREEN_PLAY_WINDOW; + screen->cols = COLS; + screen->rows = LINES; + screen->buf = g_malloc(screen->cols); + screen->buf_size = screen->cols; + screen->findbuf = NULL; + screen->painted = 0; + screen->start_timestamp = time(NULL); + screen->input_timestamp = time(NULL); + screen->last_cmd = CMD_NONE; + + /* create top window */ + screen->top_window.rows = 2; + screen->top_window.cols = screen->cols; + screen->top_window.w = newwin(screen->top_window.rows, + screen->top_window.cols, + 0, 0); + leaveok(screen->top_window.w, TRUE); + keypad(screen->top_window.w, TRUE); + + /* create main window */ + screen->main_window.rows = screen->rows-4; + screen->main_window.cols = screen->cols; + screen->main_window.w = newwin(screen->main_window.rows, + screen->main_window.cols, + 2, + 0); + + // leaveok(screen->main_window.w, TRUE); temporary disabled + keypad(screen->main_window.w, TRUE); + + /* create progress window */ + screen->progress_window.rows = 1; + screen->progress_window.cols = screen->cols; + screen->progress_window.w = newwin(screen->progress_window.rows, + screen->progress_window.cols, + screen->rows-2, + 0); + leaveok(screen->progress_window.w, TRUE); + + /* create status window */ + screen->status_window.rows = 1; + screen->status_window.cols = screen->cols; + screen->status_window.w = newwin(screen->status_window.rows, + screen->status_window.cols, + screen->rows-1, + 0); + + leaveok(screen->status_window.w, FALSE); + keypad(screen->status_window.w, TRUE); + + if( options.enable_colors ) + { + /* set background attributes */ + wbkgd(stdscr, COLOR_PAIR(COLOR_LIST)); + wbkgd(screen->main_window.w, COLOR_PAIR(COLOR_LIST)); + wbkgd(screen->top_window.w, COLOR_PAIR(COLOR_TITLE)); + wbkgd(screen->progress_window.w, COLOR_PAIR(COLOR_PROGRESSBAR)); + wbkgd(screen->status_window.w, COLOR_PAIR(COLOR_STATUS)); + colors_use(screen->progress_window.w, COLOR_PROGRESSBAR); + } + + /* initialize screens */ + screen->screen_list = NULL; + screen->screen_list = g_list_append(screen->screen_list, + (gpointer) get_screen_playlist()); + screen->screen_list = g_list_append(screen->screen_list, + (gpointer) get_screen_file()); + screen->screen_list = g_list_append(screen->screen_list, + (gpointer) get_screen_help()); +#ifdef ENABLE_KEYDEF_SCREEN + screen->screen_list = g_list_append(screen->screen_list, + (gpointer) get_screen_keydef()); +#endif + + list = screen->screen_list; + while( list ) + { + screen_functions_t *fn = list->data; + + if( fn && fn->init ) + fn->init(screen->main_window.w, + screen->main_window.cols, + screen->main_window.rows); + + list = list->next; + } + + mode_fn = get_screen_playlist(); + + return 0; +} + +void +screen_paint(mpd_client_t *c) +{ + /* paint the title/header window */ + if( mode_fn && mode_fn->get_title ) + paint_top_window(mode_fn->get_title(), c, 1); + else + paint_top_window("", c, 1); + + /* paint the main window */ + if( mode_fn && mode_fn->paint ) + mode_fn->paint(screen, c); + + paint_progress_window(c); + paint_status_window(c); + screen->painted = 1; + wmove(screen->main_window.w, 0, 0); + wnoutrefresh(screen->main_window.w); + + /* tell curses to update */ + doupdate(); +} + +void +screen_update(mpd_client_t *c) +{ + static int repeat = -1; + static int random = -1; + static int crossfade = -1; + static int dbupdate = -1; + static int welcome = 1; + list_window_t *lw = NULL; + + if( !screen->painted ) + return screen_paint(c); + + /* print a message if mpd status has changed */ + if( repeat<0 ) + { + repeat = c->status->repeat; + random = c->status->random; + crossfade = c->status->crossfade; + dbupdate = c->status->updatingDb; + } + if( repeat != c->status->repeat ) + screen_status_printf("Repeat is %s", + c->status->repeat ? "On" : "Off"); + if( random != c->status->random ) + screen_status_printf("Random is %s", + c->status->random ? "On" : "Off"); + if( crossfade != c->status->crossfade ) + screen_status_printf("Crossfade %d seconds", c->status->crossfade); + if( dbupdate && dbupdate != c->status->updatingDb ) + screen_status_printf("Database updated!"); + + repeat = c->status->repeat; + random = c->status->random; + crossfade = c->status->crossfade; + dbupdate = c->status->updatingDb; + + /* update title/header window */ + if( welcome && screen->last_cmd==CMD_NONE && + time(NULL)-screen->start_timestamp <= SCREEN_WELCOME_TIME) + paint_top_window("", c, 0); + else if( mode_fn && mode_fn->get_title ) + { + paint_top_window(mode_fn->get_title(), c, 0); + welcome = 0; + } + else + paint_top_window("", c, 0); + + /* update the main window */ + if( mode_fn && mode_fn->paint ) + mode_fn->update(screen, c); + + if( mode_fn && mode_fn->get_lw ) + lw = mode_fn->get_lw(); + + /* update progress window */ + paint_progress_window(c); + + /* update status window */ + paint_status_window(c); + + /* move the cursor to the selected row in the main window */ + if( lw ) + wmove(screen->main_window.w, LW_ROW(lw), 0); + else + wmove(screen->main_window.w, 0, 0); + wnoutrefresh(screen->main_window.w); + + /* tell curses to update */ + doupdate(); +} + +void +screen_idle(mpd_client_t *c) +{ + if( c->seek_song_id == c->song_id && + (screen->last_cmd == CMD_SEEK_FORWARD || + screen->last_cmd == CMD_SEEK_BACKWARD) ) + { + mpd_sendSeekCommand(c->connection, + c->seek_song_id, + c->seek_target_time); + mpd_finishCommand(c->connection); + } + + screen->last_cmd = CMD_NONE; + c->seek_song_id = -1; +} + +void +screen_cmd(mpd_client_t *c, command_t cmd) +{ + int n; + screen_mode_t new_mode = screen->mode; + + screen->input_timestamp = time(NULL); + screen->last_cmd = cmd; + + if( mode_fn && mode_fn->cmd && mode_fn->cmd(screen, c, cmd) ) + return; + + switch(cmd) + { + case CMD_PLAY: + mpd_sendPlayCommand(c->connection, play_get_selected()); + mpd_finishCommand(c->connection); + break; + case CMD_PAUSE: + mpd_sendPauseCommand(c->connection); + mpd_finishCommand(c->connection); + break; + case CMD_STOP: + mpd_sendStopCommand(c->connection); + mpd_finishCommand(c->connection); + break; + case CMD_SEEK_FORWARD: + if( !IS_STOPPED(c->status->state) ) + { + if( c->seek_song_id != c->song_id ) + { + c->seek_song_id = c->song_id; + c->seek_target_time = c->status->elapsedTime; + } + c->seek_target_time++; + if( c->seek_target_time < c->status->totalTime ) + break; + c->seek_target_time=0; + } + /* fall through... */ + case CMD_TRACK_NEXT: + if( !IS_STOPPED(c->status->state) ) + { + mpd_sendNextCommand(c->connection); + mpd_finishCommand(c->connection); + } + break; + case CMD_SEEK_BACKWARD: + if( !IS_STOPPED(c->status->state) ) + { + if( c->seek_song_id != c->song_id ) + { + c->seek_song_id = c->song_id; + c->seek_target_time = c->status->elapsedTime; + } + c->seek_target_time--; + if( c->seek_target_time < 0 ) + c->seek_target_time=0; + } + break; + case CMD_TRACK_PREVIOUS: + if( !IS_STOPPED(c->status->state) ) + { + mpd_sendPrevCommand(c->connection); + mpd_finishCommand(c->connection); + } + break; + case CMD_SHUFFLE: + mpd_sendShuffleCommand(c->connection); + mpd_finishCommand(c->connection); + screen_status_message("Shuffled playlist!"); + break; + case CMD_CLEAR: + mpd_sendClearCommand(c->connection); + mpd_finishCommand(c->connection); + file_clear_highlights(c); + screen_status_message("Cleared playlist!"); + break; + case CMD_REPEAT: + n = !c->status->repeat; + mpd_sendRepeatCommand(c->connection, n); + mpd_finishCommand(c->connection); + break; + case CMD_RANDOM: + n = !c->status->random; + mpd_sendRandomCommand(c->connection, n); + mpd_finishCommand(c->connection); + break; + case CMD_CROSSFADE: + if( c->status->crossfade ) + n = 0; + else + n = DEFAULT_CROSSFADE_TIME; + mpd_sendCrossfadeCommand(c->connection, n); + mpd_finishCommand(c->connection); + break; + case CMD_VOLUME_UP: + if( c->status->volume!=MPD_STATUS_NO_VOLUME && c->status->volume<100 ) + { + c->status->volume=c->status->volume+1; + mpd_sendSetvolCommand(c->connection, c->status->volume ); + mpd_finishCommand(c->connection); + } + break; + case CMD_VOLUME_DOWN: + if( c->status->volume!=MPD_STATUS_NO_VOLUME && c->status->volume>0 ) + { + c->status->volume=c->status->volume-1; + mpd_sendSetvolCommand(c->connection, c->status->volume ); + mpd_finishCommand(c->connection); + } + break; + case CMD_TOGGLE_FIND_WRAP: + options.find_wrap = !options.find_wrap; + screen_status_printf("Find mode: %s", + options.find_wrap ? "Wrapped" : "Normal"); + break; + case CMD_TOGGLE_AUTOCENTER: + options.auto_center = !options.auto_center; + screen_status_printf("Auto center mode: %s", + options.auto_center ? "On" : "Off"); + break; + case CMD_SCREEN_PREVIOUS: + if( screen->mode > SCREEN_PLAY_WINDOW ) + new_mode = screen->mode - 1; + else + new_mode = SCREEN_HELP_WINDOW-1; + switch_screen_mode(new_mode, c); + break; + case CMD_SCREEN_NEXT: + new_mode = screen->mode + 1; + if( new_mode >= SCREEN_HELP_WINDOW ) + new_mode = SCREEN_PLAY_WINDOW; + switch_screen_mode(new_mode, c); + break; + case CMD_SCREEN_PLAY: + switch_screen_mode(SCREEN_PLAY_WINDOW, c); + break; + case CMD_SCREEN_FILE: + switch_screen_mode(SCREEN_FILE_WINDOW, c); + break; + case CMD_SCREEN_SEARCH: + switch_screen_mode(SCREEN_SEARCH_WINDOW, c); + break; + case CMD_SCREEN_HELP: + switch_screen_mode(SCREEN_HELP_WINDOW, c); + break; +#ifdef ENABLE_KEYDEF_SCREEN + case CMD_SCREEN_KEYDEF: + switch_screen_mode(SCREEN_KEYDEF_WINDOW, c); + break; +#endif + case CMD_QUIT: + exit(EXIT_SUCCESS); + default: + break; + } + +} + + + diff --git a/src/screen.h b/src/screen.h new file mode 100644 index 000000000..a7e6b7250 --- /dev/null +++ b/src/screen.h @@ -0,0 +1,113 @@ +#ifndef SCREEN_H +#define SCREEN_H +#include <ncurses.h> +#include "list_window.h" + +/* top window headers */ +#define TOP_HEADER_PREFIX "Music Player Client - " +#define TOP_HEADER_PLAY TOP_HEADER_PREFIX "Playlist" +#define TOP_HEADER_FILE TOP_HEADER_PREFIX "Browse" +#define TOP_HEADER_HELP TOP_HEADER_PREFIX "Help " +#define TOP_HEADER_SEARCH TOP_HEADER_PREFIX "Search " + +/* minumum window size */ +#define SCREEN_MIN_COLS 14 +#define SCREEN_MIN_ROWS 5 + +/* timeout for non blocking read [ms] */ +#define SCREEN_TIMEOUT 500 + +/* welcome message time [s] */ +#define SCREEN_WELCOME_TIME 10 + +#define IS_PLAYING(s) (s==MPD_STATUS_STATE_PLAY) +#define IS_PAUSED(s) (s==MPD_STATUS_STATE_PAUSE) +#define IS_STOPPED(s) (!(IS_PLAYING(s) | IS_PAUSED(s))) + + +typedef enum +{ + SCREEN_PLAY_WINDOW = 0, + SCREEN_FILE_WINDOW, + SCREEN_HELP_WINDOW, + SCREEN_KEYDEF_WINDOW, + SCREEN_SEARCH_WINDOW + +} screen_mode_t; + +typedef struct +{ + WINDOW *w; + int rows, cols; + +} window_t; + + + +typedef struct +{ + window_t top_window; + window_t main_window; + window_t progress_window; + window_t status_window; + + GList *screen_list; + + time_t start_timestamp; + time_t status_timestamp; + time_t input_timestamp; + command_t last_cmd; + + int cols, rows; + + screen_mode_t mode; + + char *buf; + size_t buf_size; + + char *findbuf; + + int painted; + +} screen_t; + + +typedef void (*screen_init_fn_t) (WINDOW *w, int cols, int rows); +typedef void (*screen_exit_fn_t) (void); +typedef void (*screen_open_fn_t) (screen_t *screen, mpd_client_t *c); +typedef void (*screen_close_fn_t) (void); +typedef void (*screen_resize_fn_t) (int cols, int rows); +typedef void (*screen_paint_fn_t) (screen_t *screen, mpd_client_t *c); +typedef void (*screen_update_fn_t) (screen_t *screen, mpd_client_t *c); +typedef int (*screen_cmd_fn_t) (screen_t *scr, mpd_client_t *c, command_t cmd); +typedef char * (*screen_title_fn_t) (void); +typedef list_window_t * (*screen_get_lw_fn_t) (void); + +typedef struct +{ + screen_init_fn_t init; + screen_exit_fn_t exit; + screen_open_fn_t open; + screen_close_fn_t close; + screen_resize_fn_t resize; + screen_paint_fn_t paint; + screen_update_fn_t update; + screen_cmd_fn_t cmd; + screen_title_fn_t get_title; + screen_get_lw_fn_t get_lw; + +} screen_functions_t; + + +int screen_init(void); +int screen_exit(void); +void screen_resize(void); +void screen_status_message(char *msg); +void screen_status_printf(char *format, ...); +char *screen_error(void); +void screen_paint(mpd_client_t *c); +void screen_update(mpd_client_t *c); +void screen_idle(mpd_client_t *c); +void screen_cmd(mpd_client_t *c, command_t cmd); + +#endif diff --git a/src/screen_file.c b/src/screen_file.c new file mode 100644 index 000000000..d2b1f7844 --- /dev/null +++ b/src/screen_file.c @@ -0,0 +1,503 @@ +/* + * (c) 2004 by Kalle Wallin (kaw@linux.se) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <glib.h> +#include <ncurses.h> + +#include "config.h" +#include "support.h" +#include "libmpdclient.h" +#include "mpc.h" +#include "command.h" +#include "screen.h" +#include "screen_utils.h" +#include "screen_play.h" +#include "screen_file.h" + +#define BUFSIZE 1024 +#define TITLESIZE 256 + +#define USE_OLD_LAYOUT + +static list_window_t *lw; +static mpd_client_t *mpc = NULL; + +static char * +list_callback(int index, int *highlight, void *data) +{ + static char buf[BUFSIZE]; + mpd_client_t *c = (mpd_client_t *) data; + filelist_entry_t *entry; + mpd_InfoEntity *entity; + + *highlight = 0; + if( (entry=(filelist_entry_t *) g_list_nth_data(c->filelist, index))==NULL ) + return NULL; + + entity = entry->entity; + *highlight = entry->selected; + + if( entity == NULL ) + { +#ifdef USE_OLD_LAYOUT + return "[..]"; +#else + return "d .."; +#endif + } + if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) + { + mpd_Directory *dir = entity->info.directory; + char *dirname = utf8_to_locale(basename(dir->path)); + +#ifdef USE_OLD_LAYOUT + snprintf(buf, BUFSIZE, "[%s]", dirname); +#else + snprintf(buf, BUFSIZE, "d %s", dirname); +#endif + g_free(dirname); + return buf; + } + else if( entity->type==MPD_INFO_ENTITY_TYPE_SONG ) + { + mpd_Song *song = entity->info.song; + +#ifdef USE_OLD_LAYOUT + return mpc_get_song_name(song); +#else + snprintf(buf, BUFSIZE, "m %s", mpc_get_song_name(song)); + return buf; +#endif + + } + else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) + { + mpd_PlaylistFile *plf = entity->info.playlistFile; + char *filename = utf8_to_locale(basename(plf->path)); + +#ifdef USE_OLD_LAYOUT + snprintf(buf, BUFSIZE, "*%s*", filename); +#else + snprintf(buf, BUFSIZE, "p %s", filename); +#endif + g_free(filename); + return buf; + } + return "Error: Unknow entry!"; +} + +static int +change_directory(screen_t *screen, mpd_client_t *c, filelist_entry_t *entry) +{ + mpd_InfoEntity *entity = entry->entity; + + if( entity==NULL ) + { + char *parent = g_path_get_dirname(c->cwd); + + if( strcmp(parent,".") == 0 ) + { + parent[0] = '\0'; + } + if( c->cwd ) + g_free(c->cwd); + c->cwd = parent; + } + else + if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY) + { + mpd_Directory *dir = entity->info.directory; + if( c->cwd ) + g_free(c->cwd); + c->cwd = g_strdup(dir->path); + } + else + return -1; + + mpc_update_filelist(c); + list_window_reset(lw); + return 0; +} + +static int +load_playlist(screen_t *screen, mpd_client_t *c, filelist_entry_t *entry) +{ + mpd_InfoEntity *entity = entry->entity; + mpd_PlaylistFile *plf = entity->info.playlistFile; + char *filename = utf8_to_locale(basename(plf->path)); + + mpd_sendLoadCommand(c->connection, plf->path); + mpd_finishCommand(c->connection); + + screen_status_printf("Loading playlist %s...", filename); + g_free(filename); + return 0; +} + +static int +handle_delete(screen_t *screen, mpd_client_t *c) +{ + filelist_entry_t *entry; + mpd_InfoEntity *entity; + mpd_PlaylistFile *plf; + char *str, buf[BUFSIZE]; + int key; + + entry = ( filelist_entry_t *) g_list_nth_data(c->filelist, lw->selected); + if( entry==NULL || entry->entity==NULL ) + return -1; + + entity = entry->entity; + + if( entity->type!=MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) + { + screen_status_printf("You can only delete playlists!"); + beep(); + return -1; + } + + plf = entity->info.playlistFile; + str = utf8_to_locale(basename(plf->path)); + snprintf(buf, BUFSIZE, "Delete playlist %s [y/n] ? ", str); + g_free(str); + key = tolower(screen_getch(screen->status_window.w, buf)); + if( key==KEY_RESIZE ) + screen_resize(); + if( key!='y' ) + { + screen_status_printf("Aborted!"); + return 0; + } + + mpd_sendRmCommand(c->connection, plf->path); + mpd_finishCommand(c->connection); + if( mpc_error(c)) + { + str = utf8_to_locale(mpc_error_str(c)); + screen_status_printf("Error: %s", str); + g_free(str); + beep(); + return -1; + } + screen_status_printf("Playlist deleted!"); + mpc_update_filelist(c); + list_window_check_selected(lw, c->filelist_length); + return 0; +} + + +static int +handle_play_cmd(screen_t *screen, mpd_client_t *c) +{ + filelist_entry_t *entry; + mpd_InfoEntity *entity; + + entry = ( filelist_entry_t *) g_list_nth_data(c->filelist, lw->selected); + if( entry==NULL ) + return -1; + + entity = entry->entity; + if( entity==NULL || entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) + return change_directory(screen, c, entry); + else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) + return load_playlist(screen, c, entry); + return -1; +} + +static int +add_directory(mpd_client_t *c, char *dir) +{ + mpd_InfoEntity *entity; + GList *subdir_list = NULL; + GList *list = NULL; + char *dirname; + + dirname = utf8_to_locale(dir); + screen_status_printf("Adding directory %s...\n", dirname); + doupdate(); + g_free(dirname); + dirname = NULL; + + mpd_sendLsInfoCommand(c->connection, dir); + mpd_sendCommandListBegin(c->connection); + while( (entity=mpd_getNextInfoEntity(c->connection)) ) + { + if( entity->type==MPD_INFO_ENTITY_TYPE_SONG ) + { + mpd_Song *song = entity->info.song; + mpd_sendAddCommand(c->connection, song->file); + mpd_freeInfoEntity(entity); + } + else if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) + { + subdir_list = g_list_append(subdir_list, (gpointer) entity); + } + else + mpd_freeInfoEntity(entity); + } + mpd_sendCommandListEnd(c->connection); + mpd_finishCommand(c->connection); + + list = g_list_first(subdir_list); + while( list!=NULL ) + { + mpd_Directory *dir; + + entity = list->data; + dir = entity->info.directory; + add_directory(c, dir->path); + mpd_freeInfoEntity(entity); + list->data=NULL; + list=list->next; + } + g_list_free(subdir_list); + return 0; +} + +static int +handle_select(screen_t *screen, mpd_client_t *c) +{ + filelist_entry_t *entry; + + entry = ( filelist_entry_t *) g_list_nth_data(c->filelist, lw->selected); + if( entry==NULL || entry->entity==NULL) + return -1; + + if( entry->entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) + { + mpd_Directory *dir = entry->entity->info.directory; + add_directory(c, dir->path); + return 0; + } + + if( entry->entity->type!=MPD_INFO_ENTITY_TYPE_SONG ) + return -1; + + entry->selected = !entry->selected; + + if( entry->selected ) + { + if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) + { + mpd_Song *song = entry->entity->info.song; + + playlist_add_song(c, song); + + screen_status_printf("Adding \'%s\' to playlist\n", + mpc_get_song_name(song)); + } + } + else + { + /* remove song from playlist */ + if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) + { + mpd_Song *song = entry->entity->info.song; + + if( song ) + { + int index = mpc_playlist_get_song_index(c, song->file); + + while( (index=mpc_playlist_get_song_index(c, song->file))>=0 ) + playlist_delete_song(c, index); + } + } + } + return 0; +} + +static void +file_init(WINDOW *w, int cols, int rows) +{ + lw = list_window_init(w, cols, rows); +} + +static void +file_resize(int cols, int rows) +{ + lw->cols = cols; + lw->rows = rows; +} + +static void +file_exit(void) +{ + list_window_free(lw); +} + +static void +file_open(screen_t *screen, mpd_client_t *c) +{ + if( c->filelist == NULL ) + { + mpc_update_filelist(c); + } + mpc = c; +} + +static void +file_close(void) +{ +} + +static char * +file_title(void) +{ + static char buf[TITLESIZE]; + char *tmp; + + tmp = utf8_to_locale(basename(mpc->cwd)); + snprintf(buf, TITLESIZE, + TOP_HEADER_FILE ": %s ", + tmp + ); + g_free(tmp); + + return buf; +} + +static void +file_paint(screen_t *screen, mpd_client_t *c) +{ + lw->clear = 1; + + list_window_paint(lw, list_callback, (void *) c); + wnoutrefresh(lw->w); +} + +static void +file_update(screen_t *screen, mpd_client_t *c) +{ + if( c->filelist_updated ) + { + file_paint(screen, c); + c->filelist_updated = 0; + return; + } + list_window_paint(lw, list_callback, (void *) c); + wnoutrefresh(lw->w); +} + + +static int +file_cmd(screen_t *screen, mpd_client_t *c, command_t cmd) +{ + switch(cmd) + { + case CMD_PLAY: + handle_play_cmd(screen, c); + return 1; + case CMD_SELECT: + if( handle_select(screen, c) == 0 ) + { + /* continue and select next item... */ + cmd = CMD_LIST_NEXT; + } + break; + case CMD_DELETE: + handle_delete(screen, c); + break; + case CMD_SCREEN_UPDATE: + mpc_update_filelist(c); + list_window_check_selected(lw, c->filelist_length); + screen_status_printf("Screen updated!"); + return 1; + case CMD_LIST_FIND: + case CMD_LIST_RFIND: + case CMD_LIST_FIND_NEXT: + case CMD_LIST_RFIND_NEXT: + return screen_find(screen, c, + lw, c->filelist_length, + cmd, list_callback); + default: + break; + } + return list_window_cmd(lw, c->filelist_length, cmd); +} + + +list_window_t * +get_filelist_window() +{ + return lw; +} + + +void +file_clear_highlights(mpd_client_t *c) +{ + GList *list = g_list_first(c->filelist); + + while( list ) + { + filelist_entry_t *entry = list->data; + + entry->selected = 0; + list = list->next; + } +} + +void +file_set_highlight(mpd_client_t *c, mpd_Song *song, int highlight) +{ + GList *list = g_list_first(c->filelist); + + if( !song ) + return; + + while( list ) + { + filelist_entry_t *entry = list->data; + mpd_InfoEntity *entity = entry->entity; + + if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG ) + { + mpd_Song *song2 = entity->info.song; + + if( strcmp(song->file, song2->file) == 0 ) + { + entry->selected = highlight; + } + } + list = list->next; + } +} + +screen_functions_t * +get_screen_file(void) +{ + static screen_functions_t functions; + + memset(&functions, 0, sizeof(screen_functions_t)); + functions.init = file_init; + functions.exit = file_exit; + functions.open = file_open; + functions.close = file_close; + functions.resize = file_resize; + functions.paint = file_paint; + functions.update = file_update; + functions.cmd = file_cmd; + functions.get_lw = get_filelist_window; + functions.get_title = file_title; + + return &functions; +} + diff --git a/src/screen_file.h b/src/screen_file.h new file mode 100644 index 000000000..a7199a62e --- /dev/null +++ b/src/screen_file.h @@ -0,0 +1,7 @@ + +void file_set_highlight(mpd_client_t *c, mpd_Song *song, int hightlight); +void file_clear_highlights(mpd_client_t *c); + +list_window_t *get_filelist_window(void); + +screen_functions_t *get_screen_file(void); diff --git a/src/screen_help.c b/src/screen_help.c new file mode 100644 index 000000000..c55c96380 --- /dev/null +++ b/src/screen_help.c @@ -0,0 +1,254 @@ +/* + * (c) 2004 by Kalle Wallin (kaw@linux.se) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <stdlib.h> +#include <string.h> +#include <glib.h> +#include <ncurses.h> + +#include "config.h" +#include "libmpdclient.h" +#include "mpc.h" +#include "command.h" +#include "screen.h" +#include "screen_utils.h" +#include "screen_help.h" + + +typedef struct +{ + signed char highlight; + command_t command; + char *text; +} help_text_row_t; + +static help_text_row_t help_text[] = +{ + { 1, CMD_NONE, " Movement keys " }, + { 0, CMD_NONE, " -----------------" }, + { 0, CMD_LIST_PREVIOUS, NULL }, + { 0, CMD_LIST_NEXT, NULL }, + { 0, CMD_LIST_PREVIOUS_PAGE, NULL }, + { 0, CMD_LIST_NEXT_PAGE, NULL }, + { 0, CMD_LIST_FIRST, NULL }, + { 0, CMD_LIST_LAST, NULL }, + { 0, CMD_NONE, NULL }, + { 0, CMD_SCREEN_NEXT, NULL }, + { 0, CMD_SCREEN_HELP, NULL }, + { 0, CMD_SCREEN_PLAY, NULL }, + { 0, CMD_SCREEN_FILE, NULL }, +#ifdef ENABLE_KEYDEF_SCREEN + { 0, CMD_SCREEN_KEYDEF, NULL }, +#endif + + { 0, CMD_NONE, NULL }, + { 0, CMD_NONE, NULL }, + { 1, CMD_NONE, " General keys " }, + { 0, CMD_NONE, " ----------------" }, + { 0, CMD_STOP, NULL }, + { 0, CMD_PAUSE, NULL }, + { 0, CMD_TRACK_NEXT, NULL }, + { 0, CMD_TRACK_PREVIOUS, NULL }, + { 0, CMD_SEEK_FORWARD, NULL }, + { 0, CMD_SEEK_BACKWARD, NULL }, + { 0, CMD_VOLUME_DOWN, NULL }, + { 0, CMD_VOLUME_UP, NULL }, + { 0, CMD_NONE, NULL }, + { 0, CMD_SHUFFLE, NULL }, + { 0, CMD_REPEAT, NULL }, + { 0, CMD_RANDOM, NULL }, + { 0, CMD_CROSSFADE, NULL }, + { 0, CMD_NONE, NULL }, + { 0, CMD_QUIT, NULL }, + + { 0, CMD_NONE, NULL }, + { 0, CMD_NONE, NULL }, + { 1, CMD_NONE, " Keys - Playlist screen " }, + { 0, CMD_NONE, " --------------------------" }, + { 0, CMD_PLAY, "Play" }, + { 0, CMD_DELETE, NULL }, + { 0, CMD_CLEAR, NULL }, + { 0, CMD_LIST_MOVE_UP, "Move song up" }, + { 0, CMD_LIST_MOVE_DOWN, "Move song down" }, + { 0, CMD_SAVE_PLAYLIST, NULL }, + { 0, CMD_SCREEN_UPDATE, "Center" }, + { 0, CMD_TOGGLE_AUTOCENTER, NULL }, + + { 0, CMD_NONE, NULL }, + { 0, CMD_NONE, NULL }, + { 1, CMD_NONE, " Keys - Browse screen " }, + { 0, CMD_NONE, " ------------------------" }, + { 0, CMD_PLAY, "Enter directory" }, + { 0, CMD_SELECT, NULL }, + { 0, CMD_DELETE, NULL }, + { 0, CMD_SCREEN_UPDATE, NULL }, + + { 0, CMD_NONE, NULL }, + { 0, CMD_NONE, NULL }, + { 1, CMD_NONE, " Find keys " }, + { 0, CMD_NONE, " -------------" }, + { 0, CMD_LIST_FIND, NULL }, + { 0, CMD_LIST_RFIND, NULL }, + { 0, CMD_LIST_FIND_NEXT, NULL }, + { 0, CMD_LIST_RFIND_NEXT, NULL }, + { 0, CMD_TOGGLE_FIND_WRAP, NULL }, + + { 0, CMD_NONE, NULL }, + { 0, CMD_NONE, NULL }, + { 1, CMD_NONE, " ncmpc build information " }, + { 0, CMD_NONE, " ---------------------------" }, + { 0, CMD_NONE, " Version : " VERSION }, + { 0, CMD_NONE, " Configuration dirs : ~/.ncmpc, " SYSCONFDIR "/" PACKAGE }, +#ifdef ENABLE_KEYDEF_SCREEN + { 0, CMD_NONE, " Key Editor : yes" }, +#else + { 0, CMD_NONE, " Key Editor : no" }, +#endif + + { 0, CMD_NONE, NULL }, + {-1, CMD_NONE, NULL } +}; + +static int help_text_rows = -1; +static list_window_t *lw = NULL; + + +static char * +list_callback(int index, int *highlight, void *data) +{ + static char buf[256]; + + if( help_text_rows<0 ) + { + help_text_rows = 0; + while( help_text[help_text_rows].highlight != -1 ) + help_text_rows++; + } + + *highlight = 0; + if( index<help_text_rows ) + { + *highlight = help_text[index].highlight > 0; + if( help_text[index].command == CMD_NONE ) + { + if( help_text[index].text ) + return help_text[index].text; + else + return " "; + } + if( help_text[index].text ) + snprintf(buf, 256, + "%20s : %s ", + get_key_names(help_text[index].command, TRUE), + help_text[index].text); + else + snprintf(buf, 256, + "%20s : %s ", + get_key_names(help_text[index].command, TRUE), + get_key_description(help_text[index].command)); + return buf; + } + + return NULL; +} + +static void +help_init(WINDOW *w, int cols, int rows) +{ + lw = list_window_init(w, cols, rows); +} + +static void +help_resize(int cols, int rows) +{ + lw->cols = cols; + lw->rows = rows; +} + +static void +help_exit(void) +{ + list_window_free(lw); +} + + +static char * +help_title(void) +{ + return (TOP_HEADER_PREFIX "Help"); +} + +static void +help_paint(screen_t *screen, mpd_client_t *c) +{ + lw->clear = 1; + list_window_paint(lw, list_callback, NULL); + wrefresh(lw->w); +} + +static void +help_update(screen_t *screen, mpd_client_t *c) +{ + if( lw->repaint ) + { + list_window_paint(lw, list_callback, NULL); + wrefresh(lw->w); + lw->repaint = 0; + } +} + + +static int +help_cmd(screen_t *screen, mpd_client_t *c, command_t cmd) +{ + int retval; + + retval = list_window_cmd(lw, help_text_rows, cmd); + if( !retval ) + return screen_find(screen, c, + lw, help_text_rows, + cmd, list_callback); + + return retval; +} + +static list_window_t * +help_lw(void) +{ + return lw; +} + +screen_functions_t * +get_screen_help(void) +{ + static screen_functions_t functions; + + memset(&functions, 0, sizeof(screen_functions_t)); + functions.init = help_init; + functions.exit = help_exit; + functions.open = NULL; + functions.close = NULL; + functions.resize = help_resize; + functions.paint = help_paint; + functions.update = help_update; + functions.cmd = help_cmd; + functions.get_lw = help_lw; + functions.get_title = help_title; + + return &functions; +} diff --git a/src/screen_help.h b/src/screen_help.h new file mode 100644 index 000000000..ba2c57c8e --- /dev/null +++ b/src/screen_help.h @@ -0,0 +1,2 @@ + +screen_functions_t *get_screen_help(void); diff --git a/src/screen_keydef.c b/src/screen_keydef.c new file mode 100644 index 000000000..4bda6ea21 --- /dev/null +++ b/src/screen_keydef.c @@ -0,0 +1,393 @@ +/* + * (c) 2004 by Kalle Wallin (kaw@linux.se) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <glib.h> +#include <ncurses.h> + +#include "config.h" + +#ifdef ENABLE_KEYDEF_SCREEN +#include "libmpdclient.h" +#include "options.h" +#include "conf.h" +#include "mpc.h" +#include "command.h" +#include "screen.h" +#include "screen_utils.h" + +#define STATIC_ITEMS 0 +#define STATIC_SUB_ITEMS 1 +#define BUFSIZE 256 + +#define LIST_ITEM_APPLY() (command_list_length) +#define LIST_ITEM_SAVE() (LIST_ITEM_APPLY()+1) +#define LIST_LENGTH() (LIST_ITEM_SAVE()+1) + +#define LIST_ITEM_SAVE_LABEL "===> Apply & Save key bindings " +#define LIST_ITEM_APPLY_LABEL "===> Apply key bindings " + + +static list_window_t *lw = NULL; +static int command_list_length = 0; +static command_definition_t *cmds = NULL; + +static int subcmd = -1; +static int subcmd_length = 0; +static int subcmd_addpos = 0; + +static int +keybindings_changed(void) +{ + command_definition_t *orginal_cmds = get_command_definitions(); + size_t size = command_list_length*sizeof(command_definition_t); + + return memcmp(orginal_cmds, cmds, size); +} + +static void +apply_keys(void) +{ + if( keybindings_changed() ) + { + command_definition_t *orginal_cmds = get_command_definitions(); + size_t size = command_list_length*sizeof(command_definition_t); + + memcpy(orginal_cmds, cmds, size); + screen_status_printf("You have new key bindings!"); + } + else + screen_status_printf("Keybindings unchanged."); +} + +static int +save_keys(void) +{ + FILE *f; + char *filename; + + if( check_user_conf_dir() ) + { + screen_status_printf("Error: Unable to create direcory ~/.ncmpc - %s", + strerror(errno)); + beep(); + return -1; + } + + filename = get_user_key_binding_filename(); + + if( (f=fopen(filename,"w")) == NULL ) + { + screen_status_printf("Error: %s - %s", filename, strerror(errno)); + beep(); + g_free(filename); + return -1; + } + if( write_key_bindings(f) ) + screen_status_printf("Error: %s - %s", filename, strerror(errno)); + else + screen_status_printf("Wrote %s", filename); + + g_free(filename); + return fclose(f); +} + +static void +check_subcmd_length(void) +{ + subcmd_length = 0; + while( subcmd_length<MAX_COMMAND_KEYS && cmds[subcmd].keys[subcmd_length]>0 ) + subcmd_length ++; + + if( subcmd_length<MAX_COMMAND_KEYS ) + { + subcmd_addpos = subcmd_length; + subcmd_length++; + } + else + subcmd_addpos = 0; + subcmd_length += STATIC_SUB_ITEMS; +} + +static void +delete_key(int cmd_index, int key_index) +{ + int i = key_index+1; + + screen_status_printf("Delete..."); + while( i<MAX_COMMAND_KEYS && cmds[cmd_index].keys[i] ) + cmds[cmd_index].keys[key_index++] = cmds[cmd_index].keys[i++]; + cmds[cmd_index].keys[key_index] = 0; + + check_subcmd_length(); + lw->clear = 1; + lw->repaint = 1; +} + +static void +assign_new_key(WINDOW *w, int cmd_index, int key_index) +{ + int key; + char buf[BUFSIZE]; + command_t cmd; + + snprintf(buf, BUFSIZE, "Enter new key for %s: ", cmds[cmd_index].name); + key = screen_getch(w, buf); + if( key==KEY_RESIZE ) + screen_resize(); + if( key==ERR ) + { + screen_status_printf("Aborted!"); + return; + } + cmd = find_key_command(key, cmds); + if( cmd!=CMD_NONE && cmd!= cmds[cmd_index].command ) + { + screen_status_printf("Error: key %s is already used for %s", + key2str(key), + get_key_command_name(cmd)); + beep(); + return; + } + cmds[cmd_index].keys[key_index] = key; + screen_status_printf("Assigned %s to %s", key2str(key),cmds[cmd_index].name); + check_subcmd_length(); + lw->repaint = 1; +} + +static char * +list_callback(int index, int *highlight, void *data) +{ + static char buf[BUFSIZE]; + + if( subcmd <0 ) + { + if( index<command_list_length ) + return cmds[index].name; + else if( index==LIST_ITEM_APPLY() ) + return LIST_ITEM_APPLY_LABEL; + else if( index==LIST_ITEM_SAVE() ) + return LIST_ITEM_SAVE_LABEL; + } + else + { + if( index== 0 ) + return "[..]"; + index--; + if( index<MAX_COMMAND_KEYS && cmds[subcmd].keys[index]>0 ) + { + snprintf(buf, + BUFSIZE, "%d. %-20s (%d) ", + index+1, + key2str(cmds[subcmd].keys[index]), + cmds[subcmd].keys[index]); + return buf; + } + else if ( index==subcmd_addpos ) + { + snprintf(buf, BUFSIZE, "%d. Add new key ", index+1 ); + return buf; + } + } + + return NULL; +} + +static void +keydef_init(WINDOW *w, int cols, int rows) +{ + lw = list_window_init(w, cols, rows); +} + +static void +keydef_resize(int cols, int rows) +{ + lw->cols = cols; + lw->rows = rows; +} + +static void +keydef_exit(void) +{ + list_window_free(lw); + if( cmds ) + g_free(cmds); + cmds = NULL; + lw = NULL; +} + +static void +keydef_open(screen_t *screen, mpd_client_t *c) +{ + if( cmds == NULL ) + { + command_definition_t *current_cmds = get_command_definitions(); + size_t cmds_size; + + command_list_length = 0; + while( current_cmds[command_list_length].name ) + command_list_length++; + + cmds_size = (command_list_length+1)*sizeof(command_definition_t); + cmds = g_malloc0(cmds_size); + memcpy(cmds, current_cmds, cmds_size); + command_list_length += STATIC_ITEMS; + screen_status_printf("Welcome to the key editor!"); + } + + subcmd = -1; + list_window_check_selected(lw, LIST_LENGTH()); +} + +static void +keydef_close(void) +{ + if( cmds && !keybindings_changed() ) + { + g_free(cmds); + cmds = NULL; + } + else + screen_status_printf("Note: Did you forget to \'Apply\' your changes?"); +} + +static char * +keydef_title(void) +{ + static char buf[BUFSIZE]; + + if( subcmd<0 ) + return (TOP_HEADER_PREFIX "Edit key bindings"); + + snprintf(buf, BUFSIZE, + TOP_HEADER_PREFIX "Edit keys for %s", + cmds[subcmd].name); + return buf; +} + +static void +keydef_paint(screen_t *screen, mpd_client_t *c) +{ + lw->clear = 1; + list_window_paint(lw, list_callback, NULL); + wrefresh(lw->w); +} + +static void +keydef_update(screen_t *screen, mpd_client_t *c) +{ + if( lw->repaint ) + { + list_window_paint(lw, list_callback, NULL); + wrefresh(lw->w); + lw->repaint = 0; + } +} + +static int +keydef_cmd(screen_t *screen, mpd_client_t *c, command_t cmd) +{ + int length = LIST_LENGTH(); + + if( subcmd>=0 ) + length = subcmd_length; + + switch(cmd) + { + case CMD_PLAY: + if( subcmd<0 ) + { + if( lw->selected == LIST_ITEM_APPLY() ) + apply_keys(); + else if( lw->selected == LIST_ITEM_SAVE() ) + { + apply_keys(); + save_keys(); + } + else + { + subcmd = lw->selected; + lw->selected=0; + check_subcmd_length(); + } + } + else + { + if( lw->selected == 0 ) /* up */ + { + lw->selected = subcmd; + subcmd = -1; + } + else + assign_new_key(screen->status_window.w, + subcmd, + lw->selected-STATIC_SUB_ITEMS); + } + lw->repaint = 1; + lw->clear = 1; + return 1; + case CMD_DELETE: + if( subcmd>=0 && lw->selected-STATIC_SUB_ITEMS>=0 ) + delete_key(subcmd, lw->selected-STATIC_SUB_ITEMS); + return 1; + break; + case CMD_LIST_FIND: + case CMD_LIST_RFIND: + case CMD_LIST_FIND_NEXT: + case CMD_LIST_RFIND_NEXT: + return screen_find(screen, c, + lw, length, + cmd, list_callback); + + default: + break; + } + + return list_window_cmd(lw, length, cmd); +} + +static list_window_t * +keydef_lw(void) +{ + return lw; +} + +screen_functions_t * +get_screen_keydef(void) +{ + static screen_functions_t functions; + + memset(&functions, 0, sizeof(screen_functions_t)); + functions.init = keydef_init; + functions.exit = keydef_exit; + functions.open = keydef_open; + functions.close = keydef_close; + functions.resize = keydef_resize; + functions.paint = keydef_paint; + functions.update = keydef_update; + functions.cmd = keydef_cmd; + functions.get_lw = keydef_lw; + functions.get_title = keydef_title; + + return &functions; +} + + +#endif diff --git a/src/screen_play.c b/src/screen_play.c new file mode 100644 index 000000000..cdec4f77e --- /dev/null +++ b/src/screen_play.c @@ -0,0 +1,390 @@ +/* + * (c) 2004 by Kalle Wallin (kaw@linux.se) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <stdlib.h> +#include <string.h> +#include <glib.h> +#include <ncurses.h> + +#include "config.h" +#include "options.h" +#include "support.h" +#include "libmpdclient.h" +#include "mpc.h" +#include "command.h" +#include "screen.h" +#include "screen_utils.h" +#include "screen_file.h" +#include "screen_play.h" + +#ifdef DEBUG +#define D(x) x +#else +#define D(x) +#endif + +#define BUFSIZE 256 + +static list_window_t *lw = NULL; + +static char * +list_callback(int index, int *highlight, void *data) +{ + mpd_client_t *c = (mpd_client_t *) data; + mpd_Song *song; + + *highlight = 0; + if( (song=mpc_playlist_get_song(c, index)) == NULL ) + { + return NULL; + } + + if( index==c->song_id && !IS_STOPPED(c->status->state) ) + { + *highlight = 1; + } + + return mpc_get_song_name(song); +} + +static int +center_playing_item(screen_t *screen, mpd_client_t *c) +{ + int length = c->playlist_length; + int offset = lw->selected-lw->start; + + if( !lw || length<lw->rows || IS_STOPPED(c->status->state) ) + return 0; + + /* try to center the song that are playing */ + lw->start = c->song_id-(lw->rows/2); + if( lw->start+lw->rows > length ) + lw->start = length-lw->rows; + if( lw->start<0 ) + lw->start=0; + + /* make sure the cursor is in the window */ + lw->selected = lw->start+offset; + list_window_check_selected(lw, length); + + lw->clear = 1; + lw->repaint = 1; + + return 0; +} + +static int +handle_save_playlist(screen_t *screen, mpd_client_t *c) +{ + char *filename, *filename_utf8; + + filename=screen_getstr(screen->status_window.w, "Save playlist as: "); + filename=trim(filename); + if( filename==NULL || filename[0]=='\0' ) + return -1; + /* convert filename to utf-8 */ + filename_utf8 = locale_to_utf8(filename); + /* send save command to mpd */ + mpd_sendSaveCommand(c->connection, filename_utf8); + mpd_finishCommand(c->connection); + g_free(filename_utf8); + /* handle errors */ + if( mpc_error(c)) + { + if( mpc_error_str(c) ) + { + char *str = utf8_to_locale(mpc_error_str(c)); + screen_status_message(str); + g_free(str); + } + else + screen_status_printf("Error: Unable to save playlist as %s", filename); + mpd_clearError(c->connection); + beep(); + return -1; + } + /* success */ + screen_status_printf("Saved %s", filename); + g_free(filename); + /* update the file list if it has been initalized */ + if( c->filelist ) + { + list_window_t *file_lw = get_filelist_window(); + + mpc_update_filelist(c); + list_window_check_selected(file_lw, c->filelist_length); + } + return 0; +} + +static void +play_init(WINDOW *w, int cols, int rows) +{ + lw = list_window_init(w, cols, rows); +} + +static void +play_resize(int cols, int rows) +{ + lw->cols = cols; + lw->rows = rows; +} + + +static void +play_exit(void) +{ + list_window_free(lw); +} + +static char * +play_title(void) +{ + return (TOP_HEADER_PREFIX "Playlist"); +} + +static void +play_paint(screen_t *screen, mpd_client_t *c) +{ + lw->clear = 1; + + list_window_paint(lw, list_callback, (void *) c); + wnoutrefresh(lw->w); +} + +static void +play_update(screen_t *screen, mpd_client_t *c) +{ + if( options.auto_center ) + { + static int prev_song_id = 0; + + if( prev_song_id != c->song_id ) + { + center_playing_item(screen, c); + prev_song_id = c->song_id; + } + } + + if( c->playlist_updated ) + { + if( lw->selected >= c->playlist_length ) + lw->selected = c->playlist_length-1; + if( lw->start >= c->playlist_length ) + list_window_reset(lw); + + play_paint(screen, c); + c->playlist_updated = 0; + } + else if( lw->repaint || 1) + { + list_window_paint(lw, list_callback, (void *) c); + wnoutrefresh(lw->w); + lw->repaint = 0; + } +} + +static int +play_cmd(screen_t *screen, mpd_client_t *c, command_t cmd) +{ + switch(cmd) + { + case CMD_DELETE: + playlist_delete_song(c, lw->selected); + return 1; + case CMD_SAVE_PLAYLIST: + handle_save_playlist(screen, c); + return 1; + case CMD_SCREEN_UPDATE: + center_playing_item(screen, c); + return 1; + case CMD_LIST_MOVE_UP: + playlist_move_song(c, lw->selected, lw->selected-1); + break; + case CMD_LIST_MOVE_DOWN: + playlist_move_song(c, lw->selected, lw->selected+1); + break; + case CMD_LIST_FIND: + case CMD_LIST_RFIND: + case CMD_LIST_FIND_NEXT: + case CMD_LIST_RFIND_NEXT: + return screen_find(screen, c, + lw, c->playlist_length, + cmd, list_callback); + default: + break; + } + return list_window_cmd(lw, c->playlist_length, cmd) ; +} + + + +static list_window_t * +play_lw(void) +{ + return lw; +} + +int +play_get_selected(void) +{ + return lw->selected; +} + +int +playlist_move_song(mpd_client_t *c, int old_index, int new_index) +{ + int index1, index2; + GList *item1, *item2; + gpointer data1, data2; + + if( old_index==new_index || new_index<0 || new_index>=c->playlist_length ) + return -1; + + /* send the move command to mpd */ + mpd_sendMoveCommand(c->connection, old_index, new_index); + mpd_finishCommand(c->connection); + if( mpc_error(c) ) + return -1; + + index1 = MIN(old_index, new_index); + index2 = MAX(old_index, new_index); + item1 = g_list_nth(c->playlist, index1); + item2 = g_list_nth(c->playlist, index2); + data1 = item1->data; + data2 = item2->data; + + /* move the second item */ + D(fprintf(stderr, "move second item [%d->%d]...\n", index2, index1)); + c->playlist = g_list_remove(c->playlist, data2); + c->playlist = g_list_insert_before(c->playlist, item1, data2); + + /* move the first item */ + if( index2-index1 >1 ) + { + D(fprintf(stderr, "move first item [%d->%d]...\n", index1, index2)); + item2 = g_list_nth(c->playlist, index2); + c->playlist = g_list_remove(c->playlist, data1); + c->playlist = g_list_insert_before(c->playlist, item2, data1); + } + + /* increment the playlist id, so we dont retrives a new playlist */ + c->playlist_id++; + + /* make shure the playlist is repainted */ + lw->clear = 1; + lw->repaint = 1; + + /* keep song selected */ + lw->selected = new_index; + + return 0; +} + +int +playlist_add_song(mpd_client_t *c, mpd_Song *song) +{ + if( !song || !song->file ) + return -1; + + /* send the add command to mpd */ + mpd_sendAddCommand(c->connection, song->file); + mpd_finishCommand(c->connection); + if( mpc_error(c) ) + return -1; + + /* add the song to playlist */ + c->playlist = g_list_append(c->playlist, (gpointer) mpd_songDup(song)); + c->playlist_length++; + + /* increment the playlist id, so we dont retrives a new playlist */ + c->playlist_id++; + + /* make shure the playlist is repainted */ + lw->clear = 1; + lw->repaint = 1; + + /* set selected highlight in the browse screen */ + file_set_highlight(c, song, 1); + + return 0; +} + +int +playlist_delete_song(mpd_client_t *c, int index) +{ + mpd_Song *song = mpc_playlist_get_song(c, index); + + if( !song ) + return -1; + + /* send the delete command to mpd */ + mpd_sendDeleteCommand(c->connection, index); + mpd_finishCommand(c->connection); + if( mpc_error(c) ) + return -1; + + /* print a status message */ + screen_status_printf("Removed \'%s\' from playlist!", + mpc_get_song_name(song)); + /* clear selected highlight in the browse screen */ + file_set_highlight(c, song, 0); + + /* increment the playlist id, so we dont retrives a new playlist */ + c->playlist_id++; + + /* remove references to the song */ + if( c->song == song ) + { + c->song = NULL; + c->song_id = -1; + } + + /* remove the song from the playlist */ + c->playlist = g_list_remove(c->playlist, (gpointer) song); + c->playlist_length = g_list_length(c->playlist); + mpd_freeSong(song); + + /* make shure the playlist is repainted */ + lw->clear = 1; + lw->repaint = 1; + list_window_check_selected(lw, c->playlist_length); + + return 0; +} + + +screen_functions_t * +get_screen_playlist(void) +{ + static screen_functions_t functions; + + memset(&functions, 0, sizeof(screen_functions_t)); + functions.init = play_init; + functions.exit = play_exit; + functions.open = NULL; + functions.close = NULL; + functions.resize = play_resize; + functions.paint = play_paint; + functions.update = play_update; + functions.cmd = play_cmd; + functions.get_lw = play_lw; + functions.get_title = play_title; + + return &functions; +} diff --git a/src/screen_play.h b/src/screen_play.h new file mode 100644 index 000000000..2155ae593 --- /dev/null +++ b/src/screen_play.h @@ -0,0 +1,9 @@ + +int play_get_selected(void); + +int playlist_move_song(mpd_client_t *c, int old_index, int new_index); +int playlist_add_song(mpd_client_t *c, mpd_Song *song); +int playlist_delete_song(mpd_client_t *c, int index); + +screen_functions_t *get_screen_playlist(void); + diff --git a/src/screen_search.c b/src/screen_search.c new file mode 100644 index 000000000..6b6719e30 --- /dev/null +++ b/src/screen_search.c @@ -0,0 +1,12 @@ +#include <stdlib.h> +#include <string.h> +#include <glib.h> +#include <ncurses.h> + +#include "libmpdclient.h" +#include "config.h" +#include "mpc.h" +#include "command.h" +#include "screen.h" +#include "screen_search.h" + diff --git a/src/screen_search.h b/src/screen_search.h new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/screen_search.h diff --git a/src/screen_utils.c b/src/screen_utils.c new file mode 100644 index 000000000..f8dfa4c1d --- /dev/null +++ b/src/screen_utils.c @@ -0,0 +1,153 @@ +/* + * (c) 2004 by Kalle Wallin (kaw@linux.se) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <glib.h> +#include <ncurses.h> + +#include "config.h" +#include "libmpdclient.h" +#include "mpc.h" +#include "support.h" +#include "command.h" +#include "options.h" +#include "list_window.h" +#include "colors.h" +#include "screen.h" + +#define FIND_PROMPT "Find: " +#define RFIND_PROMPT "Find backward: " + +int +screen_getch(WINDOW *w, char *prompt) +{ + int key = -1; + int prompt_len = strlen(prompt); + + colors_use(w, COLOR_STATUS_ALERT); + wclear(w); + wmove(w, 0, 0); + waddstr(w, prompt); + wmove(w, 0, prompt_len); + + echo(); + curs_set(1); + timeout(-1); + + key = wgetch(w); + if( key==KEY_RESIZE ) + screen_resize(); + + noecho(); + curs_set(0); + timeout(SCREEN_TIMEOUT); + + return key; +} + + +char * +screen_getstr(WINDOW *w, char *prompt) +{ + char buf[256], *line = NULL; + int prompt_len = strlen(prompt); + + colors_use(w, COLOR_STATUS_ALERT); + wclear(w); + wmove(w, 0, 0); + waddstr(w, prompt); + wmove(w, 0, prompt_len); + + echo(); + curs_set(1); + + if( wgetnstr(w, buf, 256) == OK ) + line = g_strdup(buf); + + noecho(); + curs_set(0); + + return line; +} + + +/* query user for a string and find it in a list window */ +int +screen_find(screen_t *screen, + mpd_client_t *c, + list_window_t *lw, + int rows, + command_t findcmd, + list_window_callback_fn_t callback_fn) +{ + int reversed = 0; + int retval = 0; + char *prompt = FIND_PROMPT; + + if( findcmd==CMD_LIST_RFIND ||findcmd==CMD_LIST_RFIND_NEXT ) + { + prompt = RFIND_PROMPT; + reversed = 1; + } + + switch(findcmd) + { + case CMD_LIST_FIND: + case CMD_LIST_RFIND: + if( screen->findbuf ) + { + g_free(screen->findbuf); + screen->findbuf=NULL; + } + /* continue... */ + case CMD_LIST_FIND_NEXT: + case CMD_LIST_RFIND_NEXT: + if( !screen->findbuf ) + screen->findbuf=screen_getstr(screen->status_window.w, prompt); + if( reversed ) + retval = list_window_rfind(lw, + callback_fn, + c, + screen->findbuf, + options.find_wrap, + rows); + else + retval = list_window_find(lw, + callback_fn, + c, + screen->findbuf, + options.find_wrap); + if( retval == 0 ) + { + lw->repaint = 1; + } + else + { + screen_status_printf("Unable to find \'%s\'", screen->findbuf); + beep(); + } + return 1; + default: + break; + } + return 0; +} + + diff --git a/src/screen_utils.h b/src/screen_utils.h new file mode 100644 index 000000000..30c58bfca --- /dev/null +++ b/src/screen_utils.h @@ -0,0 +1,18 @@ + +/* read a characher from the status window */ +int screen_getch(WINDOW *w, char *prompt); + +/* read a string from the status window */ +char *screen_getstr(WINDOW *w, char *prompt); + +/* query user for a string and find it in a list window */ +int screen_find(screen_t *screen, + mpd_client_t *c, + list_window_t *lw, + int rows, + command_t findcmd, + list_window_callback_fn_t callback_fn); + + +int my_waddstr(WINDOW *, const char *, int); +int my_mvwaddstr(WINDOW *, int, int, const char *, int); diff --git a/src/support.c b/src/support.c new file mode 100644 index 000000000..d292e8462 --- /dev/null +++ b/src/support.c @@ -0,0 +1,216 @@ +/* + * (c) 2004 by Kalle Wallin (kaw@linux.se) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <glib.h> + +#include "config.h" +#include "support.h" + +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif + +#ifdef DEBUG +#define D(x) x +#else +#define D(x) +#endif + +#define BUFSIZE 1024 + +extern void screen_status_printf(char *format, ...); + +static const char *charset = NULL; +static const char *locale = NULL; +static gboolean noconvert = TRUE; + +char * +trim(char *str) +{ + char *end; + + if( str==NULL ) + return NULL; + + while( IS_WHITESPACE(*str) ) + str++; + + end=str+strlen(str)-1; + while( end>str && IS_WHITESPACE(*end) ) + { + *end = '\0'; + end--; + } + return str; +} + +char * +remove_trailing_slash(char *path) +{ + int len; + + if( path==NULL ) + return NULL; + + len=strlen(path); + if( len>1 && path[len-1] == '/' ) + path[len-1] = '\0'; + + return path; +} + +char * +lowerstr(char *str) +{ + size_t i; + size_t len = strlen(str); + + if( str==NULL ) + return NULL; + + i=0; + while(i<len && str[i]) + { + str[i] = tolower(str[i]); + i++; + } + return str; +} + + +#ifndef HAVE_BASENAME +char * +basename(char *path) +{ + char *end; + + path = remove_trailing_slash(path); + end = path + strlen(path); + + while( end>path && *end!='/' ) + end--; + + if( *end=='/' && end!=path ) + return end+1; + + return path; +} +#endif /* HAVE_BASENAME */ + + +#ifndef HAVE_STRCASESTR +char * +strcasestr(const char *haystack, const char *needle) +{ + return strstr(lowerstr(haystack), lowerstr(needle)); +} +#endif /* HAVE_STRCASESTR */ + + +int +charset_init(void) +{ +#ifdef HAVE_LOCALE_H + /* get current locale */ + if( (locale=setlocale(LC_CTYPE,"")) == NULL ) + { + g_printerr("setlocale() - failed!\n"); + return -1; + } +#endif + + /* get charset */ + noconvert = g_get_charset(&charset); + +#ifdef DEBUG + g_printerr("charset: %s [%d]\n", charset, noconvert); + fflush(stderr); +#endif + + return 0; +} + +int +charset_close(void) +{ + return 0; +} + +char * +utf8_to_locale(char *utf8str) +{ + gchar *str; + gsize rb, wb; + GError *error; + + if( noconvert ) + return g_strdup(utf8str); + + rb = 0; /* bytes read */ + wb = 0; /* bytes written */ + error = NULL; + str=g_locale_from_utf8(utf8str, + strlen(utf8str), + &wb, &rb, + &error); + if( error ) + { + screen_status_printf("Error: Unable to convert characters to %s", + charset); + D(g_printerr("utf8_to_locale(): %s\n", error->message)); + g_error_free(error); + return g_strdup(utf8str); + } + + return str; +} + +char * +locale_to_utf8(char *localestr) +{ + gchar *str; + gsize rb, wb; + GError *error; + + if( noconvert ) + return g_strdup(localestr); + + rb = 0; /* bytes read */ + wb = 0; /* bytes written */ + error = NULL; + str=g_locale_to_utf8(localestr, + strlen(localestr), + &wb, &rb, + &error); + if( error ) + { + screen_status_printf("Error: Unable to convert characters to UTF-8"); + D(g_printerr("locale_to_utf8: %s\n", error->message)); + g_error_free(error); + return g_strdup(localestr); + } + + return str; +} + + + diff --git a/src/support.h b/src/support.h new file mode 100644 index 000000000..1f3aca160 --- /dev/null +++ b/src/support.h @@ -0,0 +1,24 @@ +#ifndef SUPPORT_H +#define SUPPORT_H + +#ifdef HAVE_LIBGEN_H +#include <libgen.h> +#endif + +#ifndef HAVE_BASENAME +char *basename(char *path); +#endif + +#define IS_WHITESPACE(c) (c==' ' || c=='\t' || c=='\r' || c=='\n') + +char *trim(char *str); +char *remove_trailing_slash(char *path); +char *lowerstr(char *str); +char *strcasestr(const char *haystack, const char *needle); + +int charset_init(void); +int charset_close(void); +char *utf8_to_locale(char *str); +char *locale_to_utf8(char *str); + +#endif |