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