1 /*****************************************************************************
2 * osd_parser.c - The OSD Menu parser core code.
3 *****************************************************************************
4 * Copyright (C) 2005 M2X
7 * Authors: Jean-Paul Saman <jpsaman #_at_# m2x dot nl>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
30 #include <vlc_config.h>
33 #include <vlc_image.h>
35 #include <vlc_charset.h>
40 /*****************************************************************************
42 *****************************************************************************/
43 static const char *ppsz_button_states[] = { "unselect", "select", "pressed" };
45 /* OSD Menu structure support routines */
46 static osd_menu_t *osd_MenuNew( osd_menu_t *, const char *, int, int );
47 static osd_button_t *osd_ButtonNew( const char *, int, int );
48 static osd_state_t *osd_StateNew( vlc_object_t *, const char *, const char * );
50 static void osd_MenuFree ( vlc_object_t *, osd_menu_t * );
51 static void osd_ButtonFree( vlc_object_t *, osd_button_t * );
52 static void osd_StatesFree( vlc_object_t *, osd_state_t * );
54 static picture_t *osd_LoadImage( vlc_object_t *, const char *);
56 /*****************************************************************************
57 * osd_LoadImage: loads the logo image into memory
58 *****************************************************************************/
59 static picture_t *osd_LoadImage( vlc_object_t *p_this, const char *psz_filename )
61 picture_t *p_pic = NULL;
62 image_handler_t *p_image;
63 video_format_t fmt_in, fmt_out;
65 memset( &fmt_in, 0, sizeof(video_format_t) );
66 memset( &fmt_out, 0, sizeof(video_format_t) );
68 fmt_out.i_chroma = VLC_FOURCC('Y','U','V','A');
69 p_image = image_HandlerCreate( p_this );
72 p_pic = image_ReadUrl( p_image, psz_filename, &fmt_in, &fmt_out );
73 image_HandlerDelete( p_image );
75 else msg_Err( p_this, "unable to handle this chroma" );
80 /*****************************************************************************
81 * Create a new Menu structure
82 *****************************************************************************/
83 static osd_menu_t *osd_MenuNew( osd_menu_t *p_menu, const char *psz_path, int i_x, int i_y )
85 if( !p_menu ) return NULL;
87 p_menu->p_state = (osd_menu_state_t *) malloc( sizeof( osd_menu_state_t ) );
88 if( !p_menu->p_state )
90 msg_Err( p_menu, "Memory allocation for OSD Menu state failed" );
94 memset(p_menu->p_state, 0, sizeof(osd_menu_state_t));
95 if( psz_path != NULL )
96 p_menu->psz_path = strdup( psz_path );
98 p_menu->psz_path = NULL;
101 p_menu->i_style = OSD_MENU_STYLE_SIMPLE;
106 /*****************************************************************************
108 *****************************************************************************/
109 static void osd_MenuFree( vlc_object_t *p_this, osd_menu_t *p_menu )
111 msg_Dbg( p_this, "freeing menu" );
112 osd_ButtonFree( p_this, p_menu->p_button );
113 p_menu->p_button = NULL;
114 p_menu->p_last_button = NULL;
115 if( p_menu->psz_path ) free( p_menu->psz_path );
116 p_menu->psz_path = NULL;
117 if( p_menu->p_state ) free( p_menu->p_state );
118 p_menu->p_state = NULL;
121 /*****************************************************************************
122 * Create a new button
123 *****************************************************************************/
124 static osd_button_t *osd_ButtonNew( const char *psz_action, int i_x, int i_y )
126 osd_button_t *p_button = NULL;
127 p_button = (osd_button_t*) malloc( sizeof(osd_button_t) );
131 memset( p_button, 0, sizeof(osd_button_t) );
132 p_button->psz_action = strdup(psz_action);
133 p_button->psz_action_down = NULL;
134 p_button->p_feedback = NULL;
141 /*****************************************************************************
143 *****************************************************************************/
144 static void osd_ButtonFree( vlc_object_t *p_this, osd_button_t *p_button )
146 osd_button_t *p_current = p_button;
147 osd_button_t *p_next = NULL;
148 osd_button_t *p_prev = NULL;
150 /* First walk to the end. */
151 while( p_current->p_next )
153 p_next = p_current->p_next;
156 /* Then free end first and walk to the start. */
157 while( p_current->p_prev )
159 msg_Dbg( p_this, "+ freeing button %s [%p]", p_current->psz_action, p_current );
160 p_prev = p_current->p_prev;
162 if( p_current->p_next )
164 if( p_current->p_next->psz_name )
165 free( p_current->p_next->psz_name );
166 if( p_current->p_next->psz_action )
167 free( p_current->p_next->psz_action );
168 if( p_current->p_next->psz_action_down )
169 free( p_current->p_next->psz_action_down );
170 if( p_current->p_feedback && p_current->p_feedback->p_data_orig )
171 free( p_current->p_feedback->p_data_orig );
172 if( p_current->p_feedback )
173 free( p_current->p_feedback );
175 p_current->p_next->psz_action_down = NULL;
176 p_current->p_next->psz_action = NULL;
177 p_current->p_next->psz_name = NULL;
178 p_current->p_feedback = NULL;
180 /* Free all states first */
181 if( p_current->p_next->p_states )
182 osd_StatesFree( p_this, p_current->p_next->p_states );
183 p_current->p_next->p_states = NULL;
184 if( p_current->p_next) free( p_current->p_next );
185 p_current->p_next = NULL;
188 if( p_current->p_up )
190 if( p_current->p_up->psz_name )
191 free( p_current->p_up->psz_name );
192 if( p_current->p_up->psz_action )
193 free( p_current->p_up->psz_action );
194 if( p_current->p_up->psz_action_down )
195 free( p_current->p_up->psz_action_down );
196 if( p_current->p_feedback && p_current->p_feedback->p_data_orig )
197 free( p_current->p_feedback->p_data_orig );
198 if( p_current->p_feedback )
199 free( p_current->p_feedback );
201 p_current->p_up->psz_action_down = NULL;
202 p_current->p_up->psz_action = NULL;
203 p_current->p_up->psz_name = NULL;
204 p_current->p_feedback = NULL;
206 /* Free all states first */
207 if( p_current->p_up->p_states )
208 osd_StatesFree( p_this, p_current->p_up->p_states );
209 p_current->p_up->p_states = NULL;
210 if( p_current->p_up ) free( p_current->p_up );
211 p_current->p_up = NULL;
214 /* Free the last one. */
217 msg_Dbg( p_this, "+ freeing button %s [%p]", p_button->psz_action, p_button );
218 if( p_button->psz_name ) free( p_button->psz_name );
219 if( p_button->psz_action ) free( p_button->psz_action );
220 if( p_button->psz_action_down ) free( p_button->psz_action_down );
221 if( p_current->p_feedback && p_current->p_feedback->p_data_orig )
222 free( p_current->p_feedback->p_data_orig );
223 if( p_current->p_feedback )
224 free( p_current->p_feedback );
226 p_button->psz_name = NULL;
227 p_button->psz_action = NULL;
228 p_button->psz_action_down = NULL;
229 p_current->p_feedback = NULL;
231 if( p_button->p_states )
232 osd_StatesFree( p_this, p_button->p_states );
233 p_button->p_states = NULL;
239 /*****************************************************************************
240 * Create a new state image
241 *****************************************************************************/
242 static osd_state_t *osd_StateNew( vlc_object_t *p_this, const char *psz_file, const char *psz_state )
244 osd_state_t *p_state = NULL;
245 p_state = (osd_state_t*) malloc( sizeof(osd_state_t) );
249 memset( p_state, 0, sizeof(osd_state_t) );
250 p_state->p_pic = osd_LoadImage( p_this, psz_file );
254 p_state->psz_state = strdup( psz_state );
255 if( strncmp( ppsz_button_states[0], psz_state, strlen(ppsz_button_states[0]) ) == 0 )
256 p_state->i_state = OSD_BUTTON_UNSELECT;
257 else if( strncmp( ppsz_button_states[1], psz_state, strlen(ppsz_button_states[1]) ) == 0 )
258 p_state->i_state = OSD_BUTTON_SELECT;
259 else if( strncmp( ppsz_button_states[2], psz_state, strlen(ppsz_button_states[2]) ) == 0 )
260 p_state->i_state = OSD_BUTTON_PRESSED;
265 /*****************************************************************************
267 *****************************************************************************/
268 static void osd_StatesFree( vlc_object_t *p_this, osd_state_t *p_states )
270 osd_state_t *p_state = p_states;
271 osd_state_t *p_next = NULL;
272 osd_state_t *p_prev = NULL;
274 while( p_state->p_next )
276 p_next = p_state->p_next;
279 /* Then free end first and walk to the start. */
280 while( p_state->p_prev )
282 msg_Dbg( p_this, " |- freeing state %s [%p]", p_state->psz_state, p_state );
283 p_prev = p_state->p_prev;
285 if( p_state->p_next )
287 if( p_state->p_next->p_pic && p_state->p_next->p_pic->p_data_orig )
288 free( p_state->p_next->p_pic->p_data_orig );
289 if( p_state->p_next->p_pic ) free( p_state->p_next->p_pic );
290 p_state->p_next->p_pic = NULL;
291 if( p_state->p_next->psz_state ) free( p_state->p_next->psz_state );
292 p_state->p_next->psz_state = NULL;
293 free( p_state->p_next );
294 p_state->p_next = NULL;
297 /* Free the last one. */
300 msg_Dbg( p_this, " |- freeing state %s [%p]", p_state->psz_state, p_states );
301 if( p_states->p_pic && p_states->p_pic->p_data_orig )
302 free( p_states->p_pic->p_data_orig );
303 if( p_states->p_pic ) free( p_states->p_pic );
304 p_states->p_pic = NULL;
305 if( p_state->psz_state ) free( p_state->psz_state );
306 p_state->psz_state = NULL;
312 /*****************************************************************************
313 * osd_ConfigLoader: Load and parse osd text configurationfile
314 *****************************************************************************/
315 int osd_ConfigLoader( vlc_object_t *p_this, const char *psz_file,
316 osd_menu_t **p_menu )
318 osd_button_t *p_current = NULL; /* button currently processed */
319 osd_button_t *p_prev = NULL; /* previous processed button */
321 #define MAX_FILE_PATH 256
325 msg_Dbg( p_this, "opening osdmenu definition file %s", psz_file );
326 fd = utf8_fopen( psz_file, "r" );
329 msg_Err( p_this, "failed to open osdmenu definition file %s", psz_file );
333 /* Read first line */
336 char action[25] = "";
338 char path[MAX_FILE_PATH] = "";
339 char *psz_path = NULL;
343 /* override images path ? */
344 psz_path = config_GetPsz( p_this, "osdmenu-file-path" );
345 if( psz_path == NULL )
347 result = fscanf(fd, "%24s %255s", &action[0], &path[0] );
351 /* psz_path is not null and therefor &path[0] cannot be NULL
352 * it might be null terminated.
354 strncpy( &path[0], psz_path, MAX_FILE_PATH );
358 /* NULL terminate before asking the length of path[] */
359 path[MAX_FILE_PATH-1] = '\0';
360 i_len = strlen(&path[0]);
361 if( i_len == MAX_FILE_PATH )
362 i_len--; /* truncate to prevent buffer overflow */
363 #if defined(WIN32) || defined(UNDER_CE)
364 if( (i_len > 0) && path[i_len] != '\\' )
367 if( (i_len > 0) && path[i_len] != '/' )
370 path[i_len+1] = '\0';
371 if( result == 0 || result == EOF )
373 msg_Dbg( p_this, "osdmenu dir %s", &path[0] );
376 *p_menu = osd_MenuNew( *p_menu, NULL, 0, 0 );
378 *p_menu = osd_MenuNew( *p_menu, &path[0], 0, 0 );
380 /* Peek for 'style' argument */
385 result = fscanf(fd, "%24s %24s", &cmd[0], &action[0] );
386 if( result == 0 || result == EOF )
389 msg_Dbg( p_this, "osdmenu %s %s", &cmd[0], &action[0] );
390 if( strncmp( &cmd[0], "style", 5 ) == 0 )
392 if( strncmp( &action[0], "default", 7) == 0 )
394 (*p_menu)->i_style = OSD_MENU_STYLE_SIMPLE;
396 else if( strncmp( &action[0], "concat", 6) == 0 )
398 (*p_menu)->i_style = OSD_MENU_STYLE_CONCAT;
403 result = fseek( fd, pos, SEEK_SET );
412 /* read successive lines */
415 osd_state_t *p_state_current = NULL; /* button state currently processed */
416 osd_state_t *p_state_prev = NULL; /* previous state processed button */
419 char action[25] = "";
426 result = fscanf( fd, "%24s %24s (%d,%d)", &cmd[0], &action[0], &i_x, &i_y );
429 if( strncmp( &cmd[0], "action", 6 ) != 0 )
431 msg_Dbg( p_this, " + %s hotkey=%s (%d,%d)", &cmd[0], &action[0], i_x, i_y );
434 p_current = osd_ButtonNew( &action[0], i_x, i_y );
439 p_prev->p_next = p_current;
441 (*p_menu)->p_button = p_current;
442 p_current->p_prev = p_prev;
444 /* parse all states */
449 result = fscanf( fd, "\t%24s", &state[0] );
453 /* FIXME: We only parse one level deep now */
454 if( strncmp( &state[0], "action", 6 ) == 0 )
456 osd_button_t *p_up = NULL;
458 result = fscanf( fd, "%24s (%d,%d)", &action[0], &i_x, &i_y );
461 /* create new button */
462 p_up = osd_ButtonNew( &action[0], i_x, i_y );
466 p_up->p_down = p_current;
467 p_current->p_up = p_up;
468 msg_Dbg( p_this, " + (menu up) hotkey=%s (%d,%d)", &action[0], i_x, i_y );
469 /* Parse type state */
470 result = fscanf( fd, "\t%24s %24s", &cmd[0], &type[0] );
473 if( strncmp( &cmd[0], "type", 4 ) == 0 )
475 if( strncmp( &type[0], "volume", 6 ) == 0 )
477 (*p_menu)->p_state->p_volume = p_up;
478 msg_Dbg( p_this, " + type=%s", &type[0] );
481 /* Parse range state */
482 result = fscanf( fd, "\t%24s", &state[0] );
485 /* Parse the range state */
486 if( strncmp( &state[0], "range", 5 ) == 0 )
488 osd_state_t *p_range_current = NULL; /* range state currently processed */
489 osd_state_t *p_range_prev = NULL; /* previous state processed range */
492 p_up->b_range = VLC_TRUE;
494 result = fscanf( fd, "\t%24s", &action[0] );
498 result = fscanf( fd, "\t%d", &i_index );
502 msg_Dbg( p_this, " + (menu up) hotkey down %s, file=%s%s", &action[0], (*p_menu)->psz_path, &file[0] );
504 if( p_up->psz_action_down ) free( p_up->psz_action_down );
505 p_up->psz_action_down = strdup( &action[0] );
507 /* Parse range contstruction :
516 result = fscanf( fd, "\t%255s", &file[0] );
519 if( strncmp( &file[0], "end", 3 ) == 0 )
522 p_range_prev = p_range_current;
524 if( (*p_menu)->psz_path )
526 size_t i_path_size = strlen( (*p_menu)->psz_path );
527 size_t i_file_size = strlen( &file[0] );
529 strncpy( &path[0], (*p_menu)->psz_path, i_path_size );
530 strncpy( &path[i_path_size], &file[0], 512 - (i_path_size + i_file_size) );
531 path[ i_path_size + i_file_size ] = '\0';
533 p_range_current = osd_StateNew( p_this, &path[0], "pressed" );
535 else /* absolute paths are used. */
536 p_range_current = osd_StateNew( p_this, &file[0], "pressed" );
538 if( !p_range_current || !p_range_current->p_pic )
541 /* increment the number of ranges for this button */
545 p_range_prev->p_next = p_range_current;
547 p_up->p_states = p_range_current;
548 p_range_current->p_prev = p_range_prev;
550 msg_Dbg( p_this, " |- range=%d, file=%s%s",
552 (*p_menu)->psz_path, &file[0] );
556 osd_state_t *p_range = NULL;
558 /* Find the default index for state range */
559 p_range = p_up->p_states;
560 while( (--i_index > 0) && p_range->p_next )
562 osd_state_t *p_temp = NULL;
563 p_temp = p_range->p_next;
566 p_up->p_current_state = p_range;
568 else p_up->p_current_state = p_up->p_states;
571 result = fscanf( fd, "\t%24s", &state[0] );
574 if( strncmp( &state[0], "end", 3 ) != 0 )
577 /* Continue at the beginning of the while() */
581 /* Parse the range state */
582 if( strncmp( &state[0], "range", 5 ) == 0 )
584 osd_state_t *p_range_current = NULL; /* range state currently processed */
585 osd_state_t *p_range_prev = NULL; /* previous state processed range */
588 p_current->b_range = VLC_TRUE;
590 result = fscanf( fd, "\t%24s", &action[0] );
594 result = fscanf( fd, "\t%d", &i_index );
598 msg_Dbg( p_this, " + hotkey down %s, file=%s%s", &action[0], (*p_menu)->psz_path, &file[0] );
599 if( p_current->psz_action_down ) free( p_current->psz_action_down );
600 p_current->psz_action_down = strdup( &action[0] );
602 /* Parse range contstruction :
611 result = fscanf( fd, "\t%255s", &file[0] );
614 if( strncmp( &file[0], "end", 3 ) == 0 )
617 p_range_prev = p_range_current;
619 if( (*p_menu)->psz_path )
621 size_t i_path_size = strlen( (*p_menu)->psz_path );
622 size_t i_file_size = strlen( &file[0] );
624 strncpy( &path[0], (*p_menu)->psz_path, i_path_size );
625 strncpy( &path[i_path_size], &file[0], 512 - (i_path_size + i_file_size) );
626 path[ i_path_size + i_file_size ] = '\0';
628 p_range_current = osd_StateNew( p_this, &path[0], "pressed" );
630 else /* absolute paths are used. */
631 p_range_current = osd_StateNew( p_this, &file[0], "pressed" );
633 if( !p_range_current || !p_range_current->p_pic )
636 /* increment the number of ranges for this button */
637 p_current->i_ranges++;
640 p_range_prev->p_next = p_range_current;
642 p_current->p_states = p_range_current;
643 p_range_current->p_prev = p_range_prev;
645 msg_Dbg( p_this, " |- range=%d, file=%s%s",
647 (*p_menu)->psz_path, &file[0] );
651 osd_state_t *p_range = NULL;
653 /* Find the default index for state range */
654 p_range = p_current->p_states;
655 while( (--i_index > 0) && p_range->p_next )
657 osd_state_t *p_temp = NULL;
658 p_temp = p_range->p_next;
661 p_current->p_current_state = p_range;
663 else p_current->p_current_state = p_current->p_states;
664 /* Continue at the beginning of the while() */
667 if( strncmp( &state[0], "end", 3 ) == 0 )
670 result = fscanf( fd, "\t%255s", &file[0] );
674 p_state_prev = p_state_current;
676 if( ( strncmp( ppsz_button_states[0], &state[0], strlen(ppsz_button_states[0]) ) != 0 ) &&
677 ( strncmp( ppsz_button_states[1], &state[0], strlen(ppsz_button_states[1]) ) != 0 ) &&
678 ( strncmp( ppsz_button_states[2], &state[0], strlen(ppsz_button_states[2]) ) != 0 ) )
680 msg_Err( p_this, "invalid button state %s for button %s expected %d: unselect, select or pressed)",
681 &state[0], &action[0], strlen(&state[0]));
685 if( (*p_menu)->psz_path )
687 size_t i_path_size = strlen( (*p_menu)->psz_path );
688 size_t i_file_size = strlen( &file[0] );
690 strncpy( &path[0], (*p_menu)->psz_path, i_path_size );
691 strncpy( &path[i_path_size], &file[0], 512 - (i_path_size + i_file_size) );
692 path[ i_path_size + i_file_size ] = '\0';
694 p_state_current = osd_StateNew( p_this, &path[0], &state[0] );
696 else /* absolute paths are used. */
697 p_state_current = osd_StateNew( p_this, &file[0], &state[0] );
699 if( !p_state_current || !p_state_current->p_pic )
703 p_state_prev->p_next = p_state_current;
705 p_current->p_states = p_state_current;
706 p_state_current->p_prev = p_state_prev;
708 msg_Dbg( p_this, " |- state=%s, file=%s%s", &state[0], (*p_menu)->psz_path, &file[0] );
710 p_current->p_current_state = p_current->p_states;
713 /* Find the last button and store its pointer.
714 * The OSD menu behaves like a roundrobin list.
716 p_current = (*p_menu)->p_button;
717 while( p_current && p_current->p_next )
719 osd_button_t *p_temp = NULL;
720 p_temp = p_current->p_next;
723 (*p_menu)->p_last_button = p_current;
729 msg_Err( p_this, "parsing file failed (returned %d)", result );
730 osd_MenuFree( p_this, *p_menu );
735 /*****************************************************************************
736 * osd_ConfigUnload: Load and parse osd text configurationfile
737 *****************************************************************************/
738 void osd_ConfigUnload( vlc_object_t *p_this, osd_menu_t **p_osd)
740 msg_Dbg( p_this, "unloading OSD menu structure" );
741 osd_MenuFree( p_this, *p_osd );