]> git.sesse.net Git - vlc/blob - modules/gui/skins2/src/theme_loader.cpp
* Fix missing include causing a build failure with GCC 4.3; patch by
[vlc] / modules / gui / skins2 / src / theme_loader.cpp
1 /*****************************************************************************
2  * theme_loader.cpp
3  *****************************************************************************
4  * Copyright (C) 2003 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Cyril Deguet     <asmax@via.ecp.fr>
8  *          Olivier Teulière <ipkiss@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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #include "theme_loader.hpp"
26 #include "theme.hpp"
27 #include "../parser/builder.hpp"
28 #include "../parser/skin_parser.hpp"
29 #include "../src/os_factory.hpp"
30 #include "../src/vlcproc.hpp"
31 #include "../src/window_manager.hpp"
32
33 #include <cctype>
34
35 #ifdef HAVE_FCNTL_H
36 #   include <fcntl.h>
37 #endif
38 #ifdef HAVE_SYS_STAT_H
39 #   include <sys/stat.h>
40 #endif
41 #ifdef HAVE_UNISTD_H
42 #   include <unistd.h>
43 #elif defined( WIN32 ) && !defined( UNDER_CE )
44 #   include <direct.h>
45 #endif
46
47 #ifdef HAVE_DIRENT_H
48 #   include <dirent.h>
49 #endif
50
51
52 #if defined( HAVE_ZLIB_H )
53 #   include <zlib.h>
54 #   include <errno.h>
55 int gzopen_frontend ( char *pathname, int oflags, int mode );
56 int gzclose_frontend( int );
57 int gzread_frontend ( int, void *, size_t );
58 int gzwrite_frontend( int, const void *, size_t );
59 #if defined( HAVE_LIBTAR_H )
60 #   include <libtar.h>
61 #else
62 typedef gzFile TAR;
63 int tar_open        ( TAR **t, char *pathname, int oflags );
64 int tar_extract_all ( TAR *t, char *prefix );
65 int tar_close       ( TAR *t );
66 int getoct( char *p, int width );
67 #endif
68 int makedir( const char *newdir );
69 #endif
70
71 #define DEFAULT_XML_FILE "theme.xml"
72 #define WINAMP2_XML_FILE "winamp2.xml"
73 #define ZIP_BUFFER_SIZE 4096
74
75
76 bool ThemeLoader::load( const string &fileName )
77 {
78     // First, we try to un-targz the file, and if it fails we hope it's a XML
79     // file...
80     string path = getFilePath( fileName );
81 #if defined( HAVE_ZLIB_H )
82     if( ! extract( sToLocale( fileName ) ) && ! parse( path, fileName ) )
83         return false;
84 #else
85     if( ! parse( path, fileName ) )
86         return false;
87 #endif
88
89     Theme *pNewTheme = getIntf()->p_sys->p_theme;
90     if( !pNewTheme )
91     {
92         return false;
93     }
94
95     // Check if the skin to load is in the config file, to load its config
96     char *skin_last = config_GetPsz( getIntf(), "skins2-last" );
97     if( skin_last != NULL && fileName == (string)skin_last )
98     {
99         // Restore the theme configuration
100         getIntf()->p_sys->p_theme->loadConfig();
101         // Used to anchor the windows at the beginning
102         pNewTheme->getWindowManager().stopMove();
103     }
104     else
105     {
106         config_PutPsz( getIntf(), "skins2-last", fileName.c_str() );
107         // Show the windows
108         pNewTheme->getWindowManager().showAll( true );
109     }
110     if( skin_last ) free( skin_last );
111
112     // The new theme cannot embed a video output yet
113     VlcProc::instance( getIntf() )->dropVout();
114
115     return true;
116 }
117
118
119 #if defined( HAVE_ZLIB_H )
120 bool ThemeLoader::extractTarGz( const string &tarFile, const string &rootDir )
121 {
122     TAR *t;
123 #if defined( HAVE_LIBTAR_H )
124     tartype_t gztype = { (openfunc_t) gzopen_frontend,
125                          (closefunc_t) gzclose_frontend,
126                          (readfunc_t) gzread_frontend,
127                          (writefunc_t) gzwrite_frontend };
128
129     if( tar_open( &t, (char *)tarFile.c_str(), &gztype, O_RDONLY, 0,
130                   TAR_GNU ) == -1 )
131 #else
132     if( tar_open( &t, (char *)tarFile.c_str(), O_RDONLY ) == -1 )
133 #endif
134     {
135         return false;
136     }
137
138     if( tar_extract_all( t, (char *)rootDir.c_str() ) != 0 )
139     {
140         tar_close( t );
141         return false;
142     }
143
144     if( tar_close( t ) != 0 )
145     {
146         return false;
147     }
148
149     return true;
150 }
151
152
153 bool ThemeLoader::extractZip( const string &zipFile, const string &rootDir )
154 {
155     // Try to open the ZIP file
156     unzFile file = unzOpen( zipFile.c_str() );
157     unz_global_info info;
158
159     if( unzGetGlobalInfo( file, &info ) != UNZ_OK )
160     {
161         return false;
162     }
163     // Extract all the files in the archive
164     for( unsigned long i = 0; i < info.number_entry; i++ )
165     {
166         if( !extractFileInZip( file, rootDir ) )
167         {
168             msg_Warn( getIntf(), "error while unzipping %s",
169                       zipFile.c_str() );
170             unzClose( file );
171             return false;
172         }
173
174         if( i < info.number_entry - 1 )
175         {
176             // Go the next file in the archive
177             if( unzGoToNextFile( file ) !=UNZ_OK )
178             {
179                 msg_Warn( getIntf(), "error while unzipping %s",
180                           zipFile.c_str() );
181                 unzClose( file );
182                 return false;
183             }
184         }
185     }
186     unzClose( file );
187     return true;
188 }
189
190
191 bool ThemeLoader::extractFileInZip( unzFile file, const string &rootDir )
192 {
193     // Read info for the current file
194     char filenameInZip[256];
195     unz_file_info fileInfo;
196     if( unzGetCurrentFileInfo( file, &fileInfo, filenameInZip,
197                                sizeof( filenameInZip), NULL, 0, NULL, 0 )
198         != UNZ_OK )
199     {
200         return false;
201     }
202
203     // Convert the file name to lower case, because some winamp skins
204     // use the wrong case...
205     for( size_t i=0; i< strlen( filenameInZip ); i++)
206     {
207         filenameInZip[i] = tolower( filenameInZip[i] );
208     }
209
210     // Allocate the buffer
211     void *pBuffer = malloc( ZIP_BUFFER_SIZE );
212     if( !pBuffer )
213     {
214         msg_Err( getIntf(), "failed to allocate memory" );
215         return false;
216     }
217
218     // Get the path of the file
219     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
220     string fullPath = rootDir
221         + pOsFactory->getDirSeparator()
222         + fixDirSeparators( filenameInZip );
223     string basePath = getFilePath( fullPath );
224
225     // Extract the file if is not a directory
226     if( basePath != fullPath )
227     {
228         if( unzOpenCurrentFile( file ) )
229         {
230             free( pBuffer );
231             return false;
232         }
233         makedir( basePath.c_str() );
234         FILE *fout = fopen( fullPath.c_str(), "wb" );
235         if( fout == NULL )
236         {
237             msg_Err( getIntf(), "error opening %s", fullPath.c_str() );
238             free( pBuffer );
239             return false;
240         }
241
242         // Extract the current file
243         int n;
244         do
245         {
246             n = unzReadCurrentFile( file, pBuffer, ZIP_BUFFER_SIZE );
247             if( n < 0 )
248             {
249                 msg_Err( getIntf(), "error while reading zip file" );
250                 free( pBuffer );
251                 return false;
252             }
253             else if( n > 0 )
254             {
255                 if( fwrite( pBuffer, n , 1, fout) != 1 )
256                 {
257                     msg_Err( getIntf(), "error while writing %s",
258                              fullPath.c_str() );
259                     free( pBuffer );
260                     return false;
261                 }
262             }
263         } while( n > 0 );
264
265         fclose(fout);
266
267         if( unzCloseCurrentFile( file ) != UNZ_OK )
268         {
269             free( pBuffer );
270             return false;
271         }
272     }
273
274     free( pBuffer );
275     return true;
276 }
277
278
279 bool ThemeLoader::extract( const string &fileName )
280 {
281     bool result = true;
282     char *tmpdir = tempnam( NULL, "vlt" );
283     string tempPath = sFromLocale( tmpdir );
284     free( tmpdir );
285
286     // Extract the file in a temporary directory
287     if( ! extractTarGz( fileName, tempPath ) &&
288         ! extractZip( fileName, tempPath ) )
289     {
290         deleteTempFiles( tempPath );
291         return false;
292     }
293
294     string path;
295     string xmlFile;
296     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
297     // Find the XML file in the theme
298     if( findFile( tempPath, DEFAULT_XML_FILE, xmlFile ) )
299     {
300         path = getFilePath( xmlFile );
301     }
302     else
303     {
304         // No XML file, check if it is a winamp2 skin
305         string mainBmp;
306         if( findFile( tempPath, "main.bmp", mainBmp ) )
307         {
308             msg_Dbg( getIntf(), "trying to load a winamp2 skin" );
309             path = getFilePath( mainBmp );
310
311             // Look for winamp2.xml in the resource path
312             list<string> resPath = pOsFactory->getResourcePath();
313             list<string>::const_iterator it;
314             for( it = resPath.begin(); it != resPath.end(); it++ )
315             {
316                 if( findFile( *it, WINAMP2_XML_FILE, xmlFile ) )
317                     break;
318             }
319         }
320     }
321
322     if( !xmlFile.empty() )
323     {
324         // Parse the XML file
325         if (! parse( path, xmlFile ) )
326         {
327             msg_Err( getIntf(), "error while parsing %s", xmlFile.c_str() );
328             result = false;
329         }
330     }
331     else
332     {
333         msg_Err( getIntf(), "no XML found in theme %s", fileName.c_str() );
334         result = false;
335     }
336
337     // Clean-up
338     deleteTempFiles( tempPath );
339     return result;
340 }
341
342
343 void ThemeLoader::deleteTempFiles( const string &path )
344 {
345     OSFactory::instance( getIntf() )->rmDir( path );
346 }
347 #endif // HAVE_ZLIB_H
348
349
350 bool ThemeLoader::parse( const string &path, const string &xmlFile )
351 {
352     // File loaded
353     msg_Dbg( getIntf(), "using skin file: %s", xmlFile.c_str() );
354
355     // Start the parser
356     SkinParser parser( getIntf(), xmlFile, path );
357     if( ! parser.parse() )
358     {
359         msg_Err( getIntf(), "failed to parse %s", xmlFile.c_str() );
360         return false;
361     }
362
363     // Build and store the theme
364     Builder builder( getIntf(), parser.getData(), path );
365     getIntf()->p_sys->p_theme = builder.build();
366
367     return true;
368 }
369
370
371 string ThemeLoader::getFilePath( const string &rFullPath )
372 {
373     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
374     const string &sep = pOsFactory->getDirSeparator();
375     // Find the last separator ('/' or '\')
376     string::size_type p = rFullPath.rfind( sep, rFullPath.size() );
377     string basePath;
378     if( p != string::npos )
379     {
380         if( p < rFullPath.size() - 1)
381         {
382             basePath = rFullPath.substr( 0, p );
383         }
384         else
385         {
386             basePath = rFullPath;
387         }
388     }
389     return basePath;
390 }
391
392
393 string ThemeLoader::fixDirSeparators( const string &rPath )
394 {
395     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
396     const string &sep = pOsFactory->getDirSeparator();
397     string::size_type p = rPath.find( "/", 0 );
398     string newPath = rPath;
399     while( p != string::npos )
400     {
401         newPath = newPath.replace( p, 1, sep );
402         p = newPath.find( "/", p + 1 );
403     }
404     return newPath;
405 }
406
407
408 bool ThemeLoader::findFile( const string &rootDir, const string &rFileName,
409                             string &themeFilePath )
410 {
411     // Path separator
412     const string &sep = OSFactory::instance( getIntf() )->getDirSeparator();
413
414     DIR *pCurrDir;
415     char *pszDirContent;
416
417     // Open the dir
418     pCurrDir = utf8_opendir( rootDir.c_str() );
419
420     if( pCurrDir == NULL )
421     {
422         // An error occurred
423         msg_Dbg( getIntf(), "cannot open directory %s", rootDir.c_str() );
424         return false;
425     }
426
427     // While we still have entries in the directory
428     while( ( pszDirContent = utf8_readdir( pCurrDir ) ) != NULL )
429     {
430         string newURI = rootDir + sep + pszDirContent;
431
432         // Skip . and ..
433         if( string( pszDirContent ) != "." &&
434             string( pszDirContent ) != ".." )
435         {
436 #if defined( S_ISDIR )
437             struct stat stat_data;
438
439             if( ( utf8_stat( newURI.c_str(), &stat_data ) == 0 )
440              && S_ISDIR(stat_data.st_mode) )
441 #elif defined( DT_DIR )
442             if( pDirContent->d_type & DT_DIR )
443 #else
444             if( 0 )
445 #endif
446             {
447                 // Can we find the file in this subdirectory?
448                 if( findFile( newURI, rFileName, themeFilePath ) )
449                 {
450                     free( pszDirContent );
451                     closedir( pCurrDir );
452                     return true;
453                 }
454             }
455             else
456             {
457                 // Found the theme file?
458                 if( rFileName == string( pszDirContent ) )
459                 {
460                     themeFilePath = newURI;
461                     free( pszDirContent );
462                     closedir( pCurrDir );
463                     return true;
464                 }
465             }
466         }
467
468         free( pszDirContent );
469     }
470
471     closedir( pCurrDir );
472     return false;
473 }
474
475
476 #if !defined( HAVE_LIBTAR_H ) && defined( HAVE_ZLIB_H )
477
478 /* Values used in typeflag field */
479 #define REGTYPE  '0'            /* regular file */
480 #define AREGTYPE '\0'           /* regular file */
481 #define DIRTYPE  '5'            /* directory */
482
483 #define BLOCKSIZE 512
484
485 struct tar_header
486 {                               /* byte offset */
487     char name[100];             /*   0 */
488     char mode[8];               /* 100 */
489     char uid[8];                /* 108 */
490     char gid[8];                /* 116 */
491     char size[12];              /* 124 */
492     char mtime[12];             /* 136 */
493     char chksum[8];             /* 148 */
494     char typeflag;              /* 156 */
495     char linkname[100];         /* 157 */
496     char magic[6];              /* 257 */
497     char version[2];            /* 263 */
498     char uname[32];             /* 265 */
499     char gname[32];             /* 297 */
500     char devmajor[8];           /* 329 */
501     char devminor[8];           /* 337 */
502     char prefix[155];           /* 345 */
503                                 /* 500 */
504 };
505
506
507 union tar_buffer {
508     char              buffer[BLOCKSIZE];
509     struct tar_header header;
510 };
511
512
513
514 int tar_open( TAR **t, char *pathname, int oflags )
515 {
516     gzFile f = gzopen( pathname, "rb" );
517     if( f == NULL )
518     {
519         fprintf( stderr, "Couldn't gzopen %s\n", pathname );
520         return -1;
521     }
522
523     *t = (gzFile *)malloc( sizeof(gzFile) );
524     **t = f;
525     return 0;
526 }
527
528
529 int tar_extract_all( TAR *t, char *prefix )
530 {
531     union tar_buffer buffer;
532     int   len, err, getheader = 1, remaining = 0;
533     FILE  *outfile = NULL;
534     char  fname[BLOCKSIZE + PATH_MAX];
535
536     while( 1 )
537     {
538         len = gzread( *t, &buffer, BLOCKSIZE );
539         if( len < 0 )
540         {
541             fprintf( stderr, "%s\n", gzerror(*t, &err) );
542         }
543
544         /*
545          * Always expect complete blocks to process
546          * the tar information.
547          */
548         if( len != 0 && len != BLOCKSIZE )
549         {
550             fprintf( stderr, "gzread: incomplete block read\n" );
551             return -1;
552         }
553
554         /*
555          * If we have to get a tar header
556          */
557         if( getheader == 1 )
558         {
559             /*
560              * If we met the end of the tar
561              * or the end-of-tar block, we are done
562              */
563             if( (len == 0) || (buffer.header.name[0] == 0) )
564             {
565                 break;
566             }
567
568             sprintf( fname, "%s/%s", prefix, buffer.header.name );
569
570             /* Check magic value in header */
571             if( strncmp( buffer.header.magic, "GNUtar", 6 ) &&
572                 strncmp( buffer.header.magic, "ustar", 5 ) )
573             {
574                 //fprintf(stderr, "not a tar file\n");
575                 return -1;
576             }
577
578             switch( buffer.header.typeflag )
579             {
580                 case DIRTYPE:
581                     makedir( fname );
582                     break;
583                 case REGTYPE:
584                 case AREGTYPE:
585                     remaining = getoct( buffer.header.size, 12 );
586                     if( remaining )
587                     {
588                         outfile = fopen( fname, "wb" );
589                         if( outfile == NULL )
590                         {
591                             /* try creating directory */
592                             char *p = strrchr( fname, '/' );
593                             if( p != NULL )
594                             {
595                                 *p = '\0';
596                                 makedir( fname );
597                                 *p = '/';
598                                 outfile = fopen( fname, "wb" );
599                                 if( !outfile )
600                                 {
601                                     fprintf( stderr, "tar couldn't create %s\n",
602                                              fname );
603                                 }
604                             }
605                         }
606                     }
607                     else outfile = NULL;
608
609                 /*
610                  * could have no contents
611                  */
612                 getheader = (remaining) ? 0 : 1;
613                 break;
614             default:
615                 break;
616             }
617         }
618         else
619         {
620             unsigned int bytes = (remaining > BLOCKSIZE)?BLOCKSIZE:remaining;
621
622             if( outfile != NULL )
623             {
624                 if( fwrite( &buffer, sizeof(char), bytes, outfile ) != bytes )
625                 {
626                     fprintf( stderr, "error writing %s skipping...\n", fname );
627                     fclose( outfile );
628                     unlink( fname );
629                 }
630             }
631             remaining -= bytes;
632             if( remaining == 0 )
633             {
634                 getheader = 1;
635                 if( outfile != NULL )
636                 {
637                     fclose(outfile);
638                     outfile = NULL;
639                 }
640             }
641         }
642     }
643
644     return 0;
645 }
646
647
648 int tar_close( TAR *t )
649 {
650     if( gzclose( *t ) != Z_OK ) fprintf( stderr, "failed gzclose\n" );
651     free( t );
652     return 0;
653 }
654
655
656 /* helper functions */
657 int getoct( char *p, int width )
658 {
659     int result = 0;
660     char c;
661
662     while( width-- )
663     {
664         c = *p++;
665         if( c == ' ' )
666             continue;
667         if( c == 0 )
668             break;
669         result = result * 8 + (c - '0');
670     }
671     return result;
672 }
673
674 #endif
675
676 #ifdef WIN32
677 #  define mkdir(dirname,mode) _mkdir(dirname)
678 #endif
679
680 /* Recursive make directory
681  * Abort if you get an ENOENT errno somewhere in the middle
682  * e.g. ignore error "mkdir on existing directory"
683  *
684  * return 1 if OK, 0 on error
685  */
686 int makedir( const char *newdir )
687 {
688     char *p, *buffer = strdup( newdir );
689     int  len = strlen( buffer );
690
691     if( len <= 0 )
692     {
693         free( buffer );
694         return 0;
695     }
696
697     if( buffer[len-1] == '/' )
698     {
699         buffer[len-1] = '\0';
700     }
701
702     if( mkdir( buffer, 0775 ) == 0 )
703     {
704         free( buffer );
705         return 1;
706     }
707
708     p = buffer + 1;
709     while( 1 )
710     {
711         char hold;
712
713         while( *p && *p != '\\' && *p != '/' ) p++;
714         hold = *p;
715         *p = 0;
716         if( ( mkdir( buffer, 0775 ) == -1 ) && ( errno == ENOENT ) )
717         {
718             fprintf( stderr, "couldn't create directory %s\n", buffer );
719             free( buffer );
720             return 0;
721         }
722         if( hold == 0 ) break;
723         *p++ = hold;
724     }
725     free( buffer );
726     return 1;
727 }
728
729 #ifdef HAVE_ZLIB_H
730
731 static int currentGzFd = -1;
732 static void * currentGzVp = NULL;
733
734 int gzopen_frontend( char *pathname, int oflags, int mode )
735 {
736     char *gzflags;
737     gzFile gzf;
738
739     switch( oflags )
740     {
741         case O_WRONLY:
742             gzflags = "wb";
743             break;
744         case O_RDONLY:
745             gzflags = "rb";
746             break;
747         case O_RDWR:
748         default:
749             errno = EINVAL;
750             return -1;
751     }
752
753     gzf = gzopen( pathname, gzflags );
754     if( !gzf )
755     {
756         errno = ENOMEM;
757         return -1;
758     }
759
760     /** Hum ... */
761     currentGzFd = 42;
762     currentGzVp = gzf;
763
764     return currentGzFd;
765 }
766
767 int gzclose_frontend( int fd )
768 {
769     if( currentGzVp != NULL && fd != -1 )
770     {
771         void *toClose = currentGzVp;
772         currentGzVp = NULL;  currentGzFd = -1;
773         return gzclose( toClose );
774     }
775     return -1;
776 }
777
778 int gzread_frontend( int fd, void *p_buffer, size_t i_length )
779 {
780     if( currentGzVp != NULL && fd != -1 )
781     {
782         return gzread( currentGzVp, p_buffer, i_length );
783     }
784     return -1;
785 }
786
787 int gzwrite_frontend( int fd, const void * p_buffer, size_t i_length )
788 {
789     if( currentGzVp != NULL && fd != -1 )
790     {
791         return gzwrite( currentGzVp, const_cast<void*>(p_buffer), i_length );
792     }
793     return -1;
794 }
795
796 #endif