summaryrefslogtreecommitdiff
path: root/apps/gui/skin_engine/skin_parser.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/gui/skin_engine/skin_parser.c')
-rw-r--r--apps/gui/skin_engine/skin_parser.c1732
1 files changed, 1732 insertions, 0 deletions
diff --git a/apps/gui/skin_engine/skin_parser.c b/apps/gui/skin_engine/skin_parser.c
new file mode 100644
index 0000000000..4445ee86c3
--- /dev/null
+++ b/apps/gui/skin_engine/skin_parser.c
@@ -0,0 +1,1732 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007 Nicolas Pennequin, Dan Everton, Matthias Mohr
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include <stdio.h>
23#include <string.h>
24#include <stdlib.h>
25#include "file.h"
26#include "misc.h"
27#include "plugin.h"
28#include "viewport.h"
29
30#ifdef __PCTOOL__
31#ifdef WPSEDITOR
32#include "proxy.h"
33#include "sysfont.h"
34#else
35#include "checkwps.h"
36#include "audio.h"
37#define DEBUGF printf
38#endif /*WPSEDITOR*/
39#else
40#include "debug.h"
41#endif /*__PCTOOL__*/
42
43#include <ctype.h>
44#include <stdbool.h>
45#include "font.h"
46
47#include "wps_internals.h"
48#include "skin_engine.h"
49#include "settings.h"
50#include "settings_list.h"
51
52#ifdef HAVE_LCD_BITMAP
53#include "bmp.h"
54#endif
55
56#include "backdrop.h"
57
58#define WPS_DEFAULTCFG WPS_DIR "/rockbox_default.wps"
59#define RWPS_DEFAULTCFG WPS_DIR "/rockbox_default.rwps"
60
61#define WPS_ERROR_INVALID_PARAM -1
62
63/* level of current conditional.
64 -1 means we're not in a conditional. */
65static int level = -1;
66
67/* index of the last WPS_TOKEN_CONDITIONAL_OPTION
68 or WPS_TOKEN_CONDITIONAL_START in current level */
69static int lastcond[WPS_MAX_COND_LEVEL];
70
71/* index of the WPS_TOKEN_CONDITIONAL in current level */
72static int condindex[WPS_MAX_COND_LEVEL];
73
74/* number of condtional options in current level */
75static int numoptions[WPS_MAX_COND_LEVEL];
76
77/* the current line in the file */
78static int line;
79
80#ifdef HAVE_LCD_BITMAP
81
82#if LCD_DEPTH > 1
83#define MAX_BITMAPS (MAX_IMAGES+MAX_PROGRESSBARS+1) /* WPS images + pbar bitmap + backdrop */
84#else
85#define MAX_BITMAPS (MAX_IMAGES+MAX_PROGRESSBARS) /* WPS images + pbar bitmap */
86#endif
87
88#define PROGRESSBAR_BMP MAX_IMAGES
89#define BACKDROP_BMP (MAX_BITMAPS-1)
90
91/* pointers to the bitmap filenames in the WPS source */
92static const char *bmp_names[MAX_BITMAPS];
93
94#endif /* HAVE_LCD_BITMAP */
95
96#if defined(DEBUG) || defined(SIMULATOR)
97/* debugging function */
98extern void print_debug_info(struct wps_data *data, int fail, int line);
99#endif
100
101static void wps_reset(struct wps_data *data);
102
103/* Function for parsing of details for a token. At the moment the
104 function is called, the token type has already been set. The
105 function must fill in the details and possibly add more tokens
106 to the token array. It should return the number of chars that
107 has been consumed.
108
109 wps_bufptr points to the char following the tag (i.e. where
110 details begin).
111 token is the pointer to the 'main' token being parsed
112 */
113typedef int (*wps_tag_parse_func)(const char *wps_bufptr,
114 struct wps_token *token, struct wps_data *wps_data);
115
116struct wps_tag {
117 enum wps_token_type type;
118 const char name[3];
119 unsigned char refresh_type;
120 const wps_tag_parse_func parse_func;
121};
122static int skip_end_of_line(const char *wps_bufptr);
123/* prototypes of all special parse functions : */
124static int parse_timeout(const char *wps_bufptr,
125 struct wps_token *token, struct wps_data *wps_data);
126static int parse_progressbar(const char *wps_bufptr,
127 struct wps_token *token, struct wps_data *wps_data);
128static int parse_dir_level(const char *wps_bufptr,
129 struct wps_token *token, struct wps_data *wps_data);
130static int parse_setting(const char *wps_bufptr,
131 struct wps_token *token, struct wps_data *wps_data);
132
133#ifdef HAVE_LCD_BITMAP
134static int parse_viewport_display(const char *wps_bufptr,
135 struct wps_token *token, struct wps_data *wps_data);
136static int parse_viewport(const char *wps_bufptr,
137 struct wps_token *token, struct wps_data *wps_data);
138static int parse_statusbar_enable(const char *wps_bufptr,
139 struct wps_token *token, struct wps_data *wps_data);
140static int parse_statusbar_disable(const char *wps_bufptr,
141 struct wps_token *token, struct wps_data *wps_data);
142static int parse_image_display(const char *wps_bufptr,
143 struct wps_token *token, struct wps_data *wps_data);
144static int parse_image_load(const char *wps_bufptr,
145 struct wps_token *token, struct wps_data *wps_data);
146#endif /*HAVE_LCD_BITMAP */
147#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
148static int parse_image_special(const char *wps_bufptr,
149 struct wps_token *token, struct wps_data *wps_data);
150#endif
151#ifdef HAVE_ALBUMART
152static int parse_albumart_load(const char *wps_bufptr,
153 struct wps_token *token, struct wps_data *wps_data);
154static int parse_albumart_conditional(const char *wps_bufptr,
155 struct wps_token *token, struct wps_data *wps_data);
156#endif /* HAVE_ALBUMART */
157#ifdef HAVE_TOUCHSCREEN
158static int parse_touchregion(const char *wps_bufptr,
159 struct wps_token *token, struct wps_data *wps_data);
160#else
161static int fulline_tag_not_supported(const char *wps_bufptr,
162 struct wps_token *token, struct wps_data *wps_data)
163{
164 (void)token; (void)wps_data;
165 return skip_end_of_line(wps_bufptr);
166}
167#define parse_touchregion fulline_tag_not_supported
168#endif
169#ifdef CONFIG_RTC
170#define WPS_RTC_REFRESH WPS_REFRESH_DYNAMIC
171#else
172#define WPS_RTC_REFRESH WPS_REFRESH_STATIC
173#endif
174
175/* array of available tags - those with more characters have to go first
176 (e.g. "xl" and "xd" before "x"). It needs to end with the unknown token. */
177static const struct wps_tag all_tags[] = {
178
179 { WPS_TOKEN_ALIGN_CENTER, "ac", 0, NULL },
180 { WPS_TOKEN_ALIGN_LEFT, "al", 0, NULL },
181 { WPS_TOKEN_ALIGN_RIGHT, "ar", 0, NULL },
182
183 { WPS_TOKEN_BATTERY_PERCENT, "bl", WPS_REFRESH_DYNAMIC, NULL },
184 { WPS_TOKEN_BATTERY_VOLTS, "bv", WPS_REFRESH_DYNAMIC, NULL },
185 { WPS_TOKEN_BATTERY_TIME, "bt", WPS_REFRESH_DYNAMIC, NULL },
186 { WPS_TOKEN_BATTERY_SLEEPTIME, "bs", WPS_REFRESH_DYNAMIC, NULL },
187#if CONFIG_CHARGING >= CHARGING_MONITOR
188 { WPS_TOKEN_BATTERY_CHARGING, "bc", WPS_REFRESH_DYNAMIC, NULL },
189#endif
190#if CONFIG_CHARGING
191 { WPS_TOKEN_BATTERY_CHARGER_CONNECTED,"bp", WPS_REFRESH_DYNAMIC, NULL },
192#endif
193
194 { WPS_TOKEN_RTC_PRESENT , "cc", WPS_REFRESH_STATIC, NULL },
195 { WPS_TOKEN_RTC_DAY_OF_MONTH, "cd", WPS_RTC_REFRESH, NULL },
196 { WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED,"ce", WPS_RTC_REFRESH, NULL },
197 { WPS_TOKEN_RTC_12HOUR_CFG, "cf", WPS_RTC_REFRESH, NULL },
198 { WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED, "cH", WPS_RTC_REFRESH, NULL },
199 { WPS_TOKEN_RTC_HOUR_24, "ck", WPS_RTC_REFRESH, NULL },
200 { WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED, "cI", WPS_RTC_REFRESH, NULL },
201 { WPS_TOKEN_RTC_HOUR_12, "cl", WPS_RTC_REFRESH, NULL },
202 { WPS_TOKEN_RTC_MONTH, "cm", WPS_RTC_REFRESH, NULL },
203 { WPS_TOKEN_RTC_MINUTE, "cM", WPS_RTC_REFRESH, NULL },
204 { WPS_TOKEN_RTC_SECOND, "cS", WPS_RTC_REFRESH, NULL },
205 { WPS_TOKEN_RTC_YEAR_2_DIGITS, "cy", WPS_RTC_REFRESH, NULL },
206 { WPS_TOKEN_RTC_YEAR_4_DIGITS, "cY", WPS_RTC_REFRESH, NULL },
207 { WPS_TOKEN_RTC_AM_PM_UPPER, "cP", WPS_RTC_REFRESH, NULL },
208 { WPS_TOKEN_RTC_AM_PM_LOWER, "cp", WPS_RTC_REFRESH, NULL },
209 { WPS_TOKEN_RTC_WEEKDAY_NAME, "ca", WPS_RTC_REFRESH, NULL },
210 { WPS_TOKEN_RTC_MONTH_NAME, "cb", WPS_RTC_REFRESH, NULL },
211 { WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON, "cu", WPS_RTC_REFRESH, NULL },
212 { WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN, "cw", WPS_RTC_REFRESH, NULL },
213
214 /* current file */
215 { WPS_TOKEN_FILE_BITRATE, "fb", WPS_REFRESH_STATIC, NULL },
216 { WPS_TOKEN_FILE_CODEC, "fc", WPS_REFRESH_STATIC, NULL },
217 { WPS_TOKEN_FILE_FREQUENCY, "ff", WPS_REFRESH_STATIC, NULL },
218 { WPS_TOKEN_FILE_FREQUENCY_KHZ, "fk", WPS_REFRESH_STATIC, NULL },
219 { WPS_TOKEN_FILE_NAME_WITH_EXTENSION, "fm", WPS_REFRESH_STATIC, NULL },
220 { WPS_TOKEN_FILE_NAME, "fn", WPS_REFRESH_STATIC, NULL },
221 { WPS_TOKEN_FILE_PATH, "fp", WPS_REFRESH_STATIC, NULL },
222 { WPS_TOKEN_FILE_SIZE, "fs", WPS_REFRESH_STATIC, NULL },
223 { WPS_TOKEN_FILE_VBR, "fv", WPS_REFRESH_STATIC, NULL },
224 { WPS_TOKEN_FILE_DIRECTORY, "d", WPS_REFRESH_STATIC,
225 parse_dir_level },
226
227 /* next file */
228 { WPS_TOKEN_FILE_BITRATE, "Fb", WPS_REFRESH_STATIC, NULL },
229 { WPS_TOKEN_FILE_CODEC, "Fc", WPS_REFRESH_STATIC, NULL },
230 { WPS_TOKEN_FILE_FREQUENCY, "Ff", WPS_REFRESH_STATIC, NULL },
231 { WPS_TOKEN_FILE_FREQUENCY_KHZ, "Fk", WPS_REFRESH_STATIC, NULL },
232 { WPS_TOKEN_FILE_NAME_WITH_EXTENSION, "Fm", WPS_REFRESH_STATIC, NULL },
233 { WPS_TOKEN_FILE_NAME, "Fn", WPS_REFRESH_STATIC, NULL },
234 { WPS_TOKEN_FILE_PATH, "Fp", WPS_REFRESH_STATIC, NULL },
235 { WPS_TOKEN_FILE_SIZE, "Fs", WPS_REFRESH_STATIC, NULL },
236 { WPS_TOKEN_FILE_VBR, "Fv", WPS_REFRESH_STATIC, NULL },
237 { WPS_TOKEN_FILE_DIRECTORY, "D", WPS_REFRESH_STATIC,
238 parse_dir_level },
239
240 /* current metadata */
241 { WPS_TOKEN_METADATA_ARTIST, "ia", WPS_REFRESH_STATIC, NULL },
242 { WPS_TOKEN_METADATA_COMPOSER, "ic", WPS_REFRESH_STATIC, NULL },
243 { WPS_TOKEN_METADATA_ALBUM, "id", WPS_REFRESH_STATIC, NULL },
244 { WPS_TOKEN_METADATA_ALBUM_ARTIST, "iA", WPS_REFRESH_STATIC, NULL },
245 { WPS_TOKEN_METADATA_GROUPING, "iG", WPS_REFRESH_STATIC, NULL },
246 { WPS_TOKEN_METADATA_GENRE, "ig", WPS_REFRESH_STATIC, NULL },
247 { WPS_TOKEN_METADATA_DISC_NUMBER, "ik", WPS_REFRESH_STATIC, NULL },
248 { WPS_TOKEN_METADATA_TRACK_NUMBER, "in", WPS_REFRESH_STATIC, NULL },
249 { WPS_TOKEN_METADATA_TRACK_TITLE, "it", WPS_REFRESH_STATIC, NULL },
250 { WPS_TOKEN_METADATA_VERSION, "iv", WPS_REFRESH_STATIC, NULL },
251 { WPS_TOKEN_METADATA_YEAR, "iy", WPS_REFRESH_STATIC, NULL },
252 { WPS_TOKEN_METADATA_COMMENT, "iC", WPS_REFRESH_STATIC, NULL },
253
254 /* next metadata */
255 { WPS_TOKEN_METADATA_ARTIST, "Ia", WPS_REFRESH_STATIC, NULL },
256 { WPS_TOKEN_METADATA_COMPOSER, "Ic", WPS_REFRESH_STATIC, NULL },
257 { WPS_TOKEN_METADATA_ALBUM, "Id", WPS_REFRESH_STATIC, NULL },
258 { WPS_TOKEN_METADATA_ALBUM_ARTIST, "IA", WPS_REFRESH_STATIC, NULL },
259 { WPS_TOKEN_METADATA_GROUPING, "IG", WPS_REFRESH_STATIC, NULL },
260 { WPS_TOKEN_METADATA_GENRE, "Ig", WPS_REFRESH_STATIC, NULL },
261 { WPS_TOKEN_METADATA_DISC_NUMBER, "Ik", WPS_REFRESH_STATIC, NULL },
262 { WPS_TOKEN_METADATA_TRACK_NUMBER, "In", WPS_REFRESH_STATIC, NULL },
263 { WPS_TOKEN_METADATA_TRACK_TITLE, "It", WPS_REFRESH_STATIC, NULL },
264 { WPS_TOKEN_METADATA_VERSION, "Iv", WPS_REFRESH_STATIC, NULL },
265 { WPS_TOKEN_METADATA_YEAR, "Iy", WPS_REFRESH_STATIC, NULL },
266 { WPS_TOKEN_METADATA_COMMENT, "IC", WPS_REFRESH_STATIC, NULL },
267
268#if (CONFIG_CODEC != MAS3507D)
269 { WPS_TOKEN_SOUND_PITCH, "Sp", WPS_REFRESH_DYNAMIC, NULL },
270#endif
271
272#if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
273 { WPS_TOKEN_VLED_HDD, "lh", WPS_REFRESH_DYNAMIC, NULL },
274#endif
275
276 { WPS_TOKEN_MAIN_HOLD, "mh", WPS_REFRESH_DYNAMIC, NULL },
277
278#ifdef HAS_REMOTE_BUTTON_HOLD
279 { WPS_TOKEN_REMOTE_HOLD, "mr", WPS_REFRESH_DYNAMIC, NULL },
280#else
281 { WPS_TOKEN_UNKNOWN, "mr", 0, NULL },
282#endif
283
284 { WPS_TOKEN_REPEAT_MODE, "mm", WPS_REFRESH_DYNAMIC, NULL },
285 { WPS_TOKEN_PLAYBACK_STATUS, "mp", WPS_REFRESH_DYNAMIC, NULL },
286 { WPS_TOKEN_BUTTON_VOLUME, "mv", WPS_REFRESH_DYNAMIC,
287 parse_timeout },
288
289#ifdef HAVE_LCD_BITMAP
290 { WPS_TOKEN_PEAKMETER, "pm", WPS_REFRESH_PEAK_METER, NULL },
291#else
292 { WPS_TOKEN_PLAYER_PROGRESSBAR, "pf",
293 WPS_REFRESH_DYNAMIC | WPS_REFRESH_PLAYER_PROGRESS, parse_progressbar },
294#endif
295 { WPS_TOKEN_PROGRESSBAR, "pb", WPS_REFRESH_PLAYER_PROGRESS,
296 parse_progressbar },
297
298 { WPS_TOKEN_VOLUME, "pv", WPS_REFRESH_DYNAMIC, NULL },
299
300 { WPS_TOKEN_TRACK_ELAPSED_PERCENT, "px", WPS_REFRESH_DYNAMIC, NULL },
301 { WPS_TOKEN_TRACK_TIME_ELAPSED, "pc", WPS_REFRESH_DYNAMIC, NULL },
302 { WPS_TOKEN_TRACK_TIME_REMAINING, "pr", WPS_REFRESH_DYNAMIC, NULL },
303 { WPS_TOKEN_TRACK_LENGTH, "pt", WPS_REFRESH_STATIC, NULL },
304
305 { WPS_TOKEN_PLAYLIST_POSITION, "pp", WPS_REFRESH_STATIC, NULL },
306 { WPS_TOKEN_PLAYLIST_ENTRIES, "pe", WPS_REFRESH_STATIC, NULL },
307 { WPS_TOKEN_PLAYLIST_NAME, "pn", WPS_REFRESH_STATIC, NULL },
308 { WPS_TOKEN_PLAYLIST_SHUFFLE, "ps", WPS_REFRESH_DYNAMIC, NULL },
309
310#ifdef HAVE_TAGCACHE
311 { WPS_TOKEN_DATABASE_PLAYCOUNT, "rp", WPS_REFRESH_DYNAMIC, NULL },
312 { WPS_TOKEN_DATABASE_RATING, "rr", WPS_REFRESH_DYNAMIC, NULL },
313 { WPS_TOKEN_DATABASE_AUTOSCORE, "ra", WPS_REFRESH_DYNAMIC, NULL },
314#endif
315
316#if CONFIG_CODEC == SWCODEC
317 { WPS_TOKEN_REPLAYGAIN, "rg", WPS_REFRESH_STATIC, NULL },
318 { WPS_TOKEN_CROSSFADE, "xf", WPS_REFRESH_DYNAMIC, NULL },
319#endif
320
321 { WPS_NO_TOKEN, "s", WPS_REFRESH_SCROLL, NULL },
322 { WPS_TOKEN_SUBLINE_TIMEOUT, "t", 0, parse_timeout },
323
324#ifdef HAVE_LCD_BITMAP
325 { WPS_NO_TOKEN, "we", 0, parse_statusbar_enable },
326 { WPS_NO_TOKEN, "wd", 0, parse_statusbar_disable },
327
328 { WPS_NO_TOKEN, "xl", 0, parse_image_load },
329
330 { WPS_TOKEN_IMAGE_PRELOAD_DISPLAY, "xd", WPS_REFRESH_STATIC,
331 parse_image_display },
332
333 { WPS_TOKEN_IMAGE_DISPLAY, "x", 0, parse_image_load },
334#ifdef HAVE_ALBUMART
335 { WPS_NO_TOKEN, "Cl", 0, parse_albumart_load },
336 { WPS_TOKEN_ALBUMART_DISPLAY, "C", WPS_REFRESH_STATIC,
337 parse_albumart_conditional },
338#endif
339
340 { WPS_VIEWPORT_ENABLE, "Vd", WPS_REFRESH_DYNAMIC,
341 parse_viewport_display },
342 { WPS_NO_TOKEN, "V", 0, parse_viewport },
343
344#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
345 { WPS_TOKEN_IMAGE_BACKDROP, "X", 0, parse_image_special },
346#endif
347#endif
348
349 { WPS_TOKEN_SETTING, "St", WPS_REFRESH_DYNAMIC, parse_setting },
350
351 { WPS_TOKEN_LASTTOUCH, "Tl", WPS_REFRESH_DYNAMIC, parse_timeout },
352 { WPS_NO_TOKEN, "T", 0, parse_touchregion },
353
354 { WPS_TOKEN_UNKNOWN, "", 0, NULL }
355 /* the array MUST end with an empty string (first char is \0) */
356};
357
358
359/* add a wpsll item to the list chain. ALWAYS appended because some of the
360 * chains require the order to be kept.
361 */
362static void add_to_ll_chain(struct skin_token_list **list, struct skin_token_list *item)
363{
364 if (*list == NULL)
365 *list = item;
366 else
367 {
368 struct skin_token_list *t = *list;
369 while (t->next)
370 t = t->next;
371 t->next = item;
372 }
373}
374/* create and init a new wpsll item.
375 * passing NULL to token will alloc a new one.
376 */
377static struct skin_token_list *new_skin_token_list_item(struct wps_token *token,
378 void* token_data)
379{
380 struct skin_token_list *llitem = skin_buffer_alloc(sizeof(struct skin_token_list));
381 if (!token)
382 token = skin_buffer_alloc(sizeof(struct wps_token));
383 if (!llitem || !token)
384 return NULL;
385 llitem->next = NULL;
386 llitem->token = token;
387 if (token_data)
388 llitem->token->value.data = token_data;
389 return llitem;
390}
391
392/* Returns the number of chars that should be skipped to jump
393 immediately after the first eol, i.e. to the start of the next line */
394static int skip_end_of_line(const char *wps_bufptr)
395{
396 line++;
397 int skip = 0;
398 while(*(wps_bufptr + skip) != '\n')
399 skip++;
400 return ++skip;
401}
402
403/* Starts a new subline in the current line during parsing */
404static void wps_start_new_subline(struct wps_data *data)
405{
406 data->num_sublines++;
407 data->sublines[data->num_sublines].first_token_idx = data->num_tokens;
408 data->lines[data->num_lines].num_sublines++;
409}
410
411#ifdef HAVE_LCD_BITMAP
412
413static int parse_statusbar_enable(const char *wps_bufptr,
414 struct wps_token *token,
415 struct wps_data *wps_data)
416{
417 (void)token; /* Kill warnings */
418 wps_data->wps_sb_tag = true;
419 wps_data->show_sb_on_wps = true;
420 if (wps_data->viewports[0].vp.y == 0)
421 {
422 wps_data->viewports[0].vp.y = STATUSBAR_HEIGHT;
423 wps_data->viewports[0].vp.height -= STATUSBAR_HEIGHT;
424 }
425 return skip_end_of_line(wps_bufptr);
426}
427
428static int parse_statusbar_disable(const char *wps_bufptr,
429 struct wps_token *token,
430 struct wps_data *wps_data)
431{
432 (void)token; /* Kill warnings */
433 wps_data->wps_sb_tag = true;
434 wps_data->show_sb_on_wps = false;
435 if (wps_data->viewports[0].vp.y == STATUSBAR_HEIGHT)
436 {
437 wps_data->viewports[0].vp.y = 0;
438 wps_data->viewports[0].vp.height += STATUSBAR_HEIGHT;
439 }
440 return skip_end_of_line(wps_bufptr);
441}
442
443static int get_image_id(int c)
444{
445 if(c >= 'a' && c <= 'z')
446 return c - 'a';
447 else if(c >= 'A' && c <= 'Z')
448 return c - 'A' + 26;
449 else
450 return -1;
451}
452
453static char *get_image_filename(const char *start, const char* bmpdir,
454 char *buf, int buf_size)
455{
456 const char *end = strchr(start, '|');
457
458 if ( !end || (end - start) >= (buf_size - (int)ROCKBOX_DIR_LEN - 2) )
459 {
460 buf = "\0";
461 return NULL;
462 }
463
464 int bmpdirlen = strlen(bmpdir);
465
466 strcpy(buf, bmpdir);
467 buf[bmpdirlen] = '/';
468 memcpy( &buf[bmpdirlen + 1], start, end - start);
469 buf[bmpdirlen + 1 + end - start] = 0;
470
471 return buf;
472}
473
474static int parse_image_display(const char *wps_bufptr,
475 struct wps_token *token,
476 struct wps_data *wps_data)
477{
478 int n = get_image_id(wps_bufptr[0]);
479 int subimage;
480 struct gui_img *img;;
481
482 if (n == -1)
483 {
484 /* invalid picture display tag */
485 return WPS_ERROR_INVALID_PARAM;
486 }
487
488 if ((subimage = get_image_id(wps_bufptr[1])) != -1)
489 {
490 img = find_image(n, wps_data);
491 /* Sanity check */
492 if (!img || subimage >= img->num_subimages)
493 return WPS_ERROR_INVALID_PARAM;
494
495 /* Store sub-image number to display in high bits */
496 token->value.i = n | (subimage << 8);
497 return 2; /* We have consumed 2 bytes */
498 } else {
499 token->value.i = n;
500 return 1; /* We have consumed 1 byte */
501 }
502}
503
504static int parse_image_load(const char *wps_bufptr,
505 struct wps_token *token,
506 struct wps_data *wps_data)
507{
508 int n;
509 const char *ptr = wps_bufptr;
510 const char *pos;
511 const char* filename;
512 const char* id;
513 const char *newline;
514 int x,y;
515 struct gui_img *img;
516
517 /* format: %x|n|filename.bmp|x|y|
518 or %xl|n|filename.bmp|x|y|
519 or %xl|n|filename.bmp|x|y|num_subimages|
520 */
521
522 if (*ptr != '|')
523 return WPS_ERROR_INVALID_PARAM;
524
525 ptr++;
526
527 if (!(ptr = parse_list("ssdd", NULL, '|', ptr, &id, &filename, &x, &y)))
528 return WPS_ERROR_INVALID_PARAM;
529
530 /* Check there is a terminating | */
531 if (*ptr != '|')
532 return WPS_ERROR_INVALID_PARAM;
533
534 /* get the image ID */
535 n = get_image_id(*id);
536
537 /* check the image number and load state */
538 if(n < 0 || find_image(n, wps_data))
539 {
540 /* Invalid image ID */
541 return WPS_ERROR_INVALID_PARAM;
542 }
543 img = skin_buffer_alloc(sizeof(struct gui_img));
544 if (!img)
545 return WPS_ERROR_INVALID_PARAM;
546 /* save a pointer to the filename */
547 img->bm.data = (char*)filename;
548 img->id = n;
549 img->x = x;
550 img->y = y;
551 img->num_subimages = 1;
552 img->always_display = false;
553
554 /* save current viewport */
555 img->vp = &wps_data->viewports[wps_data->num_viewports].vp;
556
557 if (token->type == WPS_TOKEN_IMAGE_DISPLAY)
558 {
559 img->always_display = true;
560 }
561 else
562 {
563 /* Parse the (optional) number of sub-images */
564 ptr++;
565 newline = strchr(ptr, '\n');
566 pos = strchr(ptr, '|');
567 if (pos && pos < newline)
568 img->num_subimages = atoi(ptr);
569
570 if (img->num_subimages <= 0)
571 return WPS_ERROR_INVALID_PARAM;
572 }
573 struct skin_token_list *item = new_skin_token_list_item(NULL, img);
574 if (!item)
575 return WPS_ERROR_INVALID_PARAM;
576 add_to_ll_chain(&wps_data->images, item);
577
578 /* Skip the rest of the line */
579 return skip_end_of_line(wps_bufptr);
580}
581
582static int parse_viewport_display(const char *wps_bufptr,
583 struct wps_token *token,
584 struct wps_data *wps_data)
585{
586 (void)wps_data;
587 char letter = wps_bufptr[0];
588
589 if (letter < 'a' || letter > 'z')
590 {
591 /* invalid viewport tag */
592 return WPS_ERROR_INVALID_PARAM;
593 }
594 token->value.i = letter;
595 return 1;
596}
597
598static int parse_viewport(const char *wps_bufptr,
599 struct wps_token *token,
600 struct wps_data *wps_data)
601{
602 (void)token; /* Kill warnings */
603 const char *ptr = wps_bufptr;
604
605 const int screen =
606#ifdef HAVE_REMOTE_LCD
607 wps_data->remote_wps ? SCREEN_REMOTE :
608#endif
609 SCREEN_MAIN;
610
611 if (wps_data->num_viewports >= WPS_MAX_VIEWPORTS)
612 return WPS_ERROR_INVALID_PARAM;
613
614 wps_data->num_viewports++;
615 /* check for the optional letter to signify its a hideable viewport */
616 /* %Vl|<label>|<rest of tags>| */
617 wps_data->viewports[wps_data->num_viewports].hidden_flags = 0;
618
619 if (*ptr == 'l')
620 {
621 if (*(ptr+1) == '|')
622 {
623 char label = *(ptr+2);
624 if (label >= 'a' && label <= 'z')
625 {
626 wps_data->viewports[wps_data->num_viewports].hidden_flags = VP_DRAW_HIDEABLE;
627 wps_data->viewports[wps_data->num_viewports].label = label;
628 }
629 else
630 return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */
631 ptr += 3;
632 }
633 }
634 if (*ptr != '|')
635 return WPS_ERROR_INVALID_PARAM;
636
637 ptr++;
638 struct viewport *vp = &wps_data->viewports[wps_data->num_viewports].vp;
639 /* format: %V|x|y|width|height|font|fg_pattern|bg_pattern| */
640
641 if (!(ptr = viewport_parse_viewport(vp, screen, ptr, '|')))
642 return WPS_ERROR_INVALID_PARAM;
643
644 /* Check for trailing | */
645 if (*ptr != '|')
646 return WPS_ERROR_INVALID_PARAM;
647
648
649 wps_data->viewports[wps_data->num_viewports-1].last_line = wps_data->num_lines - 1;
650
651 wps_data->viewports[wps_data->num_viewports].first_line = wps_data->num_lines;
652
653 if (wps_data->num_sublines < WPS_MAX_SUBLINES)
654 {
655 wps_data->lines[wps_data->num_lines].first_subline_idx =
656 wps_data->num_sublines;
657
658 wps_data->sublines[wps_data->num_sublines].first_token_idx =
659 wps_data->num_tokens;
660 }
661
662 /* Skip the rest of the line */
663 return skip_end_of_line(wps_bufptr);
664}
665
666#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
667static int parse_image_special(const char *wps_bufptr,
668 struct wps_token *token,
669 struct wps_data *wps_data)
670{
671 (void)wps_data; /* kill warning */
672 (void)token;
673 const char *pos = NULL;
674 const char *newline;
675
676 pos = strchr(wps_bufptr + 1, '|');
677 newline = strchr(wps_bufptr, '\n');
678
679 if (pos > newline)
680 return WPS_ERROR_INVALID_PARAM;
681#if LCD_DEPTH > 1
682 if (token->type == WPS_TOKEN_IMAGE_BACKDROP)
683 {
684 /* format: %X|filename.bmp| */
685 bmp_names[BACKDROP_BMP] = wps_bufptr + 1;
686 }
687#endif
688
689 /* Skip the rest of the line */
690 return skip_end_of_line(wps_bufptr);
691}
692#endif
693
694#endif /* HAVE_LCD_BITMAP */
695
696static int parse_setting(const char *wps_bufptr,
697 struct wps_token *token,
698 struct wps_data *wps_data)
699{
700 (void)wps_data;
701 const char *ptr = wps_bufptr;
702 const char *end;
703 int i;
704
705 /* Find the setting's cfg_name */
706 if (*ptr != '|')
707 return WPS_ERROR_INVALID_PARAM;
708 ptr++;
709 end = strchr(ptr,'|');
710 if (!end)
711 return WPS_ERROR_INVALID_PARAM;
712
713 /* Find the setting */
714 for (i=0; i<nb_settings; i++)
715 if (settings[i].cfg_name &&
716 !strncmp(settings[i].cfg_name,ptr,end-ptr) &&
717 /* prevent matches on cfg_name prefixes */
718 strlen(settings[i].cfg_name)==(size_t)(end-ptr))
719 break;
720 if (i == nb_settings)
721 return WPS_ERROR_INVALID_PARAM;
722
723 /* Store the setting number */
724 token->value.i = i;
725
726 /* Skip the rest of the line */
727 return end-ptr+2;
728}
729
730
731static int parse_dir_level(const char *wps_bufptr,
732 struct wps_token *token,
733 struct wps_data *wps_data)
734{
735 char val[] = { *wps_bufptr, '\0' };
736 token->value.i = atoi(val);
737 (void)wps_data; /* Kill warnings */
738 return 1;
739}
740
741static int parse_timeout(const char *wps_bufptr,
742 struct wps_token *token,
743 struct wps_data *wps_data)
744{
745 int skip = 0;
746 int val = 0;
747 bool have_point = false;
748 bool have_tenth = false;
749
750 (void)wps_data; /* Kill the warning */
751
752 while ( isdigit(*wps_bufptr) || *wps_bufptr == '.' )
753 {
754 if (*wps_bufptr != '.')
755 {
756 val *= 10;
757 val += *wps_bufptr - '0';
758 if (have_point)
759 {
760 have_tenth = true;
761 wps_bufptr++;
762 skip++;
763 break;
764 }
765 }
766 else
767 have_point = true;
768
769 wps_bufptr++;
770 skip++;
771 }
772
773 if (have_tenth == false)
774 val *= 10;
775
776 if (val == 0 && skip == 0)
777 {
778 /* decide what to do if no value was specified */
779 switch (token->type)
780 {
781 case WPS_TOKEN_SUBLINE_TIMEOUT:
782 return -1;
783 case WPS_TOKEN_BUTTON_VOLUME:
784 val = 10;
785 break;
786 }
787 }
788 token->value.i = val;
789
790 return skip;
791}
792
793static int parse_progressbar(const char *wps_bufptr,
794 struct wps_token *token,
795 struct wps_data *wps_data)
796{
797 /* %pb or %pb|filename|x|y|width|height|
798 using - for any of the params uses "sane" values */
799#ifdef HAVE_LCD_BITMAP
800 enum {
801 PB_FILENAME = 0,
802 PB_X,
803 PB_Y,
804 PB_WIDTH,
805 PB_HEIGHT
806 };
807 const char *filename;
808 int x, y, height, width;
809 uint32_t set = 0;
810 const char *ptr = wps_bufptr;
811 struct progressbar *pb = skin_buffer_alloc(sizeof(struct progressbar));
812 struct skin_token_list *item = new_skin_token_list_item(token, pb);
813
814 if (!pb || !item)
815 return WPS_ERROR_INVALID_PARAM;
816
817 struct viewport *vp = &wps_data->viewports[wps_data->num_viewports].vp;
818#ifndef __PCTOOL__
819 int font_height = font_get(vp->font)->height;
820#else
821 int font_height = 8;
822#endif
823 int line_num = wps_data->num_lines -
824 wps_data->viewports[wps_data->num_viewports].first_line;
825
826 pb->have_bitmap_pb = false;
827 pb->bm.data = NULL; /* no bitmap specified */
828
829 if (*wps_bufptr != '|') /* regular old style */
830 {
831 pb->x = 0;
832 pb->width = vp->width;
833 pb->height = SYSFONT_HEIGHT-2;
834 pb->y = -line_num - 1; /* Will be computed during the rendering */
835
836 wps_data->viewports[wps_data->num_viewports].pb = pb;
837 add_to_ll_chain(&wps_data->progressbars, item);
838 return 0;
839 }
840 ptr = wps_bufptr + 1;
841
842 if (!(ptr = parse_list("sdddd", &set, '|', ptr, &filename,
843 &x, &y, &width, &height)))
844 return WPS_ERROR_INVALID_PARAM;
845
846 if (LIST_VALUE_PARSED(set, PB_FILENAME)) /* filename */
847 pb->bm.data = (char*)filename;
848
849 if (LIST_VALUE_PARSED(set, PB_X)) /* x */
850 pb->x = x;
851 else
852 pb->x = vp->x;
853
854 if (LIST_VALUE_PARSED(set, PB_WIDTH)) /* width */
855 {
856 /* A zero width causes a divide-by-zero error later, so reject it */
857 if (width == 0)
858 return WPS_ERROR_INVALID_PARAM;
859
860 pb->width = width;
861 }
862 else
863 pb->width = vp->width - pb->x;
864
865 if (LIST_VALUE_PARSED(set, PB_HEIGHT)) /* height, default to font height */
866 {
867 /* A zero height makes no sense - reject it */
868 if (height == 0)
869 return WPS_ERROR_INVALID_PARAM;
870
871 pb->height = height;
872 }
873 else
874 pb->height = font_height;
875
876 if (LIST_VALUE_PARSED(set, PB_Y)) /* y */
877 pb->y = y;
878 else
879 pb->y = -line_num - 1; /* Will be computed during the rendering */
880
881 wps_data->viewports[wps_data->num_viewports].pb = pb;
882 add_to_ll_chain(&wps_data->progressbars, item);
883
884 /* Skip the rest of the line */
885 return skip_end_of_line(wps_bufptr)-1;
886#else
887
888 if (*(wps_bufptr-1) == 'f')
889 wps_data->full_line_progressbar = true;
890 else
891 wps_data->full_line_progressbar = false;
892
893 return 0;
894
895#endif
896}
897
898#ifdef HAVE_ALBUMART
899static int parse_albumart_load(const char *wps_bufptr,
900 struct wps_token *token,
901 struct wps_data *wps_data)
902{
903 const char *_pos, *newline;
904 bool parsing;
905 (void)token; /* silence warning */
906
907 /* reset albumart info in wps */
908 wps_data->albumart_max_width = -1;
909 wps_data->albumart_max_height = -1;
910 wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_CENTER; /* default */
911 wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_CENTER; /* default */
912
913 /* format: %Cl|x|y|[[l|c|r]mwidth]|[[t|c|b]mheight]| */
914
915 newline = strchr(wps_bufptr, '\n');
916
917 /* initial validation and parsing of x and y components */
918 if (*wps_bufptr != '|')
919 return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */
920
921 _pos = wps_bufptr + 1;
922 if (!isdigit(*_pos))
923 return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl|@ */
924 wps_data->albumart_x = atoi(_pos);
925
926 _pos = strchr(_pos, '|');
927 if (!_pos || _pos > newline || !isdigit(*(++_pos)))
928 return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl|7\n or %Cl|7|@ */
929
930 wps_data->albumart_y = atoi(_pos);
931
932 _pos = strchr(_pos, '|');
933 if (!_pos || _pos > newline)
934 return WPS_ERROR_INVALID_PARAM; /* malformed token: no | after y coordinate
935 e.g. %Cl|7|59\n */
936
937 /* parsing width field */
938 parsing = true;
939 while (parsing)
940 {
941 /* apply each modifier in turn */
942 ++_pos;
943 switch (*_pos)
944 {
945 case 'l':
946 case 'L':
947 case '+':
948 wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_LEFT;
949 break;
950 case 'c':
951 case 'C':
952 wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_CENTER;
953 break;
954 case 'r':
955 case 'R':
956 case '-':
957 wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_RIGHT;
958 break;
959 case 'd':
960 case 'D':
961 case 'i':
962 case 'I':
963 case 's':
964 case 'S':
965 /* simply ignored */
966 break;
967 default:
968 parsing = false;
969 break;
970 }
971 }
972 /* extract max width data */
973 if (*_pos != '|')
974 {
975 if (!isdigit(*_pos)) /* malformed token: e.g. %Cl|7|59|# */
976 return WPS_ERROR_INVALID_PARAM;
977
978 wps_data->albumart_max_width = atoi(_pos);
979
980 _pos = strchr(_pos, '|');
981 if (!_pos || _pos > newline)
982 return WPS_ERROR_INVALID_PARAM; /* malformed token: no | after width field
983 e.g. %Cl|7|59|200\n */
984 }
985
986 /* parsing height field */
987 parsing = true;
988 while (parsing)
989 {
990 /* apply each modifier in turn */
991 ++_pos;
992 switch (*_pos)
993 {
994 case 't':
995 case 'T':
996 case '-':
997 wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_TOP;
998 break;
999 case 'c':
1000 case 'C':
1001 wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_CENTER;
1002 break;
1003 case 'b':
1004 case 'B':
1005 case '+':
1006 wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_BOTTOM;
1007 break;
1008 case 'd':
1009 case 'D':
1010 case 'i':
1011 case 'I':
1012 case 's':
1013 case 'S':
1014 /* simply ignored */
1015 break;
1016 default:
1017 parsing = false;
1018 break;
1019 }
1020 }
1021 /* extract max height data */
1022 if (*_pos != '|')
1023 {
1024 if (!isdigit(*_pos))
1025 return WPS_ERROR_INVALID_PARAM; /* malformed token e.g. %Cl|7|59|200|@ */
1026
1027 wps_data->albumart_max_height = atoi(_pos);
1028
1029 _pos = strchr(_pos, '|');
1030 if (!_pos || _pos > newline)
1031 return WPS_ERROR_INVALID_PARAM; /* malformed token: no closing |
1032 e.g. %Cl|7|59|200|200\n */
1033 }
1034
1035 /* if we got here, we parsed everything ok .. ! */
1036 if (wps_data->albumart_max_width < 0)
1037 wps_data->albumart_max_width = 0;
1038 else if (wps_data->albumart_max_width > LCD_WIDTH)
1039 wps_data->albumart_max_width = LCD_WIDTH;
1040
1041 if (wps_data->albumart_max_height < 0)
1042 wps_data->albumart_max_height = 0;
1043 else if (wps_data->albumart_max_height > LCD_HEIGHT)
1044 wps_data->albumart_max_height = LCD_HEIGHT;
1045
1046 wps_data->wps_uses_albumart = WPS_ALBUMART_LOAD;
1047
1048 /* Skip the rest of the line */
1049 return skip_end_of_line(wps_bufptr);
1050}
1051
1052static int parse_albumart_conditional(const char *wps_bufptr,
1053 struct wps_token *token,
1054 struct wps_data *wps_data)
1055{
1056 struct wps_token *prevtoken = token;
1057 --prevtoken;
1058 if (wps_data->num_tokens >= 1 && prevtoken->type == WPS_TOKEN_CONDITIONAL)
1059 {
1060 /* This %C is part of a %?C construct.
1061 It's either %?C<blah> or %?Cn<blah> */
1062 token->type = WPS_TOKEN_ALBUMART_FOUND;
1063 if (*wps_bufptr == 'n' && *(wps_bufptr + 1) == '<')
1064 {
1065 token->next = true;
1066 return 1;
1067 }
1068 else if (*wps_bufptr == '<')
1069 {
1070 return 0;
1071 }
1072 else
1073 {
1074 token->type = WPS_NO_TOKEN;
1075 return 0;
1076 }
1077 }
1078 else
1079 {
1080 /* This %C tag is in a conditional construct. */
1081 wps_data->albumart_cond_index = condindex[level];
1082 return 0;
1083 }
1084};
1085#endif /* HAVE_ALBUMART */
1086
1087#ifdef HAVE_TOUCHSCREEN
1088
1089struct touchaction {char* s; int action;};
1090static struct touchaction touchactions[] = {
1091 {"play", ACTION_WPS_PLAY }, {"stop", ACTION_WPS_STOP },
1092 {"prev", ACTION_WPS_SKIPPREV }, {"next", ACTION_WPS_SKIPNEXT },
1093 {"ffwd", ACTION_WPS_SEEKFWD }, {"rwd", ACTION_WPS_SEEKBACK },
1094 {"menu", ACTION_WPS_MENU }, {"browse", ACTION_WPS_BROWSE },
1095 {"shuffle", ACTION_TOUCH_SHUFFLE }, {"repmode", ACTION_TOUCH_REPMODE },
1096 {"quickscreen", ACTION_WPS_QUICKSCREEN },{"contextmenu", ACTION_WPS_CONTEXT },
1097 {"playlist", ACTION_WPS_VIEW_PLAYLIST },
1098};
1099static int parse_touchregion(const char *wps_bufptr,
1100 struct wps_token *token, struct wps_data *wps_data)
1101{
1102 (void)token;
1103 unsigned i, imax;
1104 struct touchregion *region;
1105 const char *ptr = wps_bufptr;
1106 const char *action;
1107 const char pb_string[] = "progressbar";
1108 const char vol_string[] = "volume";
1109 int x,y,w,h;
1110
1111 /* format: %T|x|y|width|height|action|
1112 * if action starts with & the area must be held to happen
1113 * action is one of:
1114 * play - play/pause playback
1115 * stop - stop playback, exit the wps
1116 * prev - prev track
1117 * next - next track
1118 * ffwd - seek forward
1119 * rwd - seek backwards
1120 * menu - go back to the main menu
1121 * browse - go back to the file/db browser
1122 * shuffle - toggle shuffle mode
1123 * repmode - cycle the repeat mode
1124 * quickscreen - go into the quickscreen
1125 * contextmenu - open the context menu
1126 */
1127
1128
1129 if ((wps_data->touchregion_count +1 >= MAX_TOUCHREGIONS) || (*ptr != '|'))
1130 return WPS_ERROR_INVALID_PARAM;
1131 ptr++;
1132
1133 if (!(ptr = parse_list("dddds", NULL, '|', ptr, &x, &y, &w, &h, &action)))
1134 return WPS_ERROR_INVALID_PARAM;
1135
1136 /* Check there is a terminating | */
1137 if (*ptr != '|')
1138 return WPS_ERROR_INVALID_PARAM;
1139
1140 /* should probably do some bounds checking here with the viewport... but later */
1141 region = &wps_data->touchregion[wps_data->touchregion_count];
1142 region->action = ACTION_NONE;
1143 region->x = x;
1144 region->y = y;
1145 region->width = w;
1146 region->height = h;
1147 region->wvp = &wps_data->viewports[wps_data->num_viewports];
1148
1149 if(!strncmp(pb_string, action, sizeof(pb_string)-1)
1150 && *(action + sizeof(pb_string)-1) == '|')
1151 region->type = WPS_TOUCHREGION_SCROLLBAR;
1152 else if(!strncmp(vol_string, action, sizeof(vol_string)-1)
1153 && *(action + sizeof(vol_string)-1) == '|')
1154 region->type = WPS_TOUCHREGION_VOLUME;
1155 else
1156 {
1157 region->type = WPS_TOUCHREGION_ACTION;
1158
1159 if (*action == '&')
1160 {
1161 action++;
1162 region->repeat = true;
1163 }
1164 else
1165 region->repeat = false;
1166
1167 i = 0;
1168 imax = ARRAYLEN(touchactions);
1169 while ((region->action == ACTION_NONE) &&
1170 (i < imax))
1171 {
1172 /* try to match with one of our touchregion screens */
1173 int len = strlen(touchactions[i].s);
1174 if (!strncmp(touchactions[i].s, action, len)
1175 && *(action+len) == '|')
1176 region->action = touchactions[i].action;
1177 i++;
1178 }
1179 if (region->action == ACTION_NONE)
1180 return WPS_ERROR_INVALID_PARAM;
1181 }
1182
1183 wps_data->touchregion_count++;
1184 return skip_end_of_line(wps_bufptr);
1185}
1186#endif
1187
1188/* Parse a generic token from the given string. Return the length read */
1189static int parse_token(const char *wps_bufptr, struct wps_data *wps_data)
1190{
1191 int skip = 0, taglen = 0, ret;
1192 struct wps_token *token = wps_data->tokens + wps_data->num_tokens;
1193 const struct wps_tag *tag;
1194
1195 switch(*wps_bufptr)
1196 {
1197
1198 case '%':
1199 case '<':
1200 case '|':
1201 case '>':
1202 case ';':
1203 case '#':
1204 /* escaped characters */
1205 token->type = WPS_TOKEN_CHARACTER;
1206 token->value.c = *wps_bufptr;
1207 taglen = 1;
1208 wps_data->num_tokens++;
1209 break;
1210
1211 case '?':
1212 /* conditional tag */
1213 token->type = WPS_TOKEN_CONDITIONAL;
1214 level++;
1215 condindex[level] = wps_data->num_tokens;
1216 numoptions[level] = 1;
1217 wps_data->num_tokens++;
1218 ret = parse_token(wps_bufptr + 1, wps_data);
1219 if (ret < 0) return ret;
1220 taglen = 1 + ret;
1221 break;
1222
1223 default:
1224 /* find what tag we have */
1225 for (tag = all_tags;
1226 strncmp(wps_bufptr, tag->name, strlen(tag->name)) != 0;
1227 tag++) ;
1228
1229 taglen = (tag->type != WPS_TOKEN_UNKNOWN) ? strlen(tag->name) : 2;
1230 token->type = tag->type;
1231 wps_data->sublines[wps_data->num_sublines].line_type |=
1232 tag->refresh_type;
1233
1234 /* if the tag has a special parsing function, we call it */
1235 if (tag->parse_func)
1236 {
1237 ret = tag->parse_func(wps_bufptr + taglen, token, wps_data);
1238 if (ret < 0) return ret;
1239 skip += ret;
1240 }
1241
1242 /* Some tags we don't want to save as tokens */
1243 if (tag->type == WPS_NO_TOKEN)
1244 break;
1245
1246 /* tags that start with 'F', 'I' or 'D' are for the next file */
1247 if ( *(tag->name) == 'I' || *(tag->name) == 'F' ||
1248 *(tag->name) == 'D')
1249 token->next = true;
1250
1251 wps_data->num_tokens++;
1252 break;
1253 }
1254
1255 skip += taglen;
1256 return skip;
1257}
1258
1259/* Parses the WPS.
1260 data is the pointer to the structure where the parsed WPS should be stored.
1261 It is initialised.
1262 wps_bufptr points to the string containing the WPS tags */
1263static bool wps_parse(struct wps_data *data, const char *wps_bufptr)
1264{
1265 if (!data || !wps_bufptr || !*wps_bufptr)
1266 return false;
1267 enum wps_parse_error fail = PARSE_OK;
1268 int ret;
1269 line = 1;
1270 level = -1;
1271
1272 while(*wps_bufptr && !fail && data->num_tokens < WPS_MAX_TOKENS - 1
1273 && data->num_viewports < WPS_MAX_VIEWPORTS
1274 && data->num_lines < WPS_MAX_LINES)
1275 {
1276 switch(*wps_bufptr++)
1277 {
1278
1279 /* Regular tag */
1280 case '%':
1281 if ((ret = parse_token(wps_bufptr, data)) < 0)
1282 {
1283 fail = PARSE_FAIL_COND_INVALID_PARAM;
1284 break;
1285 }
1286 else if (level >= WPS_MAX_COND_LEVEL - 1)
1287 {
1288 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1289 break;
1290 }
1291 wps_bufptr += ret;
1292 break;
1293
1294 /* Alternating sublines separator */
1295 case ';':
1296 if (level >= 0) /* there are unclosed conditionals */
1297 {
1298 fail = PARSE_FAIL_UNCLOSED_COND;
1299 break;
1300 }
1301
1302 if (data->num_sublines+1 < WPS_MAX_SUBLINES)
1303 wps_start_new_subline(data);
1304 else
1305 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1306
1307 break;
1308
1309 /* Conditional list start */
1310 case '<':
1311 if (data->tokens[data->num_tokens-2].type != WPS_TOKEN_CONDITIONAL)
1312 {
1313 fail = PARSE_FAIL_COND_SYNTAX_ERROR;
1314 break;
1315 }
1316
1317 data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_START;
1318 lastcond[level] = data->num_tokens++;
1319 break;
1320
1321 /* Conditional list end */
1322 case '>':
1323 if (level < 0) /* not in a conditional, invalid char */
1324 {
1325 fail = PARSE_FAIL_INVALID_CHAR;
1326 break;
1327 }
1328
1329 data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_END;
1330 if (lastcond[level])
1331 data->tokens[lastcond[level]].value.i = data->num_tokens;
1332 else
1333 {
1334 fail = PARSE_FAIL_COND_SYNTAX_ERROR;
1335 break;
1336 }
1337
1338 lastcond[level] = 0;
1339 data->num_tokens++;
1340 data->tokens[condindex[level]].value.i = numoptions[level];
1341 level--;
1342 break;
1343
1344 /* Conditional list option */
1345 case '|':
1346 if (level < 0) /* not in a conditional, invalid char */
1347 {
1348 fail = PARSE_FAIL_INVALID_CHAR;
1349 break;
1350 }
1351
1352 data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_OPTION;
1353 if (lastcond[level])
1354 data->tokens[lastcond[level]].value.i = data->num_tokens;
1355 else
1356 {
1357 fail = PARSE_FAIL_COND_SYNTAX_ERROR;
1358 break;
1359 }
1360
1361 lastcond[level] = data->num_tokens;
1362 numoptions[level]++;
1363 data->num_tokens++;
1364 break;
1365
1366 /* Comment */
1367 case '#':
1368 if (level >= 0) /* there are unclosed conditionals */
1369 {
1370 fail = PARSE_FAIL_UNCLOSED_COND;
1371 break;
1372 }
1373
1374 wps_bufptr += skip_end_of_line(wps_bufptr);
1375 break;
1376
1377 /* End of this line */
1378 case '\n':
1379 if (level >= 0) /* there are unclosed conditionals */
1380 {
1381 fail = PARSE_FAIL_UNCLOSED_COND;
1382 break;
1383 }
1384
1385 line++;
1386 wps_start_new_subline(data);
1387 data->num_lines++; /* Start a new line */
1388
1389 if ((data->num_lines < WPS_MAX_LINES) &&
1390 (data->num_sublines < WPS_MAX_SUBLINES))
1391 {
1392 data->lines[data->num_lines].first_subline_idx =
1393 data->num_sublines;
1394
1395 data->sublines[data->num_sublines].first_token_idx =
1396 data->num_tokens;
1397 }
1398
1399 break;
1400
1401 /* String */
1402 default:
1403 {
1404 unsigned int len = 1;
1405 const char *string_start = wps_bufptr - 1;
1406
1407 /* find the length of the string */
1408 while (*wps_bufptr && *wps_bufptr != '#' &&
1409 *wps_bufptr != '%' && *wps_bufptr != ';' &&
1410 *wps_bufptr != '<' && *wps_bufptr != '>' &&
1411 *wps_bufptr != '|' && *wps_bufptr != '\n')
1412 {
1413 wps_bufptr++;
1414 len++;
1415 }
1416
1417 /* look if we already have that string */
1418 char *str;
1419 bool found = false;
1420 struct skin_token_list *list = data->strings;
1421 while (list)
1422 {
1423 str = (char*)list->token->value.data;
1424 found = (strlen(str) == len &&
1425 strncmp(string_start, str, len) == 0);
1426 if (found)
1427 break; /* break here because the list item is
1428 used if its found */
1429 list = list->next;
1430 }
1431 /* If a matching string is found, found is true and i is
1432 the index of the string. If not, found is false */
1433
1434 if (!found)
1435 {
1436 /* new string */
1437 str = (char*)skin_buffer_alloc(len+1);
1438 if (!str)
1439 {
1440 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1441 break;
1442 }
1443 strlcpy(str, string_start, len+1);
1444 struct skin_token_list *item =
1445 new_skin_token_list_item(&data->tokens[data->num_tokens], str);
1446 if(!item)
1447 {
1448 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1449 break;
1450 }
1451 add_to_ll_chain(&data->strings, item);
1452 }
1453 else
1454 {
1455 /* another occurrence of an existing string */
1456 data->tokens[data->num_tokens].value.data = list->token->value.data;
1457 }
1458 data->tokens[data->num_tokens].type = WPS_TOKEN_STRING;
1459 data->num_tokens++;
1460 }
1461 break;
1462 }
1463 }
1464
1465 if (!fail && level >= 0) /* there are unclosed conditionals */
1466 fail = PARSE_FAIL_UNCLOSED_COND;
1467
1468 if (*wps_bufptr && !fail)
1469 /* one of the limits of the while loop was exceeded */
1470 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1471
1472 data->viewports[data->num_viewports].last_line = data->num_lines - 1;
1473
1474 /* We have finished with the last viewport, so increment count */
1475 data->num_viewports++;
1476
1477#if defined(DEBUG) || defined(SIMULATOR)
1478 print_debug_info(data, fail, line);
1479#endif
1480
1481 return (fail == 0);
1482}
1483
1484static void wps_reset(struct wps_data *data)
1485{
1486#ifdef HAVE_REMOTE_LCD
1487 bool rwps = data->remote_wps; /* remember whether the data is for a RWPS */
1488#endif
1489 memset(data, 0, sizeof(*data));
1490 skin_data_init(data);
1491#ifdef HAVE_REMOTE_LCD
1492 data->remote_wps = rwps;
1493#endif
1494}
1495
1496#ifdef HAVE_LCD_BITMAP
1497static bool load_skin_bmp(struct wps_data *wps_data, struct bitmap *bitmap, char* bmpdir)
1498{
1499 (void)wps_data; /* only needed for remote targets */
1500 bool loaded = false;
1501 char img_path[MAX_PATH];
1502 get_image_filename(bitmap->data, bmpdir,
1503 img_path, sizeof(img_path));
1504
1505 /* load the image */
1506 int format;
1507#ifdef HAVE_REMOTE_LCD
1508 if (wps_data->remote_wps)
1509 format = FORMAT_ANY|FORMAT_REMOTE;
1510 else
1511#endif
1512 format = FORMAT_ANY|FORMAT_TRANSPARENT;
1513
1514 size_t max_buf;
1515 char* imgbuf = (char*)skin_buffer_grab(&max_buf);
1516 bitmap->data = imgbuf;
1517 int ret = read_bmp_file(img_path, bitmap, max_buf, format, NULL);
1518
1519 if (ret > 0)
1520 {
1521 skin_buffer_increment(ret);
1522 loaded = true;
1523 }
1524 else
1525 {
1526 /* Abort if we can't load an image */
1527 DEBUGF("ERR: Failed to load image - %s\n",img_path);
1528 loaded = false;
1529 }
1530 return loaded;
1531}
1532
1533static bool load_skin_bitmaps(struct wps_data *wps_data, char *bmpdir)
1534{
1535 struct skin_token_list *list;
1536 /* do the progressbars */
1537 list = wps_data->progressbars;
1538 while (list)
1539 {
1540 struct progressbar *pb = (struct progressbar*)list->token->value.data;
1541 if (pb->bm.data)
1542 {
1543 pb->have_bitmap_pb = load_skin_bmp(wps_data, &pb->bm, bmpdir);
1544 }
1545 list = list->next;
1546 }
1547 /* regular images */
1548 list = wps_data->images;
1549 while (list)
1550 {
1551 struct gui_img *img = (struct gui_img*)list->token->value.data;
1552 if (img->bm.data)
1553 {
1554 img->loaded = load_skin_bmp(wps_data, &img->bm, bmpdir);
1555 if (img->loaded)
1556 img->subimage_height = img->bm.height / img->num_subimages;
1557 }
1558 list = list->next;
1559 }
1560
1561#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
1562 if (bmp_names[BACKDROP_BMP])
1563 {
1564 int screen = SCREEN_MAIN;
1565 char img_path[MAX_PATH];
1566 get_image_filename(bmp_names[BACKDROP_BMP], bmpdir,
1567 img_path, sizeof(img_path));
1568#if defined(HAVE_REMOTE_LCD)
1569 /* We only need to check LCD type if there is a remote LCD */
1570 if (wps_data->remote_wps)
1571 screen = SCREEN_REMOTE;
1572#endif
1573 screens[screen].backdrop_load(BACKDROP_SKIN_WPS, img_path);
1574 }
1575#endif /* has backdrop support */
1576
1577 /* If we got here, everything was OK */
1578 return true;
1579}
1580
1581#endif /* HAVE_LCD_BITMAP */
1582
1583/* to setup up the wps-data from a format-buffer (isfile = false)
1584 from a (wps-)file (isfile = true)*/
1585bool skin_data_load(struct wps_data *wps_data,
1586 struct screen *display,
1587 const char *buf,
1588 bool isfile)
1589{
1590#ifdef HAVE_ALBUMART
1591 struct mp3entry *curtrack;
1592 long offset;
1593 int status;
1594 int wps_uses_albumart = wps_data->wps_uses_albumart;
1595 int albumart_max_height = wps_data->albumart_max_height;
1596 int albumart_max_width = wps_data->albumart_max_width;
1597#endif
1598 if (!wps_data || !buf)
1599 return false;
1600
1601 wps_reset(wps_data);
1602
1603 /* Initialise the first (default) viewport */
1604 wps_data->viewports[0].vp.x = 0;
1605 wps_data->viewports[0].vp.width = display->getwidth();
1606 wps_data->viewports[0].vp.height = display->getheight();
1607 switch (statusbar_position(display->screen_type))
1608 {
1609 case STATUSBAR_OFF:
1610 wps_data->viewports[0].vp.y = 0;
1611 break;
1612 case STATUSBAR_TOP:
1613 wps_data->viewports[0].vp.y = STATUSBAR_HEIGHT;
1614 wps_data->viewports[0].vp.height -= STATUSBAR_HEIGHT;
1615 break;
1616 case STATUSBAR_BOTTOM:
1617 wps_data->viewports[0].vp.y = 0;
1618 wps_data->viewports[0].vp.height -= STATUSBAR_HEIGHT;
1619 break;
1620 }
1621#ifdef HAVE_LCD_BITMAP
1622 wps_data->viewports[0].vp.font = FONT_UI;
1623 wps_data->viewports[0].vp.drawmode = DRMODE_SOLID;
1624#endif
1625#if LCD_DEPTH > 1
1626 if (display->depth > 1)
1627 {
1628 wps_data->viewports[0].vp.fg_pattern = display->get_foreground();
1629 wps_data->viewports[0].vp.bg_pattern = display->get_background();
1630 }
1631#endif
1632 if (!isfile)
1633 {
1634 return wps_parse(wps_data, buf);
1635 }
1636 else
1637 {
1638 /*
1639 * Hardcode loading WPS_DEFAULTCFG to cause a reset ideally this
1640 * wants to be a virtual file. Feel free to modify dirbrowse()
1641 * if you're feeling brave.
1642 */
1643#ifndef __PCTOOL__
1644 if (! strcmp(buf, WPS_DEFAULTCFG) )
1645 {
1646 global_settings.wps_file[0] = 0;
1647 return false;
1648 }
1649
1650#ifdef HAVE_REMOTE_LCD
1651 if (! strcmp(buf, RWPS_DEFAULTCFG) )
1652 {
1653 global_settings.rwps_file[0] = 0;
1654 return false;
1655 }
1656#endif
1657#endif /* __PCTOOL__ */
1658
1659 int fd = open_utf8(buf, O_RDONLY);
1660
1661 if (fd < 0)
1662 return false;
1663
1664 /* get buffer space from the plugin buffer */
1665 size_t buffersize = 0;
1666 char *wps_buffer = (char *)plugin_get_buffer(&buffersize);
1667
1668 if (!wps_buffer)
1669 return false;
1670
1671 /* copy the file's content to the buffer for parsing,
1672 ensuring that every line ends with a newline char. */
1673 unsigned int start = 0;
1674 while(read_line(fd, wps_buffer + start, buffersize - start) > 0)
1675 {
1676 start += strlen(wps_buffer + start);
1677 if (start < buffersize - 1)
1678 {
1679 wps_buffer[start++] = '\n';
1680 wps_buffer[start] = 0;
1681 }
1682 }
1683
1684 close(fd);
1685
1686 if (start <= 0)
1687 return false;
1688
1689#ifdef HAVE_LCD_BITMAP
1690 /* Set all filename pointers to NULL */
1691 memset(bmp_names, 0, sizeof(bmp_names));
1692#endif
1693
1694 /* parse the WPS source */
1695 if (!wps_parse(wps_data, wps_buffer)) {
1696 wps_reset(wps_data);
1697 return false;
1698 }
1699
1700 wps_data->wps_loaded = true;
1701
1702#ifdef HAVE_LCD_BITMAP
1703 /* get the bitmap dir */
1704 char bmpdir[MAX_PATH];
1705 char *dot = strrchr(buf, '.');
1706
1707 strlcpy(bmpdir, buf, dot - buf + 1);
1708
1709 /* load the bitmaps that were found by the parsing */
1710 if (!load_skin_bitmaps(wps_data, bmpdir)) {
1711 wps_reset(wps_data);
1712 return false;
1713 }
1714#endif
1715#ifdef HAVE_ALBUMART
1716 status = audio_status();
1717 if (((!wps_uses_albumart && wps_data->wps_uses_albumart) ||
1718 (wps_data->wps_uses_albumart &&
1719 (albumart_max_height != wps_data->albumart_max_height ||
1720 albumart_max_width != wps_data->albumart_max_width))) &&
1721 status & AUDIO_STATUS_PLAY)
1722 {
1723 curtrack = audio_current_track();
1724 offset = curtrack->offset;
1725 audio_stop();
1726 if (!(status & AUDIO_STATUS_PAUSE))
1727 audio_play(offset);
1728 }
1729#endif
1730 return true;
1731 }
1732}