| /*************************************************************************** |
| * __________ __ ___. |
| * 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 "config.h" |
| #include "file.h" |
| #include "misc.h" |
| #include "plugin.h" |
| #include "viewport.h" |
| |
| #ifdef __PCTOOL__ |
| #ifdef WPSEDITOR |
| #include "proxy.h" |
| #include "sysfont.h" |
| #else |
| #include "action.h" |
| #include "checkwps.h" |
| #include "audio.h" |
| #define lang_is_rtl() (false) |
| #define DEBUGF printf |
| #endif /*WPSEDITOR*/ |
| #else |
| #include "debug.h" |
| #include "language.h" |
| #endif /*__PCTOOL__*/ |
| |
| #include <ctype.h> |
| #include <stdbool.h> |
| #include "font.h" |
| |
| #include "wps_internals.h" |
| #include "skin_engine.h" |
| #include "settings.h" |
| #include "settings_list.h" |
| #if CONFIG_TUNER |
| #include "radio.h" |
| #include "tuner.h" |
| #endif |
| #include "skin_fonts.h" |
| |
| #ifdef HAVE_LCD_BITMAP |
| #include "bmp.h" |
| #endif |
| |
| #ifdef HAVE_ALBUMART |
| #include "playback.h" |
| #endif |
| |
| #include "backdrop.h" |
| #include "statusbar-skinned.h" |
| |
| #define WPS_ERROR_INVALID_PARAM -1 |
| |
| /* which screen are we parsing for? */ |
| static enum screen_type curr_screen; |
| |
| /* 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]; |
| |
| /* line number, debug only */ |
| static int line_number; |
| |
| /* the current viewport */ |
| static struct skin_viewport *curr_vp; |
| /* the current line, linked to the above viewport */ |
| static struct skin_line *curr_line; |
| |
| static int follow_lang_direction = 0; |
| |
| #if defined(DEBUG) || defined(SIMULATOR) |
| /* debugging function */ |
| extern void print_debug_info(struct wps_data *data, int fail, int line); |
| extern void debug_skin_usage(void); |
| #endif |
| |
| /* 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_and_lang(const char *wps_bufptr, |
| struct wps_token *token, struct wps_data *wps_data); |
| |
| |
| static int parse_languagedirection(const char *wps_bufptr, |
| struct wps_token *token, struct wps_data *wps_data) |
| { |
| (void)wps_bufptr; |
| (void)token; |
| (void)wps_data; |
| follow_lang_direction = 2; /* 2 because it is decremented immediatly after |
| this token is parsed, after the next token it |
| will be 0 again. */ |
| return 0; |
| } |
| |
| #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_playlistview(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_statusbar_inbuilt(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); |
| static int parse_font_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_viewportcolour(const char *wps_bufptr, |
| struct wps_token *token, struct wps_data *wps_data); |
| 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_display(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_LEFT_RTL, "aL", 0, NULL }, |
| { WPS_TOKEN_ALIGN_RIGHT, "ar", 0, NULL }, |
| { WPS_TOKEN_ALIGN_RIGHT_RTL, "aR", 0, NULL }, |
| { WPS_NO_TOKEN, "ax", 0, parse_languagedirection }, |
| |
| { WPS_TOKEN_BATTERY_PERCENT, "bl", WPS_REFRESH_DYNAMIC, parse_progressbar }, |
| { 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 |
| #ifdef HAVE_USB_POWER |
| { WPS_TOKEN_USB_POWERED, "bu", 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_CODEC == SWCODEC) |
| { WPS_TOKEN_SOUND_SPEED, "Ss", 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, |
| parse_progressbar }, |
| |
| { 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_TRACK_STARTING, "pS", WPS_REFRESH_DYNAMIC, parse_timeout }, |
| { WPS_TOKEN_TRACK_ENDING, "pE", WPS_REFRESH_DYNAMIC, parse_timeout }, |
| |
| { 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_TOKEN_HAVE_TUNER, "tp", WPS_REFRESH_STATIC, NULL }, |
| #if CONFIG_TUNER /* Re-uses the 't' and 'T' prefixes, be careful about doubleups */ |
| { WPS_TOKEN_TUNER_TUNED, "tt", WPS_REFRESH_DYNAMIC, NULL }, |
| { WPS_TOKEN_TUNER_SCANMODE, "tm", WPS_REFRESH_DYNAMIC, NULL }, |
| { WPS_TOKEN_TUNER_STEREO, "ts", WPS_REFRESH_DYNAMIC, NULL }, |
| { WPS_TOKEN_TUNER_MINFREQ, "ta", WPS_REFRESH_STATIC, NULL }, |
| { WPS_TOKEN_TUNER_MAXFREQ, "tb", WPS_REFRESH_STATIC, NULL }, |
| { WPS_TOKEN_TUNER_CURFREQ, "tf", WPS_REFRESH_DYNAMIC, NULL }, |
| { WPS_TOKEN_PRESET_ID, "Ti", WPS_REFRESH_STATIC, NULL }, |
| { WPS_TOKEN_PRESET_NAME, "Tn", WPS_REFRESH_STATIC, NULL }, |
| { WPS_TOKEN_PRESET_FREQ, "Tf", WPS_REFRESH_STATIC, NULL }, |
| { WPS_TOKEN_PRESET_COUNT, "Tc", WPS_REFRESH_STATIC, NULL }, |
| { WPS_TOKEN_HAVE_RDS, "tx", WPS_REFRESH_STATIC, NULL }, |
| #ifdef HAVE_RDS_CAP |
| { WPS_TOKEN_RDS_NAME, "ty", WPS_REFRESH_DYNAMIC, NULL }, |
| { WPS_TOKEN_RDS_TEXT, "tz", WPS_REFRESH_DYNAMIC, NULL }, |
| #endif |
| #endif /* CONFIG_TUNER */ |
| |
| { 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_TOKEN_DRAW_INBUILTBAR, "wi", WPS_REFRESH_DYNAMIC, parse_statusbar_inbuilt }, |
| |
| { 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 }, |
| { WPS_NO_TOKEN, "Fl", 0, parse_font_load }, |
| #ifdef HAVE_ALBUMART |
| { WPS_NO_TOKEN, "Cl", 0, parse_albumart_load }, |
| { WPS_TOKEN_ALBUMART_DISPLAY, "Cd", WPS_REFRESH_STATIC, parse_albumart_display }, |
| #endif |
| |
| { WPS_VIEWPORT_ENABLE, "Vd", WPS_REFRESH_DYNAMIC, |
| parse_viewport_display }, |
| { WPS_TOKEN_UIVIEWPORT_ENABLE, "VI", WPS_REFRESH_STATIC, |
| parse_viewport_display }, |
| #ifdef HAVE_LCD_BITMAP |
| { WPS_VIEWPORT_CUSTOMLIST, "Vp", WPS_REFRESH_STATIC, parse_playlistview }, |
| { WPS_TOKEN_LIST_TITLE_TEXT, "Lt", WPS_REFRESH_DYNAMIC, NULL }, |
| { WPS_TOKEN_LIST_TITLE_ICON, "Li", WPS_REFRESH_DYNAMIC, NULL }, |
| #endif |
| #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)) |
| { WPS_TOKEN_VIEWPORT_FGCOLOUR, "Vf", WPS_REFRESH_STATIC, parse_viewportcolour }, |
| { WPS_TOKEN_VIEWPORT_BGCOLOUR, "Vb", WPS_REFRESH_STATIC, parse_viewportcolour }, |
| #endif |
| { 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_and_lang }, |
| { WPS_TOKEN_TRANSLATEDSTRING, "Sx", WPS_REFRESH_STATIC, |
| parse_setting_and_lang }, |
| { WPS_TOKEN_LANG_IS_RTL , "Sr", WPS_REFRESH_STATIC, NULL }, |
| |
| { WPS_TOKEN_LASTTOUCH, "Tl", WPS_REFRESH_DYNAMIC, parse_timeout }, |
| { WPS_TOKEN_CURRENT_SCREEN, "cs", WPS_REFRESH_DYNAMIC, NULL }, |
| { WPS_NO_TOKEN, "T", 0, parse_touchregion }, |
| |
| |
| /* Recording Tokens */ |
| { WPS_TOKEN_HAVE_RECORDING, "Rp", WPS_REFRESH_STATIC, NULL }, |
| #ifdef HAVE_RECORDING |
| { WPS_TOKEN_IS_RECORDING, "Rr", WPS_REFRESH_DYNAMIC, NULL }, |
| { WPS_TOKEN_REC_FREQ, "Rf", WPS_REFRESH_DYNAMIC, NULL }, |
| { WPS_TOKEN_REC_ENCODER, "Re", WPS_REFRESH_DYNAMIC, NULL }, |
| { WPS_TOKEN_REC_BITRATE, "Rb", WPS_REFRESH_DYNAMIC, NULL }, |
| { WPS_TOKEN_REC_MONO, "Rm", WPS_REFRESH_DYNAMIC, NULL }, |
| { WPS_TOKEN_REC_SECONDS, "Rs", WPS_REFRESH_DYNAMIC, NULL }, |
| { WPS_TOKEN_REC_MINUTES, "Rn", WPS_REFRESH_DYNAMIC, NULL }, |
| { WPS_TOKEN_REC_HOURS, "Rh", WPS_REFRESH_DYNAMIC, NULL }, |
| #endif |
| { WPS_TOKEN_UNKNOWN, "", 0, NULL } |
| /* the array MUST end with an empty string (first char is \0) */ |
| }; |
| |
| |
| /* add a skin_token_list item to the list chain. ALWAYS appended because some of the |
| * chains require the order to be kept. |
| */ |
| static void add_to_ll_chain(struct skin_token_list **list, struct skin_token_list *item) |
| { |
| if (*list == NULL) |
| *list = item; |
| else |
| { |
| struct skin_token_list *t = *list; |
| while (t->next) |
| t = t->next; |
| t->next = item; |
| } |
| } |
| |
| /* traverse the image linked-list for an image */ |
| #ifdef HAVE_LCD_BITMAP |
| struct gui_img* find_image(char label, struct wps_data *data) |
| { |
| struct skin_token_list *list = data->images; |
| while (list) |
| { |
| struct gui_img *img = (struct gui_img *)list->token->value.data; |
| if (img->label == label) |
| return img; |
| list = list->next; |
| } |
| return NULL; |
| } |
| |
| #endif |
| |
| /* traverse the viewport linked list for a viewport */ |
| struct skin_viewport* find_viewport(char label, struct wps_data *data) |
| { |
| struct skin_token_list *list = data->viewports; |
| while (list) |
| { |
| struct skin_viewport *vp = (struct skin_viewport *)list->token->value.data; |
| if (vp->label == label) |
| return vp; |
| list = list->next; |
| } |
| return NULL; |
| } |
| |
| |
| /* create and init a new wpsll item. |
| * passing NULL to token will alloc a new one. |
| * You should only pass NULL for the token when the token type (table above) |
| * is WPS_NO_TOKEN which means it is not stored automatically in the skins token array |
| */ |
| static struct skin_token_list *new_skin_token_list_item(struct wps_token *token, |
| void* token_data) |
| { |
| struct skin_token_list *llitem = skin_buffer_alloc(sizeof(struct skin_token_list)); |
| if (!token) |
| token = skin_buffer_alloc(sizeof(struct wps_token)); |
| if (!llitem || !token) |
| return NULL; |
| llitem->next = NULL; |
| llitem->token = token; |
| if (token_data) |
| llitem->token->value.data = token_data; |
| return llitem; |
| } |
| |
| /* 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_number++; |
| int skip = 0; |
| while(*(wps_bufptr + skip) != '\n') |
| skip++; |
| return ++skip; |
| } |
| |
| /* Starts a new subline in the current line during parsing */ |
| static bool skin_start_new_subline(struct skin_line *line, int curr_token) |
| { |
| struct skin_subline *subline = skin_buffer_alloc(sizeof(struct skin_subline)); |
| if (!subline) |
| return false; |
| |
| subline->first_token_idx = curr_token; |
| subline->next = NULL; |
| |
| subline->line_type = 0; |
| subline->time_mult = 0; |
| |
| line->curr_subline->last_token_idx = curr_token-1; |
| line->curr_subline->next = subline; |
| line->curr_subline = subline; |
| return true; |
| } |
| |
| static bool skin_start_new_line(struct skin_viewport *vp, int curr_token) |
| { |
| struct skin_line *line = skin_buffer_alloc(sizeof(struct skin_line)); |
| struct skin_subline *subline = NULL; |
| if (!line) |
| return false; |
| |
| /* init the subline */ |
| subline = &line->sublines; |
| subline->first_token_idx = curr_token; |
| subline->next = NULL; |
| subline->line_type = 0; |
| subline->time_mult = 0; |
| |
| /* init the new line */ |
| line->curr_subline = &line->sublines; |
| line->next = NULL; |
| line->subline_expire_time = 0; |
| |
| /* connect to curr_line and vp pointers. |
| * 1) close the previous lines subline |
| * 2) connect to vp pointer |
| * 3) connect to curr_line global pointer |
| */ |
| if (curr_line) |
| { |
| curr_line->curr_subline->last_token_idx = curr_token - 1; |
| curr_line->next = line; |
| curr_line->curr_subline = NULL; |
| } |
| curr_line = line; |
| if (!vp->lines) |
| vp->lines = line; |
| return true; |
| } |
| |
| #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; |
| struct skin_viewport *default_vp = find_viewport(VP_DEFAULT_LABEL, wps_data); |
| viewport_set_defaults(&default_vp->vp, curr_screen); |
| default_vp->vp.font = FONT_UI; |
| 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; |
| struct skin_viewport *default_vp = find_viewport(VP_DEFAULT_LABEL, wps_data); |
| viewport_set_fullscreen(&default_vp->vp, curr_screen); |
| default_vp->vp.font = FONT_UI; |
| return skip_end_of_line(wps_bufptr); |
| } |
| |
| static int parse_statusbar_inbuilt(const char *wps_bufptr, |
| struct wps_token *token, struct wps_data *wps_data) |
| { |
| (void)wps_data; |
| token->value.data = (void*)&curr_vp->vp; |
| return skip_end_of_line(wps_bufptr); |
| } |
| |
| 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; |
| } |
| |
| char *get_image_filename(const char *start, const char* bmpdir, |
| char *buf, int buf_size) |
| { |
| const char *end = start; |
| int bmpdirlen = strlen(bmpdir); |
| |
| while (*end && *end != ',' && *end != ')') |
| end++; |
| if ( !end || (end - start) >= (buf_size - bmpdirlen - 2) ) |
| { |
| buf[0] = '\0'; |
| return NULL; |
| } |
| |
| 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) |
| { |
| char label = wps_bufptr[1]; |
| int subimage; |
| struct gui_img *img;; |
| |
| /* sanity check */ |
| img = find_image(label, wps_data); |
| if (!img) |
| { |
| token->value.i = label; /* so debug works */ |
| return WPS_ERROR_INVALID_PARAM; |
| } |
| |
| if ((subimage = get_image_id(wps_bufptr[2])) != -1) |
| { |
| if (subimage >= img->num_subimages) |
| return WPS_ERROR_INVALID_PARAM; |
| |
| /* Store sub-image number to display in high bits */ |
| token->value.i = label | (subimage << 8); |
| return 4; /* We have consumed 2 bytes */ |
| } else { |
| token->value.i = label; |
| return 3; /* We have consumed 1 byte */ |
| } |
| } |
| |
| static int parse_image_load(const char *wps_bufptr, |
| struct wps_token *token, |
| struct wps_data *wps_data) |
| { |
| const char *ptr = wps_bufptr; |
| const char* filename; |
| const char* id; |
| int x,y; |
| struct gui_img *img; |
| |
| /* 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 != ')' && *ptr != ',') |
| return WPS_ERROR_INVALID_PARAM; |
| |
| /* check the image number and load state */ |
| if(find_image(*id, wps_data)) |
| { |
| /* Invalid image ID */ |
| return WPS_ERROR_INVALID_PARAM; |
| } |
| img = skin_buffer_alloc(sizeof(struct gui_img)); |
| if (!img) |
| return WPS_ERROR_INVALID_PARAM; |
| /* save a pointer to the filename */ |
| img->bm.data = (char*)filename; |
| img->label = *id; |
| img->x = x; |
| img->y = y; |
| img->num_subimages = 1; |
| img->always_display = false; |
| |
| /* save current viewport */ |
| img->vp = &curr_vp->vp; |
| |
| if (token->type == WPS_TOKEN_IMAGE_DISPLAY) |
| { |
| img->always_display = true; |
| } |
| else if (*ptr == ',') |
| { |
| /* Parse the (optional) number of sub-images */ |
| ptr++; |
| img->num_subimages = atoi(ptr); |
| if (img->num_subimages <= 0) |
| return WPS_ERROR_INVALID_PARAM; |
| } |
| struct skin_token_list *item = new_skin_token_list_item(NULL, img); |
| if (!item) |
| return WPS_ERROR_INVALID_PARAM; |
| add_to_ll_chain(&wps_data->images, item); |
| |
| /* Skip the rest of the line */ |
| return skip_end_of_line(wps_bufptr); |
| } |
| struct skin_font { |
| int id; /* the id from font_load */ |
| char *name; /* filename without path and extension */ |
| }; |
| static struct skin_font skinfonts[MAXUSERFONTS]; |
| static int parse_font_load(const char *wps_bufptr, |
| struct wps_token *token, struct wps_data *wps_data) |
| { |
| (void)wps_data; (void)token; |
| const char *ptr = wps_bufptr; |
| int id; |
| char *filename; |
| |
| if (*ptr != '(') |
| return WPS_ERROR_INVALID_PARAM; |
| |
| ptr++; |
| |
| if (!(ptr = parse_list("ds", NULL, ',', ptr, &id, &filename))) |
| return WPS_ERROR_INVALID_PARAM; |
| |
| /* Check there is a terminating | */ |
| if (*ptr != ')') |
| return WPS_ERROR_INVALID_PARAM; |
| |
| if (id <= FONT_UI || id >= MAXFONTS-1) |
| return WPS_ERROR_INVALID_PARAM; |
| #if defined(DEBUG) || defined(SIMULATOR) |
| if (skinfonts[id-FONT_FIRSTUSERFONT].name != NULL) |
| { |
| DEBUGF("font id %d already being used\n", id); |
| } |
| #endif |
| /* make sure the filename contains .fnt, |
| * we dont actually use it, but require it anyway */ |
| ptr = strchr(filename, '.'); |
| if (!ptr || strncmp(ptr, ".fnt)", 5)) |
| return WPS_ERROR_INVALID_PARAM; |
| skinfonts[id-FONT_FIRSTUSERFONT].id = -1; |
| skinfonts[id-FONT_FIRSTUSERFONT].name = filename; |
| |
| 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[1]; |
| |
| if (letter < 'a' || letter > 'z') |
| { |
| /* invalid viewport tag */ |
| return WPS_ERROR_INVALID_PARAM; |
| } |
| token->value.i = letter; |
| return 3; |
| } |
| |
| #ifdef HAVE_LCD_BITMAP |
| static int parse_playlistview_text(struct playlistviewer *viewer, |
| enum info_line_type line, char* text) |
| { |
| int cur_string = 0; |
| const struct wps_tag *tag; |
| int taglen = 0; |
| const char *start = text; |
| if (*text != ',') |
| return -1; |
| text++; |
| viewer->lines[line].count = 0; |
| viewer->lines[line].scroll = false; |
| while (*text != ',' && *text != ')') |
| { |
| if (*text == '%') /* it is a token of some type */ |
| { |
| text++; |
| taglen = 0; |
| switch(*text) |
| { |
| case '%': |
| case '<': |
| case '|': |
| case '>': |
| case ';': |
| case '#': |
| case '(': |
| case ')': |
| case ',': |
| /* escaped characters */ |
| viewer->lines[line].tokens[viewer->lines[line].count++] = WPS_TOKEN_CHARACTER; |
| viewer->lines[line].strings[cur_string][0] = *text; |
| viewer->lines[line].strings[cur_string++][1] = '\0'; |
| text++; |
| break; |
| default: |
| for (tag = all_tags; |
| strncmp(text, tag->name, strlen(tag->name)) != 0; |
| tag++) ; |
| /* %s isnt stored as a tag so manually check for it */ |
| if (tag->type == WPS_NO_TOKEN) |
| { |
| if (!strncmp(tag->name, "s", 1)) |
| { |
| viewer->lines[line].scroll = true; |
| taglen = 1; |
| } |
| } |
| else if (tag->type == WPS_TOKEN_UNKNOWN) |
| { |
| int i = 0; |
| /* just copy the string */ |
| viewer->lines[line].tokens[viewer->lines[line].count++] = WPS_TOKEN_STRING; |
| while (i<(MAX_PLAYLISTLINE_STRLEN-1) && text[i] != ',' && text[i] != ')' && text[i] != '%') |
| { |
| viewer->lines[line].strings[cur_string][i] = text[i]; |
| i++; |
| } |
| viewer->lines[line].strings[cur_string][i] = '\0'; |
| cur_string++; |
| taglen = i; |
| } |
| else |
| { |
| if (tag->parse_func) |
| { |
| /* unsupported tag, reject */ |
| return -1; |
| } |
| taglen = strlen(tag->name); |
| viewer->lines[line].tokens[viewer->lines[line].count++] = tag->type; |
| } |
| text += taglen; |
| } |
| } |
| else |
| { |
| /* regular string */ |
| int i = 0; |
| /* just copy the string */ |
| viewer->lines[line].tokens[viewer->lines[line].count++] = WPS_TOKEN_STRING; |
| while (i<(MAX_PLAYLISTLINE_STRLEN-1) && text[i] != ',' && text[i] != ')' && text[i] != '%') |
| { |
| viewer->lines[line].strings[cur_string][i] = text[i]; |
| i++; |
| } |
| viewer->lines[line].strings[cur_string][i] = '\0'; |
| cur_string++; |
| text += i; |
| } |
| } |
| return text - start; |
| } |
| |
| static int parse_playlistview(const char *wps_bufptr, |
| struct wps_token *token, struct wps_data *wps_data) |
| { |
| (void)wps_data; |
| /* %Vp|<use icons>|<start offset>|info line text|no info text| */ |
| struct playlistviewer *viewer = skin_buffer_alloc(sizeof(struct playlistviewer)); |
| char *ptr = strchr(wps_bufptr, '('); |
| int length; |
| if (!viewer || !ptr) |
| return WPS_ERROR_INVALID_PARAM; |
| viewer->vp = &curr_vp->vp; |
| viewer->show_icons = true; |
| viewer->start_offset = atoi(ptr+1); |
| token->value.data = (void*)viewer; |
| ptr = strchr(ptr+1, ','); |
| length = parse_playlistview_text(viewer, TRACK_HAS_INFO, ptr); |
| if (length < 0) |
| return WPS_ERROR_INVALID_PARAM; |
| length = parse_playlistview_text(viewer, TRACK_HAS_NO_INFO, ptr+length); |
| if (length < 0) |
| return WPS_ERROR_INVALID_PARAM; |
| |
| return skip_end_of_line(wps_bufptr); |
| } |
| #endif |
| |
| 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 skin_viewport *skin_vp = skin_buffer_alloc(sizeof(struct skin_viewport)); |
| |
| /* check for the optional letter to signify its a hideable viewport */ |
| /* %Vl|<label>|<rest of tags>| */ |
| skin_vp->hidden_flags = 0; |
| skin_vp->label = VP_NO_LABEL; |
| skin_vp->lines = NULL; |
| if (curr_line) |
| { |
| curr_line->curr_subline->last_token_idx = wps_data->num_tokens |
| - (wps_data->num_tokens > 0 ? 1 : 0); |
| } |
| |
| curr_line = NULL; |
| if (!skin_start_new_line(skin_vp, wps_data->num_tokens)) |
| return WPS_ERROR_INVALID_PARAM; |
| |
| if (*ptr == 'i') |
| { |
| if (*(ptr+1) == '(') |
| { |
| char label = *(ptr+2); |
| if (label >= 'a' && label <= 'z') |
| { |
| skin_vp->hidden_flags = VP_NEVER_VISIBLE; |
| skin_vp->label = VP_INFO_LABEL|label; |
| ptr += 3; |
| } |
| else |
| { |
| if (label != '-') |
| return WPS_ERROR_INVALID_PARAM; |
| skin_vp->label = VP_INFO_LABEL|VP_DEFAULT_LABEL; |
| skin_vp->hidden_flags = VP_NEVER_VISIBLE; |
| ptr += 3; |
| } |
| } |
| else |
| return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */ |
| |
| } |
| else if (*ptr == 'l') |
| { |
| if (*(ptr+1) == '(') |
| { |
| char label = *(ptr+2); |
| if (label >= 'a' && label <= 'z') |
| { |
| skin_vp->hidden_flags = VP_DRAW_HIDEABLE; |
| skin_vp->label = label; |
| } |
| else |
| return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */ |
| ptr += 3; |
| } |
| } |
| if (*ptr != ',' && *ptr != '(') |
| return WPS_ERROR_INVALID_PARAM; |
| |
| ptr++; |
| struct viewport *vp = &skin_vp->vp; |
| /* format: %V|x|y|width|height|font| */ |
| if (!(ptr = viewport_parse_viewport(vp, curr_screen, ptr, ','))) |
| return WPS_ERROR_INVALID_PARAM; |
| |
| /* Check for trailing ) */ |
| if (*ptr != ')') |
| return WPS_ERROR_INVALID_PARAM; |
| ptr++; |
| |
| if (follow_lang_direction && lang_is_rtl()) |
| { |
| vp->flags |= VP_FLAG_ALIGN_RIGHT; |
| vp->x = screens[curr_screen].lcdwidth - vp->width - vp->x; |
| } |
| else |
| vp->flags &= ~VP_FLAG_ALIGN_RIGHT; /* ignore right-to-left languages */ |
| |
| #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)) |
| skin_vp->start_fgcolour = vp->fg_pattern; |
| skin_vp->start_bgcolour = vp->bg_pattern; |
| #endif |
| |
| struct skin_token_list *list = new_skin_token_list_item(NULL, skin_vp); |
| if (!list) |
| return WPS_ERROR_INVALID_PARAM; |
| add_to_ll_chain(&wps_data->viewports, list); |
| curr_vp = skin_vp; |
| /* Skip the rest of the line */ |
| return ptr-wps_bufptr; |
| } |
| #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)) |
| static int parse_viewportcolour(const char *wps_bufptr, |
| struct wps_token *token, struct wps_data *wps_data) |
| { |
| (void)wps_data; |
| const char *ptr = wps_bufptr; |
| int i; |
| bool found_text; |
| struct viewport_colour *colour = skin_buffer_alloc(sizeof(struct viewport_colour)); |
| uint32_t set; |
| if (*ptr != '(' || !colour) |
| return -1; |
| ptr++; |
| if (!(ptr = parse_list("c", &set, ',', ptr, &colour->colour))) |
| return -1; |
| if (*ptr != ')') |
| return -1; |
| if (!set) |
| colour->colour = get_viewport_default_colour(curr_screen, |
| token->type == WPS_TOKEN_VIEWPORT_FGCOLOUR); |
| colour->vp = &curr_vp->vp; |
| token->value.data = colour; |
| /* If there havnt been any text tags between the %V() line and here use |
| * the colour as the viewport colour. fixes scrolling lines not |
| * having the correct colour */ |
| i = curr_vp->lines->sublines.first_token_idx; |
| found_text = false; |
| while (!found_text && i< curr_vp->lines->sublines.last_token_idx) |
| { |
| if (wps_data->tokens[i++].type != WPS_TOKEN_CHARACTER && |
| wps_data->tokens[i++].type != WPS_TOKEN_VIEWPORT_FGCOLOUR && |
| wps_data->tokens[i++].type != WPS_TOKEN_VIEWPORT_BGCOLOUR ) |
| found_text = true; |
| } |
| if (!found_text) |
| { |
| if (token->type == WPS_TOKEN_VIEWPORT_FGCOLOUR) |
| { |
| curr_vp->start_fgcolour = colour->colour; |
| curr_vp->vp.fg_pattern = colour->colour; |
| } |
| else |
| { |
| curr_vp->start_bgcolour = colour->colour; |
| curr_vp->vp.bg_pattern = colour->colour; |
| } |
| } |
| ptr++; |
| return ptr - wps_bufptr; |
| } |
| |
| 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; |
| bool error = false; |
| |
| pos = strchr(wps_bufptr + 1, ')'); |
| newline = strchr(wps_bufptr, '\n'); |
| |
| error = (pos > newline); |
| |
| #if LCD_DEPTH > 1 |
| if (token->type == WPS_TOKEN_IMAGE_BACKDROP) |
| { |
| /* format: %X|filename.bmp| or %Xd */ |
| if (!strncmp(wps_bufptr, "(d)", 3)) |
| { |
| wps_data->backdrop = NULL; |
| return skip_end_of_line(wps_bufptr); |
| } |
| else if (!error) |
| wps_data->backdrop = (char*)wps_bufptr + 1; |
| } |
| #endif |
| if (error) |
| return WPS_ERROR_INVALID_PARAM; |
| /* Skip the rest of the line */ |
| return skip_end_of_line(wps_bufptr); |
| } |
| #endif |
| |
| #endif /* HAVE_LCD_BITMAP */ |
| |
| static int parse_setting_and_lang(const char *wps_bufptr, |
| struct wps_token *token, |
| struct wps_data *wps_data) |
| { |
| /* NOTE: both the string validations that happen in here will |
| * automatically PASS on checkwps because its too hard to get |
| * settings_list.c and englinsh.lang built for it. |
| * If that ever changes remove the #ifndef __PCTOOL__'s here |
| */ |
| (void)wps_data; |
| const char *ptr = wps_bufptr; |
| const char *end; |
| int i = 0; |
| char temp[64]; |
| |
| /* Find the setting's cfg_name */ |
| if (*ptr != '(') |
| return WPS_ERROR_INVALID_PARAM; |
| ptr++; |
| end = strchr(ptr,')'); |
| if (!end || (size_t)(end-ptr+1) > sizeof temp) |
| return WPS_ERROR_INVALID_PARAM; |
| strlcpy(temp, ptr,end-ptr+1); |
| |
| if (token->type == WPS_TOKEN_TRANSLATEDSTRING) |
| { |
| #ifndef __PCTOOL__ |
| i = lang_english_to_id(temp); |
| if (i < 0) |
| return WPS_ERROR_INVALID_PARAM; |
| #endif |
| } |
| else |
| { |
| /* Find the setting */ |
| for (i=0; i<nb_settings; i++) |
| if (settings[i].cfg_name && |
| !strcmp(settings[i].cfg_name, temp)) |
| break; |
| #ifndef __PCTOOL__ |
| if (i == nb_settings) |
| return WPS_ERROR_INVALID_PARAM; |
| #endif |
| } |
| /* 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[1], '\0' }; |
| if (wps_bufptr[0] != '(' || wps_bufptr[2] != ')') |
| return WPS_ERROR_INVALID_PARAM; |
| token->value.i = atoi(val); |
| (void)wps_data; /* Kill warnings */ |
| return 3; |
| } |
| |
| 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 */ |
| if (*wps_bufptr == '(') |
| { |
| wps_bufptr++; |
| skip++; |
| 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 (*wps_bufptr != ')') |
| return -1; |
| 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: |
| case WPS_TOKEN_TRACK_STARTING: |
| case WPS_TOKEN_TRACK_ENDING: |
| 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) |
| { |
| /* %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 = skin_buffer_alloc(sizeof(struct progressbar)); |
| struct skin_token_list *item = new_skin_token_list_item(token, pb); |
| |
| if (!pb || !item) |
| return WPS_ERROR_INVALID_PARAM; |
| |
| struct viewport *vp = &curr_vp->vp; |
| /* we need to know what line number (viewport relative) this pb is, |
| * so count them... */ |
| int line_num = -1; |
| struct skin_line *line = curr_vp->lines; |
| while (line) |
| { |
| line_num++; |
| line = line->next; |
| } |
| pb->vp = vp; |
| pb->have_bitmap_pb = false; |
| pb->bm.data = NULL; /* no bitmap specified */ |
| pb->follow_lang_direction = follow_lang_direction > 0; |
| pb->draw = 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 */ |
| if (token->type == WPS_TOKEN_VOLUME || token->type == WPS_TOKEN_BATTERY_PERCENT) |
| return 0; /* dont add it, let the regular token handling do the work */ |
| pb->type = token->type; |
| add_to_ll_chain(&wps_data->progressbars, item); |
| return 0; |
| } |
| ptr = wps_bufptr + 1; |
| |
| if (!(ptr = parse_list("sdddd", &set, ',', ptr, &filename, |
| &x, &y, &width, &height))) |
| { |
| /* If we are in a conditional then we probably don't want to fail |
| * if the above doesnt work. So assume the | is breaking the conditional |
| * and move on. The next token will fail if this is incorrect. */ |
| if (level >= 0) |
| return 0; |
| return WPS_ERROR_INVALID_PARAM; |
| } |
| |
| if (LIST_VALUE_PARSED(set, PB_FILENAME)) /* filename */ |
| pb->bm.data = (char*)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 |
| { |
| if (vp->font > FONT_UI) |
| pb->height = -1; /* calculate at display time */ |
| else |
| { |
| #ifndef __PCTOOL__ |
| pb->height = font_get(vp->font)->height; |
| #else |
| pb->height = 8; |
| #endif |
| } |
| } |
| |
| if (LIST_VALUE_PARSED(set, PB_Y)) /* y */ |
| pb->y = y; |
| else |
| pb->y = -line_num - 1; /* Will be computed during the rendering */ |
| |
| add_to_ll_chain(&wps_data->progressbars, item); |
| if (token->type == WPS_TOKEN_VOLUME) |
| token->type = WPS_TOKEN_VOLUMEBAR; |
| else if (token->type == WPS_TOKEN_BATTERY_PERCENT) |
| token->type = WPS_TOKEN_BATTERY_PERCENTBAR; |
| pb->type = token->type; |
| |
| return ptr+1-wps_bufptr; |
| #else |
| (void)wps_bufptr; |
| if (token->type != WPS_TOKEN_VOLUME && |
| token->type != WPS_TOKEN_BATTERY_PERCENTBAR) |
| { |
| wps_data->full_line_progressbar = |
| token->type == WPS_TOKEN_PLAYER_PROGRESSBAR; |
| } |
| 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 *ptr = wps_bufptr; |
| struct dim dimensions; |
| int albumart_slot; |
| bool swap_for_rtl = lang_is_rtl() && follow_lang_direction; |
| struct skin_albumart *aa = skin_buffer_alloc(sizeof(struct skin_albumart)); |
| (void)token; /* silence warning */ |
| if (!aa) |
| return skip_end_of_line(wps_bufptr); |
| |
| /* reset albumart info in wps */ |
| aa->width = -1; |
| aa->height = -1; |
| aa->xalign = WPS_ALBUMART_ALIGN_CENTER; /* default */ |
| aa->yalign = WPS_ALBUMART_ALIGN_CENTER; /* default */ |
| aa->vp = &curr_vp->vp; |
| |
| if (*ptr != '(') |
| return WPS_ERROR_INVALID_PARAM; |
| ptr++; |
| /* format: %Cl|x|y|[[l|c|r]mwidth]|[[t|c|b]mheight]| */ |
| if (!(ptr = parse_list("dddd", NULL,',',ptr, &aa->x, &aa->y, &aa->width, &aa->height))) |
| return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */ |
| |
| /* if we got here, we parsed everything ok .. ! */ |
| if (aa->width < 0) |
| aa->width = 0; |
| else if (aa->width > LCD_WIDTH) |
| aa->width = LCD_WIDTH; |
| |
| if (aa->height < 0) |
| aa->height = 0; |
| else if (aa->height > LCD_HEIGHT) |
| aa->height = LCD_HEIGHT; |
| |
| if (swap_for_rtl) |
| aa->x = LCD_WIDTH - (aa->x + aa->width); |
| |
| aa->state = WPS_ALBUMART_LOAD; |
| aa->draw = false; |
| wps_data->albumart = aa; |
| |
| dimensions.width = aa->width; |
| dimensions.height = aa->height; |
| |
| albumart_slot = playback_claim_aa_slot(&dimensions); |
| |
| if (0 <= albumart_slot) |
| wps_data->playback_aa_slot = albumart_slot; |
| |
| if (*ptr == ')') |
| return skip_end_of_line(wps_bufptr); |
| else if (*ptr != ',') |
| return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */ |
| ptr++; |
| switch (*ptr) |
| { |
| case 'l': |
| case 'L': |
| if (swap_for_rtl) |
| aa->xalign = WPS_ALBUMART_ALIGN_RIGHT; |
| else |
| aa->xalign = WPS_ALBUMART_ALIGN_LEFT; |
| break; |
| case 'c': |
| case 'C': |
| aa->xalign = WPS_ALBUMART_ALIGN_CENTER; |
| break; |
| case 'r': |
| case 'R': |
| if (swap_for_rtl) |
| aa->xalign = WPS_ALBUMART_ALIGN_LEFT; |
| else |
| aa->xalign = WPS_ALBUMART_ALIGN_RIGHT; |
| break; |
| } |
| ptr++; |
| if (*ptr == ')') |
| return skip_end_of_line(wps_bufptr); |
| else if (*ptr != ',') |
| return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */ |
| ptr++; |
| switch (*ptr) |
| { |
| case 't': |
| case 'T': |
| aa->yalign = WPS_ALBUMART_ALIGN_TOP; |
| break; |
| case 'c': |
| case 'C': |
| aa->yalign = WPS_ALBUMART_ALIGN_CENTER; |
| break; |
| case 'b': |
| case 'B': |
| aa->yalign = WPS_ALBUMART_ALIGN_BOTTOM; |
| break; |
| case 'd': |
| case 'D': |
| case 'i': |
| case 'I': |
| case 's': |
| case 'S': |
| /* simply ignored */ |
| break; |
| } |
| return skip_end_of_line(wps_bufptr); |
| } |
| |
| static int parse_albumart_display(const char *wps_bufptr, |
| struct wps_token *token, |
| struct wps_data *wps_data) |
| { |
| (void)wps_bufptr; |
| struct wps_token *prev = token-1; |
| if ((wps_data->num_tokens >= 1) && (prev->type == WPS_TOKEN_CONDITIONAL)) |
| { |
| token->type = WPS_TOKEN_ALBUMART_FOUND; |
| } |
| else if (wps_data->albumart) |
| { |
| wps_data->albumart->vp = &curr_vp->vp; |
| } |
| #if 0 |
| /* the old code did this so keep it here for now... |
| * this is to allow the posibility to showing the next tracks AA! */ |
| if (wps_bufptr+1 == 'n') |
| return 1; |
| #endif |
| return 0; |
| }; |
| #endif /* HAVE_ALBUMART */ |
| |
| #ifdef HAVE_TOUCHSCREEN |
| |
| struct touchaction {const char* s; int action;}; |
| static const 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 }, {"pitch", ACTION_WPS_PITCHSCREEN}, |
| {"voldown", ACTION_WPS_VOLDOWN}, {"volup", ACTION_WPS_VOLUP}, |
| }; |
| 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 = NULL; |
| const char *ptr = wps_bufptr; |
| const char *action, *end; |
| const char pb_string[] = "progressbar"; |
| const char vol_string[] = "volume"; |
| int x,y,w,h; |
| char temp[20]; |
| |
| /* 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 |
| * playlist - go into the playlist |
| * pitch - go into the pitchscreen |
| * volup - increase volume by one step |
| * voldown - decrease volume by one step |
| */ |
| |
| |
| if (*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; |
| |
| region = skin_buffer_alloc(sizeof(struct touchregion)); |
| if (!region) |
| return WPS_ERROR_INVALID_PARAM; |
| |
| /* should probably do some bounds checking here with the viewport... but later */ |
| region->action = ACTION_NONE; |
| region->x = x; |
| region->y = y; |
| region->width = w; |
| region->height = h; |
| region->wvp = curr_vp; |
| region->armed = false; |
| |
| end = strchr(action, ')'); |
| if (!end || (size_t)(end-action+1) > sizeof temp) |
| return WPS_ERROR_INVALID_PARAM; |
| strlcpy(temp, action, end-action+1); |
| action = temp; |
| |
| if(!strcmp(pb_string, action)) |
| region->type = WPS_TOUCHREGION_SCROLLBAR; |
| else if(!strcmp(vol_string, action)) |
| region->type = WPS_TOUCHREGION_VOLUME; |
| else |
| { |
| region->type = WPS_TOUCHREGION_ACTION; |
| |
| if (*action == '&') |
| { |
| action++; |
| region->repeat = true; |
| } |
| else |
| region->repeat = false; |
| |
| imax = ARRAYLEN(touchactions); |
| for (i = 0; i < imax; i++) |
| { |
| /* try to match with one of our touchregion screens */ |
| if (!strcmp(touchactions[i].s, action)) |
| { |
| region->action = touchactions[i].action; |
| break; |
| } |
| } |
| if (region->action == ACTION_NONE) |
| return WPS_ERROR_INVALID_PARAM; |
| } |
| struct skin_token_list *item = new_skin_token_list_item(NULL, region); |
| if (!item) |
| return WPS_ERROR_INVALID_PARAM; |
| add_to_ll_chain(&wps_data->touchregions, item); |
| 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; |
| memset(token, 0, sizeof(*token)); |
| |
| switch(*wps_bufptr) |
| { |
| |
| case '%': |
| case '<': |
| case '|': |
| 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; |
| curr_line->curr_subline->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; |
| } |
| |
| |
| /* |
| * Returns the number of bytes to skip the buf pointer to access the false |
| * branch in a _binary_ conditional |
| * |
| * That is: |
| * - before the '|' if we have a false branch, (%?<true|false> -> %?<|false>) |
| * - or before the closing '>' if there's no false branch (%?<true> -> %?<>) |
| * |
| * depending on the features of a target it's not called from check_feature_tag, |
| * hence the __attribute__ or it issues compiler warnings |
| * |
| **/ |
| |
| static int find_false_branch(const char *wps_bufptr) __attribute__((unused)); |
| static int find_false_branch(const char *wps_bufptr) |
| { |
| const char *buf = wps_bufptr; |
| /* wps_bufptr is after the opening '<', hence level = 1*/ |
| int level = 1; |
| char ch; |
| do |
| { |
| ch = *buf; |
| if (ch == '%') |
| { /* filter out the characters we check later if they're printed |
| * as literals */ |
| ch = *(++buf); |
| if (ch == '<' || ch == '>' || ch == '|') |
| continue; |
| /* else: some tags/printed literals we skip over */ |
| } |
| else if (ch == '<') /* nested conditional */ |
| level++; |
| else if (ch == '>') |
| { /* closed our or a nested conditional, |
| * do NOT skip over the '>' so that wps_parse() sees it for closing |
| * if it is the closing one for our conditional */ |
| level--; |
| } |
| else if (ch == '|' && level == 1) |
| { /* we found our separator, point before and get out */ |
| break; |
| } |
| /* if level is 0, we don't have a false branch */ |
| } while (level > 0 && *(++buf)); |
| |
| return buf - wps_bufptr; |
| } |
| |
| /* |
| * returns the number of bytes to get the appropriate branch of a binary |
| * conditional |
| * |
| * That means: |
| * - if a feature is available, it returns 0 to not skip anything |
| * - if the feature is not available, skip to the false branch and don't |
| * parse the true branch at all |
| * |
| * */ |
| static int check_feature_tag(const char *wps_bufptr, const int type) |
| { |
| (void)wps_bufptr; |
| switch (type) |
| { |
| case WPS_TOKEN_RTC_PRESENT: |
| #if CONFIG_RTC |
| return 0; |
| #else |
| return find_false_branch(wps_bufptr); |
| #endif |
| case WPS_TOKEN_HAVE_RECORDING: |
| #ifdef HAVE_RECORDING |
| return 0; |
| #else |
| return find_false_branch(wps_bufptr); |
| #endif |
| case WPS_TOKEN_HAVE_TUNER: |
| #if CONFIG_TUNER |
| if (radio_hardware_present()) |
| return 0; |
| #endif |
| return find_false_branch(wps_bufptr); |
| |
| default: /* not a tag we care about, just don't skip */ |
| return 0; |
| } |
| } |
| |
| |
| /* 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 */ |
| #define TOKEN_BLOCK_SIZE 128 |
| static bool wps_parse(struct wps_data *data, const char *wps_bufptr, bool debug) |
| { |
| if (!data || !wps_bufptr || !*wps_bufptr) |
| return false; |
| enum wps_parse_error fail = PARSE_OK; |
| int ret; |
| int max_tokens = TOKEN_BLOCK_SIZE; |
| size_t buf_free = 0; |
| line_number = 0; |
| level = -1; |
| |
| /* allocate enough RAM for a reasonable skin, grow as needed. |
| * Free any used RAM before loading the images to be 100% RAM efficient */ |
| data->tokens = (struct wps_token *)skin_buffer_grab(&buf_free); |
| if (sizeof(struct wps_token)*max_tokens >= buf_free) |
| return false; |
| skin_buffer_increment(max_tokens * sizeof(struct wps_token), false); |
| data->num_tokens = 0; |
| |
| #if LCD_DEPTH > 1 |
| /* Backdrop defaults to the setting unless %X is used, so set it now */ |
| if (global_settings.backdrop_file[0]) |
| { |
| data->backdrop = "-"; |
| } |
| #endif |
| |
| while (*wps_bufptr && !fail) |
| { |
| if (follow_lang_direction) |
| follow_lang_direction--; |
| /* first make sure there is enough room for tokens */ |
| if (max_tokens <= data->num_tokens + 5) |
| { |
| int extra_tokens = TOKEN_BLOCK_SIZE; |
| size_t needed = extra_tokens * sizeof(struct wps_token); |
| /* do some smarts here to grow the array a bit */ |
| if (skin_buffer_freespace() < needed) |
| { |
| fail = PARSE_FAIL_LIMITS_EXCEEDED; |
| break; |
| } |
| skin_buffer_increment(needed, false); |
| max_tokens += extra_tokens; |
| } |
| |
| 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 (!skin_start_new_subline(curr_line, data->num_tokens)) |
| 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; |
| } |
| wps_bufptr += check_feature_tag(wps_bufptr, |
| data->tokens[data->num_tokens-1].type); |
| 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; |
| } |
| /* add a new token for the \n so empty lines are correct */ |
| data->tokens[data->num_tokens].type = WPS_TOKEN_CHARACTER; |
| data->tokens[data->num_tokens].value.c = '\n'; |
| data->tokens[data->num_tokens].next = false; |
| data->num_tokens++; |
| |
| if (!skin_start_new_line(curr_vp, data->num_tokens)) |
| { |
| fail = PARSE_FAIL_LIMITS_EXCEEDED; |
| break; |
| } |
| line_number++; |
| |
| 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; |
| bool found = false; |
| struct skin_token_list *list = data->strings; |
| while (list) |
| { |
| str = (char*)list->token->value.data; |
| found = (strlen(str) == len && |
| strncmp(string_start, str, len) == 0); |
| if (found) |
| break; /* break here because the list item is |
| used if its found */ |
| list = list->next; |
| } |
| /* 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 */ |
| str = (char*)skin_buffer_alloc(len+1); |
| if (!str) |
| { |
| fail = PARSE_FAIL_LIMITS_EXCEEDED; |
| break; |
| } |
| strlcpy(str, string_start, len+1); |
| struct skin_token_list *item = |
| new_skin_token_list_item(&data->tokens[data->num_tokens], str); |
| if(!item) |
| { |
| fail = PARSE_FAIL_LIMITS_EXCEEDED; |
| break; |
| } |
| add_to_ll_chain(&data->strings, item); |
| } |
| else |
| { |
| /* another occurrence of an existing string */ |
| data->tokens[data->num_tokens].value.data = list->token->value.data; |
| } |
| 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; |
| |
| /* Success! */ |
| curr_line->curr_subline->last_token_idx = data->num_tokens; |
| data->tokens[data->num_tokens++].type = WPS_NO_TOKEN; |
| /* freeup unused tokens */ |
| skin_buffer_free_from_front(sizeof(struct wps_token) |
| * (max_tokens - data->num_tokens)); |
| |
| #if defined(DEBUG) || defined(SIMULATOR) |
| if (debug) |
| { |
| print_debug_info(data, fail, line_number); |
| debug_skin_usage(); |
| } |
| #else |
| (void)debug; |
| #endif |
| |
| return (fail == 0); |
| } |
| |
| |
| /* |
| * initial setup of wps_data; does reset everything |
| * except fields which need to survive, i.e. |
| * |
| **/ |
| static void skin_data_reset(struct wps_data *wps_data) |
| { |
| #ifdef HAVE_LCD_BITMAP |
| wps_data->images = NULL; |
| wps_data->progressbars = NULL; |
| #endif |
| #if LCD_DEPTH > 1 || defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1 |
| wps_data->backdrop = NULL; |
| #endif |
| #ifdef HAVE_TOUCHSCREEN |
| wps_data->touchregions = NULL; |
| #endif |
| wps_data->viewports = NULL; |
| wps_data->strings = NULL; |
| #ifdef HAVE_ALBUMART |
| wps_data->albumart = NULL; |
| if (wps_data->playback_aa_slot >= 0) |
| { |
| playback_release_aa_slot(wps_data->playback_aa_slot); |
| wps_data->playback_aa_slot = -1; |
| } |
| #endif |
| wps_data->tokens = NULL; |
| wps_data->num_tokens = 0; |
| |
| #ifdef HAVE_LCD_BITMAP |
| wps_data->peak_meter_enabled = false; |
| wps_data->wps_sb_tag = false; |
| wps_data->show_sb_on_wps = false; |
| #else /* HAVE_LCD_CHARCELLS */ |
| /* progress bars */ |
| int i; |
| for (i = 0; i < 8; i++) |
| { |
| wps_data->wps_progress_pat[i] = 0; |
| } |
| wps_data->full_line_progressbar = false; |
| #endif |
| wps_data->wps_loaded = false; |
| } |
| |
| #ifdef HAVE_LCD_BITMAP |
| static bool load_skin_bmp(struct wps_data *wps_data, struct bitmap *bitmap, char* bmpdir) |
| { |
| (void)wps_data; /* only needed for remote targets */ |
| char img_path[MAX_PATH]; |
| get_image_filename(bitmap->data, bmpdir, |
| img_path, sizeof(img_path)); |
| |
| /* load the image */ |
| int format; |
| #ifdef HAVE_REMOTE_LCD |
| if (curr_screen == SCREEN_REMOTE) |
| format = FORMAT_ANY|FORMAT_REMOTE; |
| else |
| #endif |
| format = FORMAT_ANY|FORMAT_TRANSPARENT; |
| |
| size_t max_buf; |
| char* imgbuf = (char*)skin_buffer_grab(&max_buf); |
| bitmap->data = imgbuf; |
| int ret = read_bmp_file(img_path, bitmap, max_buf, format, NULL); |
| |
| if (ret > 0) |
| { |
| skin_buffer_increment(ret, true); |
| return true; |
| } |
| else |
| { |
| /* Abort if we can't load an image */ |
| DEBUGF("Couldn't load '%s'\n", img_path); |
| return false; |
| } |
| } |
| |
| static bool load_skin_bitmaps(struct wps_data *wps_data, char *bmpdir) |
| { |
| struct skin_token_list *list; |
| bool retval = true; /* return false if a single image failed to load */ |
| /* do the progressbars */ |
| list = wps_data->progressbars; |
| while (list) |
| { |
| struct progressbar *pb = (struct progressbar*)list->token->value.data; |
| if (pb->bm.data) |
| { |
| pb->have_bitmap_pb = load_skin_bmp(wps_data, &pb->bm, bmpdir); |
| if (!pb->have_bitmap_pb) /* no success */ |
| retval = false; |
| } |
| list = list->next; |
| } |
| /* regular images */ |
| list = wps_data->images; |
| while (list) |
| { |
| struct gui_img *img = (struct gui_img*)list->token->value.data; |
| if (img->bm.data) |
| { |
| img->loaded = load_skin_bmp(wps_data, &img->bm, bmpdir); |
| if (img->loaded) |
| img->subimage_height = img->bm.height / img->num_subimages; |
| else |
| retval = false; |
| } |
| list = list->next; |
| } |
| |
| #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)) |
| /* Backdrop load scheme: |
| * 1) %X|filename| |
| * 2) load the backdrop from settings |
| */ |
| if (wps_data->backdrop) |
| { |
| bool needed = wps_data->backdrop[0] != '-'; |
| wps_data->backdrop = skin_backdrop_load(wps_data->backdrop, |
| bmpdir, curr_screen); |
| if (!wps_data->backdrop && needed) |
| retval = false; |
| } |
| #endif /* has backdrop support */ |
| |
| return retval; |
| } |
| |
| static bool skin_load_fonts(struct wps_data *data) |
| { |
| /* don't spit out after the first failue to aid debugging */ |
| bool success = true; |
| struct skin_token_list *vp_list; |
| int font_id; |
| /* walk though each viewport and assign its font */ |
| for(vp_list = data->viewports; vp_list; vp_list = vp_list->next) |
| { |
| /* first, find the viewports that have a non-sys/ui-font font */ |
| struct skin_viewport *skin_vp = |
| (struct skin_viewport*)vp_list->token->value.data; |
| struct viewport *vp = &skin_vp->vp; |
| |
| |
| if (vp->font <= FONT_UI) |
| { /* the usual case -> built-in fonts */ |
| #ifdef HAVE_REMOTE_LCD |
| if (vp->font == FONT_UI) |
| vp->font += curr_screen; |
| #endif |
| continue; |
| } |
| font_id = vp->font; |
| |
| /* now find the corresponding skin_font */ |
| struct skin_font *font = &skinfonts[font_id-FONT_FIRSTUSERFONT]; |
| if (!font->name) |
| { |
| DEBUGF("font %d not specified\n", font_id); |
| success = false; |
| continue; |
| } |
| |
| /* load the font - will handle loading the same font again if |
| * multiple viewports use the same */ |
| if (font->id < 0) |
| { |
| char *dot = strchr(font->name, '.'); |
| *dot = '\0'; |
| font->id = skin_font_load(font->name); |
| } |
| |
| if (font->id < 0) |
| { |
| DEBUGF("Unable to load font %d: '%s.fnt'\n", |
| font_id, font->name); |
| success = false; |
| continue; |
| } |
| |
| /* finally, assign the font_id to the viewport */ |
| vp->font = font->id; |
| } |
| return success; |
| } |
| |
| #endif /* HAVE_LCD_BITMAP */ |
| |
| /* to setup up the wps-data from a format-buffer (isfile = false) |
| from a (wps-)file (isfile = true)*/ |
| bool skin_data_load(enum screen_type screen, struct wps_data *wps_data, |
| const char *buf, bool isfile) |
| { |
| char *wps_buffer = NULL; |
| if (!wps_data || !buf) |
| return false; |
| #ifdef HAVE_ALBUMART |
| int status; |
| struct mp3entry *curtrack; |
| long offset; |
| struct skin_albumart old_aa = {.state = WPS_ALBUMART_NONE}; |
| if (wps_data->albumart) |
| { |
| old_aa.state = wps_data->albumart->state; |
| old_aa.height = wps_data->albumart->height; |
| old_aa.width = wps_data->albumart->width; |
| } |
| #endif |
| #ifdef HAVE_LCD_BITMAP |
| int i; |
| for (i=0;i<MAXUSERFONTS;i++) |
| { |
| skinfonts[i].id = -1; |
| skinfonts[i].name = NULL; |
| } |
| #endif |
| #ifdef DEBUG_SKIN_ENGINE |
| if (isfile && debug_wps) |
| { |
| DEBUGF("\n=====================\nLoading '%s'\n=====================\n", buf); |
| } |
| #endif |
| |
| skin_data_reset(wps_data); |
| wps_data->wps_loaded = false; |
| curr_screen = screen; |
| |
| /* alloc default viewport, will be fixed up later */ |
| curr_vp = skin_buffer_alloc(sizeof(struct skin_viewport)); |
| if (!curr_vp) |
| return false; |
| struct skin_token_list *list = new_skin_token_list_item(NULL, curr_vp); |
| if (!list) |
| return false; |
| add_to_ll_chain(&wps_data->viewports, list); |
| |
| |
| /* Initialise the first (default) viewport */ |
| curr_vp->label = VP_DEFAULT_LABEL; |
| curr_vp->hidden_flags = 0; |
| curr_vp->lines = NULL; |
| |
| viewport_set_defaults(&curr_vp->vp, screen); |
| #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)) |
| curr_vp->start_fgcolour = curr_vp->vp.fg_pattern; |
| curr_vp->start_bgcolour = curr_vp->vp.bg_pattern; |
| #endif |
| #ifdef HAVE_LCD_BITMAP |
| curr_vp->vp.font = FONT_UI; |
| #endif |
| |
| curr_line = NULL; |
| if (!skin_start_new_line(curr_vp, 0)) |
| return false; |
| |
| if (isfile) |
| { |
| int fd = open_utf8(buf, O_RDONLY); |
| |
| if (fd < 0) |
| return false; |
| |
| /* get buffer space from the plugin buffer */ |
| size_t buffersize = 0; |
| 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; |
| } |
| else |
| { |
| wps_buffer = (char*)buf; |
| } |
| /* parse the WPS source */ |
| if (!wps_parse(wps_data, wps_buffer, isfile)) { |
| skin_data_reset(wps_data); |
| return false; |
| } |
| |
| #ifdef HAVE_LCD_BITMAP |
| char bmpdir[MAX_PATH]; |
| if (isfile) |
| { |
| /* get the bitmap dir */ |
| char *dot = strrchr(buf, '.'); |
| strlcpy(bmpdir, buf, dot - buf + 1); |
| } |
| else |
| { |
| snprintf(bmpdir, MAX_PATH, "%s", BACKDROP_DIR); |
| } |
| /* load the bitmaps that were found by the parsing */ |
| if (!load_skin_bitmaps(wps_data, bmpdir) || |
| !skin_load_fonts(wps_data)) |
| { |
| skin_data_reset(wps_data); |
| return false; |
| } |
| #endif |
| #if defined(HAVE_ALBUMART) && !defined(__PCTOOL__) |
| status = audio_status(); |
| if (status & AUDIO_STATUS_PLAY) |
| { |
| struct skin_albumart *aa = wps_data->albumart; |
| if (aa && ((aa->state && !old_aa.state) || |
| (aa->state && |
| (((old_aa.height != aa->height) || |
| (old_aa.width != aa->width)))))) |
| { |
| curtrack = audio_current_track(); |
| offset = curtrack->offset; |
| audio_stop(); |
| if (!(status & AUDIO_STATUS_PAUSE)) |
| audio_play(offset); |
| } |
| } |
| #endif |
| wps_data->wps_loaded = true; |
| #ifdef DEBUG_SKIN_ENGINE |
| if (isfile && debug_wps) |
| debug_skin_usage(); |
| #endif |
| return true; |
| } |