/*************************************************************************** * __________ __ ___. * 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 #include #include #include "gwps.h" #include "file.h" #include "misc.h" #include "plugin.h" #ifdef __PCTOOL__ #define DEBUGF printf #define FONT_SYSFIXED 0 #define FONT_UI 1 #define SYSFONT_HEIGHT 8 #include "checkwps.h" #else #include "debug.h" #endif #ifndef __PCTOOL__ #include #include #include #include "font.h" #include "gwps.h" #include "settings.h" #ifdef HAVE_LCD_BITMAP #include "bmp.h" #endif #include "backdrop.h" #endif #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 */ #ifdef DEBUG /* 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; }; /* prototypes of all special parse functions : */ static int parse_subline_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); #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 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_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_DYNAMIC, NULL }, { WPS_TOKEN_FILE_CODEC, "Fc", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_FILE_FREQUENCY, "Ff", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_FILE_FREQUENCY_KHZ, "Fk", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_FILE_NAME_WITH_EXTENSION, "Fm", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_FILE_NAME, "Fn", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_FILE_PATH, "Fp", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_FILE_SIZE, "Fs", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_FILE_VBR, "Fv", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_FILE_DIRECTORY, "D", WPS_REFRESH_DYNAMIC, 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_DYNAMIC, NULL }, { WPS_TOKEN_METADATA_COMPOSER, "Ic", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_METADATA_ALBUM, "Id", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_METADATA_ALBUM_ARTIST, "IA", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_METADATA_GROUPING, "IG", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_METADATA_GENRE, "Ig", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_METADATA_DISC_NUMBER, "Ik", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_METADATA_TRACK_NUMBER, "In", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_METADATA_TRACK_TITLE, "It", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_METADATA_VERSION, "Iv", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_METADATA_YEAR, "Iy", WPS_REFRESH_DYNAMIC, NULL }, { WPS_TOKEN_METADATA_COMMENT, "IC", WPS_REFRESH_DYNAMIC, 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 }, #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_subline_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_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); 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 - 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|