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