Index: apps/plugins/viewers.config
===================================================================
--- apps/plugins/viewers.config	(revision 12246)
+++ apps/plugins/viewers.config	(working copy)
@@ -28,3 +28,4 @@
 tzx,viewers/zxbox,66 52 4A 66 52 4A
 z80,viewers/zxbox,66 52 4A 66 52 4A
 zzz,viewers/properties,00 00 00 00 00 00
+rbt,viewers/theme_install,58 5F 45 50 53 57
Index: apps/plugins/SOURCES
===================================================================
--- apps/plugins/SOURCES	(revision 12246)
+++ apps/plugins/SOURCES	(working copy)
@@ -127,3 +127,5 @@
 #endif
 
 #endif /* iFP7xx */
+theme_install.c
+
--- /dev/null	2007-02-09 20:47:29.667832505 +0100
+++ apps/plugins/theme_install.c	2007-02-09 22:16:19.000000000 +0100
@@ -0,0 +1,313 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id:$
+ *
+ * Copyright (C) 2006 Dominik Riebeling
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include "plugin.h"
+
+/* This macros must always be included. Should be placed at the top by
+   convention, although the actual position doesn't matter */
+PLUGIN_HEADER
+
+/* here is a global api struct pointer. while not strictly necessary,
+   it's nice not to have to pass the api pointer in all function calls
+   in the plugin */
+static struct plugin_api* rb;
+
+int tar_seek_file(int fd, const char* name, char* filename);
+int oct2int(const char* buf);
+char* basename(const char* in);
+void stripext(char* in);
+void printline(char*);
+
+
+#define TAR_BLOCKSIZE 512
+#define BUFSIZE 100
+
+/* this is the plugin entry point */
+enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
+{
+    int fd, wd = -1;
+    DIR *dd;
+    int size;
+    char theme[BUFSIZE];
+    char backdrop[BUFSIZE];
+    char target[BUFSIZE];
+    char buf[BUFSIZE];
+    char* c1;
+    char* c2;
+    int n;
+    char block[TAR_BLOCKSIZE];
+    int i;
+    char file[MAX_FILENAME];
+    int skip;
+
+    /* if you are using a global api pointer, don't forget to copy it!
+       otherwise you will get lovely "I04: IllInstr" errors... :-) */
+    rb = api;
+    struct menu_item items[4];
+
+    rb->strcpy(file, parameter);
+    /* need to get called with a theme file */
+    if(file == '\0') return PLUGIN_ERROR;
+
+    fd = rb->open(file, O_RDONLY);
+    if(fd < 0) return PLUGIN_ERROR;
+
+    /* check if theme file present */
+    size = tar_seek_file(fd, ".cfg", theme);
+    if(size <= 0) {
+        rb->splash(HZ, true, "Theme incomplete");
+        return PLUGIN_ERROR;
+    }
+    c1 = basename(theme);
+    stripext(c1);
+
+    i = 0;
+    rb->snprintf(buf, sizeof(buf), "Install Theme %s", c1);
+    items[i++].desc = buf;
+    items[i++].desc = "Quit";
+
+    /* build menu */
+    int m;
+    int result = -1;
+    m = rb->menu_init(items, i, NULL, NULL, NULL, NULL);
+    while(result < 0) {
+        switch(rb->menu_show(m)) {
+        case 0:
+            result = 0;
+            break;
+        case MENU_SELECTED_EXIT:
+        case 1:
+            result = 1;
+            break;
+        default:
+            break;
+        }
+    }
+    rb->menu_exit(m);
+    rb->lcd_setmargins(0, 0);
+
+    if(result == 1) {
+        rb->splash(HZ, true, "Aborted!");
+        return PLUGIN_OK;
+    }
+
+    rb->lcd_clear_display();
+#ifdef HAVE_REMOTE_LCD
+    rb->lcd_remote_clear_display();
+#endif
+    printline("Rockbox Theme Installer");
+
+    rb->snprintf(buf, sizeof(buf), "Theme name: %s", c1);
+    printline(buf);
+    printline("Reading Theme File...");
+
+    i = 0;
+    do {
+        n = rb->read_line(fd, buf, sizeof(buf)-1);
+        if(*buf == '#' || *buf == '\n' || *buf == '\r' || *buf == 0x00)
+            continue;
+        c2 = basename(buf);
+        /* backdrop needs to get handled separately */
+        if(rb->strcasestr(buf, "backdrop") != 0)
+            rb->snprintf(backdrop, sizeof(backdrop)-1, "%s", c2);
+    }
+    while(n > 0);
+
+    rb->snprintf(buf, sizeof(buf)-1, "%s/%s", WPS_DIR, c1);
+    dd = rb->opendir(buf);
+    if(!dd) {
+        wd = rb->mkdir(buf);
+        if(wd < 0) {
+            printline("FATAL ERROR: can't access folder");
+            rb->close(fd);
+            rb->sleep(HZ);
+            return PLUGIN_ERROR;
+        }
+    }
+    else rb->closedir(dd);
+
+    printline("installing...");
+
+    /* sequentially read the theme file */
+    rb->lseek(fd, 0, SEEK_SET);
+    while((n = rb->read(fd, block, 256)) > 0) {
+        rb->lseek(fd, 256, SEEK_CUR);
+
+        /* continue on every header that is not a normal file */
+        if(block[156] != 0x00 && block[156] != '0') continue;    
+        size = oct2int(&block[124]);
+        /* handle real files */
+        if(block[0] == '\0') continue;
+        c2 = basename(block);
+
+        skip = 0;
+        if(rb->strcasestr(block, backdrop) != NULL && backdrop[0] != '\0')
+            rb->snprintf(target, sizeof(target)-1, "%s/%s", BACKDROP_DIR, c2);
+        else if(rb->strcasestr(block, ".fnt") != NULL)
+            rb->snprintf(target, sizeof(target)-1, "%s/%s", FONT_DIR, c2);
+        else if(rb->strcasestr(block, ".cfg") != NULL)
+            rb->snprintf(target, sizeof(target)-1, "%s/%s", THEME_DIR, c2);
+        else if(rb->strcasestr(block, ".wps") != NULL)
+            rb->snprintf(target, sizeof(target)-1, "%s/%s", WPS_DIR, c2);
+        else if(rb->strcasestr(block, ".rwps") != NULL)
+            rb->snprintf(target, sizeof(target)-1, "%s/%s", WPS_DIR, c2);
+        else if(rb->strcasestr(block, ".bmp") != NULL)
+            rb->snprintf(target, sizeof(target)-1, "%s/%s/%s", WPS_DIR, c1, c2);
+        else skip = 1;
+
+        /* write out the file */
+        if(!skip) {
+            wd = rb->open(target, O_CREAT|O_WRONLY|O_TRUNC);
+            if(wd < 0) {
+                printline("FATAL ERROR: can't write file");
+                rb->close(fd);
+                rb->sleep(5*HZ);
+                return PLUGIN_ERROR;
+           }
+        }
+        while(size > 0) {
+            rb->read(fd, block, 512);
+            if(!skip) {
+                if(size >= 512) rb->write(wd, block, 512);
+                else rb->write(wd, block, size);
+            }
+            size -= 512;
+        }
+        if(!skip) rb->close(wd);
+    }
+
+    /* finished */
+
+    rb->close(fd);
+
+    printline("");
+    printline("Finished.");
+    rb->sleep(HZ);
+
+    return PLUGIN_OK;
+}
+
+/* find a file and seek to it inside of a tar
+ */
+int tar_seek_file(int fd, const char* name, char* filename)
+{
+    int size = -1;
+    char block[256];
+    int n;
+    /* make sure to start at the beginning */
+    rb->lseek(fd, 0, SEEK_SET);
+    while((n = rb->read(fd, block, 256)) > 0) {
+        rb->lseek(fd, 256, SEEK_CUR);
+        /* continue on every header that is not a normal file */
+        if(block[156] != 0x00 && block[156] != '0') continue;    
+        size = oct2int(&block[124]);
+        /* handle real files -- ignore case */
+        if(rb->strcasestr(block, name) != 0) {
+            rb->memcpy(filename, block, 100);
+            return size;
+        }
+        /* skip file and start over */
+        if(size % 512 == 0) n = size / 512;
+        else n = (size / 512 + 1);
+        rb->lseek(fd, n * 512, SEEK_CUR);
+    }
+    return -1;
+}
+
+
+/** @brief convert octal string to value
+ *
+ */
+int oct2int(const char* buf)
+{
+    char* p;
+    int result = 0;
+    p = (char*) buf;
+    while(*p != '\0') {
+        result *= 8;
+        result += *p - '0';
+        p++;
+    }
+    return result;
+}
+
+/** @brief strip path from string
+ *
+ *  @return pointer to first character of basename
+ */
+char* basename(const char* in)
+{
+    char* tmp;
+    char* out;
+
+    tmp = (char*) in;
+    do {
+        if(*tmp == '/') out = tmp + 1;
+        else out = tmp;
+        tmp = rb->strcasestr(out, "/");
+    }
+    while(tmp != NULL);
+
+    return out;
+}
+
+
+/** @brief strip extension from string
+ *
+ */
+void stripext(char* in)
+{
+    char* tmp;
+    char* out;
+
+    tmp = in;
+    do {
+        out = tmp + 1;
+        tmp = rb->strcasestr(out, ".");
+    }
+    while(tmp != NULL);
+    out--;
+    *out = '\0';
+
+    return;
+}
+
+/* print a line of text to the lcd. Wraps when display full. */
+void printline(char* buf)
+{
+    static int h1 = 0;
+#ifdef HAVE_REMOTE_LCD
+    static int h2 = 0;
+#endif
+    int height;
+
+    rb->lcd_putsxy(0, h1, buf);
+#ifdef HAVE_REMOTE_LCD
+    rb->lcd_remote_putsxy(0, h2, buf);    
+#endif
+    rb->lcd_getstringsize("A", NULL, &height);
+    h1 += height;
+    if(h1 > LCD_HEIGHT - height) h1 = 0;
+    rb->lcd_update();
+#ifdef HAVE_REMOTE_LCD
+    h2 += height;
+    if(h2 > LCD_REMOTE_HEIGHT - height) h2 = 0;
+    rb->lcd_remote_update();
+#endif
+}
+
