Refactor osdmenu parsing logic.
[vlc.git] / modules / misc / osd / simple.c
1 /*****************************************************************************
2  * simple.c - The OSD Menu simple parser code.
3  *****************************************************************************
4  * Copyright (C) 2005-2007 M2X
5  * $Id: $
6  *
7  * Authors: Jean-Paul Saman
8  *
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.
13  *
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.
18  *
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  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #include <vlc/vlc.h>
29 #include <vlc_vout.h>
30 #include <vlc_config.h>
31
32 #include <vlc_keys.h>
33 #include <vlc_image.h>
34 #include <vlc_osd.h>
35 #include <vlc_charset.h>
36
37 #include "osd_menu.h"
38
39 int osd_parser_simpleOpen( vlc_object_t *p_this );
40
41 /*****************************************************************************
42  * Simple parser open function
43  *****************************************************************************/
44 int osd_parser_simpleOpen( vlc_object_t *p_this )
45 {
46     osd_menu_t     *p_menu = (osd_menu_t *) p_this;
47     osd_button_t   *p_current = NULL; /* button currently processed */
48     osd_button_t   *p_prev = NULL;    /* previous processed button */
49
50 #define MAX_FILE_PATH 256
51     FILE       *fd = NULL;
52     int        result = 0;
53
54     if( !p_menu ) return VLC_ENOOBJ;
55
56     msg_Dbg( p_this, "opening osdmenu definition file %s", p_menu->psz_file );
57     fd = utf8_fopen( p_menu->psz_file, "r" );
58     if( !fd )
59     {
60         msg_Err( p_this, "failed to open osdmenu definition file %s",
61                 p_menu->psz_file );
62         return VLC_EGENERIC;
63     }
64
65     /* Read first line */
66     if( !feof( fd ) )
67     {
68         char action[25] = "";
69         char cmd[25] = "";
70         char path[MAX_FILE_PATH] = "";
71         char *psz_path = NULL;
72         size_t i_len = 0;
73         long pos = 0;
74
75         result = fscanf(fd, "%24s %255s", &action[0], &path[0] );
76
77         /* override images path ? */
78         psz_path = config_GetPsz( p_this, "osdmenu-file-path" );
79         if( psz_path )
80         {
81             /* psz_path is not null and therefor &path[0] cannot be NULL
82              * it might be null terminated.
83              */
84             strncpy( &path[0], psz_path, MAX_FILE_PATH );
85             free( psz_path );
86             psz_path = NULL;
87         }
88         /* NULL terminate before asking the length of path[] */
89         path[MAX_FILE_PATH-1] = '\0';
90         i_len = strlen(&path[0]);
91         if( i_len == MAX_FILE_PATH )
92             i_len--; /* truncate to prevent buffer overflow */
93 #if defined(WIN32) || defined(UNDER_CE)
94         if( (i_len > 0) && path[i_len] != '\\' )
95             path[i_len] = '\\';
96 #else
97         if( (i_len > 0) && path[i_len] != '/' )
98             path[i_len] = '/';
99 #endif
100         path[i_len+1] = '\0';
101         if( result == 0 || result == EOF )
102             goto error;
103         msg_Dbg( p_this, "osdmenu dir %s", &path[0] );
104
105         if( i_len == 0 )
106             p_menu = osd_MenuNew( p_menu, NULL, 0, 0 );
107         else
108             p_menu = osd_MenuNew( p_menu, &path[0], 0, 0 );
109
110         /* Peek for 'style' argument */
111         pos = ftell( fd );
112         if( pos < 0 )
113                 goto error;
114
115         result = fscanf(fd, "%24s %24s", &cmd[0], &action[0] );
116         if( result == 0 || result == EOF )
117             goto error;
118
119         msg_Dbg( p_this, "osdmenu %s %s", &cmd[0], &action[0] );
120         if( strncmp( &cmd[0], "style", 5 ) == 0 )
121         {
122             if( strncmp( &action[0], "default", 7) == 0 )
123             {
124                 p_menu->i_style = OSD_MENU_STYLE_SIMPLE;
125             }
126             else if( strncmp( &action[0], "concat", 6) == 0 )
127             {
128                 p_menu->i_style = OSD_MENU_STYLE_CONCAT;
129             }
130         }
131         else
132         {
133             result = fseek( fd, pos, SEEK_SET );
134             if( result < 0 )
135                 goto error;
136         }
137     }
138
139     if( !p_menu )
140         goto error;
141
142     /* read successive lines */
143     while( !feof( fd ) )
144     {
145         osd_state_t   *p_state_current = NULL; /* button state currently processed */
146         osd_state_t   *p_state_prev = NULL;    /* previous state processed button */
147
148         char cmd[25] = "";
149         char action[25] = "";
150         char state[25]  = "";
151         char file[256]  = "";
152         char path[512]  = "";
153         int  i_x = 0;
154         int  i_y = 0;
155
156         result = fscanf( fd, "%24s %24s (%d,%d)", &cmd[0], &action[0], &i_x, &i_y );
157         if( result == 0 )
158             goto error;
159         if( strncmp( &cmd[0], "action", 6 ) != 0 )
160             break;
161         msg_Dbg( p_this, " + %s hotkey=%s (%d,%d)", &cmd[0], &action[0], i_x, i_y );
162
163         p_prev = p_current;
164         p_current = osd_ButtonNew( &action[0], i_x, i_y );
165         if( !p_current )
166             goto error;
167
168         if( p_prev )
169             p_prev->p_next = p_current;
170         else
171             p_menu->p_button = p_current;
172         p_current->p_prev = p_prev;
173
174         /* parse all states */
175         while( !feof( fd ) )
176         {
177             char type[25] = "";
178
179             result = fscanf( fd, "\t%24s", &state[0] );
180             if( result == 0 )
181                 goto error;
182
183             /* FIXME: We only parse one level deep now */
184             if( strncmp( &state[0], "action", 6 ) == 0 )
185             {
186                 osd_button_t   *p_up = NULL;
187
188                 result = fscanf( fd, "%24s (%d,%d)", &action[0], &i_x, &i_y );
189                 if( result == 0 )
190                     goto error;
191                 /* create new button */
192                 p_up = osd_ButtonNew( &action[0], i_x, i_y );
193                 if( !p_up )
194                     goto error;
195                 /* Link to list */
196                 p_up->p_down = p_current;
197                 p_current->p_up = p_up;
198                 msg_Dbg( p_this, " + (menu up) hotkey=%s (%d,%d)", &action[0], i_x, i_y );
199                 /* Parse type state */
200                 result = fscanf( fd, "\t%24s %24s", &cmd[0], &type[0] );
201                 if( result == 0 )
202                     goto error;
203                 if( strncmp( &cmd[0], "type", 4 ) == 0 )
204                 {
205                     if( strncmp( &type[0], "volume", 6 ) == 0 )
206                     {
207                         p_menu->p_state->p_volume = p_up;
208                         msg_Dbg( p_this, " + type=%s", &type[0] );
209                     }
210                 }
211                 /* Parse range state */
212                 result = fscanf( fd, "\t%24s", &state[0] );
213                 if( result == 0 )
214                     goto error;
215                 /* Parse the range state */
216                 if( strncmp( &state[0], "range", 5 ) == 0 )
217                 {
218                     osd_state_t   *p_range_current = NULL; /* range state currently processed */
219                     osd_state_t   *p_range_prev = NULL;    /* previous state processed range */
220                     int i_index = 0;
221
222                     p_up->b_range = VLC_TRUE;
223
224                     result = fscanf( fd, "\t%24s", &action[0] );
225                     if( result == 0 )
226                         goto error;
227
228                     result = fscanf( fd, "\t%d", &i_index );
229                     if( result == 0 )
230                         goto error;
231
232                     msg_Dbg( p_this, " + (menu up) hotkey down %s, file=%s%s",
233                              &action[0], p_menu->psz_path, &file[0] );
234
235                     if( p_up->psz_action_down ) free( p_up->psz_action_down );
236                     p_up->psz_action_down = strdup( &action[0] );
237
238                     /* Parse range contstruction :
239                      * range <hotkey>
240                      *      <state1> <file1>
241                      *
242                      *      <stateN> <fileN>
243                      * end
244                      */
245                     while( !feof( fd ) )
246                     {
247                         result = fscanf( fd, "\t%255s", &file[0] );
248                         if( result == 0 )
249                             goto error;
250                         if( strncmp( &file[0], "end", 3 ) == 0 )
251                             break;
252
253                         p_range_prev = p_range_current;
254
255                         if( p_menu->psz_path )
256                         {
257                             size_t i_path_size = strlen( p_menu->psz_path );
258                             size_t i_file_size = strlen( &file[0] );
259
260                             strncpy( &path[0], p_menu->psz_path, i_path_size );
261                             strncpy( &path[i_path_size], &file[0], 512 - (i_path_size + i_file_size) );
262                             path[ i_path_size + i_file_size ] = '\0';
263
264                             p_range_current = osd_StateNew( p_menu, &path[0], "pressed" );
265                         }
266                         else /* absolute paths are used. */
267                             p_range_current = osd_StateNew( p_menu, &file[0], "pressed" );
268
269                         if( !p_range_current || !p_range_current->p_pic )
270                             goto error;
271
272                         /* increment the number of ranges for this button */
273                         p_up->i_ranges++;
274
275                         if( p_range_prev )
276                             p_range_prev->p_next = p_range_current;
277                         else
278                             p_up->p_states = p_range_current;
279                         p_range_current->p_prev = p_range_prev;
280
281                         msg_Dbg( p_this, "  |- range=%d, file=%s%s",
282                                  p_up->i_ranges,
283                                  p_menu->psz_path, &file[0] );
284                     }
285                     if( i_index > 0 )
286                     {
287                         osd_state_t *p_range = NULL;
288
289                         /* Find the default index for state range */
290                         p_range = p_up->p_states;
291                         while( (--i_index > 0) && p_range->p_next )
292                         {
293                             osd_state_t *p_temp = NULL;
294                             p_temp = p_range->p_next;
295                             p_range = p_temp;
296                         }
297                         p_up->p_current_state = p_range;
298                     }
299                     else p_up->p_current_state = p_up->p_states;
300
301                 }
302                 result = fscanf( fd, "\t%24s", &state[0] );
303                 if( result == 0 )
304                     goto error;
305                 if( strncmp( &state[0], "end", 3 ) != 0 )
306                     goto error;
307
308                 /* Continue at the beginning of the while() */
309                 continue;
310             }
311
312             /* Parse the range state */
313             if( strncmp( &state[0], "range", 5 ) == 0 )
314             {
315                 osd_state_t   *p_range_current = NULL; /* range state currently processed */
316                 osd_state_t   *p_range_prev = NULL;    /* previous state processed range */
317                 int i_index = 0;
318
319                 p_current->b_range = VLC_TRUE;
320
321                 result = fscanf( fd, "\t%24s", &action[0] );
322                 if( result == 0 )
323                     goto error;
324
325                 result = fscanf( fd, "\t%d", &i_index );
326                 if( result == 0 )
327                     goto error;
328
329                 msg_Dbg( p_this, " + hotkey down %s, file=%s%s", 
330                          &action[0], p_menu->psz_path, &file[0] );
331                 if( p_current->psz_action_down ) 
332                     free( p_current->psz_action_down );
333                 p_current->psz_action_down = strdup( &action[0] );
334
335                 /* Parse range contstruction :
336                  * range <hotkey>
337                  *      <state1> <file1>
338                  *
339                  *      <stateN> <fileN>
340                  * end
341                  */
342                 while( !feof( fd ) )
343                 {
344                     result = fscanf( fd, "\t%255s", &file[0] );
345                     if( result == 0 )
346                         goto error;
347                     if( strncmp( &file[0], "end", 3 ) == 0 )
348                         break;
349
350                     p_range_prev = p_range_current;
351
352                     if( p_menu->psz_path )
353                     {
354                         size_t i_path_size = strlen( p_menu->psz_path );
355                         size_t i_file_size = strlen( &file[0] );
356
357                         strncpy( &path[0], p_menu->psz_path, i_path_size );
358                         strncpy( &path[i_path_size], &file[0], 512 - (i_path_size + i_file_size) );
359                         path[ i_path_size + i_file_size ] = '\0';
360
361                         p_range_current = osd_StateNew( p_menu, &path[0], "pressed" );
362                     }
363                     else /* absolute paths are used. */
364                         p_range_current = osd_StateNew( p_menu, &file[0], "pressed" );
365
366                     if( !p_range_current || !p_range_current->p_pic )
367                         goto error;
368
369                     /* increment the number of ranges for this button */
370                     p_current->i_ranges++;
371
372                     if( p_range_prev )
373                         p_range_prev->p_next = p_range_current;
374                     else
375                         p_current->p_states = p_range_current;
376                     p_range_current->p_prev = p_range_prev;
377
378                     msg_Dbg( p_this, "  |- range=%d, file=%s%s",
379                              p_current->i_ranges,
380                              p_menu->psz_path, &file[0] );
381                 }
382                 if( i_index > 0 )
383                 {
384                     osd_state_t *p_range = NULL;
385
386                     /* Find the default index for state range */
387                     p_range = p_current->p_states;
388                     while( (--i_index > 0) && p_range->p_next )
389                     {
390                         osd_state_t *p_temp = NULL;
391                         p_temp = p_range->p_next;
392                         p_range = p_temp;
393                     }
394                     p_current->p_current_state = p_range;
395                 }
396                 else p_current->p_current_state = p_current->p_states;
397                 /* Continue at the beginning of the while() */
398                 continue;
399             }
400             if( strncmp( &state[0], "end", 3 ) == 0 )
401                 break;
402
403             result = fscanf( fd, "\t%255s", &file[0] );
404             if( result == 0 )
405                 goto error;
406
407             p_state_prev = p_state_current;
408
409             if( ( strncmp( ppsz_button_states[0], &state[0], strlen(ppsz_button_states[0]) ) != 0 ) &&
410                 ( strncmp( ppsz_button_states[1], &state[0], strlen(ppsz_button_states[1]) ) != 0 ) &&
411                 ( strncmp( ppsz_button_states[2], &state[0], strlen(ppsz_button_states[2]) ) != 0 ) )
412             {
413                 msg_Err( p_this, "invalid button state %s for button %s "
414                          "expected %u: unselect, select or pressed)",
415                          &state[0], &action[0], (unsigned)strlen(&state[0]));
416                 goto error;
417             }
418
419             if( p_menu->psz_path )
420             {
421                 size_t i_path_size = strlen( p_menu->psz_path );
422                 size_t i_file_size = strlen( &file[0] );
423
424                 strncpy( &path[0], p_menu->psz_path, i_path_size );
425                 strncpy( &path[i_path_size], &file[0], 512 - (i_path_size + i_file_size) );
426                 path[ i_path_size + i_file_size ] = '\0';
427
428                 p_state_current = osd_StateNew( p_menu, &path[0], &state[0] );
429             }
430             else /* absolute paths are used. */
431                 p_state_current = osd_StateNew( p_menu, &file[0], &state[0] );
432
433             if( !p_state_current || !p_state_current->p_pic )
434                 goto error;
435
436             if( p_state_prev )
437                 p_state_prev->p_next = p_state_current;
438             else
439                 p_current->p_states = p_state_current;
440             p_state_current->p_prev = p_state_prev;
441
442             msg_Dbg( p_this, " |- state=%s, file=%s%s", &state[0],
443                      p_menu->psz_path, &file[0] );
444         }
445         p_current->p_current_state = p_current->p_states;
446     }
447
448     /* Find the last button and store its pointer.
449      * The OSD menu behaves like a roundrobin list.
450      */
451     p_current = p_menu->p_button;
452     while( p_current && p_current->p_next )
453     {
454         osd_button_t *p_temp = NULL;
455         p_temp = p_current->p_next;
456         p_current = p_temp;
457     }
458     p_menu->p_last_button = p_current;
459     fclose( fd );
460     return VLC_SUCCESS;
461
462 #undef MAX_FILE_PATH
463 error:
464     msg_Err( p_menu, "parsing file failed (returned %d)", result );
465     osd_MenuFree( p_menu );
466     fclose( fd );
467     return VLC_EGENERIC;
468 }