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