RTL language enhancements by Tomers Shalev and I.
[kugel-rb.git] / apps / gui / skin_engine / skin_parser.c
blob1e24762feea3c6b6ff9b47e43528b574c3e31f10
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 static int follow_lang_direction = 0;
94 #ifdef HAVE_LCD_BITMAP
96 #if LCD_DEPTH > 1
97 #define MAX_BITMAPS (MAX_IMAGES+MAX_PROGRESSBARS+1) /* WPS images + pbar bitmap + backdrop */
98 #else
99 #define MAX_BITMAPS (MAX_IMAGES+MAX_PROGRESSBARS) /* WPS images + pbar bitmap */
100 #endif
102 #define PROGRESSBAR_BMP MAX_IMAGES
103 #define BACKDROP_BMP (MAX_BITMAPS-1)
105 /* pointers to the bitmap filenames in the WPS source */
106 static const char *bmp_names[MAX_BITMAPS];
108 #endif /* HAVE_LCD_BITMAP */
110 #if defined(DEBUG) || defined(SIMULATOR)
111 /* debugging function */
112 extern void print_debug_info(struct wps_data *data, int fail, int line);
113 extern void debug_skin_usage(void);
114 #endif
116 /* Function for parsing of details for a token. At the moment the
117 function is called, the token type has already been set. The
118 function must fill in the details and possibly add more tokens
119 to the token array. It should return the number of chars that
120 has been consumed.
122 wps_bufptr points to the char following the tag (i.e. where
123 details begin).
124 token is the pointer to the 'main' token being parsed
126 typedef int (*wps_tag_parse_func)(const char *wps_bufptr,
127 struct wps_token *token, struct wps_data *wps_data);
129 struct wps_tag {
130 enum wps_token_type type;
131 const char name[3];
132 unsigned char refresh_type;
133 const wps_tag_parse_func parse_func;
135 static int skip_end_of_line(const char *wps_bufptr);
136 /* prototypes of all special parse functions : */
137 static int parse_timeout(const char *wps_bufptr,
138 struct wps_token *token, struct wps_data *wps_data);
139 static int parse_progressbar(const char *wps_bufptr,
140 struct wps_token *token, struct wps_data *wps_data);
141 static int parse_dir_level(const char *wps_bufptr,
142 struct wps_token *token, struct wps_data *wps_data);
143 static int parse_setting_and_lang(const char *wps_bufptr,
144 struct wps_token *token, struct wps_data *wps_data);
147 int parse_languagedirection(const char *wps_bufptr,
148 struct wps_token *token, struct wps_data *wps_data)
150 (void)wps_bufptr;
151 (void)token;
152 (void)wps_data;
153 follow_lang_direction = 2; /* 2 because it is decremented immediatly after
154 this token is parsed, after the next token it
155 will be 0 again. */
156 return 0;
159 #ifdef HAVE_LCD_BITMAP
160 static int parse_viewport_display(const char *wps_bufptr,
161 struct wps_token *token, struct wps_data *wps_data);
162 static int parse_viewport(const char *wps_bufptr,
163 struct wps_token *token, struct wps_data *wps_data);
164 static int parse_statusbar_enable(const char *wps_bufptr,
165 struct wps_token *token, struct wps_data *wps_data);
166 static int parse_statusbar_disable(const char *wps_bufptr,
167 struct wps_token *token, struct wps_data *wps_data);
168 static int parse_image_display(const char *wps_bufptr,
169 struct wps_token *token, struct wps_data *wps_data);
170 static int parse_image_load(const char *wps_bufptr,
171 struct wps_token *token, struct wps_data *wps_data);
172 #endif /*HAVE_LCD_BITMAP */
173 #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
174 static int parse_image_special(const char *wps_bufptr,
175 struct wps_token *token, struct wps_data *wps_data);
176 #endif
177 #ifdef HAVE_ALBUMART
178 static int parse_albumart_load(const char *wps_bufptr,
179 struct wps_token *token, struct wps_data *wps_data);
180 static int parse_albumart_display(const char *wps_bufptr,
181 struct wps_token *token, struct wps_data *wps_data);
182 #endif /* HAVE_ALBUMART */
183 #ifdef HAVE_TOUCHSCREEN
184 static int parse_touchregion(const char *wps_bufptr,
185 struct wps_token *token, struct wps_data *wps_data);
186 #else
187 static int fulline_tag_not_supported(const char *wps_bufptr,
188 struct wps_token *token, struct wps_data *wps_data)
190 (void)token; (void)wps_data;
191 return skip_end_of_line(wps_bufptr);
193 #define parse_touchregion fulline_tag_not_supported
194 #endif
195 #ifdef CONFIG_RTC
196 #define WPS_RTC_REFRESH WPS_REFRESH_DYNAMIC
197 #else
198 #define WPS_RTC_REFRESH WPS_REFRESH_STATIC
199 #endif
201 /* array of available tags - those with more characters have to go first
202 (e.g. "xl" and "xd" before "x"). It needs to end with the unknown token. */
203 static const struct wps_tag all_tags[] = {
205 { WPS_TOKEN_ALIGN_CENTER, "ac", 0, NULL },
206 { WPS_TOKEN_ALIGN_LEFT, "al", 0, NULL },
207 { WPS_TOKEN_ALIGN_LEFT_RTL, "aL", 0, NULL },
208 { WPS_TOKEN_ALIGN_RIGHT, "ar", 0, NULL },
209 { WPS_TOKEN_ALIGN_RIGHT_RTL, "aR", 0, NULL },
210 { WPS_NO_TOKEN, "ax", 0, parse_languagedirection },
212 { WPS_TOKEN_BATTERY_PERCENT, "bl", WPS_REFRESH_DYNAMIC, NULL },
213 { WPS_TOKEN_BATTERY_VOLTS, "bv", WPS_REFRESH_DYNAMIC, NULL },
214 { WPS_TOKEN_BATTERY_TIME, "bt", WPS_REFRESH_DYNAMIC, NULL },
215 { WPS_TOKEN_BATTERY_SLEEPTIME, "bs", WPS_REFRESH_DYNAMIC, NULL },
216 #if CONFIG_CHARGING >= CHARGING_MONITOR
217 { WPS_TOKEN_BATTERY_CHARGING, "bc", WPS_REFRESH_DYNAMIC, NULL },
218 #endif
219 #if CONFIG_CHARGING
220 { WPS_TOKEN_BATTERY_CHARGER_CONNECTED,"bp", WPS_REFRESH_DYNAMIC, NULL },
221 #endif
222 #ifdef HAVE_USB_POWER
223 { WPS_TOKEN_USB_POWERED, "bu", WPS_REFRESH_DYNAMIC, NULL },
224 #endif
226 { WPS_TOKEN_RTC_PRESENT , "cc", WPS_REFRESH_STATIC, NULL },
227 { WPS_TOKEN_RTC_DAY_OF_MONTH, "cd", WPS_RTC_REFRESH, NULL },
228 { WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED,"ce", WPS_RTC_REFRESH, NULL },
229 { WPS_TOKEN_RTC_12HOUR_CFG, "cf", WPS_RTC_REFRESH, NULL },
230 { WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED, "cH", WPS_RTC_REFRESH, NULL },
231 { WPS_TOKEN_RTC_HOUR_24, "ck", WPS_RTC_REFRESH, NULL },
232 { WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED, "cI", WPS_RTC_REFRESH, NULL },
233 { WPS_TOKEN_RTC_HOUR_12, "cl", WPS_RTC_REFRESH, NULL },
234 { WPS_TOKEN_RTC_MONTH, "cm", WPS_RTC_REFRESH, NULL },
235 { WPS_TOKEN_RTC_MINUTE, "cM", WPS_RTC_REFRESH, NULL },
236 { WPS_TOKEN_RTC_SECOND, "cS", WPS_RTC_REFRESH, NULL },
237 { WPS_TOKEN_RTC_YEAR_2_DIGITS, "cy", WPS_RTC_REFRESH, NULL },
238 { WPS_TOKEN_RTC_YEAR_4_DIGITS, "cY", WPS_RTC_REFRESH, NULL },
239 { WPS_TOKEN_RTC_AM_PM_UPPER, "cP", WPS_RTC_REFRESH, NULL },
240 { WPS_TOKEN_RTC_AM_PM_LOWER, "cp", WPS_RTC_REFRESH, NULL },
241 { WPS_TOKEN_RTC_WEEKDAY_NAME, "ca", WPS_RTC_REFRESH, NULL },
242 { WPS_TOKEN_RTC_MONTH_NAME, "cb", WPS_RTC_REFRESH, NULL },
243 { WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON, "cu", WPS_RTC_REFRESH, NULL },
244 { WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN, "cw", WPS_RTC_REFRESH, NULL },
246 /* current file */
247 { WPS_TOKEN_FILE_BITRATE, "fb", WPS_REFRESH_STATIC, NULL },
248 { WPS_TOKEN_FILE_CODEC, "fc", WPS_REFRESH_STATIC, NULL },
249 { WPS_TOKEN_FILE_FREQUENCY, "ff", WPS_REFRESH_STATIC, NULL },
250 { WPS_TOKEN_FILE_FREQUENCY_KHZ, "fk", WPS_REFRESH_STATIC, NULL },
251 { WPS_TOKEN_FILE_NAME_WITH_EXTENSION, "fm", WPS_REFRESH_STATIC, NULL },
252 { WPS_TOKEN_FILE_NAME, "fn", WPS_REFRESH_STATIC, NULL },
253 { WPS_TOKEN_FILE_PATH, "fp", WPS_REFRESH_STATIC, NULL },
254 { WPS_TOKEN_FILE_SIZE, "fs", WPS_REFRESH_STATIC, NULL },
255 { WPS_TOKEN_FILE_VBR, "fv", WPS_REFRESH_STATIC, NULL },
256 { WPS_TOKEN_FILE_DIRECTORY, "d", WPS_REFRESH_STATIC,
257 parse_dir_level },
259 /* next file */
260 { WPS_TOKEN_FILE_BITRATE, "Fb", WPS_REFRESH_STATIC, NULL },
261 { WPS_TOKEN_FILE_CODEC, "Fc", WPS_REFRESH_STATIC, NULL },
262 { WPS_TOKEN_FILE_FREQUENCY, "Ff", WPS_REFRESH_STATIC, NULL },
263 { WPS_TOKEN_FILE_FREQUENCY_KHZ, "Fk", WPS_REFRESH_STATIC, NULL },
264 { WPS_TOKEN_FILE_NAME_WITH_EXTENSION, "Fm", WPS_REFRESH_STATIC, NULL },
265 { WPS_TOKEN_FILE_NAME, "Fn", WPS_REFRESH_STATIC, NULL },
266 { WPS_TOKEN_FILE_PATH, "Fp", WPS_REFRESH_STATIC, NULL },
267 { WPS_TOKEN_FILE_SIZE, "Fs", WPS_REFRESH_STATIC, NULL },
268 { WPS_TOKEN_FILE_VBR, "Fv", WPS_REFRESH_STATIC, NULL },
269 { WPS_TOKEN_FILE_DIRECTORY, "D", WPS_REFRESH_STATIC,
270 parse_dir_level },
272 /* current metadata */
273 { WPS_TOKEN_METADATA_ARTIST, "ia", WPS_REFRESH_STATIC, NULL },
274 { WPS_TOKEN_METADATA_COMPOSER, "ic", WPS_REFRESH_STATIC, NULL },
275 { WPS_TOKEN_METADATA_ALBUM, "id", WPS_REFRESH_STATIC, NULL },
276 { WPS_TOKEN_METADATA_ALBUM_ARTIST, "iA", WPS_REFRESH_STATIC, NULL },
277 { WPS_TOKEN_METADATA_GROUPING, "iG", WPS_REFRESH_STATIC, NULL },
278 { WPS_TOKEN_METADATA_GENRE, "ig", WPS_REFRESH_STATIC, NULL },
279 { WPS_TOKEN_METADATA_DISC_NUMBER, "ik", WPS_REFRESH_STATIC, NULL },
280 { WPS_TOKEN_METADATA_TRACK_NUMBER, "in", WPS_REFRESH_STATIC, NULL },
281 { WPS_TOKEN_METADATA_TRACK_TITLE, "it", WPS_REFRESH_STATIC, NULL },
282 { WPS_TOKEN_METADATA_VERSION, "iv", WPS_REFRESH_STATIC, NULL },
283 { WPS_TOKEN_METADATA_YEAR, "iy", WPS_REFRESH_STATIC, NULL },
284 { WPS_TOKEN_METADATA_COMMENT, "iC", WPS_REFRESH_STATIC, NULL },
286 /* next metadata */
287 { WPS_TOKEN_METADATA_ARTIST, "Ia", WPS_REFRESH_STATIC, NULL },
288 { WPS_TOKEN_METADATA_COMPOSER, "Ic", WPS_REFRESH_STATIC, NULL },
289 { WPS_TOKEN_METADATA_ALBUM, "Id", WPS_REFRESH_STATIC, NULL },
290 { WPS_TOKEN_METADATA_ALBUM_ARTIST, "IA", WPS_REFRESH_STATIC, NULL },
291 { WPS_TOKEN_METADATA_GROUPING, "IG", WPS_REFRESH_STATIC, NULL },
292 { WPS_TOKEN_METADATA_GENRE, "Ig", WPS_REFRESH_STATIC, NULL },
293 { WPS_TOKEN_METADATA_DISC_NUMBER, "Ik", WPS_REFRESH_STATIC, NULL },
294 { WPS_TOKEN_METADATA_TRACK_NUMBER, "In", WPS_REFRESH_STATIC, NULL },
295 { WPS_TOKEN_METADATA_TRACK_TITLE, "It", WPS_REFRESH_STATIC, NULL },
296 { WPS_TOKEN_METADATA_VERSION, "Iv", WPS_REFRESH_STATIC, NULL },
297 { WPS_TOKEN_METADATA_YEAR, "Iy", WPS_REFRESH_STATIC, NULL },
298 { WPS_TOKEN_METADATA_COMMENT, "IC", WPS_REFRESH_STATIC, NULL },
300 #if (CONFIG_CODEC != MAS3507D)
301 { WPS_TOKEN_SOUND_PITCH, "Sp", WPS_REFRESH_DYNAMIC, NULL },
302 #endif
303 #if (CONFIG_CODEC == SWCODEC)
304 { WPS_TOKEN_SOUND_SPEED, "Ss", WPS_REFRESH_DYNAMIC, NULL },
305 #endif
306 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
307 { WPS_TOKEN_VLED_HDD, "lh", WPS_REFRESH_DYNAMIC, NULL },
308 #endif
310 { WPS_TOKEN_MAIN_HOLD, "mh", WPS_REFRESH_DYNAMIC, NULL },
312 #ifdef HAS_REMOTE_BUTTON_HOLD
313 { WPS_TOKEN_REMOTE_HOLD, "mr", WPS_REFRESH_DYNAMIC, NULL },
314 #else
315 { WPS_TOKEN_UNKNOWN, "mr", 0, NULL },
316 #endif
318 { WPS_TOKEN_REPEAT_MODE, "mm", WPS_REFRESH_DYNAMIC, NULL },
319 { WPS_TOKEN_PLAYBACK_STATUS, "mp", WPS_REFRESH_DYNAMIC, NULL },
320 { WPS_TOKEN_BUTTON_VOLUME, "mv", WPS_REFRESH_DYNAMIC,
321 parse_timeout },
323 #ifdef HAVE_LCD_BITMAP
324 { WPS_TOKEN_PEAKMETER, "pm", WPS_REFRESH_PEAK_METER, NULL },
325 #else
326 { WPS_TOKEN_PLAYER_PROGRESSBAR, "pf",
327 WPS_REFRESH_DYNAMIC | WPS_REFRESH_PLAYER_PROGRESS, parse_progressbar },
328 #endif
329 { WPS_TOKEN_PROGRESSBAR, "pb", WPS_REFRESH_PLAYER_PROGRESS,
330 parse_progressbar },
332 { WPS_TOKEN_VOLUME, "pv", WPS_REFRESH_DYNAMIC, NULL },
334 { WPS_TOKEN_TRACK_ELAPSED_PERCENT, "px", WPS_REFRESH_DYNAMIC, NULL },
335 { WPS_TOKEN_TRACK_TIME_ELAPSED, "pc", WPS_REFRESH_DYNAMIC, NULL },
336 { WPS_TOKEN_TRACK_TIME_REMAINING, "pr", WPS_REFRESH_DYNAMIC, NULL },
337 { WPS_TOKEN_TRACK_LENGTH, "pt", WPS_REFRESH_STATIC, NULL },
339 { WPS_TOKEN_PLAYLIST_POSITION, "pp", WPS_REFRESH_STATIC, NULL },
340 { WPS_TOKEN_PLAYLIST_ENTRIES, "pe", WPS_REFRESH_STATIC, NULL },
341 { WPS_TOKEN_PLAYLIST_NAME, "pn", WPS_REFRESH_STATIC, NULL },
342 { WPS_TOKEN_PLAYLIST_SHUFFLE, "ps", WPS_REFRESH_DYNAMIC, NULL },
344 #ifdef HAVE_TAGCACHE
345 { WPS_TOKEN_DATABASE_PLAYCOUNT, "rp", WPS_REFRESH_DYNAMIC, NULL },
346 { WPS_TOKEN_DATABASE_RATING, "rr", WPS_REFRESH_DYNAMIC, NULL },
347 { WPS_TOKEN_DATABASE_AUTOSCORE, "ra", WPS_REFRESH_DYNAMIC, NULL },
348 #endif
350 #if CONFIG_CODEC == SWCODEC
351 { WPS_TOKEN_REPLAYGAIN, "rg", WPS_REFRESH_STATIC, NULL },
352 { WPS_TOKEN_CROSSFADE, "xf", WPS_REFRESH_DYNAMIC, NULL },
353 #endif
355 { WPS_NO_TOKEN, "s", WPS_REFRESH_SCROLL, NULL },
356 { WPS_TOKEN_SUBLINE_TIMEOUT, "t", 0, parse_timeout },
358 #ifdef HAVE_LCD_BITMAP
359 { WPS_NO_TOKEN, "we", 0, parse_statusbar_enable },
360 { WPS_NO_TOKEN, "wd", 0, parse_statusbar_disable },
362 { WPS_NO_TOKEN, "xl", 0, parse_image_load },
364 { WPS_TOKEN_IMAGE_PRELOAD_DISPLAY, "xd", WPS_REFRESH_STATIC,
365 parse_image_display },
367 { WPS_TOKEN_IMAGE_DISPLAY, "x", 0, parse_image_load },
368 #ifdef HAVE_ALBUMART
369 { WPS_NO_TOKEN, "Cl", 0, parse_albumart_load },
370 { WPS_TOKEN_ALBUMART_DISPLAY, "C", WPS_REFRESH_STATIC, parse_albumart_display },
371 #endif
373 { WPS_VIEWPORT_ENABLE, "Vd", WPS_REFRESH_DYNAMIC,
374 parse_viewport_display },
375 { WPS_NO_TOKEN, "V", 0, parse_viewport },
377 #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
378 { WPS_TOKEN_IMAGE_BACKDROP, "X", 0, parse_image_special },
379 #endif
380 #endif
382 { WPS_TOKEN_SETTING, "St", WPS_REFRESH_DYNAMIC,
383 parse_setting_and_lang },
384 { WPS_TOKEN_TRANSLATEDSTRING, "Sx", WPS_REFRESH_STATIC,
385 parse_setting_and_lang },
386 { WPS_TOKEN_LANG_IS_RTL , "Sr", WPS_REFRESH_STATIC, NULL },
388 { WPS_TOKEN_LASTTOUCH, "Tl", WPS_REFRESH_DYNAMIC, parse_timeout },
389 { WPS_TOKEN_CURRENT_SCREEN, "cs", WPS_REFRESH_DYNAMIC, NULL },
390 { WPS_NO_TOKEN, "T", 0, parse_touchregion },
393 /* Recording Tokens */
394 { WPS_TOKEN_HAVE_RECORDING, "Rp", WPS_REFRESH_STATIC, NULL },
395 #ifdef HAVE_RECORDING
396 { WPS_TOKEN_REC_FREQ, "Rf", WPS_REFRESH_DYNAMIC, NULL },
397 { WPS_TOKEN_REC_ENCODER, "Re", WPS_REFRESH_DYNAMIC, NULL },
398 { WPS_TOKEN_REC_BITRATE, "Rb", WPS_REFRESH_DYNAMIC, NULL },
399 { WPS_TOKEN_REC_MONO, "Rm", WPS_REFRESH_DYNAMIC, NULL },
400 #endif
401 { WPS_TOKEN_UNKNOWN, "", 0, NULL }
402 /* the array MUST end with an empty string (first char is \0) */
406 /* add a skin_token_list item to the list chain. ALWAYS appended because some of the
407 * chains require the order to be kept.
409 static void add_to_ll_chain(struct skin_token_list **list, struct skin_token_list *item)
411 if (*list == NULL)
412 *list = item;
413 else
415 struct skin_token_list *t = *list;
416 while (t->next)
417 t = t->next;
418 t->next = item;
422 /* traverse the image linked-list for an image */
423 #ifdef HAVE_LCD_BITMAP
424 struct gui_img* find_image(char label, struct wps_data *data)
426 struct skin_token_list *list = data->images;
427 while (list)
429 struct gui_img *img = (struct gui_img *)list->token->value.data;
430 if (img->label == label)
431 return img;
432 list = list->next;
434 return NULL;
436 #endif
438 /* traverse the viewport linked list for a viewport */
439 struct skin_viewport* find_viewport(char label, struct wps_data *data)
441 struct skin_token_list *list = data->viewports;
442 while (list)
444 struct skin_viewport *vp = (struct skin_viewport *)list->token->value.data;
445 if (vp->label == label)
446 return vp;
447 list = list->next;
449 return NULL;
453 /* create and init a new wpsll item.
454 * passing NULL to token will alloc a new one.
455 * You should only pass NULL for the token when the token type (table above)
456 * is WPS_NO_TOKEN which means it is not stored automatically in the skins token array
458 static struct skin_token_list *new_skin_token_list_item(struct wps_token *token,
459 void* token_data)
461 struct skin_token_list *llitem = skin_buffer_alloc(sizeof(struct skin_token_list));
462 if (!token)
463 token = skin_buffer_alloc(sizeof(struct wps_token));
464 if (!llitem || !token)
465 return NULL;
466 llitem->next = NULL;
467 llitem->token = token;
468 if (token_data)
469 llitem->token->value.data = token_data;
470 return llitem;
473 /* Returns the number of chars that should be skipped to jump
474 immediately after the first eol, i.e. to the start of the next line */
475 static int skip_end_of_line(const char *wps_bufptr)
477 line_number++;
478 int skip = 0;
479 while(*(wps_bufptr + skip) != '\n')
480 skip++;
481 return ++skip;
484 /* Starts a new subline in the current line during parsing */
485 static bool skin_start_new_subline(struct skin_line *line, int curr_token)
487 struct skin_subline *subline = skin_buffer_alloc(sizeof(struct skin_subline));
488 if (!subline)
489 return false;
491 subline->first_token_idx = curr_token;
492 subline->next = NULL;
494 subline->line_type = 0;
495 subline->time_mult = 0;
497 line->curr_subline->last_token_idx = curr_token-1;
498 line->curr_subline->next = subline;
499 line->curr_subline = subline;
500 return true;
503 static bool skin_start_new_line(struct skin_viewport *vp, int curr_token)
505 struct skin_line *line = skin_buffer_alloc(sizeof(struct skin_line));
506 struct skin_subline *subline = NULL;
507 if (!line)
508 return false;
510 /* init the subline */
511 subline = &line->sublines;
512 subline->first_token_idx = curr_token;
513 subline->next = NULL;
514 subline->line_type = 0;
515 subline->time_mult = 0;
517 /* init the new line */
518 line->curr_subline = &line->sublines;
519 line->next = NULL;
520 line->subline_expire_time = 0;
522 /* connect to curr_line and vp pointers.
523 * 1) close the previous lines subline
524 * 2) connect to vp pointer
525 * 3) connect to curr_line global pointer
527 if (curr_line)
529 curr_line->curr_subline->last_token_idx = curr_token - 1;
530 curr_line->next = line;
531 curr_line->curr_subline = NULL;
533 curr_line = line;
534 if (!vp->lines)
535 vp->lines = line;
536 return true;
539 #ifdef HAVE_LCD_BITMAP
541 static int parse_statusbar_enable(const char *wps_bufptr,
542 struct wps_token *token,
543 struct wps_data *wps_data)
545 (void)token; /* Kill warnings */
546 wps_data->wps_sb_tag = true;
547 wps_data->show_sb_on_wps = true;
548 struct skin_viewport *default_vp = find_viewport(VP_DEFAULT_LABEL, wps_data);
549 viewport_set_defaults(&default_vp->vp, curr_screen);
550 return skip_end_of_line(wps_bufptr);
553 static int parse_statusbar_disable(const char *wps_bufptr,
554 struct wps_token *token,
555 struct wps_data *wps_data)
557 (void)token; /* Kill warnings */
558 wps_data->wps_sb_tag = true;
559 wps_data->show_sb_on_wps = false;
560 struct skin_viewport *default_vp = find_viewport(VP_DEFAULT_LABEL, wps_data);
561 viewport_set_fullscreen(&default_vp->vp, curr_screen);
562 return skip_end_of_line(wps_bufptr);
565 static int get_image_id(int c)
567 if(c >= 'a' && c <= 'z')
568 return c - 'a';
569 else if(c >= 'A' && c <= 'Z')
570 return c - 'A' + 26;
571 else
572 return -1;
575 static char *get_image_filename(const char *start, const char* bmpdir,
576 char *buf, int buf_size)
578 const char *end = strchr(start, '|');
580 if ( !end || (end - start) >= (buf_size - (int)ROCKBOX_DIR_LEN - 2) )
582 buf = "\0";
583 return NULL;
586 int bmpdirlen = strlen(bmpdir);
588 strcpy(buf, bmpdir);
589 buf[bmpdirlen] = '/';
590 memcpy( &buf[bmpdirlen + 1], start, end - start);
591 buf[bmpdirlen + 1 + end - start] = 0;
593 return buf;
596 static int parse_image_display(const char *wps_bufptr,
597 struct wps_token *token,
598 struct wps_data *wps_data)
600 char label = wps_bufptr[0];
601 int subimage;
602 struct gui_img *img;;
604 /* sanity check */
605 img = find_image(label, wps_data);
606 if (!img)
608 token->value.i = label; /* so debug works */
609 return WPS_ERROR_INVALID_PARAM;
612 if ((subimage = get_image_id(wps_bufptr[1])) != -1)
614 if (subimage >= img->num_subimages)
615 return WPS_ERROR_INVALID_PARAM;
617 /* Store sub-image number to display in high bits */
618 token->value.i = label | (subimage << 8);
619 return 2; /* We have consumed 2 bytes */
620 } else {
621 token->value.i = label;
622 return 1; /* We have consumed 1 byte */
626 static int parse_image_load(const char *wps_bufptr,
627 struct wps_token *token,
628 struct wps_data *wps_data)
630 const char *ptr = wps_bufptr;
631 const char *pos;
632 const char* filename;
633 const char* id;
634 const char *newline;
635 int x,y;
636 struct gui_img *img;
638 /* format: %x|n|filename.bmp|x|y|
639 or %xl|n|filename.bmp|x|y|
640 or %xl|n|filename.bmp|x|y|num_subimages|
643 if (*ptr != '|')
644 return WPS_ERROR_INVALID_PARAM;
646 ptr++;
648 if (!(ptr = parse_list("ssdd", NULL, '|', ptr, &id, &filename, &x, &y)))
649 return WPS_ERROR_INVALID_PARAM;
651 /* Check there is a terminating | */
652 if (*ptr != '|')
653 return WPS_ERROR_INVALID_PARAM;
655 /* check the image number and load state */
656 if(find_image(*id, wps_data))
658 /* Invalid image ID */
659 return WPS_ERROR_INVALID_PARAM;
661 img = skin_buffer_alloc(sizeof(struct gui_img));
662 if (!img)
663 return WPS_ERROR_INVALID_PARAM;
664 /* save a pointer to the filename */
665 img->bm.data = (char*)filename;
666 img->label = *id;
667 img->x = x;
668 img->y = y;
669 img->num_subimages = 1;
670 img->always_display = false;
672 /* save current viewport */
673 img->vp = &curr_vp->vp;
675 if (token->type == WPS_TOKEN_IMAGE_DISPLAY)
677 img->always_display = true;
679 else
681 /* Parse the (optional) number of sub-images */
682 ptr++;
683 newline = strchr(ptr, '\n');
684 pos = strchr(ptr, '|');
685 if (pos && pos < newline)
686 img->num_subimages = atoi(ptr);
688 if (img->num_subimages <= 0)
689 return WPS_ERROR_INVALID_PARAM;
691 struct skin_token_list *item = new_skin_token_list_item(NULL, img);
692 if (!item)
693 return WPS_ERROR_INVALID_PARAM;
694 add_to_ll_chain(&wps_data->images, item);
696 /* Skip the rest of the line */
697 return skip_end_of_line(wps_bufptr);
700 static int parse_viewport_display(const char *wps_bufptr,
701 struct wps_token *token,
702 struct wps_data *wps_data)
704 (void)wps_data;
705 char letter = wps_bufptr[0];
707 if (letter < 'a' || letter > 'z')
709 /* invalid viewport tag */
710 return WPS_ERROR_INVALID_PARAM;
712 token->value.i = letter;
713 return 1;
716 static int parse_viewport(const char *wps_bufptr,
717 struct wps_token *token,
718 struct wps_data *wps_data)
720 (void)token; /* Kill warnings */
721 const char *ptr = wps_bufptr;
723 struct skin_viewport *skin_vp = skin_buffer_alloc(sizeof(struct skin_viewport));
725 /* check for the optional letter to signify its a hideable viewport */
726 /* %Vl|<label>|<rest of tags>| */
727 skin_vp->hidden_flags = 0;
728 skin_vp->label = VP_NO_LABEL;
729 skin_vp->pb = NULL;
730 skin_vp->lines = NULL;
731 if (curr_line)
733 curr_line->curr_subline->last_token_idx = wps_data->num_tokens
734 - (wps_data->num_tokens > 0 ? 1 : 0);
737 curr_line = NULL;
738 if (!skin_start_new_line(skin_vp, wps_data->num_tokens))
739 return WPS_ERROR_INVALID_PARAM;
742 if (*ptr == 'i')
744 skin_vp->label = VP_INFO_LABEL;
745 skin_vp->hidden_flags = VP_NEVER_VISIBLE;
746 ++ptr;
748 else if (*ptr == 'l')
750 if (*(ptr+1) == '|')
752 char label = *(ptr+2);
753 if (label >= 'a' && label <= 'z')
755 skin_vp->hidden_flags = VP_DRAW_HIDEABLE;
756 skin_vp->label = label;
758 else
759 return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */
760 ptr += 3;
763 if (*ptr != '|')
764 return WPS_ERROR_INVALID_PARAM;
766 ptr++;
767 struct viewport *vp = &skin_vp->vp;
768 /* format: %V|x|y|width|height|font|fg_pattern|bg_pattern| */
769 if (!(ptr = viewport_parse_viewport(vp, curr_screen, ptr, '|')))
770 return WPS_ERROR_INVALID_PARAM;
772 /* Check for trailing | */
773 if (*ptr != '|')
774 return WPS_ERROR_INVALID_PARAM;
776 if (follow_lang_direction && lang_is_rtl())
778 vp->flags |= VP_FLAG_ALIGN_RIGHT;
779 vp->x = screens[curr_screen].lcdwidth - vp->width - vp->x;
781 else
782 vp->flags &= ~VP_FLAG_ALIGN_RIGHT; /* ignore right-to-left languages */
786 struct skin_token_list *list = new_skin_token_list_item(NULL, skin_vp);
787 if (!list)
788 return WPS_ERROR_INVALID_PARAM;
789 add_to_ll_chain(&wps_data->viewports, list);
790 curr_vp = skin_vp;
791 /* Skip the rest of the line */
792 return skip_end_of_line(wps_bufptr);
795 #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
796 static int parse_image_special(const char *wps_bufptr,
797 struct wps_token *token,
798 struct wps_data *wps_data)
800 (void)wps_data; /* kill warning */
801 (void)token;
802 const char *pos = NULL;
803 const char *newline;
805 pos = strchr(wps_bufptr + 1, '|');
806 newline = strchr(wps_bufptr, '\n');
808 if (pos > newline)
809 return WPS_ERROR_INVALID_PARAM;
810 #if LCD_DEPTH > 1
811 if (token->type == WPS_TOKEN_IMAGE_BACKDROP)
813 /* format: %X|filename.bmp| */
814 bmp_names[BACKDROP_BMP] = wps_bufptr + 1;
816 #endif
818 /* Skip the rest of the line */
819 return skip_end_of_line(wps_bufptr);
821 #endif
823 #endif /* HAVE_LCD_BITMAP */
825 static int parse_setting_and_lang(const char *wps_bufptr,
826 struct wps_token *token,
827 struct wps_data *wps_data)
829 /* NOTE: both the string validations that happen in here will
830 * automatically PASS on checkwps because its too hard to get
831 * settings_list.c and englinsh.lang built for it.
832 * If that ever changes remove the #ifndef __PCTOOL__'s here
834 (void)wps_data;
835 const char *ptr = wps_bufptr;
836 const char *end;
837 int i = 0;
838 char temp[64];
840 /* Find the setting's cfg_name */
841 if (*ptr != '|')
842 return WPS_ERROR_INVALID_PARAM;
843 ptr++;
844 end = strchr(ptr,'|');
845 if (!end)
846 return WPS_ERROR_INVALID_PARAM;
847 strlcpy(temp, ptr,end-ptr+1);
849 if (token->type == WPS_TOKEN_TRANSLATEDSTRING)
851 #ifndef __PCTOOL__
852 i = lang_english_to_id(temp);
853 if (i < 0)
854 return WPS_ERROR_INVALID_PARAM;
855 #endif
857 else
859 /* Find the setting */
860 for (i=0; i<nb_settings; i++)
861 if (settings[i].cfg_name &&
862 !strncmp(settings[i].cfg_name,ptr,end-ptr) &&
863 /* prevent matches on cfg_name prefixes */
864 strlen(settings[i].cfg_name)==(size_t)(end-ptr))
865 break;
866 #ifndef __PCTOOL__
867 if (i == nb_settings)
868 return WPS_ERROR_INVALID_PARAM;
869 #endif
871 /* Store the setting number */
872 token->value.i = i;
874 /* Skip the rest of the line */
875 return end-ptr+2;
879 static int parse_dir_level(const char *wps_bufptr,
880 struct wps_token *token,
881 struct wps_data *wps_data)
883 char val[] = { *wps_bufptr, '\0' };
884 token->value.i = atoi(val);
885 (void)wps_data; /* Kill warnings */
886 return 1;
889 static int parse_timeout(const char *wps_bufptr,
890 struct wps_token *token,
891 struct wps_data *wps_data)
893 int skip = 0;
894 int val = 0;
895 bool have_point = false;
896 bool have_tenth = false;
898 (void)wps_data; /* Kill the warning */
900 while ( isdigit(*wps_bufptr) || *wps_bufptr == '.' )
902 if (*wps_bufptr != '.')
904 val *= 10;
905 val += *wps_bufptr - '0';
906 if (have_point)
908 have_tenth = true;
909 wps_bufptr++;
910 skip++;
911 break;
914 else
915 have_point = true;
917 wps_bufptr++;
918 skip++;
921 if (have_tenth == false)
922 val *= 10;
924 if (val == 0 && skip == 0)
926 /* decide what to do if no value was specified */
927 switch (token->type)
929 case WPS_TOKEN_SUBLINE_TIMEOUT:
930 return -1;
931 case WPS_TOKEN_BUTTON_VOLUME:
932 val = 10;
933 break;
936 token->value.i = val;
938 return skip;
941 static int parse_progressbar(const char *wps_bufptr,
942 struct wps_token *token,
943 struct wps_data *wps_data)
945 /* %pb or %pb|filename|x|y|width|height|
946 using - for any of the params uses "sane" values */
947 #ifdef HAVE_LCD_BITMAP
948 enum {
949 PB_FILENAME = 0,
950 PB_X,
951 PB_Y,
952 PB_WIDTH,
953 PB_HEIGHT
955 const char *filename;
956 int x, y, height, width;
957 uint32_t set = 0;
958 const char *ptr = wps_bufptr;
959 struct progressbar *pb = skin_buffer_alloc(sizeof(struct progressbar));
960 struct skin_token_list *item = new_skin_token_list_item(token, pb);
962 if (!pb || !item)
963 return WPS_ERROR_INVALID_PARAM;
965 struct viewport *vp = &curr_vp->vp;
966 #ifndef __PCTOOL__
967 int font_height = font_get(vp->font)->height;
968 #else
969 int font_height = 8;
970 #endif
971 /* we need to know what line number (viewport relative) this pb is,
972 * so count them... */
973 int line_num = -1;
974 struct skin_line *line = curr_vp->lines;
975 while (line)
977 line_num++;
978 line = line->next;
980 pb->have_bitmap_pb = false;
981 pb->bm.data = NULL; /* no bitmap specified */
982 pb->follow_lang_direction = follow_lang_direction > 0;
984 if (*wps_bufptr != '|') /* regular old style */
986 pb->x = 0;
987 pb->width = vp->width;
988 pb->height = SYSFONT_HEIGHT-2;
989 pb->y = -line_num - 1; /* Will be computed during the rendering */
991 curr_vp->pb = pb;
992 add_to_ll_chain(&wps_data->progressbars, item);
993 return 0;
995 ptr = wps_bufptr + 1;
997 if (!(ptr = parse_list("sdddd", &set, '|', ptr, &filename,
998 &x, &y, &width, &height)))
999 return WPS_ERROR_INVALID_PARAM;
1001 if (LIST_VALUE_PARSED(set, PB_FILENAME)) /* filename */
1002 pb->bm.data = (char*)filename;
1004 if (LIST_VALUE_PARSED(set, PB_X)) /* x */
1005 pb->x = x;
1006 else
1007 pb->x = vp->x;
1009 if (LIST_VALUE_PARSED(set, PB_WIDTH)) /* width */
1011 /* A zero width causes a divide-by-zero error later, so reject it */
1012 if (width == 0)
1013 return WPS_ERROR_INVALID_PARAM;
1015 pb->width = width;
1017 else
1018 pb->width = vp->width - pb->x;
1020 if (LIST_VALUE_PARSED(set, PB_HEIGHT)) /* height, default to font height */
1022 /* A zero height makes no sense - reject it */
1023 if (height == 0)
1024 return WPS_ERROR_INVALID_PARAM;
1026 pb->height = height;
1028 else
1029 pb->height = font_height;
1031 if (LIST_VALUE_PARSED(set, PB_Y)) /* y */
1032 pb->y = y;
1033 else
1034 pb->y = -line_num - 1; /* Will be computed during the rendering */
1036 curr_vp->pb = pb;
1037 add_to_ll_chain(&wps_data->progressbars, item);
1039 /* Skip the rest of the line */
1040 return skip_end_of_line(wps_bufptr)-1;
1041 #else
1042 (void)token;
1044 if (*(wps_bufptr-1) == 'f')
1045 wps_data->full_line_progressbar = true;
1046 else
1047 wps_data->full_line_progressbar = false;
1049 return 0;
1051 #endif
1054 #ifdef HAVE_ALBUMART
1055 static int parse_int(const char *newline, const char **_pos, int *num)
1057 *_pos = parse_list("d", NULL, '|', *_pos, num);
1059 return (!*_pos || *_pos > newline || **_pos != '|');
1062 static int parse_albumart_load(const char *wps_bufptr,
1063 struct wps_token *token,
1064 struct wps_data *wps_data)
1066 const char *_pos, *newline;
1067 bool parsing;
1068 struct dim dimensions;
1069 int albumart_slot;
1070 bool swap_for_rtl = lang_is_rtl() && follow_lang_direction;
1071 struct skin_albumart *aa = skin_buffer_alloc(sizeof(struct skin_albumart));
1072 (void)token; /* silence warning */
1073 if (!aa)
1074 return skip_end_of_line(wps_bufptr);
1076 /* reset albumart info in wps */
1077 aa->width = -1;
1078 aa->height = -1;
1079 aa->xalign = WPS_ALBUMART_ALIGN_CENTER; /* default */
1080 aa->yalign = WPS_ALBUMART_ALIGN_CENTER; /* default */
1081 aa->vp = &curr_vp->vp;
1083 /* format: %Cl|x|y|[[l|c|r]mwidth]|[[t|c|b]mheight]| */
1085 newline = strchr(wps_bufptr, '\n');
1087 _pos = wps_bufptr;
1089 if (*_pos != '|')
1090 return WPS_ERROR_INVALID_PARAM; /* malformed token: e.g. %Cl7 */
1092 ++_pos;
1094 /* initial validation and parsing of x component */
1095 if (parse_int(newline, &_pos, &aa->x))
1096 return WPS_ERROR_INVALID_PARAM;
1098 ++_pos;
1100 /* initial validation and parsing of y component */
1101 if (parse_int(newline, &_pos, &aa->y))
1102 return WPS_ERROR_INVALID_PARAM;
1104 /* parsing width field */
1105 parsing = true;
1106 while (parsing)
1108 /* apply each modifier in turn */
1109 ++_pos;
1110 switch (*_pos)
1112 case 'l':
1113 case 'L':
1114 case '+':
1115 if (swap_for_rtl)
1116 aa->xalign = WPS_ALBUMART_ALIGN_RIGHT;
1117 else
1118 aa->xalign = WPS_ALBUMART_ALIGN_LEFT;
1119 break;
1120 case 'c':
1121 case 'C':
1122 aa->xalign = WPS_ALBUMART_ALIGN_CENTER;
1123 break;
1124 case 'r':
1125 case 'R':
1126 case '-':
1127 if (swap_for_rtl)
1128 aa->xalign = WPS_ALBUMART_ALIGN_LEFT;
1129 else
1130 aa->xalign = WPS_ALBUMART_ALIGN_RIGHT;
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 width data */
1146 if (*_pos != '|')
1148 if (parse_int(newline, &_pos, &aa->width))
1149 return WPS_ERROR_INVALID_PARAM;
1152 /* parsing height field */
1153 parsing = true;
1154 while (parsing)
1156 /* apply each modifier in turn */
1157 ++_pos;
1158 switch (*_pos)
1160 case 't':
1161 case 'T':
1162 case '-':
1163 aa->yalign = WPS_ALBUMART_ALIGN_TOP;
1164 break;
1165 case 'c':
1166 case 'C':
1167 aa->yalign = WPS_ALBUMART_ALIGN_CENTER;
1168 break;
1169 case 'b':
1170 case 'B':
1171 case '+':
1172 aa->yalign = WPS_ALBUMART_ALIGN_BOTTOM;
1173 break;
1174 case 'd':
1175 case 'D':
1176 case 'i':
1177 case 'I':
1178 case 's':
1179 case 'S':
1180 /* simply ignored */
1181 break;
1182 default:
1183 parsing = false;
1184 break;
1187 /* extract max height data */
1188 if (*_pos != '|')
1190 if (parse_int(newline, &_pos, &aa->height))
1191 return WPS_ERROR_INVALID_PARAM;
1194 /* if we got here, we parsed everything ok .. ! */
1195 if (aa->width < 0)
1196 aa->width = 0;
1197 else if (aa->width > LCD_WIDTH)
1198 aa->width = LCD_WIDTH;
1200 if (aa->height < 0)
1201 aa->height = 0;
1202 else if (aa->height > LCD_HEIGHT)
1203 aa->height = LCD_HEIGHT;
1205 if (swap_for_rtl)
1206 aa->x = LCD_WIDTH - (aa->x + aa->width);
1208 aa->state = WPS_ALBUMART_LOAD;
1209 aa->draw = false;
1210 wps_data->albumart = aa;
1212 dimensions.width = aa->width;
1213 dimensions.height = aa->height;
1215 albumart_slot = playback_claim_aa_slot(&dimensions);
1217 if (0 <= albumart_slot)
1218 wps_data->playback_aa_slot = albumart_slot;
1220 /* Skip the rest of the line */
1221 return skip_end_of_line(wps_bufptr);
1224 static int parse_albumart_display(const char *wps_bufptr,
1225 struct wps_token *token,
1226 struct wps_data *wps_data)
1228 (void)wps_bufptr;
1229 struct wps_token *prev = token-1;
1230 if ((wps_data->num_tokens >= 1) && (prev->type == WPS_TOKEN_CONDITIONAL))
1232 token->type = WPS_TOKEN_ALBUMART_FOUND;
1234 else if (wps_data->albumart)
1236 wps_data->albumart->vp = &curr_vp->vp;
1238 #if 0
1239 /* the old code did this so keep it here for now...
1240 * this is to allow the posibility to showing the next tracks AA! */
1241 if (wps_bufptr+1 == 'n')
1242 return 1;
1243 #endif
1244 return 0;
1246 #endif /* HAVE_ALBUMART */
1248 #ifdef HAVE_TOUCHSCREEN
1250 struct touchaction {const char* s; int action;};
1251 static const struct touchaction touchactions[] = {
1252 {"play", ACTION_WPS_PLAY }, {"stop", ACTION_WPS_STOP },
1253 {"prev", ACTION_WPS_SKIPPREV }, {"next", ACTION_WPS_SKIPNEXT },
1254 {"ffwd", ACTION_WPS_SEEKFWD }, {"rwd", ACTION_WPS_SEEKBACK },
1255 {"menu", ACTION_WPS_MENU }, {"browse", ACTION_WPS_BROWSE },
1256 {"shuffle", ACTION_TOUCH_SHUFFLE }, {"repmode", ACTION_TOUCH_REPMODE },
1257 {"quickscreen", ACTION_WPS_QUICKSCREEN },{"contextmenu", ACTION_WPS_CONTEXT },
1258 {"playlist", ACTION_WPS_VIEW_PLAYLIST }, {"pitch", ACTION_WPS_PITCHSCREEN},
1259 {"voldown", ACTION_WPS_VOLDOWN}, {"volup", ACTION_WPS_VOLUP},
1261 static int parse_touchregion(const char *wps_bufptr,
1262 struct wps_token *token, struct wps_data *wps_data)
1264 (void)token;
1265 unsigned i, imax;
1266 struct touchregion *region = NULL;
1267 const char *ptr = wps_bufptr;
1268 const char *action;
1269 const char pb_string[] = "progressbar";
1270 const char vol_string[] = "volume";
1271 int x,y,w,h;
1273 /* format: %T|x|y|width|height|action|
1274 * if action starts with & the area must be held to happen
1275 * action is one of:
1276 * play - play/pause playback
1277 * stop - stop playback, exit the wps
1278 * prev - prev track
1279 * next - next track
1280 * ffwd - seek forward
1281 * rwd - seek backwards
1282 * menu - go back to the main menu
1283 * browse - go back to the file/db browser
1284 * shuffle - toggle shuffle mode
1285 * repmode - cycle the repeat mode
1286 * quickscreen - go into the quickscreen
1287 * contextmenu - open the context menu
1288 * playlist - go into the playlist
1289 * pitch - go into the pitchscreen
1290 * volup - increase volume by one step
1291 * voldown - decrease volume by one step
1295 if (*ptr != '|')
1296 return WPS_ERROR_INVALID_PARAM;
1297 ptr++;
1299 if (!(ptr = parse_list("dddds", NULL, '|', ptr, &x, &y, &w, &h, &action)))
1300 return WPS_ERROR_INVALID_PARAM;
1302 /* Check there is a terminating | */
1303 if (*ptr != '|')
1304 return WPS_ERROR_INVALID_PARAM;
1306 region = skin_buffer_alloc(sizeof(struct touchregion));
1307 if (!region)
1308 return WPS_ERROR_INVALID_PARAM;
1310 /* should probably do some bounds checking here with the viewport... but later */
1311 region->action = ACTION_NONE;
1312 region->x = x;
1313 region->y = y;
1314 region->width = w;
1315 region->height = h;
1316 region->wvp = curr_vp;
1318 if(!strncmp(pb_string, action, sizeof(pb_string)-1)
1319 && *(action + sizeof(pb_string)-1) == '|')
1320 region->type = WPS_TOUCHREGION_SCROLLBAR;
1321 else if(!strncmp(vol_string, action, sizeof(vol_string)-1)
1322 && *(action + sizeof(vol_string)-1) == '|')
1323 region->type = WPS_TOUCHREGION_VOLUME;
1324 else
1326 region->type = WPS_TOUCHREGION_ACTION;
1328 if (*action == '&')
1330 action++;
1331 region->repeat = true;
1333 else
1334 region->repeat = false;
1336 i = 0;
1337 imax = ARRAYLEN(touchactions);
1338 while ((region->action == ACTION_NONE) &&
1339 (i < imax))
1341 /* try to match with one of our touchregion screens */
1342 int len = strlen(touchactions[i].s);
1343 if (!strncmp(touchactions[i].s, action, len)
1344 && *(action+len) == '|')
1345 region->action = touchactions[i].action;
1346 i++;
1348 if (region->action == ACTION_NONE)
1349 return WPS_ERROR_INVALID_PARAM;
1351 struct skin_token_list *item = new_skin_token_list_item(NULL, region);
1352 if (!item)
1353 return WPS_ERROR_INVALID_PARAM;
1354 add_to_ll_chain(&wps_data->touchregions, item);
1355 return skip_end_of_line(wps_bufptr);
1357 #endif
1359 /* Parse a generic token from the given string. Return the length read */
1360 static int parse_token(const char *wps_bufptr, struct wps_data *wps_data)
1362 int skip = 0, taglen = 0, ret;
1363 struct wps_token *token = wps_data->tokens + wps_data->num_tokens;
1364 const struct wps_tag *tag;
1365 memset(token, 0, sizeof(*token));
1367 switch(*wps_bufptr)
1370 case '%':
1371 case '<':
1372 case '|':
1373 case '>':
1374 case ';':
1375 case '#':
1376 /* escaped characters */
1377 token->type = WPS_TOKEN_CHARACTER;
1378 token->value.c = *wps_bufptr;
1379 taglen = 1;
1380 wps_data->num_tokens++;
1381 break;
1383 case '?':
1384 /* conditional tag */
1385 token->type = WPS_TOKEN_CONDITIONAL;
1386 level++;
1387 condindex[level] = wps_data->num_tokens;
1388 numoptions[level] = 1;
1389 wps_data->num_tokens++;
1390 ret = parse_token(wps_bufptr + 1, wps_data);
1391 if (ret < 0) return ret;
1392 taglen = 1 + ret;
1393 break;
1395 default:
1396 /* find what tag we have */
1397 for (tag = all_tags;
1398 strncmp(wps_bufptr, tag->name, strlen(tag->name)) != 0;
1399 tag++) ;
1401 taglen = (tag->type != WPS_TOKEN_UNKNOWN) ? strlen(tag->name) : 2;
1402 token->type = tag->type;
1403 curr_line->curr_subline->line_type |= tag->refresh_type;
1405 /* if the tag has a special parsing function, we call it */
1406 if (tag->parse_func)
1408 ret = tag->parse_func(wps_bufptr + taglen, token, wps_data);
1409 if (ret < 0) return ret;
1410 skip += ret;
1413 /* Some tags we don't want to save as tokens */
1414 if (tag->type == WPS_NO_TOKEN)
1415 break;
1417 /* tags that start with 'F', 'I' or 'D' are for the next file */
1418 if ( *(tag->name) == 'I' || *(tag->name) == 'F' ||
1419 *(tag->name) == 'D')
1420 token->next = true;
1422 wps_data->num_tokens++;
1423 break;
1426 skip += taglen;
1427 return skip;
1432 * Returns the number of bytes to skip the buf pointer to access the false
1433 * branch in a _binary_ conditional
1435 * That is:
1436 * - before the '|' if we have a false branch, (%?<true|false> -> %?<|false>)
1437 * - or before the closing '>' if there's no false branch (%?<true> -> %?<>)
1439 * depending on the features of a target it's not called from check_feature_tag,
1440 * hence the __attribute__ or it issues compiler warnings
1444 static int find_false_branch(const char *wps_bufptr) __attribute__((unused));
1445 static int find_false_branch(const char *wps_bufptr)
1447 const char *buf = wps_bufptr;
1448 /* wps_bufptr is after the opening '<', hence level = 1*/
1449 int level = 1;
1450 char ch;
1453 ch = *buf;
1454 if (ch == '%')
1455 { /* filter out the characters we check later if they're printed
1456 * as literals */
1457 ch = *(++buf);
1458 if (ch == '<' || ch == '>' || ch == '|')
1459 continue;
1460 /* else: some tags/printed literals we skip over */
1462 else if (ch == '<') /* nested conditional */
1463 level++;
1464 else if (ch == '>')
1465 { /* closed our or a nested conditional,
1466 * do NOT skip over the '>' so that wps_parse() sees it for closing
1467 * if it is the closing one for our conditional */
1468 level--;
1470 else if (ch == '|' && level == 1)
1471 { /* we found our separator, point before and get out */
1472 break;
1474 /* if level is 0, we don't have a false branch */
1475 } while (level > 0 && *(++buf));
1477 return buf - wps_bufptr;
1481 * returns the number of bytes to get the appropriate branch of a binary
1482 * conditional
1484 * That means:
1485 * - if a feature is available, it returns 0 to not skip anything
1486 * - if the feature is not available, skip to the false branch and don't
1487 * parse the true branch at all
1489 * */
1490 static int check_feature_tag(const char *wps_bufptr, const int type)
1492 (void)wps_bufptr;
1493 switch (type)
1495 case WPS_TOKEN_RTC_PRESENT:
1496 #if CONFIG_RTC
1497 return 0;
1498 #else
1499 return find_false_branch(wps_bufptr);
1500 #endif
1501 case WPS_TOKEN_HAVE_RECORDING:
1502 #ifdef HAVE_RECORDING
1503 return 0;
1504 #else
1505 return find_false_branch(wps_bufptr);
1506 #endif
1507 default: /* not a tag we care about, just don't skip */
1508 return 0;
1513 /* Parses the WPS.
1514 data is the pointer to the structure where the parsed WPS should be stored.
1515 It is initialised.
1516 wps_bufptr points to the string containing the WPS tags */
1517 #define TOKEN_BLOCK_SIZE 128
1518 static bool wps_parse(struct wps_data *data, const char *wps_bufptr, bool debug)
1520 if (!data || !wps_bufptr || !*wps_bufptr)
1521 return false;
1522 enum wps_parse_error fail = PARSE_OK;
1523 int ret;
1524 int max_tokens = TOKEN_BLOCK_SIZE;
1525 size_t buf_free = 0;
1526 line_number = 0;
1527 level = -1;
1529 /* allocate enough RAM for a reasonable skin, grow as needed.
1530 * Free any used RAM before loading the images to be 100% RAM efficient */
1531 data->tokens = (struct wps_token *)skin_buffer_grab(&buf_free);
1532 if (sizeof(struct wps_token)*max_tokens >= buf_free)
1533 return false;
1534 skin_buffer_increment(max_tokens * sizeof(struct wps_token), false);
1535 data->num_tokens = 0;
1537 while (*wps_bufptr && !fail)
1539 if (follow_lang_direction)
1540 follow_lang_direction--;
1541 /* first make sure there is enough room for tokens */
1542 if (max_tokens <= data->num_tokens + 5)
1544 int extra_tokens = TOKEN_BLOCK_SIZE;
1545 size_t needed = extra_tokens * sizeof(struct wps_token);
1546 /* do some smarts here to grow the array a bit */
1547 if (skin_buffer_freespace() < needed)
1549 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1550 break;
1552 skin_buffer_increment(needed, false);
1553 max_tokens += extra_tokens;
1556 switch(*wps_bufptr++)
1559 /* Regular tag */
1560 case '%':
1561 if ((ret = parse_token(wps_bufptr, data)) < 0)
1563 fail = PARSE_FAIL_COND_INVALID_PARAM;
1564 break;
1566 else if (level >= WPS_MAX_COND_LEVEL - 1)
1568 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1569 break;
1571 wps_bufptr += ret;
1572 break;
1574 /* Alternating sublines separator */
1575 case ';':
1576 if (level >= 0) /* there are unclosed conditionals */
1578 fail = PARSE_FAIL_UNCLOSED_COND;
1579 break;
1582 if (!skin_start_new_subline(curr_line, data->num_tokens))
1583 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1585 break;
1587 /* Conditional list start */
1588 case '<':
1589 if (data->tokens[data->num_tokens-2].type != WPS_TOKEN_CONDITIONAL)
1591 fail = PARSE_FAIL_COND_SYNTAX_ERROR;
1592 break;
1594 wps_bufptr += check_feature_tag(wps_bufptr,
1595 data->tokens[data->num_tokens-1].type);
1596 data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_START;
1597 lastcond[level] = data->num_tokens++;
1598 break;
1600 /* Conditional list end */
1601 case '>':
1602 if (level < 0) /* not in a conditional, invalid char */
1604 fail = PARSE_FAIL_INVALID_CHAR;
1605 break;
1608 data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_END;
1609 if (lastcond[level])
1610 data->tokens[lastcond[level]].value.i = data->num_tokens;
1611 else
1613 fail = PARSE_FAIL_COND_SYNTAX_ERROR;
1614 break;
1617 lastcond[level] = 0;
1618 data->num_tokens++;
1619 data->tokens[condindex[level]].value.i = numoptions[level];
1620 level--;
1621 break;
1623 /* Conditional list option */
1624 case '|':
1625 if (level < 0) /* not in a conditional, invalid char */
1627 fail = PARSE_FAIL_INVALID_CHAR;
1628 break;
1631 data->tokens[data->num_tokens].type = WPS_TOKEN_CONDITIONAL_OPTION;
1632 if (lastcond[level])
1633 data->tokens[lastcond[level]].value.i = data->num_tokens;
1634 else
1636 fail = PARSE_FAIL_COND_SYNTAX_ERROR;
1637 break;
1640 lastcond[level] = data->num_tokens;
1641 numoptions[level]++;
1642 data->num_tokens++;
1643 break;
1645 /* Comment */
1646 case '#':
1647 if (level >= 0) /* there are unclosed conditionals */
1649 fail = PARSE_FAIL_UNCLOSED_COND;
1650 break;
1653 wps_bufptr += skip_end_of_line(wps_bufptr);
1654 break;
1656 /* End of this line */
1657 case '\n':
1658 if (level >= 0) /* there are unclosed conditionals */
1660 fail = PARSE_FAIL_UNCLOSED_COND;
1661 break;
1663 /* add a new token for the \n so empty lines are correct */
1664 data->tokens[data->num_tokens].type = WPS_TOKEN_CHARACTER;
1665 data->tokens[data->num_tokens].value.c = '\n';
1666 data->tokens[data->num_tokens].next = false;
1667 data->num_tokens++;
1669 if (!skin_start_new_line(curr_vp, data->num_tokens))
1671 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1672 break;
1674 line_number++;
1676 break;
1678 /* String */
1679 default:
1681 unsigned int len = 1;
1682 const char *string_start = wps_bufptr - 1;
1684 /* find the length of the string */
1685 while (*wps_bufptr && *wps_bufptr != '#' &&
1686 *wps_bufptr != '%' && *wps_bufptr != ';' &&
1687 *wps_bufptr != '<' && *wps_bufptr != '>' &&
1688 *wps_bufptr != '|' && *wps_bufptr != '\n')
1690 wps_bufptr++;
1691 len++;
1694 /* look if we already have that string */
1695 char *str;
1696 bool found = false;
1697 struct skin_token_list *list = data->strings;
1698 while (list)
1700 str = (char*)list->token->value.data;
1701 found = (strlen(str) == len &&
1702 strncmp(string_start, str, len) == 0);
1703 if (found)
1704 break; /* break here because the list item is
1705 used if its found */
1706 list = list->next;
1708 /* If a matching string is found, found is true and i is
1709 the index of the string. If not, found is false */
1711 if (!found)
1713 /* new string */
1714 str = (char*)skin_buffer_alloc(len+1);
1715 if (!str)
1717 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1718 break;
1720 strlcpy(str, string_start, len+1);
1721 struct skin_token_list *item =
1722 new_skin_token_list_item(&data->tokens[data->num_tokens], str);
1723 if(!item)
1725 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1726 break;
1728 add_to_ll_chain(&data->strings, item);
1730 else
1732 /* another occurrence of an existing string */
1733 data->tokens[data->num_tokens].value.data = list->token->value.data;
1735 data->tokens[data->num_tokens].type = WPS_TOKEN_STRING;
1736 data->num_tokens++;
1738 break;
1742 if (!fail && level >= 0) /* there are unclosed conditionals */
1743 fail = PARSE_FAIL_UNCLOSED_COND;
1745 if (*wps_bufptr && !fail)
1746 /* one of the limits of the while loop was exceeded */
1747 fail = PARSE_FAIL_LIMITS_EXCEEDED;
1749 /* Success! */
1750 curr_line->curr_subline->last_token_idx = data->num_tokens;
1751 data->tokens[data->num_tokens++].type = WPS_NO_TOKEN;
1752 /* freeup unused tokens */
1753 skin_buffer_free_from_front(sizeof(struct wps_token)
1754 * (max_tokens - data->num_tokens));
1756 #if defined(DEBUG) || defined(SIMULATOR)
1757 if (debug)
1758 print_debug_info(data, fail, line_number);
1759 #else
1760 (void)debug;
1761 #endif
1763 return (fail == 0);
1768 * initial setup of wps_data; does reset everything
1769 * except fields which need to survive, i.e.
1772 static void skin_data_reset(struct wps_data *wps_data)
1774 #ifdef HAVE_LCD_BITMAP
1775 wps_data->images = NULL;
1776 wps_data->progressbars = NULL;
1777 #endif
1778 #ifdef HAVE_TOUCHSCREEN
1779 wps_data->touchregions = NULL;
1780 #endif
1781 wps_data->viewports = NULL;
1782 wps_data->strings = NULL;
1783 #ifdef HAVE_ALBUMART
1784 wps_data->albumart = NULL;
1785 if (wps_data->playback_aa_slot >= 0)
1787 playback_release_aa_slot(wps_data->playback_aa_slot);
1788 wps_data->playback_aa_slot = -1;
1790 #endif
1791 wps_data->tokens = NULL;
1792 wps_data->num_tokens = 0;
1794 #ifdef HAVE_LCD_BITMAP
1795 wps_data->peak_meter_enabled = false;
1796 wps_data->wps_sb_tag = false;
1797 wps_data->show_sb_on_wps = false;
1798 #else /* HAVE_LCD_CHARCELLS */
1799 /* progress bars */
1800 int i;
1801 for (i = 0; i < 8; i++)
1803 wps_data->wps_progress_pat[i] = 0;
1805 wps_data->full_line_progressbar = false;
1806 #endif
1807 wps_data->wps_loaded = false;
1810 #ifdef HAVE_LCD_BITMAP
1811 static bool load_skin_bmp(struct wps_data *wps_data, struct bitmap *bitmap, char* bmpdir)
1813 (void)wps_data; /* only needed for remote targets */
1814 bool loaded = false;
1815 char img_path[MAX_PATH];
1816 get_image_filename(bitmap->data, bmpdir,
1817 img_path, sizeof(img_path));
1819 /* load the image */
1820 int format;
1821 #ifdef HAVE_REMOTE_LCD
1822 if (curr_screen == SCREEN_REMOTE)
1823 format = FORMAT_ANY|FORMAT_REMOTE;
1824 else
1825 #endif
1826 format = FORMAT_ANY|FORMAT_TRANSPARENT;
1828 size_t max_buf;
1829 char* imgbuf = (char*)skin_buffer_grab(&max_buf);
1830 bitmap->data = imgbuf;
1831 int ret = read_bmp_file(img_path, bitmap, max_buf, format, NULL);
1833 if (ret > 0)
1835 skin_buffer_increment(ret, true);
1836 loaded = true;
1838 else
1840 /* Abort if we can't load an image */
1841 loaded = false;
1843 return loaded;
1846 static bool load_skin_bitmaps(struct wps_data *wps_data, char *bmpdir)
1848 struct skin_token_list *list;
1849 /* do the progressbars */
1850 list = wps_data->progressbars;
1851 while (list)
1853 struct progressbar *pb = (struct progressbar*)list->token->value.data;
1854 if (pb->bm.data)
1856 pb->have_bitmap_pb = load_skin_bmp(wps_data, &pb->bm, bmpdir);
1858 list = list->next;
1860 /* regular images */
1861 list = wps_data->images;
1862 while (list)
1864 struct gui_img *img = (struct gui_img*)list->token->value.data;
1865 if (img->bm.data)
1867 img->loaded = load_skin_bmp(wps_data, &img->bm, bmpdir);
1868 if (img->loaded)
1869 img->subimage_height = img->bm.height / img->num_subimages;
1871 list = list->next;
1874 #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
1875 if (bmp_names[BACKDROP_BMP])
1877 char img_path[MAX_PATH];
1878 get_image_filename(bmp_names[BACKDROP_BMP], bmpdir,
1879 img_path, sizeof(img_path));
1880 screens[curr_screen].backdrop_load(BACKDROP_SKIN_WPS, img_path);
1882 #endif /* has backdrop support */
1884 /* If we got here, everything was OK */
1885 return true;
1888 #endif /* HAVE_LCD_BITMAP */
1890 /* to setup up the wps-data from a format-buffer (isfile = false)
1891 from a (wps-)file (isfile = true)*/
1892 bool skin_data_load(enum screen_type screen, struct wps_data *wps_data,
1893 const char *buf, bool isfile)
1896 if (!wps_data || !buf)
1897 return false;
1898 #ifdef HAVE_ALBUMART
1899 int status;
1900 struct mp3entry *curtrack;
1901 long offset;
1902 struct skin_albumart old_aa = {.state = WPS_ALBUMART_NONE};
1903 if (wps_data->albumart)
1905 old_aa.state = wps_data->albumart->state;
1906 old_aa.height = wps_data->albumart->height;
1907 old_aa.width = wps_data->albumart->width;
1909 #endif
1911 skin_data_reset(wps_data);
1912 curr_screen = screen;
1914 /* alloc default viewport, will be fixed up later */
1915 curr_vp = skin_buffer_alloc(sizeof(struct skin_viewport));
1916 if (!curr_vp)
1917 return false;
1918 struct skin_token_list *list = new_skin_token_list_item(NULL, curr_vp);
1919 if (!list)
1920 return false;
1921 add_to_ll_chain(&wps_data->viewports, list);
1924 /* Initialise the first (default) viewport */
1925 curr_vp->label = VP_DEFAULT_LABEL;
1926 curr_vp->pb = NULL;
1927 curr_vp->hidden_flags = 0;
1928 curr_vp->lines = NULL;
1930 viewport_set_defaults(&curr_vp->vp, screen);
1932 curr_line = NULL;
1933 if (!skin_start_new_line(curr_vp, 0))
1934 return false;
1936 if (!isfile)
1938 return wps_parse(wps_data, buf, false);
1940 else
1942 int fd = open_utf8(buf, O_RDONLY);
1944 if (fd < 0)
1945 return false;
1947 /* get buffer space from the plugin buffer */
1948 size_t buffersize = 0;
1949 char *wps_buffer = (char *)plugin_get_buffer(&buffersize);
1951 if (!wps_buffer)
1952 return false;
1954 /* copy the file's content to the buffer for parsing,
1955 ensuring that every line ends with a newline char. */
1956 unsigned int start = 0;
1957 while(read_line(fd, wps_buffer + start, buffersize - start) > 0)
1959 start += strlen(wps_buffer + start);
1960 if (start < buffersize - 1)
1962 wps_buffer[start++] = '\n';
1963 wps_buffer[start] = 0;
1967 close(fd);
1969 if (start <= 0)
1970 return false;
1972 #ifdef HAVE_LCD_BITMAP
1973 /* Set all filename pointers to NULL */
1974 memset(bmp_names, 0, sizeof(bmp_names));
1975 #endif
1977 /* parse the WPS source */
1978 if (!wps_parse(wps_data, wps_buffer, true)) {
1979 skin_data_reset(wps_data);
1980 return false;
1983 wps_data->wps_loaded = true;
1985 #ifdef HAVE_LCD_BITMAP
1986 /* get the bitmap dir */
1987 char bmpdir[MAX_PATH];
1988 char *dot = strrchr(buf, '.');
1990 strlcpy(bmpdir, buf, dot - buf + 1);
1992 /* load the bitmaps that were found by the parsing */
1993 if (!load_skin_bitmaps(wps_data, bmpdir)) {
1994 skin_data_reset(wps_data);
1995 return false;
1997 #endif
1998 #if defined(HAVE_ALBUMART) && !defined(__PCTOOL__)
1999 status = audio_status();
2000 if (status & AUDIO_STATUS_PLAY)
2002 struct skin_albumart *aa = wps_data->albumart;
2003 if (aa && ((aa->state && !old_aa.state) ||
2004 (aa->state &&
2005 (((old_aa.height != aa->height) ||
2006 (old_aa.width != aa->width))))))
2008 curtrack = audio_current_track();
2009 offset = curtrack->offset;
2010 audio_stop();
2011 if (!(status & AUDIO_STATUS_PAUSE))
2012 audio_play(offset);
2015 #endif
2016 #if defined(DEBUG) || defined(SIMULATOR)
2017 debug_skin_usage();
2018 #endif
2019 return true;