diff options
Diffstat (limited to 'apps/gui/wps_engine/wps_parser.c')
| -rw-r--r-- | apps/gui/wps_engine/wps_parser.c | 1843 |
1 files changed, 1843 insertions, 0 deletions
diff --git a/apps/gui/wps_engine/wps_parser.c b/apps/gui/wps_engine/wps_parser.c new file mode 100644 index 0000000..15acc14 --- /dev/null +++ b/apps/gui/wps_engine/wps_parser.c @@ -0,0 +1,1843 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 Nicolas Pennequin, Dan Everton, Matthias Mohr + * + * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include "file.h" +#include "misc.h" +#include "plugin.h" + +#ifdef __PCTOOL__ +#ifdef WPSEDITOR +#include "proxy.h" +#include "sysfont.h" +#else +#include "checkwps.h" +#include "audio.h" +#define DEBUGF printf +#endif /*WPSEDITOR*/ +#else +#include "debug.h" +#endif /*__PCTOOL__*/ + +#include <ctype.h> +#include <stdbool.h> +#include "font.h" + +#include "wps_internals.h" +#include "settings.h" +#include "settings_list.h" + +#ifdef HAVE_LCD_BITMAP +#include "bmp.h" +#endif + +#include "backdrop.h" + +#define WPS_DEFAULTCFG WPS_DIR "/rockbox_default.wps" +#define RWPS_DEFAULTCFG WPS_DIR "/rockbox_default.rwps" + +#define WPS_ERROR_INVALID_PARAM -1 + +/* level of current conditional. + -1 means we're not in a conditional. */ +static int level = -1; + +/* index of the last WPS_TOKEN_CONDITIONAL_OPTION + or WPS_TOKEN_CONDITIONAL_START in current level */ +static int lastcond[WPS_MAX_COND_LEVEL]; + +/* index of the WPS_TOKEN_CONDITIONAL in current level */ +static int condindex[WPS_MAX_COND_LEVEL]; + +/* number of condtional options in current level */ +static int numoptions[WPS_MAX_COND_LEVEL]; + +/* the current line in the file */ +static int line; + +#ifdef HAVE_LCD_BITMAP + +#if LCD_DEPTH > 1 +#define MAX_BITMAPS (MAX_IMAGES+MAX_PROGRESSBARS+1) /* WPS images + pbar bitmap + backdrop */ +#else +#define MAX_BITMAPS (MAX_IMAGES+MAX_PROGRESSBARS) /* WPS images + pbar bitmap */ +#endif + +#define PROGRESSBAR_BMP MAX_IMAGES +#define BACKDROP_BMP (MAX_BITMAPS-1) + +/* pointers to the bitmap filenames in the WPS source */ +static const char *bmp_names[MAX_BITMAPS]; + +#endif /* HAVE_LCD_BITMAP */ + +#if defined(DEBUG) || defined(SIMULATOR) +/* debugging function */ +extern void print_debug_info(struct wps_data *data, int fail, int line); +#endif + +static void wps_reset(struct wps_data *data); + +/* Function for parsing of details for a token. At the moment the + function is called, the token type has already been set. The + function must fill in the details and possibly add more tokens + to the token array. It should return the number of chars that + has been consumed. + + wps_bufptr points to the char following the tag (i.e. where + details begin). + token is the pointer to the 'main' token being parsed + */ +typedef int (*wps_tag_parse_func)(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); + +struct wps_tag { + enum wps_token_type type; + const char name[3]; + unsigned char refresh_type; + const wps_tag_parse_func parse_func; +}; +static int skip_end_of_line(const char *wps_bufptr); +/* prototypes of all special parse functions : */ +static int parse_timeout(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); +static int parse_progressbar(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); +static int parse_dir_level(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); +static int parse_setting(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); + +#ifdef HAVE_LCD_BITMAP +static int parse_viewport_display(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); +static int parse_viewport(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); +static int parse_statusbar_enable(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); +static int parse_statusbar_disable(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); +static int parse_image_display(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); +static int parse_image_load(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); +#endif /*HAVE_LCD_BITMAP */ +#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)) +static int parse_image_special(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); +#endif +#ifdef HAVE_ALBUMART +static int parse_albumart_load(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); +static int parse_albumart_conditional(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); +#endif /* HAVE_ALBUMART */ +#ifdef HAVE_TOUCHSCREEN +static int parse_touchregion(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); +#else +static int fulline_tag_not_supported(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data) +{ + (void)token; (void)wps_data; + return skip_end_of_line(wps_bufptr); +} +#define parse_touchregion fulline_tag_not_supported +#endif +#ifdef CONFIG_RTC +#define WPS_RTC_REFRESH WPS_REFRESH_DYNAMIC +#else +#define WPS_RTC_REFRESH WPS_REFRESH_STATIC +#endif + +/* array of available tags - those with more characters have to go first + (e.g. "xl" and "xd" before "x"). It needs to end with the unknown token. */ +static const struct wps_tag all_tags[] = { + + { WPS_TOKEN_ALIGN_CENTER, "ac", 0, NULL }, + { WPS_TOKEN_ALIGN_LEFT, "al", 0, NULL }, + { WPS_TOKEN_ALIGN_RIGHT, "ar", 0, NULL }, + + { WPS_TOKEN_BATTERY_PERCENT, "bl", WPS_REFRESH_DYNAMIC, NULL }, + { WPS_TOKEN_BATTERY_VOLTS, "bv", WPS_REFRESH_DYNAMIC, NULL }, + { WPS_TOKEN_BATTERY_TIME, "bt", WPS_REFRESH_DYNAMIC, NULL }, + { WPS_TOKEN_BATTERY_SLEEPTIME, "bs", WPS_REFRESH_DYNAMIC, NULL }, +#if CONFIG_CHARGING >= CHARGING_MONITOR + { WPS_TOKEN_BATTERY_CHARGING, "bc", WPS_REFRESH_DYNAMIC, NULL }, +#endif +#if CONFIG_CHARGING + { WPS_TOKEN_BATTERY_CHARGER_CONNECTED,"bp", WPS_REFRESH_DYNAMIC, NULL }, +#endif + + { WPS_TOKEN_RTC_PRESENT , "cc", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_RTC_DAY_OF_MONTH, "cd", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED,"ce", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_12HOUR_CFG, "cf", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED, "cH", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_HOUR_24, "ck", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED, "cI", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_HOUR_12, "cl", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_MONTH, "cm", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_MINUTE, "cM", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_SECOND, "cS", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_YEAR_2_DIGITS, "cy", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_YEAR_4_DIGITS, "cY", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_AM_PM_UPPER, "cP", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_AM_PM_LOWER, "cp", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_WEEKDAY_NAME, "ca", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_MONTH_NAME, "cb", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON, "cu", WPS_RTC_REFRESH, NULL }, + { WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN, "cw", WPS_RTC_REFRESH, NULL }, + + /* current file */ + { WPS_TOKEN_FILE_BITRATE, "fb", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_CODEC, "fc", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_FREQUENCY, "ff", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_FREQUENCY_KHZ, "fk", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_NAME_WITH_EXTENSION, "fm", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_NAME, "fn", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_PATH, "fp", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_SIZE, "fs", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_VBR, "fv", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_DIRECTORY, "d", WPS_REFRESH_STATIC, + parse_dir_level }, + + /* next file */ + { WPS_TOKEN_FILE_BITRATE, "Fb", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_CODEC, "Fc", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_FREQUENCY, "Ff", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_FREQUENCY_KHZ, "Fk", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_NAME_WITH_EXTENSION, "Fm", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_NAME, "Fn", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_PATH, "Fp", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_SIZE, "Fs", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_VBR, "Fv", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_FILE_DIRECTORY, "D", WPS_REFRESH_STATIC, + parse_dir_level }, + + /* current metadata */ + { WPS_TOKEN_METADATA_ARTIST, "ia", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_COMPOSER, "ic", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_ALBUM, "id", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_ALBUM_ARTIST, "iA", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_GROUPING, "iG", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_GENRE, "ig", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_DISC_NUMBER, "ik", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_TRACK_NUMBER, "in", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_TRACK_TITLE, "it", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_VERSION, "iv", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_YEAR, "iy", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_COMMENT, "iC", WPS_REFRESH_STATIC, NULL }, + + /* next metadata */ + { WPS_TOKEN_METADATA_ARTIST, "Ia", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_COMPOSER, "Ic", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_ALBUM, "Id", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_ALBUM_ARTIST, "IA", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_GROUPING, "IG", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_GENRE, "Ig", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_DISC_NUMBER, "Ik", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_TRACK_NUMBER, "In", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_TRACK_TITLE, "It", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_VERSION, "Iv", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_YEAR, "Iy", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_METADATA_COMMENT, "IC", WPS_REFRESH_STATIC, NULL }, + +#if (CONFIG_CODEC != MAS3507D) + { WPS_TOKEN_SOUND_PITCH, "Sp", WPS_REFRESH_DYNAMIC, NULL }, +#endif + +#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD) + { WPS_TOKEN_VLED_HDD, "lh", WPS_REFRESH_DYNAMIC, NULL }, +#endif + + { WPS_TOKEN_MAIN_HOLD, "mh", WPS_REFRESH_DYNAMIC, NULL }, + +#ifdef HAS_REMOTE_BUTTON_HOLD + { WPS_TOKEN_REMOTE_HOLD, "mr", WPS_REFRESH_DYNAMIC, NULL }, +#else + { WPS_TOKEN_UNKNOWN, "mr", 0, NULL }, +#endif + + { WPS_TOKEN_REPEAT_MODE, "mm", WPS_REFRESH_DYNAMIC, NULL }, + { WPS_TOKEN_PLAYBACK_STATUS, "mp", WPS_REFRESH_DYNAMIC, NULL }, + { WPS_TOKEN_BUTTON_VOLUME, "mv", WPS_REFRESH_DYNAMIC, + parse_timeout }, + +#ifdef HAVE_LCD_BITMAP + { WPS_TOKEN_PEAKMETER, "pm", WPS_REFRESH_PEAK_METER, NULL }, +#else + { WPS_TOKEN_PLAYER_PROGRESSBAR, "pf", + WPS_REFRESH_DYNAMIC | WPS_REFRESH_PLAYER_PROGRESS, parse_progressbar }, +#endif + { WPS_TOKEN_PROGRESSBAR, "pb", WPS_REFRESH_PLAYER_PROGRESS, + parse_progressbar }, + + { WPS_TOKEN_VOLUME, "pv", WPS_REFRESH_DYNAMIC, NULL }, + + { WPS_TOKEN_TRACK_ELAPSED_PERCENT, "px", WPS_REFRESH_DYNAMIC, NULL }, + { WPS_TOKEN_TRACK_TIME_ELAPSED, "pc", WPS_REFRESH_DYNAMIC, NULL }, + { WPS_TOKEN_TRACK_TIME_REMAINING, "pr", WPS_REFRESH_DYNAMIC, NULL }, + { WPS_TOKEN_TRACK_LENGTH, "pt", WPS_REFRESH_STATIC, NULL }, + + { WPS_TOKEN_PLAYLIST_POSITION, "pp", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_PLAYLIST_ENTRIES, "pe", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_PLAYLIST_NAME, "pn", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_PLAYLIST_SHUFFLE, "ps", WPS_REFRESH_DYNAMIC, NULL }, + +#ifdef HAVE_TAGCACHE + { WPS_TOKEN_DATABASE_PLAYCOUNT, "rp", WPS_REFRESH_DYNAMIC, NULL }, + { WPS_TOKEN_DATABASE_RATING, "rr", WPS_REFRESH_DYNAMIC, NULL }, + { WPS_TOKEN_DATABASE_AUTOSCORE, "ra", WPS_REFRESH_DYNAMIC, NULL }, +#endif + +#if CONFIG_CODEC == SWCODEC + { WPS_TOKEN_REPLAYGAIN, "rg", WPS_REFRESH_STATIC, NULL }, + { WPS_TOKEN_CROSSFADE, "xf", WPS_REFRESH_DYNAMIC, NULL }, +#endif + + { WPS_NO_TOKEN, "s", WPS_REFRESH_SCROLL, NULL }, + { WPS_TOKEN_SUBLINE_TIMEOUT, "t", 0, parse_timeout }, + +#ifdef HAVE_LCD_BITMAP + { WPS_NO_TOKEN, "we", 0, parse_statusbar_enable }, + { WPS_NO_TOKEN, "wd", 0, parse_statusbar_disable }, + + { WPS_NO_TOKEN, "xl", 0, parse_image_load }, + + { WPS_TOKEN_IMAGE_PRELOAD_DISPLAY, "xd", WPS_REFRESH_STATIC, + parse_image_display }, + + { WPS_TOKEN_IMAGE_DISPLAY, "x", 0, parse_image_load }, +#ifdef HAVE_ALBUMART + { WPS_NO_TOKEN, "Cl", 0, parse_albumart_load }, + { WPS_TOKEN_ALBUMART_DISPLAY, "C", WPS_REFRESH_STATIC, + parse_albumart_conditional }, +#endif + + { WPS_VIEWPORT_ENABLE, "Vd", WPS_REFRESH_DYNAMIC, + parse_viewport_display }, + { WPS_NO_TOKEN, "V", 0, parse_viewport }, + +#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)) + { WPS_TOKEN_IMAGE_BACKDROP, "X", 0, parse_image_special }, +#endif +#endif + + { WPS_TOKEN_SETTING, "St", WPS_REFRESH_DYNAMIC, parse_setting }, + + { WPS_TOKEN_LASTTOUCH, "Tl", WPS_REFRESH_DYNAMIC, parse_timeout }, + { WPS_NO_TOKEN, "T", 0, parse_touchregion }, + + { WPS_TOKEN_UNKNOWN, "", 0, NULL } + /* the array MUST end with an empty string (first char is \0) */ +}; + +/* Returns the number of chars that should be skipped to jump + immediately after the first eol, i.e. to the start of the next line */ +static int skip_end_of_line(const char *wps_bufptr) +{ + line++; + int skip = 0; + while(*(wps_bufptr + skip) != '\n') + skip++; + return ++skip; +} + +/* Starts a new subline in the current line during parsing */ +static void wps_start_new_subline(struct wps_data *data) +{ + data->num_sublines++; + data->sublines[data->num_sublines].first_token_idx = data->num_tokens; + data->lines[data->num_lines].num_sublines++; +} + +#ifdef HAVE_LCD_BITMAP + +static int parse_statusbar_enable(const char *wps_bufptr, + struct wps_token *token, + struct wps_data *wps_data) +{ + (void)token; /* Kill warnings */ + wps_data->wps_sb_tag = true; + wps_data->show_sb_on_wps = true; + if (wps_data->viewports[0].vp.y == 0) + { + wps_data->viewports[0].vp.y = STATUSBAR_HEIGHT; + wps_data->viewports[0].vp.height -= STATUSBAR_HEIGHT; + } + return skip_end_of_line(wps_bufptr); +} + +static int parse_statusbar_disable(const char *wps_bufptr, + struct wps_token *token, + struct wps_data *wps_data) +{ + (void)token; /* Kill warnings */ + wps_data->wps_sb_tag = true; + wps_data->show_sb_on_wps = false; + if (wps_data->viewports[0].vp.y == STATUSBAR_HEIGHT) + { + wps_data->viewports[0].vp.y = 0; + wps_data->viewports[0].vp.height += STATUSBAR_HEIGHT; + } + return skip_end_of_line(wps_bufptr); +} + +static bool load_bitmap(struct wps_data *wps_data, + char* filename, + struct bitmap *bm) +{ + int format; +#ifdef HAVE_REMOTE_LCD + if (wps_data->remote_wps) + format = FORMAT_ANY|FORMAT_REMOTE; + else +#endif + format = FORMAT_ANY|FORMAT_TRANSPARENT; + + int ret = read_bmp_file(filename, bm, + wps_data->img_buf_free, + format,NULL); + + if (ret > 0) + { +#if LCD_DEPTH == 16 + if (ret % 2) ret++; + /* Always consume an even number of bytes */ +#endif + wps_data->img_buf_ptr += ret; + wps_data->img_buf_free -= ret; + + return true; + } + else + return false; +} + +static int get_image_id(int c) +{ + if(c >= 'a' && c <= 'z') + return c - 'a'; + else if(c >= 'A' && c <= 'Z') + return c - 'A' + 26; + else + return -1; +} + +static char *get_image_filename(const char *start, const char* bmpdir, + char *buf, int buf_size) +{ + const char *end = strchr(start, '|'); + + if ( !end || (end - start) >= (buf_size - (int)ROCKBOX_DIR_LEN - 2) ) + { + buf = "\0"; + return NULL; + } + + int bmpdirlen = strlen(bmpdir); + + strcpy(buf, bmpdir); + buf[bmpdirlen] = '/'; + memcpy( &buf[bmpdirlen + 1], start, end - start); + buf[bmpdirlen + 1 + end - start] = 0; + + return buf; +} + +static int parse_image_display(const char *wps_bufptr, + struct wps_token *token, + struct wps_data *wps_data) +{ + (void)wps_data; + int n = get_image_id(wps_bufptr[0]); + int subimage; + + if (n == -1) + { + /* invalid picture display tag */ + return WPS_ERROR_INVALID_PARAM; + } + + if ((subimage = get_image_id(wps_bufptr[1])) != -1) + { + /* Sanity check */ + if (subimage >= wps_data->img[n].num_subimages) + return WPS_ERROR_INVALID_PARAM; + + /* Store sub-image number to display in high bits */ + token->value.i = n | (subimage << 8); + return 2; /* We have consumed 2 bytes */ + } else { + token->value.i = n; + return 1; /* We have consumed 1 byte */ + } +} + +static int parse_image_load(const char *wps_bufptr, + struct wps_token *token, + struct wps_data *wps_data) +{ + int n; + const char *ptr = wps_bufptr; + const char *pos; + const char* filename; + const char* id; + const char *newline; + int x,y; + + /* format: %x|n|filename.bmp|x|y| + or %xl|n|filename.bmp|x|y| + or %xl|n|filename.bmp|x|y|num_subimages| + */ + + if (*ptr != '|') + return WPS_ERROR_INVALID_PARAM; + + ptr++; + + if (!(ptr = parse_list("ssdd", NULL, '|', ptr, &id, &filename, &x, &y))) + return WPS_ERROR_INVALID_PARAM; + + /* Check there is a terminating | */ + if (*ptr != '|') + return WPS_ERROR_INVALID_PARAM; + + /* get the image ID */ + n = get_image_id(*id); + + /* check the image number and load state */ + if(n < 0 || n >= MAX_IMAGES || wps_data->img[n].loaded) + { + /* Invalid image ID */ + return WPS_ERROR_INVALID_PARAM; + } + + /* save a pointer to the filename */ + bmp_names[n] = filename; + + wps_data->img[n].x = x; + wps_data->img[n].y = y; + + /* save current viewport */ + wps_data->img[n].vp = &wps_data->viewports[wps_data->num_viewports].vp; + + if (token->type == WPS_TOKEN_IMAGE_DISPLAY) + { + wps_data->img[n].always_display = true; + } + else + { + /* Parse the (optional) number of sub-images */ + ptr++; + newline = strchr(ptr, '\n'); + pos = strchr(ptr, '|'); + if (pos && pos < newline) + wps_data->img[n].num_subimages = atoi(ptr); + + if (wps_data->img[n].num_subimages <= 0) + return WPS_ERROR_INVALID_PARAM; + } + + /* Skip the rest of the line */ + return skip_end_of_line(wps_bufptr); +} + +static int parse_viewport_display(const char *wps_bufptr, + struct wps_token *token, + struct wps_data *wps_data) +{ + (void)wps_data; + char letter = wps_bufptr[0]; + + if (letter < 'a' || letter > 'z') + { + /* invalid viewport tag */ + return WPS_ERROR_INVALID_PARAM; + } + token->value.i = letter; + return 1; +} + +static int parse_viewport(const char *wps_bufptr, + struct wps_token *token, + struct wps_data *wps_data) +{ + (void)token; /* Kill warnings */ + const char *ptr = wps_bufptr; + struct viewport* vp; + int depth; + uint32_t set = 0; + enum { + PL_X = 0, + PL_Y, + PL_WIDTH, + PL_HEIGHT, + PL_FONT, + PL_FG, + PL_BG, + }; + int lcd_width = LCD_WIDTH, lcd_height = LCD_HEIGHT; +#ifdef HAVE_REMOTE_LCD + if (wps_data->remote_wps) + { + lcd_width = LCD_REMOTE_WIDTH; + lcd_height = LCD_REMOTE_HEIGHT; + } +#endif + + if (wps_data->num_viewports >= WPS_MAX_VIEWPORTS) + return WPS_ERROR_INVALID_PARAM; + + wps_data->num_viewports++; + /* check for the optional letter to signify its a hideable viewport */ + /* %Vl|<label>|<rest of tags>| */ + wps_data->viewports[wps_data->num_viewports].hidden_flags = 0; + + if (*ptr == 'l') + { + if (*(ptr+1) == '|') + { + char label = *(ptr+2); + if (label >= 'a' && label <= 'z') + { + wps_data->viewports[wps_data->num_viewports].hidden_flags = VP_DRAW_HIDEABLE; + wps_data->viewports[wps_data->num_viewports].label = label; + } + else + return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */ + ptr += 3; + } + } + if (*ptr != '|') + return WPS_ERROR_INVALID_PARAM; + + ptr++; + vp = &wps_data->viewports[wps_data->num_viewports].vp; + /* format: %V|x|y|width|height|font|fg_pattern|bg_pattern| */ + + /* Set the defaults for fields not user-specified */ + vp->drawmode = DRMODE_SOLID; + + /* Work out the depth of this display */ +#ifdef HAVE_REMOTE_LCD + depth = (wps_data->remote_wps ? LCD_REMOTE_DEPTH : LCD_DEPTH); +#else + depth = LCD_DEPTH; +#endif + +#ifdef HAVE_LCD_COLOR + if (depth == 16) + { + if (!(ptr = parse_list("dddddcc", &set, '|', ptr, &vp->x, &vp->y, &vp->width, + &vp->height, &vp->font, &vp->fg_pattern,&vp->bg_pattern))) + return WPS_ERROR_INVALID_PARAM; + } + else +#endif +#if (LCD_DEPTH == 2) || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH == 2) + if (depth == 2) { + /* Default to black on white */ + vp->fg_pattern = 0; + vp->bg_pattern = 3; + if (!(ptr = parse_list("dddddgg", &set, '|', ptr, &vp->x, &vp->y, &vp->width, + &vp->height, &vp->font, &vp->fg_pattern, &vp->bg_pattern))) + return WPS_ERROR_INVALID_PARAM; + } + else +#endif +#if (LCD_DEPTH == 1) || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH == 1) + if (depth == 1) + { + if (!(ptr = parse_list("ddddd", &set, '|', ptr, &vp->x, &vp->y, + &vp->width, &vp->height, &vp->font))) + return WPS_ERROR_INVALID_PARAM; + } + else +#endif + {} + + /* Check for trailing | */ + if (*ptr != '|') + return WPS_ERROR_INVALID_PARAM; + + if (!LIST_VALUE_PARSED(set, PL_X) || !LIST_VALUE_PARSED(set, PL_Y)) + return WPS_ERROR_INVALID_PARAM; + + /* fix defaults */ + if (!LIST_VALUE_PARSED(set, PL_WIDTH)) + vp->width = lcd_width - vp->x; + if (!LIST_VALUE_PARSED(set, PL_HEIGHT)) + vp->height = lcd_height - vp->y; + + /* Default to using the user font if the font was an invalid number */ + if (!LIST_VALUE_PARSED(set, PL_FONT) || + ((vp->font != FONT_SYSFIXED) && (vp->font != FONT_UI))) + vp->font = FONT_UI; + + /* Validate the viewport dimensions - we know that the numbers are + non-negative integers */ + if ((vp->x >= lcd_width) || + ((vp->x + vp->width) > lcd_width) || + (vp->y >= lcd_height) || + ((vp->y + vp->height) > lcd_height)) + { + return WPS_ERROR_INVALID_PARAM; + } + +#ifdef HAVE_LCD_COLOR + if (depth == 16) + { + if (!LIST_VALUE_PARSED(set, PL_FG)) + vp->fg_pattern = global_settings.fg_color; + if (!LIST_VALUE_PARSED(set, PL_BG)) + vp->bg_pattern = global_settings.bg_color; + } +#endif + + wps_data->viewports[wps_data->num_viewports-1].last_line = wps_data->num_lines - 1; + + wps_data->viewports[wps_data->num_viewports].first_line = wps_data->num_lines; + + if (wps_data->num_sublines < WPS_MAX_SUBLINES) + { + wps_data->lines[wps_data->num_lines].first_subline_idx = + wps_data->num_sublines; + + wps_data->sublines[wps_data->num_sublines].first_token_idx = + wps_data->num_tokens; + } + + /* Skip the rest of the line */ + return skip_end_of_line(wps_bufptr); +} + +#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)) +static int parse_image_special(const char *wps_bufptr, + struct wps_token *token, + struct wps_data *wps_data) +{ + (void)wps_data; /* kill warning */ + (void)token; + const char *pos = NULL; + const char *newline; + + pos = strchr(wps_bufptr + 1, '|'); + newline = strchr(wps_bufptr, '\n'); + + if (pos > newline) + return WPS_ERROR_INVALID_PARAM; +#if LCD_DEPTH > 1 + if (token->type == WPS_TOKEN_IMAGE_BACKDROP) + { + /* format: %X|filename.bmp| */ + bmp_names[BACKDROP_BMP] = wps_bufptr + 1; + } +#endif + + /* Skip the rest of the line */ + return skip_end_of_line(wps_bufptr); +} +#endif + +#endif /* HAVE_LCD_BITMAP */ + +static int parse_setting(const char *wps_bufptr, + struct wps_token *token, + struct wps_data *wps_data) +{ + (void)wps_data; + const char *ptr = wps_bufptr; + const char *end; + int i; + + /* Find the setting's cfg_name */ + if (*ptr != '|') + return WPS_ERROR_INVALID_PARAM; + ptr++; + end = strchr(ptr,'|'); + if (!end) + return WPS_ERROR_INVALID_PARAM; + + /* Find the setting */ + for (i=0; i<nb_settings; i++) + if (settings[i].cfg_name && + !strncmp(settings[i].cfg_name,ptr,end-ptr) && + /* prevent matches on cfg_name prefixes */ + strlen(settings[i].cfg_name)==(size_t)(end-ptr)) + break; + if (i == nb_settings) + return WPS_ERROR_INVALID_PARAM; + + /* Store the setting number */ + token->value.i = i; + + /* Skip the rest of the line */ + return end-ptr+2; +} + + +static int parse_dir_level(const char *wps_bufptr, + struct wps_token *token, + struct wps_data *wps_data) +{ + char val[] = { *wps_bufptr, '\0' }; + token->value.i = atoi(val); + (void)wps_data; /* Kill warnings */ + return 1; +} + +static int parse_timeout(const char *wps_bufptr, + struct wps_token *token, + struct wps_data *wps_data) +{ + int skip = 0; + int val = 0; + bool have_point = false; + bool have_tenth = false; + + (void)wps_data; /* Kill the warning */ + + while ( isdigit(*wps_bufptr) || *wps_bufptr == '.' ) + { + if (*wps_bufptr != '.') + { + val *= 10; + val += *wps_bufptr - '0'; + if (have_point) + { + have_tenth = true; + wps_bufptr++; + skip++; + break; + } + } + else + have_point = true; + + wps_bufptr++; + skip++; + } + + if (have_tenth == false) + val *= 10; + + if (val == 0 && skip == 0) + { + /* decide what to do if no value was specified */ + switch (token->type) + { + case WPS_TOKEN_SUBLINE_TIMEOUT: + return -1; + case WPS_TOKEN_BUTTON_VOLUME: + val = 10; + break; + } + } + token->value.i = val; + + return skip; +} + +static int parse_progressbar(const char *wps_bufptr, + struct wps_token *token, + struct wps_data *wps_data) +{ + (void)token; /* Kill warnings */ + /* %pb or %pb|filename|x|y|width|height| + using - for any of the params uses "sane" values */ +#ifdef HAVE_LCD_BITMAP + enum { + PB_FILENAME = 0, + PB_X, + PB_Y, + PB_WIDTH, + PB_HEIGHT + }; + const char *filename; + int x, y, height, width; + uint32_t set = 0; + const char *ptr = wps_bufptr; + struct progressbar *pb; + struct viewport *vp = &wps_data->viewports[wps_data->num_viewports].vp; +#ifndef __PCTOOL__ + int font_height = font_get(vp->font)->height; +#else + int font_height = 8; +#endif + int line_num = wps_data->num_lines - + wps_data->viewports[wps_data->num_viewports].first_line; + + if (wps_data->progressbar_count >= MAX_PROGRESSBARS) + return WPS_ERROR_INVALID_PARAM; + + pb = &wps_data->progressbar[wps_data->progressbar_count]; + pb->have_bitmap_pb = false; + + if (*wps_bufptr != '|') /* regular old style */ + { + pb->x = 0; + pb->width = vp->width; + pb->height = SYSFONT_HEIGHT-2; + pb->y = -line_num - 1; /* Will be computed during the rendering */ + + wps_data->viewports[wps_data->num_viewports].pb = pb; + wps_data->progressbar_count++; + return 0; + } + ptr = wps_bufptr + 1; + + if (!(ptr = parse_list("sdddd", &set, '|', ptr, &filename, + &x, &y, &width, &height))) + return WPS_ERROR_INVALID_PARAM; + + if (LIST_VALUE_PARSED(set, PB_FILENAME)) /* filename */ + bmp_names[PROGRESSBAR_BMP+wps_data->progressbar_count] = filename; + + if (LIST_VALUE_PARSED(set, PB_X)) /* x */ + pb->x = x; + else + pb->x = vp->x; + + if (LIST_VALUE_PARSED(set, PB_WIDTH)) /* width */ + { + /* A zero width causes a divide-by-zero error later, so reject it */ + if (width == 0) + return WPS_ERROR_INVALID_PARAM; + + pb->width = width; + } + else + pb->width = vp->width - pb->x; + + if (LIST_VALUE_PARSED(set, PB_HEIGHT)) /* height, default to font height */ + { + /* A zero height makes no sense - reject it */ + if (height == 0) + return WPS_ERROR_INVALID_PARAM; + + pb->height = height; + } + else + pb->height = font_height; + + if (LIST_VALUE_PARSED(set, PB_Y)) /* y */ + pb->y = y; + else + pb->y = -line_num - 1; /* Will be computed during the rendering */ + + wps_data->viewports[wps_data->num_viewports].pb = pb; + wps_data->progressbar_count++; + + /* Skip the rest of the line */ + return skip_end_of_line(wps_bufptr)-1; +#else + + if (*(wps_bufptr-1) == 'f') + wps_data->full_line_progressbar = true; + else + wps_data->full_line_progressbar = false; + + return 0; + +#endif +} + +#ifdef HAVE_ALBUMART +static int parse_albumart_load(const char *wps_bufptr, + struct wps_token *token, + struct wps_data *wps_data) +{ + const char *_pos, *newline; + bool parsing; + (void)token; /* silence warning */ + + /* reset albumart info in wps */ + wps_data->albumart_max_width = -1; + wps_data->albumart_max_height = -1; + wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_CENTER; /* default */ + wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_CENTER; /* default */ + + /* format: %Cl|x|y|[[l|c|r]mwidth]|[[t|c|b]mheight]| */ + + newline = strchr(wps_bufptr, '\n'); + + /* initial validation and parsing of x and y components */ + if (*wps_bufptr != '|') + return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */ + + _pos = wps_bufptr + 1; + if (!isdigit(*_pos)) + return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl|@ */ + wps_data->albumart_x = atoi(_pos); + + _pos = strchr(_pos, '|'); + if (!_pos || _pos > newline || !isdigit(*(++_pos))) + return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl|7\n or %Cl|7|@ */ + + wps_data->albumart_y = atoi(_pos); + + _pos = strchr(_pos, '|'); + if (!_pos || _pos > newline) + return WPS_ERROR_INVALID_PARAM; /* malformed token: no | after y coordinate + e.g. %Cl|7|59\n */ + + /* parsing width field */ + parsing = true; + while (parsing) + { + /* apply each modifier in turn */ + ++_pos; + switch (*_pos) + { + case 'l': + case 'L': + case '+': + wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_LEFT; + break; + case 'c': + case 'C': + wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_CENTER; + break; + case 'r': + case 'R': + case '-': + wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_RIGHT; + break; + case 'd': + case 'D': + case 'i': + case 'I': + case 's': + case 'S': + /* simply ignored */ + break; + default: + parsing = false; + break; + } + } + /* extract max width data */ + if (*_pos != '|') + { + if (!isdigit(*_pos)) /* malformed token: e.g. %Cl|7|59|# */ + return WPS_ERROR_INVALID_PARAM; + + wps_data->albumart_max_width = atoi(_pos); + + _pos = strchr(_pos, '|'); + if (!_pos || _pos > newline) + return WPS_ERROR_INVALID_PARAM; /* malformed token: no | after width field + e.g. %Cl|7|59|200\n */ + } + + /* parsing height field */ + parsing = true; + while (parsing) + { + /* apply each modifier in turn */ + ++_pos; + switch (*_pos) + { + case 't': + case 'T': + case '-': + wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_TOP; + break; + case 'c': + case 'C': + wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_CENTER; + break; + case 'b': + case 'B': + case '+': + wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_BOTTOM; + break; + case 'd': + case 'D': + case 'i': + case 'I': + case 's': + case 'S': + /* simply ignored */ + break; + default: + parsing = false; + break; + } + } + /* extract max height data */ + if (*_pos != '|') + { + if (!isdigit(*_pos)) + return WPS_ERROR_INVALID_PARAM; /* malformed token e.g. %Cl|7|59|200|@ */ + + wps_data->albumart_max_height = atoi(_pos); + + _pos = strchr(_pos, '|'); + if (!_pos || _pos > newline) + return WPS_ERROR_INVALID_PARAM; /* malformed token: no closing | + e.g. %Cl|7|59|200|200\n */ + } + + /* if we got here, we parsed everything ok .. ! */ + if (wps_data->albumart_max_width < 0) + wps_data->albumart_max_width = 0; + else if (wps_data->albumart_max_width > LCD_WIDTH) + wps_data->albumart_max_width = LCD_WIDTH; + + if (wps_data->albumart_max_height < 0) + wps_data->albumart_max_height = 0; + else if (wps_data->albumart_max_height > LCD_HEIGHT) + wps_data->albumart_max_height = LCD_HEIGHT; + + wps_data->wps_uses_albumart = WPS_ALBUMART_LOAD; + + /* Skip the rest of the line */ + return skip_end_of_line(wps_bufptr); +} + +static int parse_albumart_conditional(const char *wps_bufptr, + struct wps_token *token, + struct wps_data *wps_data) +{ + struct wps_token *prevtoken = token; + --prevtoken; + if (wps_data->num_tokens >= 1 && prevtoken->type == WPS_TOKEN_CONDITIONAL) + { + /* This %C is part of a %?C construct. + It's either %?C<blah> or %?Cn<blah> */ + token->type = WPS_TOKEN_ALBUMART_FOUND; + if (*wps_bufptr == 'n' && *(wps_bufptr + 1) == '<') + { + token->next = true; + return 1; + } + else if (*wps_bufptr == '<') + { + return 0; + } + else + { + token->type = WPS_NO_TOKEN; + return 0; + } + } + else + { + /* This %C tag is in a conditional construct. */ + wps_data->albumart_cond_index = condindex[level]; + return 0; + } +}; +#endif /* HAVE_ALBUMART */ + +#ifdef HAVE_TOUCHSCREEN + +struct touchaction {char* s; int action;}; +static struct touchaction touchactions[] = { + {"play", ACTION_WPS_PLAY }, {"stop", ACTION_WPS_STOP }, + {"prev", ACTION_WPS_SKIPPREV }, {"next", ACTION_WPS_SKIPNEXT }, + {"ffwd", ACTION_WPS_SEEKFWD }, {"rwd", ACTION_WPS_SEEKBACK }, + {"menu", ACTION_WPS_MENU }, {"browse", ACTION_WPS_BROWSE }, + {"shuffle", ACTION_TOUCH_SHUFFLE }, {"repmode", ACTION_TOUCH_REPMODE }, + {"quickscreen", ACTION_WPS_QUICKSCREEN },{"contextmenu", ACTION_WPS_CONTEXT }, + {"playlist", ACTION_WPS_VIEW_PLAYLIST }, +}; +static int parse_touchregion(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data) +{ + (void)token; + unsigned i, imax; + struct touchregion *region; + const char *ptr = wps_bufptr; + const char *action; + int x,y,w,h; + + /* format: %T|x|y|width|height|action| + * if action starts with & the area must be held to happen + * action is one of: + * play - play/pause playback + * stop - stop playback, exit the wps + * prev - prev track + * next - next track + * ffwd - seek forward + * rwd - seek backwards + * menu - go back to the main menu + * browse - go back to the file/db browser + * shuffle - toggle shuffle mode + * repmode - cycle the repeat mode + * quickscreen - go into the quickscreen + * contextmenu - open the context menu + */ + + + if ((wps_data->touchregion_count +1 >= MAX_TOUCHREGIONS) || (*ptr != '|')) + return WPS_ERROR_INVALID_PARAM; + ptr++; + + if (!(ptr = parse_list("dddds", NULL, '|', ptr, &x, &y, &w, &h, &action))) + return WPS_ERROR_INVALID_PARAM; + + /* Check there is a terminating | */ + if (*ptr != '|') + return WPS_ERROR_INVALID_PARAM; + + /* should probably do some bounds checking here with the viewport... but later */ + region = &wps_data->touchregion[wps_data->touchregion_count]; + region->action = ACTION_NONE; + region->x = x; + region->y = y; + region->width = w; + region->height = h; + region->wvp = &wps_data->viewports[wps_data->num_viewports]; + i = 0; + if (*action == '&') + { + action++; + region->repeat = true; + } + else + region->repeat = false; + + imax = ARRAYLEN(touchactions); + while ((region->action == ACTION_NONE) && + (i < imax)) + { + /* try to match with one of our touchregion screens */ + int len = strlen(touchactions[i].s); + if (!strncmp(touchactions[i].s, action, len) + && *(action+len) == '|') + region->action = touchactions[i].action; + i++; + } + if (region->action == ACTION_NONE) + return WPS_ERROR_INVALID_PARAM; + wps_data->touchregion_count++; + return skip_end_of_line(wps_bufptr); +} +#endif + +/* Parse a generic token from the given string. Return the length read */ +static int parse_token(const char *wps_bufptr, struct wps_data *wps_data) +{ + int skip = 0, taglen = 0, ret; + struct wps_token *token = wps_data->tokens + wps_data->num_tokens; + const struct wps_tag *tag; + + switch(*wps_bufptr) + { + + case '%': + case '<': + case '|': + case '>': + case ';': + case '#': + /* escaped characters */ + token->type = WPS_TOKEN_CHARACTER; + token->value.c = *wps_bufptr; + taglen = 1; + wps_data->num_tokens++; + break; + + case '?': + /* conditional tag */ + token->type = WPS_TOKEN_CONDITIONAL; + level++; + condindex[level] = wps_data->num_tokens; + numoptions[level] = 1; + wps_data->num_tokens++; + ret = parse_token(wps_bufptr + 1, wps_data); + if (ret < 0) return ret; + taglen = 1 + ret; + break; + + default: + /* find what tag we have */ + for (tag = all_tags; + strncmp(wps_bufptr, tag->name, strlen(tag->name)) != 0; + tag++) ; + + taglen = (tag->type != WPS_TOKEN_UNKNOWN) ? strlen(tag->name) : 2; + token->type = tag->type; + wps_data->sublines[wps_data->num_sublines].line_type |= + tag->refresh_type; + + /* if the tag has a special parsing function, we call it */ + if (tag->parse_func) + { + ret = tag->parse_func(wps_bufptr + taglen, token, wps_data); + if (ret < 0) return ret; + skip += ret; + } + + /* Some tags we don't want to save as tokens */ + if (tag->type == WPS_NO_TOKEN) + break; + + /* tags that start with 'F', 'I' or 'D' are for the next file */ + if ( *(tag->name) == 'I' || *(tag->name) == 'F' || + *(tag->name) == 'D') + token->next = true; + + wps_data->num_tokens++; + break; + } + + skip += taglen; + return skip; +} + +/* Parses the WPS. + data is the pointer to the structure where the parsed WPS should be stored. + It is initialised. + wps_bufptr points to the string containing the WPS tags */ +static bool wps_parse(struct wps_data *data, const char *wps_bufptr) +{ + if (!data || !wps_bufptr || !*wps_bufptr) + return false; + + char *stringbuf = data->string_buffer; + int stringbuf_used = 0; + enum wps_parse_error fail = PARSE_OK; + int ret; + line = 1; + level = -1; + + while(*wps_bufptr && !fail && data->num_tokens < WPS_MAX_TOKENS - 1 + && data->num_viewports < WPS_MAX_VIEWPORTS + && data->num_lines < WPS_MAX_LINES) + { + switch(*wps_bufptr++) + { + + /* Regular tag */ + case '%': + if ((ret = parse_token(wps_bufptr, data)) < 0) + { + fail = PARSE_FAIL_COND_INVALID_PARAM; + break; + } + else if (level >= WPS_MAX_COND_LEVEL - 1) + { + fail = PARSE_FAIL_LIMITS_EXCEEDED; + break; + } + wps_bufptr += ret; + break; + + /* Alternating sublines separator */ + case ';': + if (level >= 0) /* there are unclosed conditionals */ + { + fail = PARSE_FAIL_UNCLOSED_COND; + break; + } + + if (data->num_sublines+1 < WPS_MAX_SUBLINES) + wps_start_new_subline(data); + else + fail = PARSE_FAIL_LIMITS_EXCEEDED; + + break; + + /* Conditional list start */ + case '<': + if (data->tokens[data->num_tokens-2].type != WPS_TOKEN_CONDITIONAL) + { + fail = PARSE_FAIL_COND_SYNTAX_ERROR; + break; + } + + data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_START; + lastcond[level] = data->num_tokens++; + break; + + /* Conditional list end */ + case '>': + if (level < 0) /* not in a conditional, invalid char */ + { + fail = PARSE_FAIL_INVALID_CHAR; + break; + } + + data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_END; + if (lastcond[level]) + data->tokens[lastcond[level]].value.i = data->num_tokens; + else + { + fail = PARSE_FAIL_COND_SYNTAX_ERROR; + break; + } + + lastcond[level] = 0; + data->num_tokens++; + data->tokens[condindex[level]].value.i = numoptions[level]; + level--; + break; + + /* Conditional list option */ + case '|': + if (level < 0) /* not in a conditional, invalid char */ + { + fail = PARSE_FAIL_INVALID_CHAR; + break; + } + + data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_OPTION; + if (lastcond[level]) + data->tokens[lastcond[level]].value.i = data->num_tokens; + else + { + fail = PARSE_FAIL_COND_SYNTAX_ERROR; + break; + } + + lastcond[level] = data->num_tokens; + numoptions[level]++; + data->num_tokens++; + break; + + /* Comment */ + case '#': + if (level >= 0) /* there are unclosed conditionals */ + { + fail = PARSE_FAIL_UNCLOSED_COND; + break; + } + + wps_bufptr += skip_end_of_line(wps_bufptr); + break; + + /* End of this line */ + case '\n': + if (level >= 0) /* there are unclosed conditionals */ + { + fail = PARSE_FAIL_UNCLOSED_COND; + break; + } + + line++; + wps_start_new_subline(data); + data->num_lines++; /* Start a new line */ + + if ((data->num_lines < WPS_MAX_LINES) && + (data->num_sublines < WPS_MAX_SUBLINES)) + { + data->lines[data->num_lines].first_subline_idx = + data->num_sublines; + + data->sublines[data->num_sublines].first_token_idx = + data->num_tokens; + } + + break; + + /* String */ + default: + { + unsigned int len = 1; + const char *string_start = wps_bufptr - 1; + + /* find the length of the string */ + while (*wps_bufptr && *wps_bufptr != '#' && + *wps_bufptr != '%' && *wps_bufptr != ';' && + *wps_bufptr != '<' && *wps_bufptr != '>' && + *wps_bufptr != '|' && *wps_bufptr != '\n') + { + wps_bufptr++; + len++; + } + + /* look if we already have that string */ + char **str; + int i; + bool found; + for (i = 0, str = data->strings, found = false; + i < data->num_strings && + !(found = (strlen(*str) == len && + strncmp(string_start, *str, len) == 0)); + i++, str++); + /* If a matching string is found, found is true and i is + the index of the string. If not, found is false */ + + if (!found) + { + /* new string */ + + if (stringbuf_used + len > STRING_BUFFER_SIZE - 1 + || data->num_strings >= WPS_MAX_STRINGS) + { + /* too many strings or characters */ + fail = PARSE_FAIL_LIMITS_EXCEEDED; + break; + } + + strlcpy(stringbuf, string_start, len+1); + + data->strings[data->num_strings] = stringbuf; + stringbuf += len + 1; + stringbuf_used += len + 1; + data->tokens[data->num_tokens].value.i = + data->num_strings; + data->num_strings++; + } + else + { + /* another occurrence of an existing string */ + data->tokens[data->num_tokens].value.i = i; + } + data->tokens[data->num_tokens].type = WPS_TOKEN_STRING; + data->num_tokens++; + } + break; + } + } + + if (!fail && level >= 0) /* there are unclosed conditionals */ + fail = PARSE_FAIL_UNCLOSED_COND; + + if (*wps_bufptr && !fail) + /* one of the limits of the while loop was exceeded */ + fail = PARSE_FAIL_LIMITS_EXCEEDED; + + data->viewports[data->num_viewports].last_line = data->num_lines - 1; + + /* We have finished with the last viewport, so increment count */ + data->num_viewports++; + +#if defined(DEBUG) || defined(SIMULATOR) + print_debug_info(data, fail, line); +#endif + + return (fail == 0); +} + +#ifdef HAVE_LCD_BITMAP +/* Clear the WPS image cache */ +static void wps_images_clear(struct wps_data *data) +{ + int i; + /* set images to unloaded and not displayed */ + for (i = 0; i < MAX_IMAGES; i++) + { + data->img[i].loaded = false; + data->img[i].display = -1; + data->img[i].always_display = false; + data->img[i].num_subimages = 1; + } +} +#endif + +/* initial setup of wps_data */ +void wps_data_init(struct wps_data *wps_data) +{ +#ifdef HAVE_LCD_BITMAP + wps_images_clear(wps_data); + wps_data->wps_sb_tag = false; + wps_data->show_sb_on_wps = false; + wps_data->img_buf_ptr = wps_data->img_buf; /* where in image buffer */ + wps_data->img_buf_free = IMG_BUFSIZE; /* free space in image buffer */ + wps_data->peak_meter_enabled = false; + /* progress bars */ + wps_data->progressbar_count = 0; +#else /* HAVE_LCD_CHARCELLS */ + int i; + for (i = 0; i < 8; i++) + { + wps_data->wps_progress_pat[i] = 0; + } + wps_data->full_line_progressbar = false; +#endif + wps_data->button_time_volume = 0; + wps_data->wps_loaded = false; +} + +static void wps_reset(struct wps_data *data) +{ +#ifdef HAVE_REMOTE_LCD + bool rwps = data->remote_wps; /* remember whether the data is for a RWPS */ +#endif + memset(data, 0, sizeof(*data)); + wps_data_init(data); +#ifdef HAVE_REMOTE_LCD + data->remote_wps = rwps; +#endif +} + +#ifdef HAVE_LCD_BITMAP + +static bool load_wps_bitmaps(struct wps_data *wps_data, char *bmpdir) +{ + char img_path[MAX_PATH]; + struct bitmap *bitmap; + bool *loaded; + int n; + for (n = 0; n < BACKDROP_BMP; n++) + { + if (bmp_names[n]) + { + get_image_filename(bmp_names[n], bmpdir, + img_path, sizeof(img_path)); + + if (n >= PROGRESSBAR_BMP ) { + /* progressbar bitmap */ + bitmap = &wps_data->progressbar[n-PROGRESSBAR_BMP].bm; + loaded = &wps_data->progressbar[n-PROGRESSBAR_BMP].have_bitmap_pb; + } else { + /* regular bitmap */ + bitmap = &wps_data->img[n].bm; + loaded = &wps_data->img[n].loaded; + } + + /* load the image */ + bitmap->data = wps_data->img_buf_ptr; + if (load_bitmap(wps_data, img_path, bitmap)) + { + *loaded = true; + + /* Calculate and store height if this image has sub-images */ + if (n < MAX_IMAGES) + wps_data->img[n].subimage_height = wps_data->img[n].bm.height / + wps_data->img[n].num_subimages; + } + else + { + /* Abort if we can't load an image */ + DEBUGF("ERR: Failed to load image %d - %s\n",n,img_path); + return false; + } + } + } + +#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)) + if (bmp_names[BACKDROP_BMP]) + { + get_image_filename(bmp_names[BACKDROP_BMP], bmpdir, + img_path, sizeof(img_path)); + +#if defined(HAVE_REMOTE_LCD) + /* We only need to check LCD type if there is a remote LCD */ + if (!wps_data->remote_wps) +#endif + { + /* Load backdrop for the main LCD */ + if (!load_wps_backdrop(img_path)) + return false; + } +#if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1 + else + { + /* Load backdrop for the remote LCD */ + if (!load_remote_wps_backdrop(img_path)) + return false; + } +#endif + } +#endif /* has backdrop support */ + + /* If we got here, everything was OK */ + return true; +} + +#endif /* HAVE_LCD_BITMAP */ + +/* to setup up the wps-data from a format-buffer (isfile = false) + from a (wps-)file (isfile = true)*/ +bool wps_data_load(struct wps_data *wps_data, + struct screen *display, + const char *buf, + bool isfile) +{ +#ifdef HAVE_ALBUMART + struct mp3entry *curtrack; + long offset; + int status; + int wps_uses_albumart = wps_data->wps_uses_albumart; + int albumart_max_height = wps_data->albumart_max_height; + int albumart_max_width = wps_data->albumart_max_width; +#endif + if (!wps_data || !buf) + return false; + + wps_reset(wps_data); + + /* Initialise the first (default) viewport */ + wps_data->viewports[0].vp.x = 0; + wps_data->viewports[0].vp.width = display->getwidth(); + wps_data->viewports[0].vp.height = display->getheight(); + switch (statusbar_position(display->screen_type)) + { + case STATUSBAR_OFF: + wps_data->viewports[0].vp.y = 0; + break; + case STATUSBAR_TOP: + wps_data->viewports[0].vp.y = STATUSBAR_HEIGHT; + wps_data->viewports[0].vp.height -= STATUSBAR_HEIGHT; + break; + case STATUSBAR_BOTTOM: + wps_data->viewports[0].vp.y = 0; + wps_data->viewports[0].vp.height -= STATUSBAR_HEIGHT; + break; + } +#ifdef HAVE_LCD_BITMAP + wps_data->viewports[0].vp.font = FONT_UI; + wps_data->viewports[0].vp.drawmode = DRMODE_SOLID; +#endif +#if LCD_DEPTH > 1 + if (display->depth > 1) + { + wps_data->viewports[0].vp.fg_pattern = display->get_foreground(); + wps_data->viewports[0].vp.bg_pattern = display->get_background(); + } +#endif + if (!isfile) + { + return wps_parse(wps_data, buf); + } + else + { + /* + * Hardcode loading WPS_DEFAULTCFG to cause a reset ideally this + * wants to be a virtual file. Feel free to modify dirbrowse() + * if you're feeling brave. + */ +#ifndef __PCTOOL__ + if (! strcmp(buf, WPS_DEFAULTCFG) ) + { + global_settings.wps_file[0] = 0; + return false; + } + +#ifdef HAVE_REMOTE_LCD + if (! strcmp(buf, RWPS_DEFAULTCFG) ) + { + global_settings.rwps_file[0] = 0; + return false; + } +#endif +#endif /* __PCTOOL__ */ + + int fd = open_utf8(buf, O_RDONLY); + + if (fd < 0) + return false; + + /* get buffer space from the plugin buffer */ + size_t buffersize = 0; + char *wps_buffer = (char *)plugin_get_buffer(&buffersize); + + if (!wps_buffer) + return false; + + /* copy the file's content to the buffer for parsing, + ensuring that every line ends with a newline char. */ + unsigned int start = 0; + while(read_line(fd, wps_buffer + start, buffersize - start) > 0) + { + start += strlen(wps_buffer + start); + if (start < buffersize - 1) + { + wps_buffer[start++] = '\n'; + wps_buffer[start] = 0; + } + } + + close(fd); + + if (start <= 0) + return false; + +#ifdef HAVE_LCD_BITMAP + /* Set all filename pointers to NULL */ + memset(bmp_names, 0, sizeof(bmp_names)); +#endif + + /* parse the WPS source */ + if (!wps_parse(wps_data, wps_buffer)) { + wps_reset(wps_data); + return false; + } + + wps_data->wps_loaded = true; + +#ifdef HAVE_LCD_BITMAP + /* get the bitmap dir */ + char bmpdir[MAX_PATH]; + char *dot = strrchr(buf, '.'); + + strlcpy(bmpdir, buf, dot - buf + 1); + + /* load the bitmaps that were found by the parsing */ + if (!load_wps_bitmaps(wps_data, bmpdir)) { + wps_reset(wps_data); + return false; + } +#endif +#ifdef HAVE_ALBUMART + status = audio_status(); + if (((!wps_uses_albumart && wps_data->wps_uses_albumart) || + (wps_data->wps_uses_albumart && + (albumart_max_height != wps_data->albumart_max_height || + albumart_max_width != wps_data->albumart_max_width))) && + status & AUDIO_STATUS_PLAY) + { + curtrack = audio_current_track(); + offset = curtrack->offset; + audio_stop(); + if (!(status & AUDIO_STATUS_PAUSE)) + audio_play(offset); + } +#endif + return true; + } +} + +int wps_subline_index(struct wps_data *data, int line, int subline) +{ + return data->lines[line].first_subline_idx + subline; +} + +int wps_first_token_index(struct wps_data *data, int line, int subline) +{ + int first_subline_idx = data->lines[line].first_subline_idx; + return data->sublines[first_subline_idx + subline].first_token_idx; +} + +int wps_last_token_index(struct wps_data *data, int line, int subline) +{ + int first_subline_idx = data->lines[line].first_subline_idx; + int idx = first_subline_idx + subline; + if (idx < data->num_sublines - 1) + { + /* This subline ends where the next begins */ + return data->sublines[idx+1].first_token_idx - 1; + } + else + { + /* The last subline goes to the end */ + return data->num_tokens - 1; + } +} |