Implement %Ss for playback speed
[kugel-rb.git] / apps / gui / skin_engine / skin_parser.c
blob0e21ec0c81a0fa7028605b7e265630c9c448630f
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2007 Nicolas Pennequin, Dan Everton, Matthias Mohr
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.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 #include <stdio.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include "config.h"
26 #include "file.h"
27 #include "misc.h"
28 #include "plugin.h"
29 #include "viewport.h"
31 #ifdef __PCTOOL__
32 #ifdef WPSEDITOR
33 #include "proxy.h"
34 #include "sysfont.h"
35 #else
36 #include "action.h"
37 #include "checkwps.h"
38 #include "audio.h"
39 #define DEBUGF printf
40 #endif /*WPSEDITOR*/
41 #else
42 #include "debug.h"
43 #include "language.h"
44 #endif /*__PCTOOL__*/
46 #include <ctype.h>
47 #include <stdbool.h>
48 #include "font.h"
50 #include "wps_internals.h"
51 #include "skin_engine.h"
52 #include "settings.h"
53 #include "settings_list.h"
55 #ifdef HAVE_LCD_BITMAP
56 #include "bmp.h"
57 #endif
59 #ifdef HAVE_ALBUMART
60 #include "playback.h"
61 #endif
63 #include "backdrop.h"
65 #define WPS_ERROR_INVALID_PARAM -1
67 /* which screen are we parsing for? */
68 static enum screen_type curr_screen;
70 /* level of current conditional.
71 -1 means we're not in a conditional. */
72 static int level = -1;
74 /* index of the last WPS_TOKEN_CONDITIONAL_OPTION
75 or WPS_TOKEN_CONDITIONAL_START in current level */
76 static int lastcond[WPS_MAX_COND_LEVEL];
78 /* index of the WPS_TOKEN_CONDITIONAL in current level */
79 static int condindex[WPS_MAX_COND_LEVEL];
81 /* number of condtional options in current level */
82 static int numoptions[WPS_MAX_COND_LEVEL];
84 /* line number, debug only */
85 static int line_number;
87 /* the current viewport */
88 static struct skin_viewport *curr_vp;
89 /* the current line, linked to the above viewport */
90 static struct skin_line *curr_line;
92 #ifdef HAVE_LCD_BITMAP
94 #if LCD_DEPTH > 1
95 #define MAX_BITMAPS (MAX_IMAGES+MAX_PROGRESSBARS+1) /* WPS images + pbar bitmap + backdrop */
96 #else
97 #define MAX_BITMAPS (MAX_IMAGES+MAX_PROGRESSBARS) /* WPS images + pbar bitmap */
98 #endif
100 #define PROGRESSBAR_BMP MAX_IMAGES
101 #define BACKDROP_BMP (MAX_BITMAPS-1)
103 /* pointers to the bitmap filenames in the WPS source */
104 static const char *bmp_names[MAX_BITMAPS];
106 #endif /* HAVE_LCD_BITMAP */
108 #if defined(DEBUG) || defined(SIMULATOR)
109 /* debugging function */
110 extern void print_debug_info(struct wps_data *data, int fail, int line);
111 extern void debug_skin_usage(void);
112 #endif
114 /* Function for parsing of details for a token. At the moment the
115 function is called, the token type has already been set. The
116 function must fill in the details and possibly add more tokens
117 to the token array. It should return the number of chars that
118 has been consumed.
120 wps_bufptr points to the char following the tag (i.e. where
121 details begin).
122 token is the pointer to the 'main' token being parsed
124 typedef int (*wps_tag_parse_func)(const char *wps_bufptr,
125 struct wps_token *token, struct wps_data *wps_data);
127 struct wps_tag {
128 enum wps_token_type type;
129 const char name[3];
130 unsigned char refresh_type;
131 const wps_tag_parse_func parse_func;
133 static int skip_end_of_line(const char *wps_bufptr);
134 /* prototypes of all special parse functions : */
135 static int parse_timeout(const char *wps_bufptr,
136 struct wps_token *token, struct wps_data *wps_data);
137 static int parse_progressbar(const char *wps_bufptr,
138 struct wps_token *token, struct wps_data *wps_data);
139 static int parse_dir_level(const char *wps_bufptr,
140 struct wps_token *token, struct wps_data *wps_data);
141 static int parse_setting_and_lang(const char *wps_bufptr,
142 struct wps_token *token, struct wps_data *wps_data);
144 #ifdef HAVE_LCD_BITMAP
145 static int parse_viewport_display(const char *wps_bufptr,
146 struct wps_token *token, struct wps_data *wps_data);
147 static int parse_viewport(const char *wps_bufptr,
148 struct wps_token *token, struct wps_data *wps_data);
149 static int parse_statusbar_enable(const char *wps_bufptr,
150 struct wps_token *token, struct wps_data *wps_data);
151 static int parse_statusbar_disable(const char *wps_bufptr,
152 struct wps_token *token, struct wps_data *wps_data);
153 static int parse_image_display(const char *wps_bufptr,
154 struct wps_token *token, struct wps_data *wps_data);
155 static int parse_image_load(const char *wps_bufptr,
156 struct wps_token *token, struct wps_data *wps_data);
157 #endif /*HAVE_LCD_BITMAP */
158 #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
159 static int parse_image_special(const char *wps_bufptr,
160 struct wps_token *token, struct wps_data *wps_data);
161 #endif
162 #ifdef HAVE_ALBUMART
163 static int parse_albumart_load(const char *wps_bufptr,
164 struct wps_token *token, struct wps_data *wps_data);
165 static int parse_albumart_display(const char *wps_bufptr,
166 struct wps_token *token, struct wps_data *wps_data);
167 #endif /* HAVE_ALBUMART */
168 #ifdef HAVE_TOUCHSCREEN
169 static int parse_touchregion(const char *wps_bufptr,
170 struct wps_token *token, struct wps_data *wps_data);
171 #else
172 static int fulline_tag_not_supported(const char *wps_bufptr,
173 struct wps_token *token, struct wps_data *wps_data)
175 (void)token; (void)wps_data;
176 return skip_end_of_line(wps_bufptr);
178 #define parse_touchregion fulline_tag_not_supported
179 #endif
180 #ifdef CONFIG_RTC
181 #define WPS_RTC_REFRESH WPS_REFRESH_DYNAMIC
182 #else
183 #define WPS_RTC_REFRESH WPS_REFRESH_STATIC
184 #endif
186 /* array of available tags - those with more characters have to go first
187 (e.g. "xl" and "xd" before "x"). It needs to end with the unknown token. */
188 static const struct wps_tag all_tags[] = {
190 { WPS_TOKEN_ALIGN_CENTER, "ac", 0, NULL },
191 { WPS_TOKEN_ALIGN_LEFT, "al", 0, NULL },
192 { WPS_TOKEN_ALIGN_RIGHT, "ar", 0, NULL },
194 { WPS_TOKEN_BATTERY_PERCENT, "bl", WPS_REFRESH_DYNAMIC, NULL },
195 { WPS_TOKEN_BATTERY_VOLTS, "bv", WPS_REFRESH_DYNAMIC, NULL },
196 { WPS_TOKEN_BATTERY_TIME, "bt", WPS_REFRESH_DYNAMIC, NULL },
197 { WPS_TOKEN_BATTERY_SLEEPTIME, "bs", WPS_REFRESH_DYNAMIC, NULL },
198 #if CONFIG_CHARGING >= CHARGING_MONITOR
199 { WPS_TOKEN_BATTERY_CHARGING, "bc", WPS_REFRESH_DYNAMIC, NULL },
200 #endif
201 #if CONFIG_CHARGING
202 { WPS_TOKEN_BATTERY_CHARGER_CONNECTED,"bp", WPS_REFRESH_DYNAMIC, NULL },
203 #endif
205 { WPS_TOKEN_RTC_PRESENT , "cc", WPS_REFRESH_STATIC, NULL },
206 { WPS_TOKEN_RTC_DAY_OF_MONTH, "cd", WPS_RTC_REFRESH, NULL },
207 { WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED,"ce", WPS_RTC_REFRESH, NULL },
208 { WPS_TOKEN_RTC_12HOUR_CFG, "cf", WPS_RTC_REFRESH, NULL },
209 { WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED, "cH", WPS_RTC_REFRESH, NULL },
210 { WPS_TOKEN_RTC_HOUR_24, "ck", WPS_RTC_REFRESH, NULL },
211 { WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED, "cI", WPS_RTC_REFRESH, NULL },
212 { WPS_TOKEN_RTC_HOUR_12, "cl", WPS_RTC_REFRESH, NULL },
213 { WPS_TOKEN_RTC_MONTH, "cm", WPS_RTC_REFRESH, NULL },
214 { WPS_TOKEN_RTC_MINUTE, "cM", WPS_RTC_REFRESH, NULL },
215 { WPS_TOKEN_RTC_SECOND, "cS", WPS_RTC_REFRESH, NULL },
216 { WPS_TOKEN_RTC_YEAR_2_DIGITS, "cy", WPS_RTC_REFRESH, NULL },
217 { WPS_TOKEN_RTC_YEAR_4_DIGITS, "cY", WPS_RTC_REFRESH, NULL },
218 { WPS_TOKEN_RTC_AM_PM_UPPER, "cP", WPS_RTC_REFRESH, NULL },
219 { WPS_TOKEN_RTC_AM_PM_LOWER, "cp", WPS_RTC_REFRESH, NULL },
220 { WPS_TOKEN_RTC_WEEKDAY_NAME, "ca", WPS_RTC_REFRESH, NULL },
221 { WPS_TOKEN_RTC_MONTH_NAME, "cb", WPS_RTC_REFRESH, NULL },
222 { WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON, "cu", WPS_RTC_REFRESH, NULL },
223 { WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN, "cw", WPS_RTC_REFRESH, NULL },
225 /* current file */
226 { WPS_TOKEN_FILE_BITRATE, "fb", WPS_REFRESH_STATIC, NULL },
227 { WPS_TOKEN_FILE_CODEC, "fc", WPS_REFRESH_STATIC, NULL },
228 { WPS_TOKEN_FILE_FREQUENCY, "ff", WPS_REFRESH_STATIC, NULL },
229 { WPS_TOKEN_FILE_FREQUENCY_KHZ, "fk", WPS_REFRESH_STATIC, NULL },
230 { WPS_TOKEN_FILE_NAME_WITH_EXTENSION, "fm", WPS_REFRESH_STATIC, NULL },
231 { WPS_TOKEN_FILE_NAME, "fn", WPS_REFRESH_STATIC, NULL },
232 { WPS_TOKEN_FILE_PATH, "fp", WPS_REFRESH_STATIC, NULL },
233 { WPS_TOKEN_FILE_SIZE, "fs", WPS_REFRESH_STATIC, NULL },
234 { WPS_TOKEN_FILE_VBR, "fv", WPS_REFRESH_STATIC, NULL },
235 { WPS_TOKEN_FILE_DIRECTORY, "d", WPS_REFRESH_STATIC,
236 parse_dir_level },
238 /* next file */
239 { WPS_TOKEN_FILE_BITRATE, "Fb", WPS_REFRESH_STATIC, NULL },
240 { WPS_TOKEN_FILE_CODEC, "Fc", WPS_REFRESH_STATIC, NULL },
241 { WPS_TOKEN_FILE_FREQUENCY, "Ff", WPS_REFRESH_STATIC, NULL },
242 { WPS_TOKEN_FILE_FREQUENCY_KHZ, "Fk", WPS_REFRESH_STATIC, NULL },
243 { WPS_TOKEN_FILE_NAME_WITH_EXTENSION, "Fm", WPS_REFRESH_STATIC, NULL },
244 { WPS_TOKEN_FILE_NAME, "Fn", WPS_REFRESH_STATIC, NULL },
245 { WPS_TOKEN_FILE_PATH, "Fp", WPS_REFRESH_STATIC, NULL },
246 { WPS_TOKEN_FILE_SIZE, "Fs", WPS_REFRESH_STATIC, NULL },
247 { WPS_TOKEN_FILE_VBR, "Fv", WPS_REFRESH_STATIC, NULL },
248 { WPS_TOKEN_FILE_DIRECTORY, "D", WPS_REFRESH_STATIC,
249 parse_dir_level },
251 /* current metadata */
252 { WPS_TOKEN_METADATA_ARTIST, "ia", WPS_REFRESH_STATIC, NULL },
253 { WPS_TOKEN_METADATA_COMPOSER, "ic", WPS_REFRESH_STATIC, NULL },
254 { WPS_TOKEN_METADATA_ALBUM, "id", WPS_REFRESH_STATIC, NULL },
255 { WPS_TOKEN_METADATA_ALBUM_ARTIST, "iA", WPS_REFRESH_STATIC, NULL },
256 { WPS_TOKEN_METADATA_GROUPING, "iG", WPS_REFRESH_STATIC, NULL },
257 { WPS_TOKEN_METADATA_GENRE, "ig", WPS_REFRESH_STATIC, NULL },
258 { WPS_TOKEN_METADATA_DISC_NUMBER, "ik", WPS_REFRESH_STATIC, NULL },
259 { WPS_TOKEN_METADATA_TRACK_NUMBER, "in", WPS_REFRESH_STATIC, NULL },
260 { WPS_TOKEN_METADATA_TRACK_TITLE, "it", WPS_REFRESH_STATIC, NULL },
261 { WPS_TOKEN_METADATA_VERSION, "iv", WPS_REFRESH_STATIC, NULL },
262 { WPS_TOKEN_METADATA_YEAR, "iy", WPS_REFRESH_STATIC, NULL },
263 { WPS_TOKEN_METADATA_COMMENT, "iC", WPS_REFRESH_STATIC, NULL },
265 /* next metadata */
266 { WPS_TOKEN_METADATA_ARTIST, "Ia", WPS_REFRESH_STATIC, NULL },
267 { WPS_TOKEN_METADATA_COMPOSER, "Ic", WPS_REFRESH_STATIC, NULL },
268 { WPS_TOKEN_METADATA_ALBUM, "Id", WPS_REFRESH_STATIC, NULL },
269 { WPS_TOKEN_METADATA_ALBUM_ARTIST, "IA", WPS_REFRESH_STATIC, NULL },
270 { WPS_TOKEN_METADATA_GROUPING, "IG", WPS_REFRESH_STATIC, NULL },
271 { WPS_TOKEN_METADATA_GENRE, "Ig", WPS_REFRESH_STATIC, NULL },
272 { WPS_TOKEN_METADATA_DISC_NUMBER, "Ik", WPS_REFRESH_STATIC, NULL },
273 { WPS_TOKEN_METADATA_TRACK_NUMBER, "In", WPS_REFRESH_STATIC, NULL },
274 { WPS_TOKEN_METADATA_TRACK_TITLE, "It", WPS_REFRESH_STATIC, NULL },
275 { WPS_TOKEN_METADATA_VERSION, "Iv", WPS_REFRESH_STATIC, NULL },
276 { WPS_TOKEN_METADATA_YEAR, "Iy", WPS_REFRESH_STATIC, NULL },
277 { WPS_TOKEN_METADATA_COMMENT, "IC", WPS_REFRESH_STATIC, NULL },
279 #if (CONFIG_CODEC != MAS3507D)
280 { WPS_TOKEN_SOUND_PITCH, "Sp", WPS_REFRESH_DYNAMIC, NULL },
281 #endif
282 #if (CONFIG_CODEC == SWCODEC)
283 { WPS_TOKEN_SOUND_SPEED, "Ss", WPS_REFRESH_DYNAMIC, NULL },
284 #endif
285 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
286 { WPS_TOKEN_VLED_HDD, "lh", WPS_REFRESH_DYNAMIC, NULL },
287 #endif
289 { WPS_TOKEN_MAIN_HOLD, "mh", WPS_REFRESH_DYNAMIC, NULL },
291 #ifdef HAS_REMOTE_BUTTON_HOLD
292 { WPS_TOKEN_REMOTE_HOLD, "mr", WPS_REFRESH_DYNAMIC, NULL },
293 #else
294 { WPS_TOKEN_UNKNOWN, "mr", 0, NULL },
295 #endif
297 { WPS_TOKEN_REPEAT_MODE, "mm", WPS_REFRESH_DYNAMIC, NULL },
298 { WPS_TOKEN_PLAYBACK_STATUS, "mp", WPS_REFRESH_DYNAMIC, NULL },
299 { WPS_TOKEN_BUTTON_VOLUME, "mv", WPS_REFRESH_DYNAMIC,
300 parse_timeout },
302 #ifdef HAVE_LCD_BITMAP
303 { WPS_TOKEN_PEAKMETER, "pm", WPS_REFRESH_PEAK_METER, NULL },
304 #else
305 { WPS_TOKEN_PLAYER_PROGRESSBAR, "pf",
306 WPS_REFRESH_DYNAMIC | WPS_REFRESH_PLAYER_PROGRESS, parse_progressbar },
307 #endif
308 { WPS_TOKEN_PROGRESSBAR, "pb", WPS_REFRESH_PLAYER_PROGRESS,
309 parse_progressbar },
311 { WPS_TOKEN_VOLUME, "pv", WPS_REFRESH_DYNAMIC, NULL },
313 { WPS_TOKEN_TRACK_ELAPSED_PERCENT, "px", WPS_REFRESH_DYNAMIC, NULL },
314 { WPS_TOKEN_TRACK_TIME_ELAPSED, "pc", WPS_REFRESH_DYNAMIC, NULL },
315 { WPS_TOKEN_TRACK_TIME_REMAINING, "pr", WPS_REFRESH_DYNAMIC, NULL },
316 { WPS_TOKEN_TRACK_LENGTH, "pt", WPS_REFRESH_STATIC, NULL },
318 { WPS_TOKEN_PLAYLIST_POSITION, "pp", WPS_REFRESH_STATIC, NULL },
319 { WPS_TOKEN_PLAYLIST_ENTRIES, "pe", WPS_REFRESH_STATIC, NULL },
320 { WPS_TOKEN_PLAYLIST_NAME, "pn", WPS_REFRESH_STATIC, NULL },
321 { WPS_TOKEN_PLAYLIST_SHUFFLE, "ps", WPS_REFRESH_DYNAMIC, NULL },
323 #ifdef HAVE_TAGCACHE
324 { WPS_TOKEN_DATABASE_PLAYCOUNT, "rp", WPS_REFRESH_DYNAMIC, NULL },
325 { WPS_TOKEN_DATABASE_RATING, "rr", WPS_REFRESH_DYNAMIC, NULL },
326 { WPS_TOKEN_DATABASE_AUTOSCORE, "ra", WPS_REFRESH_DYNAMIC, NULL },
327 #endif
329 #if CONFIG_CODEC == SWCODEC
330 { WPS_TOKEN_REPLAYGAIN, "rg", WPS_REFRESH_STATIC, NULL },
331 { WPS_TOKEN_CROSSFADE, "xf", WPS_REFRESH_DYNAMIC, NULL },
332 #endif
334 { WPS_NO_TOKEN, "s", WPS_REFRESH_SCROLL, NULL },
335 { WPS_TOKEN_SUBLINE_TIMEOUT, "t", 0, parse_timeout },
337 #ifdef HAVE_LCD_BITMAP
338 { WPS_NO_TOKEN, "we", 0, parse_statusbar_enable },
339 { WPS_NO_TOKEN, "wd", 0, parse_statusbar_disable },
341 { WPS_NO_TOKEN, "xl", 0, parse_image_load },
343 { WPS_TOKEN_IMAGE_PRELOAD_DISPLAY, "xd", WPS_REFRESH_STATIC,
344 parse_image_display },
346 { WPS_TOKEN_IMAGE_DISPLAY, "x", 0, parse_image_load },
347 #ifdef HAVE_ALBUMART
348 { WPS_NO_TOKEN, "Cl", 0, parse_albumart_load },
349 { WPS_TOKEN_ALBUMART_DISPLAY, "C", WPS_REFRESH_STATIC, parse_albumart_display },
350 #endif
352 { WPS_VIEWPORT_ENABLE, "Vd", WPS_REFRESH_DYNAMIC,
353 parse_viewport_display },
354 { WPS_NO_TOKEN, "V", 0, parse_viewport },
356 #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
357 { WPS_TOKEN_IMAGE_BACKDROP, "X", 0, parse_image_special },
358 #endif
359 #endif
361 { WPS_TOKEN_SETTING, "St", WPS_REFRESH_DYNAMIC,
362 parse_setting_and_lang },
363 { WPS_TOKEN_TRANSLATEDSTRING, "Sx", WPS_REFRESH_STATIC,
364 parse_setting_and_lang },
366 { WPS_TOKEN_LASTTOUCH, "Tl", WPS_REFRESH_DYNAMIC, parse_timeout },
367 { WPS_TOKEN_CURRENT_SCREEN, "cs", WPS_REFRESH_DYNAMIC, NULL },
368 { WPS_NO_TOKEN, "T", 0, parse_touchregion },
371 /* Recording Tokens */
372 { WPS_TOKEN_HAVE_RECORDING, "Rp", WPS_REFRESH_STATIC, NULL },
373 #ifdef HAVE_RECORDING
374 { WPS_TOKEN_REC_FREQ, "Rf", WPS_REFRESH_DYNAMIC, NULL },
375 { WPS_TOKEN_REC_ENCODER, "Re", WPS_REFRESH_DYNAMIC, NULL },
376 { WPS_TOKEN_REC_BITRATE, "Rb", WPS_REFRESH_DYNAMIC, NULL },
377 { WPS_TOKEN_REC_MONO, "Rm", WPS_REFRESH_DYNAMIC, NULL },
378 #endif
379 { WPS_TOKEN_UNKNOWN, "", 0, NULL }
380 /* the array MUST end with an empty string (first char is \0) */
384 /* add a skin_token_list item to the list chain. ALWAYS appended because some of the
385 * chains require the order to be kept.
387 static void add_to_ll_chain(struct skin_token_list **list, struct skin_token_list *item)
389 if (*list == NULL)
390 *list = item;
391 else
393 struct skin_token_list *t = *list;
394 while (t->next)
395 t = t->next;
396 t->next = item;
400 /* traverse the image linked-list for an image */
401 #ifdef HAVE_LCD_BITMAP
402 struct gui_img* find_image(char label, struct wps_data *data)
404 struct skin_token_list *list = data->images;
405 while (list)
407 struct gui_img *img = (struct gui_img *)list->token->value.data;
408 if (img->label == label)
409 return img;
410 list = list->next;
412 return NULL;
414 #endif
416 /* traverse the viewport linked list for a viewport */
417 struct skin_viewport* find_viewport(char label, struct wps_data *data)
419 struct skin_token_list *list = data->viewports;
420 while (list)
422 struct skin_viewport *vp = (struct skin_viewport *)list->token->value.data;
423 if (vp->label == label)
424 return vp;
425 list = list->next;
427 return NULL;
431 /* create and init a new wpsll item.
432 * passing NULL to token will alloc a new one.
433 * You should only pass NULL for the token when the token type (table above)
434 * is WPS_NO_TOKEN which means it is not stored automatically in the skins token array
436 static struct skin_token_list *new_skin_token_list_item(struct wps_token *token,
437 void* token_data)
439 struct skin_token_list *llitem = skin_buffer_alloc(sizeof(struct skin_token_list));
440 if (!token)
441 token = skin_buffer_alloc(sizeof(struct wps_token));
442 if (!llitem || !token)
443 return NULL;
444 llitem->next = NULL;
445 llitem->token = token;
446 if (token_data)
447 llitem->token->value.data = token_data;
448 return llitem;
451 /* Returns the number of chars that should be skipped to jump
452 immediately after the first eol, i.e. to the start of the next line */
453 static int skip_end_of_line(const char *wps_bufptr)
455 line_number++;
456 int skip = 0;
457 while(*(wps_bufptr + skip) != '\n')
458 skip++;
459 return ++skip;
462 /* Starts a new subline in the current line during parsing */
463 static bool skin_start_new_subline(struct skin_line *line, int curr_token)
465 struct skin_subline *subline = skin_buffer_alloc(sizeof(struct skin_subline));
466 if (!subline)
467 return false;
469 subline->first_token_idx = curr_token;
470 subline->next = NULL;
472 subline->line_type = 0;
473 subline->time_mult = 0;
475 line->curr_subline->last_token_idx = curr_token-1;
476 line->curr_subline->next = subline;
477 line->curr_subline = subline;
478 return true;
481 static bool skin_start_new_line(struct skin_viewport *vp, int curr_token)
483 struct skin_line *line = skin_buffer_alloc(sizeof(struct skin_line));
484 struct skin_subline *subline = NULL;
485 if (!line)
486 return false;
488 /* init the subline */
489 subline = &line->sublines;
490 subline->first_token_idx = curr_token;
491 subline->next = NULL;
492 subline->line_type = 0;
493 subline->time_mult = 0;
495 /* init the new line */
496 line->curr_subline = &line->sublines;
497 line->next = NULL;
498 line->subline_expire_time = 0;
500 /* connect to curr_line and vp pointers.
501 * 1) close the previous lines subline
502 * 2) connect to vp pointer
503 * 3) connect to curr_line global pointer
505 if (curr_line)
507 curr_line->curr_subline->last_token_idx = curr_token - 1;
508 curr_line->next = line;
509 curr_line->curr_subline = NULL;
511 curr_line = line;
512 if (!vp->lines)
513 vp->lines = line;
514 line_number++;
515 return true;
518 #ifdef HAVE_LCD_BITMAP
520 static int parse_statusbar_enable(const char *wps_bufptr,
521 struct wps_token *token,
522 struct wps_data *wps_data)
524 (void)token; /* Kill warnings */
525 wps_data->wps_sb_tag = true;
526 wps_data->show_sb_on_wps = true;
527 struct skin_viewport *default_vp = find_viewport(VP_DEFAULT_LABEL, wps_data);
528 viewport_set_defaults(&default_vp->vp, curr_screen);
529 return skip_end_of_line(wps_bufptr);
532 static int parse_statusbar_disable(const char *wps_bufptr,
533 struct wps_token *token,
534 struct wps_data *wps_data)
536 (void)token; /* Kill warnings */
537 wps_data->wps_sb_tag = true;
538 wps_data->show_sb_on_wps = false;
539 struct skin_viewport *default_vp = find_viewport(VP_DEFAULT_LABEL, wps_data);
540 viewport_set_fullscreen(&default_vp->vp, curr_screen);
541 return skip_end_of_line(wps_bufptr);
544 static int get_image_id(int c)
546 if(c >= 'a' && c <= 'z')
547 return c - 'a';
548 else if(c >= 'A' && c <= 'Z')
549 return c - 'A' + 26;
550 else
551 return -1;
554 static char *get_image_filename(const char *start, const char* bmpdir,
555 char *buf, int buf_size)
557 const char *end = strchr(start, '|');
559 if ( !end || (end - start) >= (buf_size - (int)ROCKBOX_DIR_LEN - 2) )
561 buf = "\0";
562 return NULL;
565 int bmpdirlen = strlen(bmpdir);
567 strcpy(buf, bmpdir);
568 buf[bmpdirlen] = '/';
569 memcpy( &buf[bmpdirlen + 1], start, end - start);
570 buf[bmpdirlen + 1 + end - start] = 0;
572 return buf;
575 static int parse_image_display(const char *wps_bufptr,
576 struct wps_token *token,
577 struct wps_data *wps_data)
579 char label = wps_bufptr[0];
580 int subimage;
581 struct gui_img *img;;
583 /* sanity check */
584 img = find_image(label, wps_data);
585 if (!img)
587 token->value.i = label; /* so debug works */
588 return WPS_ERROR_INVALID_PARAM;
591 if ((subimage = get_image_id(wps_bufptr[1])) != -1)
593 if (subimage >= img->num_subimages)
594 return WPS_ERROR_INVALID_PARAM;
596 /* Store sub-image number to display in high bits */
597 token->value.i = label | (subimage << 8);
598 return 2; /* We have consumed 2 bytes */
599 } else {
600 token->value.i = label;
601 return 1; /* We have consumed 1 byte */
605 static int parse_image_load(const char *wps_bufptr,
606 struct wps_token *token,
607 struct wps_data *wps_data)
609 const char *ptr = wps_bufptr;
610 const char *pos;
611 const char* filename;
612 const char* id;
613 const char *newline;
614 int x,y;
615 struct gui_img *img;
617 /* format: %x|n|filename.bmp|x|y|
618 or %xl|n|filename.bmp|x|y|
619 or %xl|n|filename.bmp|x|y|num_subimages|
622 if (*ptr != '|')
623 return WPS_ERROR_INVALID_PARAM;
625 ptr++;
627 if (!(ptr = parse_list("ssdd", NULL, '|', ptr, &id, &filename, &x, &y)))
628 return WPS_ERROR_INVALID_PARAM;
630 /* Check there is a terminating | */
631 if (*ptr != '|')
632 return WPS_ERROR_INVALID_PARAM;
634 /* check the image number and load state */
635 if(find_image(*id, wps_data))
637 /* Invalid image ID */
638 return WPS_ERROR_INVALID_PARAM;
640 img = skin_buffer_alloc(sizeof(struct gui_img));
641 if (!img)
642 return WPS_ERROR_INVALID_PARAM;
643 /* save a pointer to the filename */
644 img->bm.data = (char*)filename;
645 img->label = *id;
646 img->x = x;
647 img->y = y;
648 img->num_subimages = 1;
649 img->always_display = false;
651 /* save current viewport */
652 img->vp = &curr_vp->vp;
654 if (token->type == WPS_TOKEN_IMAGE_DISPLAY)
656 img->always_display = true;
658 else
660 /* Parse the (optional) number of sub-images */
661 ptr++;
662 newline = strchr(ptr, '\n');
663 pos = strchr(ptr, '|');
664 if (pos && pos < newline)
665 img->num_subimages = atoi(ptr);
667 if (img->num_subimages <= 0)
668 return WPS_ERROR_INVALID_PARAM;
670 struct skin_token_list *item = new_skin_token_list_item(NULL, img);
671 if (!item)
672 return WPS_ERROR_INVALID_PARAM;
673 add_to_ll_chain(&wps_data->images, item);
675 /* Skip the rest of the line */
676 return skip_end_of_line(wps_bufptr);
679 static int parse_viewport_display(const char *wps_bufptr,
680 struct wps_token *token,
681 struct wps_data *wps_data)
683 (void)wps_data;
684 char letter = wps_bufptr[0];
686 if (letter < 'a' || letter > 'z')
688 /* invalid viewport tag */
689 return WPS_ERROR_INVALID_PARAM;
691 token->value.i = letter;
692 return 1;
695 static int parse_viewport(const char *wps_bufptr,
696 struct wps_token *token,
697 struct wps_data *wps_data)
699 (void)token; /* Kill warnings */
700 const char *ptr = wps_bufptr;
702 struct skin_viewport *skin_vp = skin_buffer_alloc(sizeof(struct skin_viewport));
704 /* check for the optional letter to signify its a hideable viewport */
705 /* %Vl|<label>|<rest of tags>| */
706 skin_vp->hidden_flags = 0;
707 skin_vp->label = VP_NO_LABEL;
708 skin_vp->pb = NULL;
709 skin_vp->lines = NULL;
710 if (curr_line)
712 curr_line->curr_subline->last_token_idx = wps_data->num_tokens
713 - (wps_data->num_tokens > 0 ? 1 : 0);
716 curr_line = NULL;
717 if (!skin_start_new_line(skin_vp, wps_data->num_tokens))
718 return WPS_ERROR_INVALID_PARAM;
721 if (*ptr == 'i')
723 skin_vp->label = VP_INFO_LABEL;
724 skin_vp->hidden_flags = VP_NEVER_VISIBLE;
725 ++ptr;
727 else if (*ptr == 'l')
729 if (*(ptr+1) == '|')
731 char label = *(ptr+2);
732 if (label >= 'a' && label <= 'z')
734 skin_vp->hidden_flags = VP_DRAW_HIDEABLE;
735 skin_vp->label = label;
737 else
738 return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */
739 ptr += 3;
742 if (*ptr != '|')
743 return WPS_ERROR_INVALID_PARAM;
745 ptr++;
746 struct viewport *vp = &skin_vp->vp;
747 /* format: %V|x|y|width|height|font|fg_pattern|bg_pattern| */
749 if (!(ptr = viewport_parse_viewport(vp, curr_screen, ptr, '|')))
750 return WPS_ERROR_INVALID_PARAM;
752 vp->flags &= ~VP_FLAG_ALIGN_RIGHT; /* ignore right-to-left languages */
753 /* Check for trailing | */
754 if (*ptr != '|')
755 return WPS_ERROR_INVALID_PARAM;
758 struct skin_token_list *list = new_skin_token_list_item(NULL, skin_vp);
759 if (!list)
760 return WPS_ERROR_INVALID_PARAM;
761 add_to_ll_chain(&wps_data->viewports, list);
762 curr_vp = skin_vp;
763 /* Skip the rest of the line */
764 return skip_end_of_line(wps_bufptr);
767 #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
768 static int parse_image_special(const char *wps_bufptr,
769 struct wps_token *token,
770 struct wps_data *wps_data)
772 (void)wps_data; /* kill warning */
773 (void)token;
774 const char *pos = NULL;
775 const char *newline;
777 pos = strchr(wps_bufptr + 1, '|');
778 newline = strchr(wps_bufptr, '\n');
780 if (pos > newline)
781 return WPS_ERROR_INVALID_PARAM;
782 #if LCD_DEPTH > 1
783 if (token->type == WPS_TOKEN_IMAGE_BACKDROP)
785 /* format: %X|filename.bmp| */
786 bmp_names[BACKDROP_BMP] = wps_bufptr + 1;
788 #endif
790 /* Skip the rest of the line */
791 return skip_end_of_line(wps_bufptr);
793 #endif
795 #endif /* HAVE_LCD_BITMAP */
797 static int parse_setting_and_lang(const char *wps_bufptr,
798 struct wps_token *token,
799 struct wps_data *wps_data)
801 /* NOTE: both the string validations that happen in here will
802 * automatically PASS on checkwps because its too hard to get
803 * settings_list.c and englinsh.lang built for it.
804 * If that ever changes remove the #ifndef __PCTOOL__'s here
806 (void)wps_data;
807 const char *ptr = wps_bufptr;
808 const char *end;
809 int i = 0;
810 char temp[64];
812 /* Find the setting's cfg_name */
813 if (*ptr != '|')
814 return WPS_ERROR_INVALID_PARAM;
815 ptr++;
816 end = strchr(ptr,'|');
817 if (!end)
818 return WPS_ERROR_INVALID_PARAM;
819 strlcpy(temp, ptr,end-ptr+1);
821 if (token->type == WPS_TOKEN_TRANSLATEDSTRING)
823 #ifndef __PCTOOL__
824 i = lang_english_to_id(temp);
825 if (i < 0)
826 return WPS_ERROR_INVALID_PARAM;
827 #endif
829 else
831 /* Find the setting */
832 for (i=0; i<nb_settings; i++)
833 if (settings[i].cfg_name &&
834 !strncmp(settings[i].cfg_name,ptr,end-ptr) &&
835 /* prevent matches on cfg_name prefixes */
836 strlen(settings[i].cfg_name)==(size_t)(end-ptr))
837 break;
838 #ifndef __PCTOOL__
839 if (i == nb_settings)
840 return WPS_ERROR_INVALID_PARAM;
841 #endif
843 /* Store the setting number */
844 token->value.i = i;
846 /* Skip the rest of the line */
847 return end-ptr+2;
851 static int parse_dir_level(const char *wps_bufptr,
852 struct wps_token *token,
853 struct wps_data *wps_data)
855 char val[] = { *wps_bufptr, '\0' };
856 token->value.i = atoi(val);
857 (void)wps_data; /* Kill warnings */
858 return 1;
861 static int parse_timeout(const char *wps_bufptr,
862 struct wps_token *token,
863 struct wps_data *wps_data)
865 int skip = 0;
866 int val = 0;
867 bool have_point = false;
868 bool have_tenth = false;
870 (void)wps_data; /* Kill the warning */
872 while ( isdigit(*wps_bufptr) || *wps_bufptr == '.' )
874 if (*wps_bufptr != '.')
876 val *= 10;
877 val += *wps_bufptr - '0';
878 if (have_point)
880 have_tenth = true;
881 wps_bufptr++;
882 skip++;
883 break;
886 else
887 have_point = true;
889 wps_bufptr++;
890 skip++;
893 if (have_tenth == false)
894 val *= 10;
896 if (val == 0 && skip == 0)
898 /* decide what to do if no value was specified */
899 switch (token->type)
901 case WPS_TOKEN_SUBLINE_TIMEOUT:
902 return -1;
903 case WPS_TOKEN_BUTTON_VOLUME:
904 val = 10;
905 break;
908 token->value.i = val;
910 return skip;
913 static int parse_progressbar(const char *wps_bufptr,
914 struct wps_token *token,
915 struct wps_data *wps_data)
917 /* %pb or %pb|filename|x|y|width|height|
918 using - for any of the params uses "sane" values */
919 #ifdef HAVE_LCD_BITMAP
920 enum {
921 PB_FILENAME = 0,
922 PB_X,
923 PB_Y,
924 PB_WIDTH,
925 PB_HEIGHT
927 const char *filename;
928 int x, y, height, width;
929 uint32_t set = 0;
930 const char *ptr = wps_bufptr;
931 struct progressbar *pb = skin_buffer_alloc(sizeof(struct progressbar));
932 struct skin_token_list *item = new_skin_token_list_item(token, pb);
934 if (!pb || !item)
935 return WPS_ERROR_INVALID_PARAM;
937 struct viewport *vp = &curr_vp->vp;
938 #ifndef __PCTOOL__
939 int font_height = font_get(vp->font)->height;
940 #else
941 int font_height = 8;
942 #endif
943 /* we need to know what line number (viewport relative) this pb is,
944 * so count them... */
945 int line_num = -1;
946 struct skin_line *line = curr_vp->lines;
947 while (line)
949 line_num++;
950 line = line->next;
952 pb->have_bitmap_pb = false;
953 pb->bm.data = NULL; /* no bitmap specified */
955 if (*wps_bufptr != '|') /* regular old style */
957 pb->x = 0;
958 pb->width = vp->width;
959 pb->height = SYSFONT_HEIGHT-2;
960 pb->y = -line_num - 1; /* Will be computed during the rendering */
962 curr_vp->pb = pb;
963 add_to_ll_chain(&wps_data->progressbars, item);
964 return 0;
966 ptr = wps_bufptr + 1;
968 if (!(ptr = parse_list("sdddd", &set, '|', ptr, &filename,
969 &x, &y, &width, &height)))
970 return WPS_ERROR_INVALID_PARAM;
972 if (LIST_VALUE_PARSED(set, PB_FILENAME)) /* filename */
973 pb->bm.data = (char*)filename;
975 if (LIST_VALUE_PARSED(set, PB_X)) /* x */
976 pb->x = x;
977 else
978 pb->x = vp->x;
980 if (LIST_VALUE_PARSED(set, PB_WIDTH)) /* width */
982 /* A zero width causes a divide-by-zero error later, so reject it */
983 if (width == 0)
984 return WPS_ERROR_INVALID_PARAM;
986 pb->width = width;
988 else
989 pb->width = vp->width - pb->x;
991 if (LIST_VALUE_PARSED(set, PB_HEIGHT)) /* height, default to font height */
993 /* A zero height makes no sense - reject it */
994 if (height == 0)
995 return WPS_ERROR_INVALID_PARAM;
997 pb->height = height;
999 else
1000 pb->height = font_height;
1002 if (LIST_VALUE_PARSED(set, PB_Y)) /* y */
1003 pb->y = y;
1004 else
1005 pb->y = -line_num - 1; /* Will be computed during the rendering */
1007 curr_vp->pb = pb;
1008 add_to_ll_chain(&wps_data->progressbars, item);
1010 /* Skip the rest of the line */
1011 return skip_end_of_line(wps_bufptr)-1;
1012 #else
1013 (void)token;
1015 if (*(wps_bufptr-1) == 'f')
1016 wps_data->full_line_progressbar = true;
1017 else
1018 wps_data->full_line_progressbar = false;
1020 return 0;
1022 #endif
1025 #ifdef HAVE_ALBUMART
1026 static int parse_albumart_load(const char *wps_bufptr,
1027 struct wps_token *token,
1028 struct wps_data *wps_data)
1030 const char *_pos, *newline;
1031 bool parsing;
1032 struct dim dimensions;
1033 int albumart_slot;
1034 struct skin_albumart *aa = skin_buffer_alloc(sizeof(struct skin_albumart));
1035 (void)token; /* silence warning */
1036 if (!aa)
1037 return skip_end_of_line(wps_bufptr);
1039 /* reset albumart info in wps */
1040 aa->width = -1;
1041 aa->height = -1;
1042 aa->xalign = WPS_ALBUMART_ALIGN_CENTER; /* default */
1043 aa->yalign = WPS_ALBUMART_ALIGN_CENTER; /* default */
1044 aa->vp = &curr_vp->vp;
1046 /* format: %Cl|x|y|[[l|c|r]mwidth]|[[t|c|b]mheight]| */
1048 newline = strchr(wps_bufptr, '\n');
1050 /* initial validation and parsing of x and y components */
1051 if (*wps_bufptr != '|')
1052 return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */
1054 _pos = wps_bufptr + 1;
1055 _pos = parse_list("dd", NULL, '|', _pos, &aa->x, &aa->y);
1057 if (!_pos || _pos > newline || *_pos != '|')
1058 return WPS_ERROR_INVALID_PARAM; /* malformed token: no | after y coordinate
1059 e.g. %Cl|7|59\n */
1061 /* parsing width field */
1062 parsing = true;
1063 while (parsing)
1065 /* apply each modifier in turn */
1066 ++_pos;
1067 switch (*_pos)
1069 case 'l':
1070 case 'L':
1071 case '+':
1072 aa->xalign = WPS_ALBUMART_ALIGN_LEFT;
1073 break;
1074 case 'c':
1075 case 'C':
1076 aa->xalign = WPS_ALBUMART_ALIGN_CENTER;
1077 break;
1078 case 'r':
1079 case 'R':
1080 case '-':
1081 aa->xalign = WPS_ALBUMART_ALIGN_RIGHT;
1082 break;
1083 case 'd':
1084 case 'D':
1085 case 'i':
1086 case 'I':
1087 case 's':
1088 case 'S':
1089 /* simply ignored */
1090 break;
1091 default:
1092 parsing = false;
1093 break;
1096 /* extract max width data */
1097 if (*_pos != '|')
1099 if (!isdigit(*_pos)) /* malformed token: e.g. %Cl|7|59|# */
1100 return WPS_ERROR_INVALID_PARAM;
1102 aa->width = atoi(_pos);
1104 _pos = strchr(_pos, '|');
1105 if (!_pos || _pos > newline)
1106 return WPS_ERROR_INVALID_PARAM; /* malformed token: no | after width field
1107 e.g. %Cl|7|59|200\n */
1110 /* parsing height field */
1111 parsing = true;
1112 while (parsing)
1114 /* apply each modifier in turn */
1115 ++_pos;
1116 switch (*_pos)
1118 case 't':
1119 case 'T':
1120 case '-':
1121 aa->yalign = WPS_ALBUMART_ALIGN_TOP;
1122 break;
1123 case 'c':
1124 case 'C':
1125 aa->yalign = WPS_ALBUMART_ALIGN_CENTER;
1126 break;
1127 case 'b':
1128 case 'B':
1129 case '+':
1130 aa->yalign = WPS_ALBUMART_ALIGN_BOTTOM;
1131 break;
1132 case 'd':
1133 case 'D':
1134 case 'i':
1135 case 'I':
1136 case 's':
1137 case 'S':
1138 /* simply ignored */
1139 break;
1140 default:
1141 parsing = false;
1142 break;
1145 /* extract max height data */
1146 if (*_pos != '|')
1148 if (!isdigit(*_pos))
1149 return WPS_ERROR_INVALID_PARAM; /* malformed token e.g. %Cl|7|59|200|@ */
1151 aa->height = atoi(_pos);
1153 _pos = strchr(_pos, '|');
1154 if (!_pos || _pos > newline)
1155 return WPS_ERROR_INVALID_PARAM; /* malformed token: no closing |
1156 e.g. %Cl|7|59|200|200\n */
1159 /* if we got here, we parsed everything ok .. ! */
1160 if (aa->width < 0)
1161 aa->width = 0;
1162 else if (aa->width > LCD_WIDTH)
1163 aa->width = LCD_WIDTH;
1165 if (aa->height < 0)
1166 aa->height = 0;
1167 else if (aa->height > LCD_HEIGHT)
1168 aa->height = LCD_HEIGHT;
1170 aa->state = WPS_ALBUMART_LOAD;
1171 aa->draw = false;
1172 wps_data->albumart = aa;
1174 dimensions.width = aa->width;
1175 dimensions.height = aa->height;
1177 albumart_slot = playback_claim_aa_slot(&dimensions);
1179 if (albumart_slot < 0) /* didn't get a slot ? */
1180 return skip_end_of_line(wps_bufptr);
1181 else
1182 wps_data->playback_aa_slot = albumart_slot;
1184 /* Skip the rest of the line */
1185 return skip_end_of_line(wps_bufptr);
1188 static int parse_albumart_display(const char *wps_bufptr,
1189 struct wps_token *token,
1190 struct wps_data *wps_data)
1192 (void)wps_bufptr;
1193 struct wps_token *prev = token-1;
1194 if ((wps_data->num_tokens >= 1) && (prev->type == WPS_TOKEN_CONDITIONAL))
1196 token->type = WPS_TOKEN_ALBUMART_FOUND;
1198 else if (wps_data->albumart)
1200 wps_data->albumart->vp = &curr_vp->vp;
1202 #if 0
1203 /* the old code did this so keep it here for now...
1204 * this is to allow the posibility to showing the next tracks AA! */
1205 if (wps_bufptr+1 == 'n')
1206 return 1;
1207 #endif
1208 return 0;
1210 #endif /* HAVE_ALBUMART */
1212 #ifdef HAVE_TOUCHSCREEN
1214 struct touchaction {char* s; int action;};
1215 static struct touchaction touchactions[] = {
1216 {"play", ACTION_WPS_PLAY }, {"stop", ACTION_WPS_STOP },
1217 {"prev", ACTION_WPS_SKIPPREV }, {"next", ACTION_WPS_SKIPNEXT },
1218 {"ffwd", ACTION_WPS_SEEKFWD }, {"rwd", ACTION_WPS_SEEKBACK },
1219 {"menu", ACTION_WPS_MENU }, {"browse", ACTION_WPS_BROWSE },
1220 {"shuffle", ACTION_TOUCH_SHUFFLE }, {"repmode", ACTION_TOUCH_REPMODE },
1221 {"quickscreen", ACTION_WPS_QUICKSCREEN },{"contextmenu", ACTION_WPS_CONTEXT },
1222 {"playlist", ACTION_WPS_VIEW_PLAYLIST }, {"pitch", ACTION_WPS_PITCHSCREEN},
1223 {"voldown", ACTION_WPS_VOLDOWN}, {"volup", ACTION_WPS_VOLUP},
1225 static int parse_touchregion(const char *wps_bufptr,
1226 struct wps_token *token, struct wps_data *wps_data)
1228 (void)token;
1229 unsigned i, imax;
1230 struct touchregion *region = NULL;
1231 const char *ptr = wps_bufptr;
1232 const char *action;
1233 const char pb_string[] = "progressbar";
1234 const char vol_string[] = "volume";
1235 int x,y,w,h;
1237 /* format: %T|x|y|width|height|action|
1238 * if action starts with & the area must be held to happen
1239 * action is one of:
1240 * play - play/pause playback
1241 * stop - stop playback, exit the wps
1242 * prev - prev track
1243 * next - next track
1244 * ffwd - seek forward
1245 * rwd - seek backwards
1246 * menu - go back to the main menu
1247 * browse - go back to the file/db browser
1248 * shuffle - toggle shuffle mode
1249 * repmode - cycle the repeat mode
1250 * quickscreen - go into the quickscreen
1251 * contextmenu - open the context menu
1252 * playlist - go into the playlist
1253 * pitch - go into the pitchscreen
1254 * volup - increase volume by one step
1255 * voldown - decrease volume by one step
1259 if (*ptr != '|')
1260 return WPS_ERROR_INVALID_PARAM;
1261 ptr++;
1263 if (!(ptr = parse_list("dddds", NULL, '|', ptr, &x, &y, &w, &h, &action)))
1264 return WPS_ERROR_INVALID_PARAM;
1266 /* Check there is a terminating | */
1267 if (*ptr != '|')
1268 return WPS_ERROR_INVALID_PARAM;
1270 region = skin_buffer_alloc(sizeof(struct touchregion));
1271 if (!region)
1272 return WPS_ERROR_INVALID_PARAM;
1274 /* should probably do some bounds checking here with the viewport... but later */
1275 region->action = ACTION_NONE;
1276 region->x = x;
1277 region->y = y;
1278 region->width = w;
1279 region->height = h;
1280 region->wvp = curr_vp;
1282 if(!strncmp(pb_string, action, sizeof(pb_string)-1)
1283 && *(action + sizeof(pb_string)-1) == '|')
1284 region->type = WPS_TOUCHREGION_SCROLLBAR;
1285 else if(!strncmp(vol_string, action, sizeof(vol_string)-1)
1286 && *(action + sizeof(vol_string)-1) == '|')
1287 region->type = WPS_TOUCHREGION_VOLUME;
1288 else
1290 region->type = WPS_TOUCHREGION_ACTION;
1292 if (*action == '&')
1294 action++;
1295 region->repeat = true;
1297 else
1298 region->repeat = false;
1300 i = 0;
1301 imax = ARRAYLEN(touchactions);
1302 while ((region->action == ACTION_NONE) &&
1303 (i < imax))
1305 /* try to match with one of our touchregion screens */
1306 int len = strlen(touchactions[i].s);
1307 if (!strncmp(touchactions[i].s, action, len)
1308 && *(action+len) == '|')
1309 region->action = touchactions[i].action;
1310 i++;
1312 if (region->action == ACTION_NONE)
1313 return WPS_ERROR_INVALID_PARAM;
1315 struct skin_token_list *item = new_skin_token_list_item(NULL, region);
1316 if (!item)
1317 return WPS_ERROR_INVALID_PARAM;
1318 add_to_ll_chain(&wps_data->touchregions, item);
1319 return skip_end_of_line(wps_bufptr);
1321 #endif
1323 /* Parse a generic token from the given string. Return the length read */
1324 static int parse_token(const char *wps_bufptr, struct wps_data *wps_data)
1326 int skip = 0, taglen = 0, ret;
1327 struct wps_token *token = wps_data->tokens + wps_data->num_tokens;
1328 const struct wps_tag *tag;
1329 memset(token, 0, sizeof(*token));
1331 switch(*wps_bufptr)
1334 case '%':
1335 case '<':
1336 case '|':
1337 case '>':
1338 case ';':
1339 case '#':
1340 /* escaped characters */
1341 token->type = WPS_TOKEN_CHARACTER;
1342 token->value.c = *wps_bufptr;
1343 taglen = 1;
1344 wps_data->num_tokens++;
1345 break;
1347 case '?':
1348 /* conditional tag */
1349 token->type = WPS_TOKEN_CONDITIONAL;
1350 level++;
1351 condindex[level] = wps_data->num_tokens;
1352 numoptions[level] = 1;
1353 wps_data->num_tokens++;
1354 ret = parse_token(wps_bufptr + 1, wps_data);
1355 if (ret < 0) return ret;
1356 taglen = 1 + ret;
1357 break;
1359 default:
1360 /* find what tag we have */
1361 for (tag = all_tags;
1362 strncmp(wps_bufptr, tag->name, strlen(tag->name)) != 0;
1363 tag++) ;
1365 taglen = (tag->type != WPS_TOKEN_UNKNOWN) ? strlen(tag->name) : 2;
1366 token->type = tag->type;
1367 curr_line->curr_subline->line_type |= tag->refresh_type;
1369 /* if the tag has a special parsing function, we call it */
1370 if (tag->parse_func)
1372 ret = tag->parse_func(wps_bufptr + taglen, token, wps_data);
1373 if (ret < 0) return ret;
1374 skip += ret;
1377 /* Some tags we don't want to save as tokens */
1378 if (tag->type == WPS_NO_TOKEN)
1379 break;
1381 /* tags that start with 'F', 'I' or 'D' are for the next file */
1382 if ( *(tag->name) == 'I' || *(tag->name) == 'F' ||
1383 *(tag->name) == 'D')
1384 token->next = true;
1386 wps_data->num_tokens++;
1387 break;
1390 skip += taglen;
1391 return skip;
1396 * Returns the number of bytes to skip the buf pointer to access the false
1397 * branch in a _binary_ conditional
1399 * That is:
1400 * - before the '|' if we have a false branch, (%?<true|false> -> %?<|false>)
1401 * - or before the closing '>' if there's no false branch (%?<true> -> %?<>)
1403 * depending on the features of a target it's not called from check_feature_tag,
1404 * hence the __attribute__ or it issues compiler warnings
1408 static int find_false_branch(const char *wps_bufptr) __attribute__((unused));
1409 static int find_false_branch(const char *wps_bufptr)
1411 const char *buf = wps_bufptr;
1412 /* wps_bufptr is after the opening '<', hence level = 1*/
1413 int level = 1;
1414 char ch;
1417 ch = *buf;
1418 if (ch == '%')
1419 { /* filter out the characters we check later if they're printed
1420 * as literals */
1421 ch = *(++buf);
1422 if (ch == '<' || ch == '>' || ch == '|')
1423 continue;
1424 /* else: some tags/printed literals we skip over */
1426 else if (ch == '<') /* nested conditional */
1427 level++;
1428 else if (ch == '>')
1429 { /* closed our or a nested conditional,
1430 * do NOT skip over the '>' so that wps_parse() sees it for closing
1431 * if it is the closing one for our conditional */
1432 level--;
1434 else if (ch == '|' && level == 1)
1435 { /* we found our separator, point before and get out */
1436 break;
1438 /* if level is 0, we don't have a false branch */
1439 } while (level > 0 && *(++buf));
1441 return buf - wps_bufptr;
1445 * returns the number of bytes to get the appropriate branch of a binary
1446 * conditional
1448 * That means:
1449 * - if a feature is available, it returns 0 to not skip anything
1450 * - if the feature is not available, skip to the false branch and don't
1451 * parse the true branch at all
1453 * */
1454 static int check_feature_tag(const char *wps_bufptr, const int type)
1456 (void)wps_bufptr;
1457 switch (type)
1459 case WPS_TOKEN_RTC_PRESENT:
1460 #if CONFIG_RTC
1461 return 0;
1462 #else
1463 return find_false_branch(wps_bufptr);
1464 #endif
1465 case WPS_TOKEN_HAVE_RECORDING:
1466 #ifdef HAVE_RECORDING
1467 return 0;
1468 #else
1469 return find_false_branch(wps_bufptr);
1470 #endif
1471 default: /* not a tag we care about, just don't skip */
1472 return 0;
1477 /* Parses the WPS.
1478 data is the pointer to the structure where the parsed WPS should be stored.
1479 It is initialised.
1480 wps_bufptr points to the string containing the WPS tags */
1481 #define TOKEN_BLOCK_SIZE 128
1482 static bool wps_parse(struct wps_data *data, const char *wps_bufptr, bool debug)
1484 if (!data || !wps_bufptr || !*wps_bufptr)
1485 return false;
1486 enum wps_parse_error fail = PARSE_OK;
1487 int ret;
1488 int max_tokens = TOKEN_BLOCK_SIZE;
1489 size_t buf_free = 0;
1490 line_number = 1;
1491 level = -1;
1493 /* allocate enough RAM for a reasonable skin, grow as needed.
1494 * Free any used RAM before loading the images to be 100% RAM efficient */
1495 data->tokens = (struct wps_token *)skin_buffer_grab(&buf_free);
1496 if (sizeof(struct wps_token)*max_tokens >= buf_free)
1497 return false;
1498 skin_buffer_increment(max_tokens * sizeof(struct wps_token), false);
1499 data->num_tokens = 0;
1501 while (*wps_bufptr && !fail)
1503 /* first make sure there is enough room for tokens */
1504 if (max_tokens <= data->num_tokens + 5)
1506 int extra_tokens = TOKEN_BLOCK_SIZE;
1507 size_t needed = extra_tokens * sizeof(struct wps_token);
1508 /* do some smarts here to grow the array a bit */
1509 if (skin_buffer_freespace() < needed)
1511 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1512 break;
1514 skin_buffer_increment(needed, false);
1515 max_tokens += extra_tokens;
1518 switch(*wps_bufptr++)
1521 /* Regular tag */
1522 case '%':
1523 if ((ret = parse_token(wps_bufptr, data)) < 0)
1525 fail = PARSE_FAIL_COND_INVALID_PARAM;
1526 break;
1528 else if (level >= WPS_MAX_COND_LEVEL - 1)
1530 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1531 break;
1533 wps_bufptr += ret;
1534 break;
1536 /* Alternating sublines separator */
1537 case ';':
1538 if (level >= 0) /* there are unclosed conditionals */
1540 fail = PARSE_FAIL_UNCLOSED_COND;
1541 break;
1544 if (!skin_start_new_subline(curr_line, data->num_tokens))
1545 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1547 break;
1549 /* Conditional list start */
1550 case '<':
1551 if (data->tokens[data->num_tokens-2].type != WPS_TOKEN_CONDITIONAL)
1553 fail = PARSE_FAIL_COND_SYNTAX_ERROR;
1554 break;
1556 wps_bufptr += check_feature_tag(wps_bufptr,
1557 data->tokens[data->num_tokens-1].type);
1558 data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_START;
1559 lastcond[level] = data->num_tokens++;
1560 break;
1562 /* Conditional list end */
1563 case '>':
1564 if (level < 0) /* not in a conditional, invalid char */
1566 fail = PARSE_FAIL_INVALID_CHAR;
1567 break;
1570 data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_END;
1571 if (lastcond[level])
1572 data->tokens[lastcond[level]].value.i = data->num_tokens;
1573 else
1575 fail = PARSE_FAIL_COND_SYNTAX_ERROR;
1576 break;
1579 lastcond[level] = 0;
1580 data->num_tokens++;
1581 data->tokens[condindex[level]].value.i = numoptions[level];
1582 level--;
1583 break;
1585 /* Conditional list option */
1586 case '|':
1587 if (level < 0) /* not in a conditional, invalid char */
1589 fail = PARSE_FAIL_INVALID_CHAR;
1590 break;
1593 data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_OPTION;
1594 if (lastcond[level])
1595 data->tokens[lastcond[level]].value.i = data->num_tokens;
1596 else
1598 fail = PARSE_FAIL_COND_SYNTAX_ERROR;
1599 break;
1602 lastcond[level] = data->num_tokens;
1603 numoptions[level]++;
1604 data->num_tokens++;
1605 break;
1607 /* Comment */
1608 case '#':
1609 if (level >= 0) /* there are unclosed conditionals */
1611 fail = PARSE_FAIL_UNCLOSED_COND;
1612 break;
1615 wps_bufptr += skip_end_of_line(wps_bufptr);
1616 break;
1618 /* End of this line */
1619 case '\n':
1620 if (level >= 0) /* there are unclosed conditionals */
1622 fail = PARSE_FAIL_UNCLOSED_COND;
1623 break;
1625 /* add a new token for the \n so empty lines are correct */
1626 data->tokens[data->num_tokens].type = WPS_TOKEN_CHARACTER;
1627 data->tokens[data->num_tokens].value.c = '\n';
1628 data->tokens[data->num_tokens].next = false;
1629 data->num_tokens++;
1631 if (!skin_start_new_line(curr_vp, data->num_tokens))
1633 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1634 break;
1637 break;
1639 /* String */
1640 default:
1642 unsigned int len = 1;
1643 const char *string_start = wps_bufptr - 1;
1645 /* find the length of the string */
1646 while (*wps_bufptr && *wps_bufptr != '#' &&
1647 *wps_bufptr != '%' && *wps_bufptr != ';' &&
1648 *wps_bufptr != '<' && *wps_bufptr != '>' &&
1649 *wps_bufptr != '|' && *wps_bufptr != '\n')
1651 wps_bufptr++;
1652 len++;
1655 /* look if we already have that string */
1656 char *str;
1657 bool found = false;
1658 struct skin_token_list *list = data->strings;
1659 while (list)
1661 str = (char*)list->token->value.data;
1662 found = (strlen(str) == len &&
1663 strncmp(string_start, str, len) == 0);
1664 if (found)
1665 break; /* break here because the list item is
1666 used if its found */
1667 list = list->next;
1669 /* If a matching string is found, found is true and i is
1670 the index of the string. If not, found is false */
1672 if (!found)
1674 /* new string */
1675 str = (char*)skin_buffer_alloc(len+1);
1676 if (!str)
1678 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1679 break;
1681 strlcpy(str, string_start, len+1);
1682 struct skin_token_list *item =
1683 new_skin_token_list_item(&data->tokens[data->num_tokens], str);
1684 if(!item)
1686 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1687 break;
1689 add_to_ll_chain(&data->strings, item);
1691 else
1693 /* another occurrence of an existing string */
1694 data->tokens[data->num_tokens].value.data = list->token->value.data;
1696 data->tokens[data->num_tokens].type = WPS_TOKEN_STRING;
1697 data->num_tokens++;
1699 break;
1703 if (!fail && level >= 0) /* there are unclosed conditionals */
1704 fail = PARSE_FAIL_UNCLOSED_COND;
1706 if (*wps_bufptr && !fail)
1707 /* one of the limits of the while loop was exceeded */
1708 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1710 /* Success! */
1711 curr_line->curr_subline->last_token_idx = data->num_tokens;
1712 data->tokens[data->num_tokens++].type = WPS_NO_TOKEN;
1713 /* freeup unused tokens */
1714 skin_buffer_free_from_front(sizeof(struct wps_token)
1715 * (max_tokens - data->num_tokens));
1717 #if defined(DEBUG) || defined(SIMULATOR)
1718 if (debug)
1719 print_debug_info(data, fail, line_number);
1720 #else
1721 (void)debug;
1722 #endif
1724 return (fail == 0);
1729 * initial setup of wps_data; does reset everything
1730 * except fields which need to survive, i.e.
1733 void skin_data_reset(struct wps_data *wps_data)
1735 #ifdef HAVE_LCD_BITMAP
1736 wps_data->images = NULL;
1737 wps_data->progressbars = NULL;
1738 #endif
1739 #ifdef HAVE_TOUCHSCREEN
1740 wps_data->touchregions = NULL;
1741 #endif
1742 wps_data->viewports = NULL;
1743 wps_data->strings = NULL;
1744 #ifdef HAVE_ALBUMART
1745 wps_data->albumart = NULL;
1746 if (wps_data->playback_aa_slot >= 0)
1748 playback_release_aa_slot(wps_data->playback_aa_slot);
1749 wps_data->playback_aa_slot = -1;
1751 #endif
1752 wps_data->tokens = NULL;
1753 wps_data->num_tokens = 0;
1755 #ifdef HAVE_LCD_BITMAP
1756 wps_data->peak_meter_enabled = false;
1757 wps_data->wps_sb_tag = false;
1758 wps_data->show_sb_on_wps = false;
1759 #else /* HAVE_LCD_CHARCELLS */
1760 /* progress bars */
1761 int i;
1762 for (i = 0; i < 8; i++)
1764 wps_data->wps_progress_pat[i] = 0;
1766 wps_data->full_line_progressbar = false;
1767 #endif
1768 wps_data->wps_loaded = false;
1771 #ifdef HAVE_LCD_BITMAP
1772 static bool load_skin_bmp(struct wps_data *wps_data, struct bitmap *bitmap, char* bmpdir)
1774 (void)wps_data; /* only needed for remote targets */
1775 bool loaded = false;
1776 char img_path[MAX_PATH];
1777 get_image_filename(bitmap->data, bmpdir,
1778 img_path, sizeof(img_path));
1780 /* load the image */
1781 int format;
1782 #ifdef HAVE_REMOTE_LCD
1783 if (curr_screen == SCREEN_REMOTE)
1784 format = FORMAT_ANY|FORMAT_REMOTE;
1785 else
1786 #endif
1787 format = FORMAT_ANY|FORMAT_TRANSPARENT;
1789 size_t max_buf;
1790 char* imgbuf = (char*)skin_buffer_grab(&max_buf);
1791 bitmap->data = imgbuf;
1792 int ret = read_bmp_file(img_path, bitmap, max_buf, format, NULL);
1794 if (ret > 0)
1796 skin_buffer_increment(ret, true);
1797 loaded = true;
1799 else
1801 /* Abort if we can't load an image */
1802 loaded = false;
1804 return loaded;
1807 static bool load_skin_bitmaps(struct wps_data *wps_data, char *bmpdir)
1809 struct skin_token_list *list;
1810 /* do the progressbars */
1811 list = wps_data->progressbars;
1812 while (list)
1814 struct progressbar *pb = (struct progressbar*)list->token->value.data;
1815 if (pb->bm.data)
1817 pb->have_bitmap_pb = load_skin_bmp(wps_data, &pb->bm, bmpdir);
1819 list = list->next;
1821 /* regular images */
1822 list = wps_data->images;
1823 while (list)
1825 struct gui_img *img = (struct gui_img*)list->token->value.data;
1826 if (img->bm.data)
1828 img->loaded = load_skin_bmp(wps_data, &img->bm, bmpdir);
1829 if (img->loaded)
1830 img->subimage_height = img->bm.height / img->num_subimages;
1832 list = list->next;
1835 #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
1836 if (bmp_names[BACKDROP_BMP])
1838 char img_path[MAX_PATH];
1839 get_image_filename(bmp_names[BACKDROP_BMP], bmpdir,
1840 img_path, sizeof(img_path));
1841 screens[curr_screen].backdrop_load(BACKDROP_SKIN_WPS, img_path);
1843 #endif /* has backdrop support */
1845 /* If we got here, everything was OK */
1846 return true;
1849 #endif /* HAVE_LCD_BITMAP */
1851 /* to setup up the wps-data from a format-buffer (isfile = false)
1852 from a (wps-)file (isfile = true)*/
1853 bool skin_data_load(enum screen_type screen, struct wps_data *wps_data,
1854 const char *buf, bool isfile)
1857 if (!wps_data || !buf)
1858 return false;
1859 #ifdef HAVE_ALBUMART
1860 int status;
1861 struct mp3entry *curtrack;
1862 long offset;
1863 struct skin_albumart old_aa = {.state = WPS_ALBUMART_NONE};
1864 if (wps_data->albumart)
1866 old_aa.state = wps_data->albumart->state;
1867 old_aa.height = wps_data->albumart->height;
1868 old_aa.width = wps_data->albumart->width;
1870 #endif
1872 skin_data_reset(wps_data);
1873 curr_screen = screen;
1875 /* alloc default viewport, will be fixed up later */
1876 curr_vp = skin_buffer_alloc(sizeof(struct skin_viewport));
1877 if (!curr_vp)
1878 return false;
1879 struct skin_token_list *list = new_skin_token_list_item(NULL, curr_vp);
1880 if (!list)
1881 return false;
1882 add_to_ll_chain(&wps_data->viewports, list);
1885 /* Initialise the first (default) viewport */
1886 curr_vp->label = VP_DEFAULT_LABEL;
1887 curr_vp->pb = NULL;
1888 curr_vp->hidden_flags = 0;
1889 curr_vp->lines = NULL;
1891 viewport_set_defaults(&curr_vp->vp, screen);
1893 curr_line = NULL;
1894 if (!skin_start_new_line(curr_vp, 0))
1895 return false;
1897 if (!isfile)
1899 return wps_parse(wps_data, buf, false);
1901 else
1903 int fd = open_utf8(buf, O_RDONLY);
1905 if (fd < 0)
1906 return false;
1908 /* get buffer space from the plugin buffer */
1909 size_t buffersize = 0;
1910 char *wps_buffer = (char *)plugin_get_buffer(&buffersize);
1912 if (!wps_buffer)
1913 return false;
1915 /* copy the file's content to the buffer for parsing,
1916 ensuring that every line ends with a newline char. */
1917 unsigned int start = 0;
1918 while(read_line(fd, wps_buffer + start, buffersize - start) > 0)
1920 start += strlen(wps_buffer + start);
1921 if (start < buffersize - 1)
1923 wps_buffer[start++] = '\n';
1924 wps_buffer[start] = 0;
1928 close(fd);
1930 if (start <= 0)
1931 return false;
1933 #ifdef HAVE_LCD_BITMAP
1934 /* Set all filename pointers to NULL */
1935 memset(bmp_names, 0, sizeof(bmp_names));
1936 #endif
1938 /* parse the WPS source */
1939 if (!wps_parse(wps_data, wps_buffer, true)) {
1940 skin_data_reset(wps_data);
1941 return false;
1944 wps_data->wps_loaded = true;
1946 #ifdef HAVE_LCD_BITMAP
1947 /* get the bitmap dir */
1948 char bmpdir[MAX_PATH];
1949 char *dot = strrchr(buf, '.');
1951 strlcpy(bmpdir, buf, dot - buf + 1);
1953 /* load the bitmaps that were found by the parsing */
1954 if (!load_skin_bitmaps(wps_data, bmpdir)) {
1955 skin_data_reset(wps_data);
1956 return false;
1958 #endif
1959 #if defined(HAVE_ALBUMART) && !defined(__PCTOOL__)
1960 status = audio_status();
1961 if (status & AUDIO_STATUS_PLAY)
1963 struct skin_albumart *aa = wps_data->albumart;
1964 if (aa && ((aa->state && !old_aa.state) ||
1965 (aa->state &&
1966 (((old_aa.height != aa->height) ||
1967 (old_aa.width != aa->width))))))
1969 curtrack = audio_current_track();
1970 offset = curtrack->offset;
1971 audio_stop();
1972 if (!(status & AUDIO_STATUS_PAUSE))
1973 audio_play(offset);
1976 #endif
1977 #if defined(DEBUG) || defined(SIMULATOR)
1978 debug_skin_usage();
1979 #endif
1980 return true;