]> git.sesse.net Git - vlc/blob - src/config/file.c
Code factorization
[vlc] / src / config / file.c
1 /*****************************************************************************
2  * file.c: configuration file handling
3  *****************************************************************************
4  * Copyright (C) 2001-2007 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Gildas Bazin <gbazin@videolan.org>
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 #include <vlc/vlc.h>
25 #include "../libvlc.h"
26 #include "vlc_charset.h"
27 #include "vlc_keys.h"
28
29 #include <errno.h>                                                  /* errno */
30 #include <stdbool.h>
31
32 #ifdef HAVE_LIMITS_H
33 #   include <limits.h>
34 #endif
35
36 #include "config.h"
37 #include "modules/modules.h"
38
39 static char *ConfigKeyToString( int );
40
41 static inline char *strdupnull (const char *src)
42 {
43     return src ? strdup (src) : NULL;
44 }
45
46 static inline char *_strdupnull (const char *src)
47 {
48     return src ? strdup (_(src)) : NULL;
49 }
50
51
52 static FILE *config_OpenConfigFile( vlc_object_t *p_obj, const char *mode )
53 {
54     char *psz_filename = p_obj->p_libvlc->psz_configfile;
55     FILE *p_stream;
56
57     if( !psz_filename )
58     {
59         psz_filename = config_GetConfigFile( p_obj->p_libvlc );
60     }
61
62     msg_Dbg( p_obj, "opening config file (%s)", psz_filename );
63
64     p_stream = utf8_fopen( psz_filename, mode );
65     if( p_stream == NULL && errno != ENOENT )
66     {
67         msg_Err( p_obj, "cannot open config file (%s): %m",
68                  psz_filename );
69
70     }
71 #if !( defined(WIN32) || defined(__APPLE__) || defined(SYS_BEOS) )
72     else if( p_stream == NULL && errno == ENOENT && mode[0] == 'r' )
73     {
74         /* This is the fallback for pre XDG Base Directory
75          * Specification configs */
76         char *psz_old;
77         if( asprintf( &psz_old, "%s" DIR_SEP CONFIG_DIR DIR_SEP CONFIG_FILE,
78                   p_obj->p_libvlc->psz_homedir ) != -1 )
79         {
80             p_stream = utf8_fopen( psz_old, mode );
81             if( p_stream )
82             {
83                 /* Old config file found. We want to write it at the
84                  * new location now. */
85                 msg_Info( p_obj->p_libvlc, "Found old config file at %s. "
86                           "VLC will now use %s.", psz_old, psz_filename );
87                 char *psz_readme;
88                 if( asprintf(&psz_readme,"%s"DIR_SEP CONFIG_DIR DIR_SEP"README",
89                               p_obj->p_libvlc->psz_homedir ) != -1 )
90                 {
91                     FILE *p_readme = utf8_fopen( psz_readme, "wt" );
92                     if( p_readme )
93                     {
94                         fputs( "The VLC media player configuration folder has "
95                                "moved to comply with the XDG Base "
96                                "Directory Specification version 0.6. Your "
97                                "configuration has been copied to the new "
98                                "location (", p_readme );
99                         fputs( p_obj->p_libvlc->psz_configdir, p_readme );
100                         fputs( "). You can delete this directory and "
101                                "all its contents.", p_readme );
102                         fclose( p_readme );
103                     }
104                     free( psz_readme );
105                 }
106             }
107             free( psz_old );
108         }
109     }
110 #endif
111     else if( p_stream != NULL )
112     {
113         p_obj->p_libvlc->psz_configfile = psz_filename;
114     }
115
116     return p_stream;
117 }
118
119
120 static int strtoi (const char *str)
121 {
122     char *end;
123     long l;
124
125     errno = 0;
126     l = strtol (str, &end, 0);
127
128     if (!errno)
129     {
130         if ((l > INT_MAX) || (l < INT_MIN))
131             errno = ERANGE;
132         if (*end)
133             errno = EINVAL;
134     }
135     return (int)l;
136 }
137
138
139 /*****************************************************************************
140  * config_LoadConfigFile: loads the configuration file.
141  *****************************************************************************
142  * This function is called to load the config options stored in the config
143  * file.
144  *****************************************************************************/
145 int __config_LoadConfigFile( vlc_object_t *p_this, const char *psz_module_name )
146 {
147     vlc_list_t *p_list;
148     FILE *file;
149
150     file = config_OpenConfigFile (p_this, "rt");
151     if (file == NULL)
152         return VLC_EGENERIC;
153
154     /* Acquire config file lock */
155     vlc_mutex_lock( &p_this->p_libvlc->config_lock );
156
157     /* Look for the selected module, if NULL then save everything */
158     p_list = vlc_list_find( p_this, VLC_OBJECT_MODULE, FIND_ANYWHERE );
159
160     /* Look for UTF-8 Byte Order Mark */
161     char * (*convert) (const char *) = strdupnull;
162     char bom[3];
163
164     if ((fread (bom, 1, 3, file) != 3)
165      || memcmp (bom, "\xEF\xBB\xBF", 3))
166     {
167         convert = FromLocaleDup;
168         rewind (file); /* no BOM, rewind */
169     }
170
171     module_t *module = NULL;
172     char line[1024], section[1022];
173     section[0] = '\0';
174
175     while (fgets (line, 1024, file) != NULL)
176     {
177         /* Ignore comments and empty lines */
178         switch (line[0])
179         {
180             case '#':
181             case '\n':
182             case '\0':
183                 continue;
184         }
185
186         if (line[0] == '[')
187         {
188             char *ptr = strchr (line, ']');
189             if (ptr == NULL)
190                 continue; /* syntax error; */
191             *ptr = '\0';
192
193             /* New section ( = a given module) */
194             strcpy (section, line + 1);
195             module = NULL;
196
197             if ((psz_module_name == NULL)
198              || (strcmp (psz_module_name, section) == 0))
199             {
200                 for (int i = 0; i < p_list->i_count; i++)
201                 {
202                     module_t *m = (module_t *)p_list->p_values[i].p_object;
203
204                     if ((strcmp (section, m->psz_object_name) == 0)
205                      && (m->i_config_items > 0)) /* ignore config-less modules */
206                     {
207                         module = m;
208                         if (psz_module_name != NULL)
209                             msg_Dbg (p_this,
210                                      "loading config for module \"%s\"",
211                                      section);
212                         break;
213                     }
214                 }
215             }
216
217             continue;
218         }
219
220         if (module == NULL)
221             continue; /* no need to parse if there is no matching module */
222
223         char *ptr = strchr (line, '\n');
224         if (ptr != NULL)
225             *ptr = '\0';
226
227         /* look for option name */
228         const char *psz_option_name = line;
229
230         ptr = strchr (line, '=');
231         if (ptr == NULL)
232             continue; /* syntax error */
233
234         *ptr = '\0';
235         const char *psz_option_value = ptr + 1;
236
237         /* try to match this option with one of the module's options */
238         for (size_t i = 0; i < module->confsize; i++)
239         {
240             module_config_t *p_item = module->p_config + i;
241
242             if ((p_item->i_type & CONFIG_HINT)
243              || strcmp (p_item->psz_name, psz_option_name))
244                 continue;
245
246             /* We found it */
247             errno = 0;
248
249             switch( p_item->i_type )
250             {
251                 case CONFIG_ITEM_BOOL:
252                 case CONFIG_ITEM_INTEGER:
253                 {
254                     long l = strtoi (psz_option_value);
255                     if (errno)
256                         msg_Warn (p_this, "Integer value (%s) for %s: %m",
257                                   psz_option_value, psz_option_name);
258                     else
259                         p_item->saved.i = p_item->value.i = (int)l;
260                     break;
261                 }
262
263                 case CONFIG_ITEM_FLOAT:
264                     if( !*psz_option_value )
265                         break;                    /* ignore empty option */
266                     p_item->value.f = (float)i18n_atof( psz_option_value);
267                     p_item->saved.f = p_item->value.f;
268                     break;
269
270                 case CONFIG_ITEM_KEY:
271                     if( !*psz_option_value )
272                         break;                    /* ignore empty option */
273                     p_item->value.i = ConfigStringToKey(psz_option_value);
274                     p_item->saved.i = p_item->value.i;
275                     break;
276
277                 default:
278                     vlc_mutex_lock( p_item->p_lock );
279
280                     /* free old string */
281                     free( (char*) p_item->value.psz );
282                     free( (char*) p_item->saved.psz );
283
284                     p_item->value.psz = convert (psz_option_value);
285                     p_item->saved.psz = strdupnull (p_item->value.psz);
286
287                     vlc_mutex_unlock( p_item->p_lock );
288                     break;
289             }
290
291             break;
292         }
293     }
294
295     if (ferror (file))
296     {
297         msg_Err (p_this, "error reading configuration: %m");
298         clearerr (file);
299     }
300     fclose (file);
301
302     vlc_list_release( p_list );
303
304     vlc_mutex_unlock( &p_this->p_libvlc->config_lock );
305     return 0;
306 }
307
308 /*****************************************************************************
309  * config_CreateDir: Create configuration directory if it doesn't exist.
310  *****************************************************************************/
311 int config_CreateDir( vlc_object_t *p_this, const char *psz_dirname )
312 {
313     if( !psz_dirname && !*psz_dirname ) return -1;
314
315     if( utf8_mkdir( psz_dirname, 0700 ) == 0 )
316         return 0;
317
318     switch( errno )
319     {
320         case EEXIST:
321             return 0;
322
323         case ENOENT:
324         {
325             /* Let's try to create the parent directory */
326             char psz_parent[strlen( psz_dirname ) + 1], *psz_end;
327             strcpy( psz_parent, psz_dirname );
328
329             psz_end = strrchr( psz_parent, DIR_SEP_CHAR );
330             if( psz_end && psz_end != psz_parent )
331             {
332                 *psz_end = '\0';
333                 if( config_CreateDir( p_this, psz_parent ) == 0 )
334                 {
335                     if( !utf8_mkdir( psz_dirname, 0700 ) )
336                         return 0;
337                 }
338             }
339         }
340     }
341
342     msg_Err( p_this, "could not create %s: %m", psz_dirname );
343     return -1;
344 }
345
346 static int
347 config_Write (FILE *file, const char *type, const char *desc,
348               bool comment, const char *name, const char *fmt, ...)
349 {
350     va_list ap;
351     int ret;
352
353     if (desc == NULL)
354         desc = "?";
355
356     if (fprintf (file, "# %s (%s)\n%s%s=", desc, gettext (type),
357                  comment ? "#" : "", name) < 0)
358         return -1;
359
360     va_start (ap, fmt);
361     ret = vfprintf (file, fmt, ap);
362     va_end (ap);
363     if (ret < 0)
364         return -1;
365
366     if (fputs ("\n\n", file) == EOF)
367         return -1;
368     return 0;
369 }
370
371
372 /*****************************************************************************
373  * config_SaveConfigFile: Save a module's config options.
374  *****************************************************************************
375  * This will save the specified module's config options to the config file.
376  * If psz_module_name is NULL then we save all the modules config options.
377  * It's no use to save the config options that kept their default values, so
378  * we'll try to be a bit clever here.
379  *
380  * When we save we mustn't delete the config options of the modules that
381  * haven't been loaded. So we cannot just create a new config file with the
382  * config structures we've got in memory.
383  * I don't really know how to deal with this nicely, so I will use a completly
384  * dumb method ;-)
385  * I will load the config file in memory, but skipping all the sections of the
386  * modules we want to save. Then I will create a brand new file, dump the file
387  * loaded in memory and then append the sections of the modules we want to
388  * save.
389  * Really stupid no ?
390  *****************************************************************************/
391 static int SaveConfigFile( vlc_object_t *p_this, const char *psz_module_name,
392                            vlc_bool_t b_autosave )
393 {
394     module_t *p_parser;
395     vlc_list_t *p_list;
396     FILE *file;
397     char p_line[1024], *p_index2;
398     int i_sizebuf = 0;
399     char *p_bigbuffer, *p_index;
400     vlc_bool_t b_backup;
401     int i_index;
402
403     /* Acquire config file lock */
404     vlc_mutex_lock( &p_this->p_libvlc->config_lock );
405
406     if( p_this->p_libvlc->psz_configfile == NULL )
407     {
408         const char *psz_configdir = p_this->p_libvlc->psz_configdir;
409         if( !psz_configdir ) /* XXX: This should never happen */
410         {
411             msg_Err( p_this, "no configuration directory defined" );
412             vlc_mutex_unlock( &p_this->p_libvlc->config_lock );
413             return -1;
414         }
415
416         config_CreateDir( p_this, psz_configdir );
417     }
418
419     file = config_OpenConfigFile( p_this, "rt" );
420     if( file != NULL )
421     {
422         /* look for file size */
423         fseek( file, 0L, SEEK_END );
424         i_sizebuf = ftell( file );
425         fseek( file, 0L, SEEK_SET );
426     }
427
428     p_bigbuffer = p_index = malloc( i_sizebuf+1 );
429     if( !p_bigbuffer )
430     {
431         msg_Err( p_this, "out of memory" );
432         if( file ) fclose( file );
433         vlc_mutex_unlock( &p_this->p_libvlc->config_lock );
434         return -1;
435     }
436     p_bigbuffer[0] = 0;
437
438     /* List all available modules */
439     p_list = vlc_list_find( p_this, VLC_OBJECT_MODULE, FIND_ANYWHERE );
440
441     /* backup file into memory, we only need to backup the sections we won't
442      * save later on */
443     b_backup = 0;
444     while( file && fgets( p_line, 1024, file ) )
445     {
446         if( (p_line[0] == '[') && (p_index2 = strchr(p_line,']')))
447         {
448
449             /* we found a section, check if we need to do a backup */
450             for( i_index = 0; i_index < p_list->i_count; i_index++ )
451             {
452                 p_parser = (module_t *)p_list->p_values[i_index].p_object ;
453
454                 if( ((p_index2 - &p_line[1])
455                        == (int)strlen(p_parser->psz_object_name) )
456                     && !memcmp( &p_line[1], p_parser->psz_object_name,
457                                 strlen(p_parser->psz_object_name) ) )
458                 {
459                     if( !psz_module_name )
460                         break;
461                     else if( !strcmp( psz_module_name,
462                                       p_parser->psz_object_name ) )
463                         break;
464                 }
465             }
466
467             if( i_index == p_list->i_count )
468             {
469                 /* we don't have this section in our list so we need to back
470                  * it up */
471                 *p_index2 = 0;
472 #if 0
473                 msg_Dbg( p_this, "backing up config for unknown module \"%s\"",
474                                  &p_line[1] );
475 #endif
476                 *p_index2 = ']';
477
478                 b_backup = 1;
479             }
480             else
481             {
482                 b_backup = 0;
483             }
484         }
485
486         /* save line if requested and line is valid (doesn't begin with a
487          * space, tab, or eol) */
488         if( b_backup && (p_line[0] != '\n') && (p_line[0] != ' ')
489             && (p_line[0] != '\t') )
490         {
491             strcpy( p_index, p_line );
492             p_index += strlen( p_line );
493         }
494     }
495     if( file ) fclose( file );
496
497
498     /*
499      * Save module config in file
500      */
501
502     file = config_OpenConfigFile (p_this, "wt");
503     if( !file )
504     {
505         vlc_list_release( p_list );
506         free( p_bigbuffer );
507         vlc_mutex_unlock( &p_this->p_libvlc->config_lock );
508         return -1;
509     }
510
511     fprintf( file, "\xEF\xBB\xBF###\n###  " COPYRIGHT_MESSAGE "\n###\n\n"
512        "###\n### lines begining with a '#' character are comments\n###\n\n" );
513
514     /* Look for the selected module, if NULL then save everything */
515     for( i_index = 0; i_index < p_list->i_count; i_index++ )
516     {
517         module_config_t *p_item, *p_end;
518         p_parser = (module_t *)p_list->p_values[i_index].p_object ;
519
520         if( psz_module_name && strcmp( psz_module_name,
521                                        p_parser->psz_object_name ) )
522             continue;
523
524         if( !p_parser->i_config_items )
525             continue;
526
527         if( psz_module_name )
528             msg_Dbg( p_this, "saving config for module \"%s\"",
529                      p_parser->psz_object_name );
530
531         fprintf( file, "[%s]", p_parser->psz_object_name );
532         if( p_parser->psz_longname )
533             fprintf( file, " # %s\n\n", p_parser->psz_longname );
534         else
535             fprintf( file, "\n\n" );
536
537         for( p_item = p_parser->p_config, p_end = p_item + p_parser->confsize;
538              p_item < p_end;
539              p_item++ )
540         {
541             /* Do not save the new value in the configuration file
542              * if doing an autosave, and the item is not an "autosaved" one. */
543             vlc_bool_t b_retain = b_autosave && !p_item->b_autosave;
544
545             if ((p_item->i_type & CONFIG_HINT) /* ignore hint */
546              || p_item->psz_current            /* ignore deprecated option */
547              || p_item->b_unsaveable)          /* ignore volatile option */
548                 continue;
549
550             if (IsConfigIntegerType (p_item->i_type))
551             {
552                 int val = b_retain ? p_item->saved.i : p_item->value.i;
553                 if (p_item->i_type == CONFIG_ITEM_KEY)
554                 {
555                     char *psz_key = ConfigKeyToString (val);
556                     config_Write (file, p_item->psz_text, N_("key"),
557                                   val == p_item->orig.i,
558                                   p_item->psz_name, "%s",
559                                   psz_key ? psz_key : "");
560                     free (psz_key);
561                 }
562                 else
563                     config_Write (file, p_item->psz_text,
564                                   (p_item->i_type == CONFIG_ITEM_BOOL)
565                                       ? N_("boolean") : N_("integer"),
566                                   val == p_item->orig.i,
567                                   p_item->psz_name, "%d", val);
568                 p_item->saved.i = val;
569             }
570             else
571             if (IsConfigFloatType (p_item->i_type))
572             {
573                 float val = b_retain ? p_item->saved.f : p_item->value.f;
574                 config_Write (file, p_item->psz_text, N_("float"),
575                               val == p_item->orig.f,
576                               p_item->psz_name, "%f", val);
577                 p_item->saved.f = val;
578             }
579             else
580             {
581                 const char *psz_value = b_retain ? p_item->saved.psz
582                                                  : p_item->value.psz;
583                 bool modified;
584
585                 assert (IsConfigStringType (p_item->i_type));
586
587                 if (b_retain && (psz_value == NULL)) /* FIXME: hack */
588                     psz_value = p_item->orig.psz;
589
590                 modified =
591                     (psz_value != NULL)
592                         ? ((p_item->orig.psz != NULL)
593                             ? (strcmp (psz_value, p_item->orig.psz) != 0)
594                             : true)
595                         : (p_item->orig.psz != NULL);
596
597                 config_Write (file, p_item->psz_text, N_("string"),
598                               modified, p_item->psz_name, "%s",
599                               psz_value ? psz_value : "");
600
601                 if (b_retain)
602                     break;
603
604                 free ((char *)p_item->saved.psz);
605                 if( (psz_value && p_item->orig.psz &&
606                      strcmp( psz_value, p_item->orig.psz )) ||
607                     !psz_value || !p_item->orig.psz)
608                     p_item->saved.psz = strdupnull (psz_value);
609                 else
610                     p_item->saved.psz = NULL;
611             }
612
613             if (!b_retain)
614                 p_item->b_dirty = VLC_FALSE;
615         }
616     }
617
618     vlc_list_release( p_list );
619
620     /*
621      * Restore old settings from the config in file
622      */
623     fputs( p_bigbuffer, file );
624     free( p_bigbuffer );
625
626     fclose( file );
627     vlc_mutex_unlock( &p_this->p_libvlc->config_lock );
628
629     return 0;
630 }
631
632 int config_AutoSaveConfigFile( vlc_object_t *p_this )
633 {
634     vlc_list_t *p_list;
635     int i_index, i_count;
636
637     if( !p_this ) return -1;
638
639     /* Check if there's anything to save */
640     vlc_mutex_lock( &p_this->p_libvlc->config_lock );
641     p_list = vlc_list_find( p_this, VLC_OBJECT_MODULE, FIND_ANYWHERE );
642     i_count = p_list->i_count;
643     for( i_index = 0; i_index < i_count; i_index++ )
644     {
645         module_t *p_parser = (module_t *)p_list->p_values[i_index].p_object ;
646         module_config_t *p_item, *p_end;
647
648         if( !p_parser->i_config_items ) continue;
649
650         for( p_item = p_parser->p_config, p_end = p_item + p_parser->confsize;
651              p_item < p_end;
652              p_item++ )
653         {
654             if( p_item->b_autosave && p_item->b_dirty ) break;
655         }
656         break;
657     }
658     vlc_list_release( p_list );
659     vlc_mutex_unlock( &p_this->p_libvlc->config_lock );
660
661     if( i_index == i_count ) return VLC_SUCCESS;
662     return SaveConfigFile( p_this, 0, VLC_TRUE );
663 }
664
665 int __config_SaveConfigFile( vlc_object_t *p_this, const char *psz_module_name )
666 {
667     return SaveConfigFile( p_this, psz_module_name, VLC_FALSE );
668 }
669
670 /**
671  * Get the user's configuration file
672  */
673 char *config_GetConfigFile( libvlc_int_t *p_libvlc )
674 {
675     char *psz_configfile;
676     if( asprintf( &psz_configfile, "%s" DIR_SEP CONFIG_FILE,
677                   p_libvlc->psz_configdir ) == -1 )
678         return NULL;
679     return psz_configfile;
680 }
681
682 /**
683  * Get the user's configuration file when given with the --config option
684  */
685 char *config_GetCustomConfigFile( libvlc_int_t *p_libvlc )
686 {
687     char *psz_configfile = config_GetPsz( p_libvlc, "config" );
688     if( psz_configfile != NULL )
689     {
690         if( psz_configfile[0] == '~' && psz_configfile[1] == '/' )
691         {
692             /* This is incomplete: we should also support the ~cmassiot/ syntax */
693             char *psz_buf;
694             if( asprintf( &psz_buf, "%s/%s", p_libvlc->psz_homedir,
695                           psz_configfile + 2 ) == -1 )
696             {
697                 free( psz_configfile );
698                 return NULL;
699             }
700             free( psz_configfile );
701             psz_configfile = psz_buf;
702         }
703     }
704     return psz_configfile;
705 }
706
707 int ConfigStringToKey( const char *psz_key )
708 {
709     int i_key = 0;
710     unsigned int i;
711     const char *psz_parser = strchr( psz_key, '-' );
712     while( psz_parser && psz_parser != psz_key )
713     {
714         for( i = 0; i < sizeof(vlc_modifiers) / sizeof(key_descriptor_t); i++ )
715         {
716             if( !strncasecmp( vlc_modifiers[i].psz_key_string, psz_key,
717                               strlen( vlc_modifiers[i].psz_key_string ) ) )
718             {
719                 i_key |= vlc_modifiers[i].i_key_code;
720             }
721         }
722         psz_key = psz_parser + 1;
723         psz_parser = strchr( psz_key, '-' );
724     }
725     for( i = 0; i < sizeof(vlc_keys) / sizeof( key_descriptor_t ); i++ )
726     {
727         if( !strcasecmp( vlc_keys[i].psz_key_string, psz_key ) )
728         {
729             i_key |= vlc_keys[i].i_key_code;
730             break;
731         }
732     }
733     return i_key;
734 }
735
736 char *ConfigKeyToString( int i_key )
737 {
738     char *psz_key = malloc( 100 );
739     char *p;
740     size_t index;
741
742     if ( !psz_key )
743     {
744         return NULL;
745     }
746     *psz_key = '\0';
747     p = psz_key;
748
749     for( index = 0; index < (sizeof(vlc_modifiers) / sizeof(key_descriptor_t));
750          index++ )
751     {
752         if( i_key & vlc_modifiers[index].i_key_code )
753         {
754             p += sprintf( p, "%s-", vlc_modifiers[index].psz_key_string );
755         }
756     }
757     for( index = 0; index < (sizeof(vlc_keys) / sizeof( key_descriptor_t));
758          index++)
759     {
760         if( (int)( i_key & ~KEY_MODIFIER ) == vlc_keys[index].i_key_code )
761         {
762             p += sprintf( p, "%s", vlc_keys[index].psz_key_string );
763             break;
764         }
765     }
766     return psz_key;
767 }
768