]> git.sesse.net Git - vlc/blob - modules/gui/gtk/playlist.c
b7d04a7f1444a6bc3df094b0230c9c84e04374c9
[vlc] / modules / gui / gtk / playlist.c
1 /*****************************************************************************
2  * gtk_playlist.c : Interface for the playlist dialog
3  *****************************************************************************
4  * Copyright (C) 2001 VideoLAN (Centrale Réseaux) and its contributors
5  * $Id$
6  *
7  * Authors: Pierre Baillet <oct@zoy.org>
8  *          Stéphane Borel <stef@via.ecp.fr>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include <vlc/vlc.h>
32 #include <vlc/intf.h>
33
34 #include <sys/types.h>          /* for readdir  and stat stuff */
35
36 #if (!defined( WIN32 ) || defined(__MINGW32__))
37 /* Mingw has its own version of dirent */
38 #   include <dirent.h>
39 #endif
40
41 #include <sys/stat.h>
42 #include <unistd.h>
43
44 #ifdef MODULE_NAME_IS_gnome
45 #   include <gnome.h>
46 #else
47 #   include <gtk/gtk.h>
48 #endif
49
50 #include "gtk_callbacks.h"
51 #include "gtk_interface.h"
52 #include "gtk_support.h"
53
54 #include "playlist.h"
55 #include "common.h"
56
57 /****************************************************************************
58  * Local prototypes
59  ****************************************************************************/
60 static void UrlDecode       ( char * );
61 static GList * GtkReadFiles ( intf_thread_t *, gchar * );
62
63 /****************************************************************************
64  * Playlist window management
65  ****************************************************************************/
66 gboolean GtkPlaylistShow( GtkWidget       *widget,
67                           gpointer         user_data )
68 {
69     intf_thread_t *  p_intf = GtkGetIntf( widget );
70     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
71                                                        FIND_ANYWHERE );
72     if( p_playlist == NULL )
73     {
74         return FALSE;
75     }
76
77     if( GTK_WIDGET_VISIBLE( p_intf->p_sys->p_playwin ) )
78     {
79         gtk_widget_hide( p_intf->p_sys->p_playwin );
80     }
81     else
82     {
83         GtkCList * p_clist;
84
85         p_clist = GTK_CLIST( gtk_object_get_data(
86             GTK_OBJECT( p_intf->p_sys->p_playwin ), "playlist_clist" ) );
87         GtkRebuildCList( p_clist , p_playlist );
88         gtk_widget_show( p_intf->p_sys->p_playwin );
89         gdk_window_raise( p_intf->p_sys->p_playwin->window );
90     }
91
92     vlc_object_release( p_playlist );
93
94     return TRUE;
95 }
96
97
98 void GtkPlaylistOk( GtkButton * button, gpointer user_data )
99 {
100      gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
101 }
102
103
104 void GtkPlaylistCancel( GtkButton * button, gpointer user_data )
105 {
106      gtk_widget_hide( gtk_widget_get_toplevel( GTK_WIDGET (button) ) );
107 }
108
109
110
111 gboolean GtkPlaylistPrev( GtkWidget       *widget,
112                           gpointer         user_data )
113 {
114     intf_thread_t *  p_intf = GtkGetIntf( widget );
115     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
116                                                        FIND_ANYWHERE );
117     if( p_playlist == NULL )
118     {
119         return FALSE;
120     }
121
122     playlist_Prev( p_playlist );
123     vlc_object_release( p_playlist );
124
125     return TRUE;
126 }
127
128
129 gboolean GtkPlaylistNext( GtkWidget       *widget,
130                           gpointer         user_data)
131 {
132     intf_thread_t *  p_intf = GtkGetIntf( widget );
133     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
134                                                        FIND_ANYWHERE );
135     if( p_playlist == NULL )
136     {
137         return FALSE;
138     }
139
140     playlist_Next( p_playlist );
141     vlc_object_release( p_playlist );
142
143     return TRUE;
144 }
145
146 /****************************************************************************
147  * Playlist core functions
148  ****************************************************************************/
149 void GtkPlaylistAddUrl( GtkMenuItem * menuitem, gpointer user_data )
150 {
151
152 }
153
154
155 void GtkPlaylistDeleteAll( GtkMenuItem * menuitem, gpointer user_data )
156 {
157
158 }
159
160
161 void GtkPlaylistDeleteSelected( GtkMenuItem * menuitem, gpointer user_data )
162 {
163     /* user wants to delete a file in the queue */
164     GList *     p_selection;
165     GtkCList *  p_clist;
166
167     intf_thread_t *  p_intf = GtkGetIntf( menuitem);
168     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
169                                                        FIND_ANYWHERE );
170     if( p_playlist == NULL )
171     {
172         return;
173     }
174
175     /* lock the struct */
176     vlc_mutex_lock( &p_intf->change_lock );
177
178     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
179         p_intf->p_sys->p_playwin ), "playlist_clist" ) );
180
181     p_selection = p_clist->selection;
182
183     if( g_list_length( p_selection ) )
184     {
185         /* reverse-sort so that we can delete from the furthest
186          * to the closest item to delete...
187          */
188         p_selection = g_list_sort( p_selection, GtkCompareItems );
189         g_list_foreach( p_selection, GtkDeleteGListItem, p_playlist );
190         /* rebuild the CList */
191         GtkRebuildCList( p_clist, p_playlist );
192     }
193
194     vlc_mutex_unlock( &p_intf->change_lock );
195
196     vlc_object_release( p_playlist );
197 }
198
199 void GtkPlaylistCrop( GtkMenuItem * menuitem, gpointer user_data )
200 {
201     /* Ok, this is a really small thing, but, hey, it works and
202        might be useful, who knows ? */
203     GtkPlaylistInvert( menuitem, user_data );
204     GtkPlaylistDeleteSelected( menuitem, user_data );
205 }
206
207 void GtkPlaylistInvert( GtkMenuItem * menuitem, gpointer user_data )
208 {
209     GtkCList *  p_clist;
210     int *       pi_selected;
211     int         i_length;
212     int         i_dummy;
213
214     /* catch the thread back */
215     intf_thread_t *p_intf = GtkGetIntf( menuitem );
216
217     /* lock the struct */
218     vlc_mutex_lock( &p_intf->change_lock );
219
220     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
221         p_intf->p_sys->p_playwin ), "playlist_clist" ) );
222
223     gtk_clist_freeze( p_clist );
224
225     /* have to copy the selection to an int *
226        I wasn't able to copy the g_list to another g_list
227        glib only does pointer copies, not real copies :( */
228
229     i_length = g_list_length( p_clist->selection );
230     pi_selected = malloc( sizeof(int) * i_length );
231
232     for( i_dummy = 0 ; i_dummy < i_length ; i_dummy++ )
233     {
234         pi_selected[i_dummy] =
235             GPOINTER_TO_UINT( g_list_nth_data( p_clist->selection, i_dummy ) );
236     }
237
238     gtk_clist_select_all( p_clist );
239
240     for( i_dummy = 0; i_dummy < i_length; i_dummy++ )
241     {
242         gtk_clist_unselect_row( p_clist, pi_selected[i_dummy], 0 );
243     }
244
245     gtk_clist_thaw( p_clist );
246
247     vlc_mutex_unlock( &p_intf->change_lock );
248
249     free( pi_selected );
250 }
251
252 void GtkPlaylistSelect( GtkMenuItem * menuitem, gpointer user_data)
253 {
254
255 }
256
257 gboolean GtkPlaylistEvent( GtkWidget * widget,
258                            GdkEvent  * event,
259                            gpointer    user_data)
260 {
261     intf_thread_t *  p_intf = GtkGetIntf( widget );
262     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
263                                                        FIND_ANYWHERE );
264     if( p_playlist == NULL )
265     {
266         return FALSE;
267     }
268
269     if( ( event->button ).type == GDK_2BUTTON_PRESS )
270     {
271         GtkCList *  p_clist;
272         gint        i_row;
273         gint        i_col;
274
275         p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
276             p_intf->p_sys->p_playwin ), "playlist_clist" ) );
277
278         if( gtk_clist_get_selection_info( p_clist, (event->button).x,
279                     (event->button).y, &i_row, &i_col ) == 1 )
280         {
281             playlist_Goto( p_playlist, i_row );
282         }
283
284         vlc_object_release( p_playlist );
285         return TRUE;
286     }
287
288     vlc_object_release( p_playlist );
289     return FALSE;
290 }
291
292 void GtkPlaylistDragData( GtkWidget       *widget,
293                           GdkDragContext  *drag_context,
294                           gint             x,
295                           gint             y,
296                           GtkSelectionData *data,
297                           guint            info,
298                           guint            time,
299                           gpointer         user_data )
300 {
301     intf_thread_t * p_intf = GtkGetIntf( widget );
302     GtkCList *      p_clist;
303     gint            i_row;
304     gint            i_col;
305
306     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
307         p_intf->p_sys->p_playwin ), "playlist_clist" ) );
308
309     if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1 )
310     {
311         /* we are dropping somewhere into the clist items */
312         GtkDropDataReceived( p_intf, data, info, i_row - 1 );
313     }
314     else
315     {
316         /* otherwise, put that at the end of the playlist */
317         GtkDropDataReceived( p_intf, data, info, PLAYLIST_END );
318     }
319 }
320
321
322 gboolean GtkPlaylistDragMotion( GtkWidget       *widget,
323                                 GdkDragContext  *drag_context,
324                                 gint             x,
325                                 gint             y,
326                                 guint            time,
327                                 gpointer         user_data )
328 {
329     GtkCList *  p_clist;
330     gint        i_row;
331     gint        i_col;
332     int         i_dummy;
333     GdkColor    color;
334
335     intf_thread_t *  p_intf = GtkGetIntf( widget );
336     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
337                                                        FIND_ANYWHERE );
338     if( p_playlist == NULL )
339     {
340         return FALSE;
341     }
342
343     p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
344         p_intf->p_sys->p_playwin ), "playlist_clist" ) );
345
346     if( !GTK_WIDGET_TOPLEVEL(widget) )
347     {
348         gdk_window_raise( p_intf->p_sys->p_playwin->window );
349     }
350
351     color.red =   0xffff;
352     color.blue =  0xffff;
353     color.green = 0xffff;
354
355     gtk_clist_freeze( p_clist );
356
357     for( i_dummy = 0; i_dummy < p_clist->rows; i_dummy++)
358     {
359         gtk_clist_set_background( p_clist, i_dummy , &color );
360     }
361
362     color.red = 0;
363     color.blue = 0xf000;
364     color.green = 0x9000;
365     if( gtk_clist_get_selection_info( p_clist, x, y, &i_row, &i_col ) == 1 )
366     {
367         gtk_clist_set_background ( p_clist, i_row - 1, &color );
368         gtk_clist_set_background ( p_clist, i_row, &color );
369     }
370     else
371     {
372         gtk_clist_set_background ( p_clist, p_clist->rows - 1, &color );
373     }
374
375     color.red = 0xffff;
376     color.blue = 0;
377     color.green = 0;
378     vlc_mutex_lock( &p_playlist->object_lock );
379     gtk_clist_set_background( p_clist, p_playlist->i_index, &color );
380     vlc_mutex_unlock( &p_playlist->object_lock );
381     vlc_object_release( p_playlist );
382
383     gtk_clist_thaw( p_clist );
384
385     return TRUE;
386 }
387
388 void GtkDropDataReceived( intf_thread_t * p_intf,
389         GtkSelectionData * p_data, guint i_info, int i_position)
390 {
391     /* first we'll have to split against all the '\n' we have */
392     gchar *     p_protocol;
393     gchar *     p_temp;
394     gchar *     p_next;
395     gchar *     p_string = (gchar *)p_data->data;
396     GList *     p_files = NULL;
397     GtkCList *  p_clist;
398
399     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
400                                                        FIND_ANYWHERE );
401     if( p_playlist == NULL )
402     {
403         return;
404     }
405
406     /* if this has been URLencoded, decode it
407      *
408      * Is it a good thing to do it in place ?
409      * probably not...
410      */
411     if( i_info == DROP_ACCEPT_TEXT_URI_LIST )
412     {
413         UrlDecode( p_string );
414     }
415
416     /* this cuts string into single file drops */
417     /* this code was borrowed from xmms, thx guys :) */
418     while( *p_string)
419     {
420         p_next = strchr( p_string, '\n' );
421         if( p_next )
422         {
423             if( *( p_next - 1 ) == '\r' )
424             {
425                 *( p_next - 1) = '\0';
426             }
427             *p_next = '\0';
428         }
429
430         /* do we have a protocol or something ? */
431         p_temp = strstr( p_string, ":" );
432         if( p_temp != NULL && p_temp[0] != '\0' )
433         {
434             char i_save;
435
436             i_save = p_temp[0];
437             p_temp[0] = '\0';
438             p_protocol = strdup( p_string );
439             p_temp[0] = i_save;
440             p_temp++;
441
442             /* Allowed things are proto: or proto:// */
443             if( p_temp[0] == '/' && p_temp[1] == '/')
444             {
445                 /* eat two '/'s */
446                 p_temp += 2;
447             }
448             msg_Dbg( p_intf, "playlist protocol '%s', target '%s'",
449                              p_protocol, p_temp );
450         }
451         else
452         {
453             p_protocol = strdup( "" );
454         }
455
456         /* if it uses the file protocol we can do something, else, sorry :(
457          * I think this is a good choice for now, as we don't have any
458          * ability to read http:// or ftp:// files
459          * what about adding dvd:// to the list of authorized proto ? */
460
461         if( strcmp( p_protocol, "file:" ) == 0 )
462         {
463             p_files = g_list_concat( p_files,
464                                      GtkReadFiles( p_intf, p_string ) );
465         }
466         else
467         {
468             p_files = g_list_concat( p_files,
469                       g_list_append( NULL, g_strdup( p_string ) ) );
470         }
471
472         /* free the malloc and go on... */
473         free( p_protocol );
474
475         if( p_next == NULL )
476         {
477             break;
478         }
479         p_string = p_next + 1;
480     }
481
482     /* At this point, we have a nice big list maybe NULL */
483     if( p_files != NULL )
484     {
485         /* lock the interface */
486         vlc_mutex_lock( &p_intf->change_lock );
487
488         msg_Dbg( p_intf, "adding %d elements", g_list_length( p_files ) );
489         GtkAppendList( p_playlist, i_position, p_files );
490
491         /* get the CList  and rebuild it. */
492         p_clist = GTK_CLIST( lookup_widget( p_intf->p_sys->p_playwin,
493                                             "playlist_clist" ) );
494         GtkRebuildCList( p_clist , p_playlist );
495
496         /* unlock the interface */
497         vlc_mutex_unlock( &p_intf->change_lock );
498     }
499
500     vlc_object_release( p_playlist );
501 }
502
503
504 void GtkDeleteGListItem( gpointer data, gpointer param )
505 {
506     int i_cur_row = (long)data;
507     playlist_t * p_playlist = param;
508
509     playlist_LockDelete( p_playlist, i_cur_row );
510 }
511
512
513 gint GtkCompareItems( gconstpointer a, gconstpointer b )
514 {
515     return (ptrdiff_t) ( (int *)b - (int *)a );
516 }
517
518
519 /* check a file (string) against supposed valid extension */
520 int GtkHasValidExtension( gchar * psz_filename )
521 {
522     char * ppsz_ext[6] = { "mpg", "mpeg", "vob", "mp2", "ts", "ps" };
523     int  i_ext = 6;
524     int  i_dummy;
525
526     gchar * psz_ext = strrchr( psz_filename, '.' ) + sizeof( char );
527
528     for( i_dummy = 0 ; i_dummy < i_ext ; i_dummy++ )
529     {
530         if( strcmp( psz_ext, ppsz_ext[i_dummy] ) == 0 )
531         {
532             return 1;
533         }
534     }
535
536     return 0;
537 }
538
539 /* recursive function: descend into folders and build a list of
540  * valid filenames */
541 static GList * GtkReadFiles( intf_thread_t * p_intf, gchar * psz_fsname )
542 {
543     struct stat statbuf;
544     GList  *    p_current = NULL;
545
546     /* get the attributes of this file */
547     stat( psz_fsname, &statbuf );
548
549     /* is it a regular file ? */
550     if( S_ISREG( statbuf.st_mode ) )
551     {
552         if( GtkHasValidExtension( psz_fsname ) )
553         {
554             msg_Dbg( p_intf, "%s is a valid file, stacking on the playlist",
555                              psz_fsname );
556             return g_list_append( NULL, g_strdup( psz_fsname ) );
557         }
558         else
559         {
560             return NULL;
561         }
562     }
563     /* is it a directory (should we check for symlinks ?) */
564     else if( S_ISDIR( statbuf.st_mode ) )
565     {
566         /* have to cd into this dir */
567         DIR *           p_current_dir = opendir( psz_fsname );
568         struct dirent * p_dir_content;
569
570         msg_Dbg( p_intf, "%s is a folder", psz_fsname );
571
572         if( p_current_dir == NULL )
573         {
574             /* something went bad, get out of here ! */
575             return p_current;
576         }
577         p_dir_content = readdir( p_current_dir );
578
579         /* while we still have entries in the directory */
580         while( p_dir_content != NULL )
581         {
582             /* if it is "." or "..", forget it */
583             if( ( strcmp( p_dir_content->d_name, "." ) != 0 ) &&
584                 ( strcmp( p_dir_content->d_name, ".." ) != 0 ) )
585             {
586                 /* else build the new directory by adding
587                    fsname "/" and the current entry name
588                    (kludgy :()
589                   */
590                 char *  psz_newfs = malloc ( 2 + strlen( psz_fsname ) +
591                             strlen( p_dir_content->d_name ) * sizeof(char) );
592                 strcpy( psz_newfs, psz_fsname );
593                 strcpy( psz_newfs + strlen( psz_fsname ) + 1,
594                         p_dir_content->d_name );
595                 psz_newfs[strlen( psz_fsname )] = '/';
596
597                 p_current = g_list_concat( p_current,
598                                            GtkReadFiles( p_intf, psz_newfs ) );
599
600                 g_free( psz_newfs );
601             }
602             p_dir_content = readdir( p_current_dir );
603         }
604         return p_current;
605     }
606     return NULL;
607 }
608
609 /* add items in a playlist
610  * when i_pos==-1 add to the end of the list...
611  */
612 int GtkAppendList( playlist_t * p_playlist, int i_pos, GList * p_list )
613 {
614     int i_dummy;
615     int i_length;
616
617     i_length = g_list_length( p_list );
618
619     for( i_dummy = 0; i_dummy < i_length ; i_dummy++ )
620     {
621         playlist_Add( p_playlist,
622                 /* ok; this is a really nasty trick to insert
623                    the item where they are suppose to go but, hey
624                    this works :P (btw, you are really nasty too) */
625                g_list_nth_data( p_list, i_dummy ),
626                g_list_nth_data( p_list, i_dummy ),
627                i_dummy == 0 ? PLAYLIST_INSERT | PLAYLIST_GO : PLAYLIST_INSERT,
628                i_pos == PLAYLIST_END ? PLAYLIST_END : ( i_pos + i_dummy ) );
629     }
630
631     return 0;
632 }
633
634 /* statis timeouted function */
635 void GtkPlayListManage( intf_thread_t * p_intf )
636 {
637     playlist_t * p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
638                                                        FIND_ANYWHERE );
639     GtkCList *   p_clist;
640
641     if( p_playlist == NULL )
642     {
643         return;
644     }
645
646     /* this thing really sucks for now :( */
647
648     /* TODO speak more with src/playlist/playlist.c */
649     if( GTK_IS_WIDGET( p_intf->p_sys->p_playwin ) )
650     {
651         p_clist = GTK_CLIST( gtk_object_get_data( GTK_OBJECT(
652                        p_intf->p_sys->p_playwin ), "playlist_clist" ) );
653
654         vlc_mutex_lock( &p_playlist->object_lock );
655
656         if( p_intf->p_sys->i_playing != p_playlist->i_index )
657         {
658             GdkColor color;
659
660             color.red = 0xffff;
661             color.blue = 0;
662             color.green = 0;
663
664             gtk_clist_set_background( p_clist, p_playlist->i_index, &color );
665
666             if( p_intf->p_sys->i_playing != -1 )
667             {
668                 color.red = 0xffff;
669                 color.blue = 0xffff;
670                 color.green = 0xffff;
671                 gtk_clist_set_background( p_clist, p_intf->p_sys->i_playing,
672                                           &color);
673             }
674             p_intf->p_sys->i_playing = p_playlist->i_index;
675         }
676
677         vlc_mutex_unlock( &p_playlist->object_lock );
678     }
679
680     vlc_object_release( p_playlist );
681 }
682
683 void GtkRebuildCList( GtkCList * p_clist, playlist_t * p_playlist )
684 {
685     int         i_dummy;
686     gchar *     ppsz_text[2];
687     GdkColor    red;
688     red.red     = 65535;
689     red.blue    = 0;
690     red.green   = 0;
691
692     gtk_clist_freeze( p_clist );
693     gtk_clist_clear( p_clist );
694
695     vlc_mutex_lock( &p_playlist->object_lock );
696     for( i_dummy = p_playlist->i_size ; i_dummy-- ; )
697     {
698         char psz_duration[MSTRTIME_MAX_SIZE];
699         mtime_t dur = p_playlist->pp_items[i_dummy]->input.i_duration;
700         if ( dur != -1 )
701         {
702             secstotimestr( psz_duration, dur/1000000 );
703         }
704         else
705         {
706             memcpy( psz_duration ,"no info",sizeof("no info" ));
707         }
708         ppsz_text[0] = p_playlist->pp_items[i_dummy]->input.psz_name;
709         ppsz_text[1] = strdup( psz_duration );
710         gtk_clist_insert( p_clist, 0, ppsz_text );
711     }
712     vlc_mutex_unlock( &p_playlist->object_lock );
713
714     gtk_clist_set_background( p_clist, p_playlist->i_index, &red);
715     gtk_clist_thaw( p_clist );
716 }
717
718 /* URL-decode a file: URL path, return NULL if it's not what we expect */
719 static void UrlDecode( char *encoded_path )
720 {
721     char *tmp = NULL, *cur = NULL, *ext = NULL;
722     int realchar;
723
724     if( !encoded_path || *encoded_path == '\0' )
725     {
726         return;
727     }
728
729     cur = encoded_path ;
730
731     tmp = calloc(strlen(encoded_path) + 1,  sizeof(char) );
732
733     while ( ( ext = strchr(cur, '%') ) != NULL)
734     {
735         strncat(tmp, cur, (ext - cur) / sizeof(char));
736         ext++;
737
738         if (!sscanf(ext, "%2x", &realchar))
739         {
740             free(tmp);
741             return;
742         }
743
744         tmp[strlen(tmp)] = (char)realchar;
745
746         cur = ext + 2;
747     }
748
749     strcat(tmp, cur);
750     strcpy(encoded_path,tmp);
751 }
752