Modifying Podzilla

From wikiPodLinux

This is a tutorial. If you want a more complete reference, look at the PZ2 API reference (http://www.get-linux.org/~oremanj/t/pz2-api.pdf).

Table of contents

Adding an application to podzilla

This section describes a step by step introduction to adding new modules to pz2. The application we are making will be called "mymodule", for no obvious reason. The full code exists in SVN as tools/podzilla2/modules/mymodule/*.

Preparations

Get the Toolchain You need to make sure that you have the podzilla2,podfile,hotdog,and ttk directory that you can get by typing:

svn co https://ipodlinux.svn.sourceforge.net/svnroot/ipodlinux/libs/hotdog/
svn co https://ipodlinux.svn.sourceforge.net/svnroot/ipodlinux/libs/ttk/
svn co https://ipodlinux.svn.sourceforge.net/svnroot/ipodlinux/legacy/podfile/
svn co https://ipodlinux.svn.sourceforge.net/svnroot/ipodlinux/apps/ipod/podzilla2/

(If this doesn't work, you have to install Subversion)

Make sure that you are able to compile podzilla and have hotdog, ttk and podfile built, copy the executable to your iPod and run the new file. In most cases being able to compile on X11 makes life a lot easier.

Setup

1. Make a folder somewhere named after your application. You can put this in podzilla2/modules/, but you don't have to. (It's easier to compile if you do, though.)

2. Create a Module file describing your application. An example is below.

Module: mymodule
Display-name: Podzilla Test Module
Author: Your Name

3. Copy any data your application needs into the folder for the module. For instance, here we use two files: message.txt and image.png. For this app, they can be whatever you want. Your app may or may not have any data.

4. Create a simple Makefile that looks like this:

obj-m = <all object files of your app listed here>
MODULE = <name of your module>
DATA = <space separated list of all data files>

Yours can look something like this:

obj-m = mymodule.o
MODULE = mymodule

Note: If you don't use any data files, don't include DATA and the obj-m is the file that end is .o that the module uses.

5. Create your main C file. If your app has only one file, it must be named after the module name. This is due to a quirk of the build system.

Coding

6. Add your includes, this should set up everything you need.

#include <errno.h>
#include <string.h>

#include "pz.h" /* This is the important one */

7. Add some global variables. Here, we're keeping track of the module pointer (used for resolving paths to data files), our picture, its width and height, and the text we loaded from the file. Make these static whenever possible.

static PzModule *module;    /* opaque type, used only to fetch path info */
static ttk_surface image;   /* ttk_surface stores pixel data, similar to GR_IMAGE_ID or GR_PIXMAP_ID */
static int imgw, imgh;      /* image width and height */
static char *text;          /* text loaded from the file */

8. Add your module's init function. This will be called as soon as your module is loaded. It should not actually open or create any windows; rather, it should just set up e.g. menu entries that, when selected, will create a window.

void init_mymodule()  /* Name is whatever you want */ 
{
    /* This does two things: (1) it sets things up so cleanup_mymodule will be called just
     * before podzilla exits, and  (2) it returns a module handle that can be used to get
     * data packaged with the module.
     */
    module = pz_register_module ("mymodule", cleanup_mymodule);

    /* This adds an entry to the menu  Extras -> Stuff  that will call our window
     * creator function.
     */
    pz_menu_add_action ("/Extras/Stuff/MyModule", new_mymodule_window);

    /* And, as a demonstration, a little diagnostic: */
    printf ("Hi! MyModule loaded, action set.\n");
}

PZ_MOD_INIT(init_mymodule)    /* No semicolon at end of line. Should probably be the *last* line in file. */

9. Here's a simple, do-nothing cleanup function to show when it gets called:

void cleanup_mymodule()
{
    printf ("Bye-bye now...\n");
}

10. Next, make your window creation handler. The following code is rather long, since it has a lot of data to load, but it's commented profusely. The handler creates a window, but does not show it onscreen; rather, it simply returns it. If it does not want to create a new window, it can return one of the TTK_MENU_* constants; for instance, TTK_MENU_DONOTHING causes no window to be shown, and nothing else to happen, either.

PzWindow *new_mymodule_window()
{
    /* Our variables. */
    static int calledyet = 0;
    PzWindow *ret;
    FILE *fp;
    
    /* Force user to select menu item a few times - just a demonstration
     * of static variables.
     */
    if (!calledyet) {
        calledyet++;
        pz_message ("Select again to see the fun stuff.");
        return TTK_MENU_DONOTHING;
    }

    /* Load the iPL logo. Note that pz_module_get_datapath() takes a filename within
     * your module and returns a full path; you are highly recommended to only access
     * data files in this way. This prevents littering of the filesystem with every
     * module's data.   Note that image is a ttk_surface; there is no distinction
     * between pixmaps and surfaces and images in TTK.
     */
    image = ttk_load_image (pz_module_get_datapath (module, "image.png"));
    if (!image) {
        /* Tell the user about our problem and abort loading. Note that pz_error()
         * accepts a printf-style format string.
         */
	pz_error ("Could not load %s: %s", pz_module_get_datapath (module, "image.png"),
                  strerror (errno));
        return TTK_MENU_DONOTHING;
    }

    /* This retrieves the width and height of the image and stores them in the two
     * globals we set aside for that purpose.
     */
    ttk_surface_get_dimen (image, &imgw, &imgh);

    /* Now let's load the text. */
    fp = fopen (pz_module_get_datapath (module, "message.txt"), "r");
    if (!fp) {
        /* In this case, we'll pretend that the text file is some piece of non-vital data
         * for which there is a sensible default - in this case, "Hi! I forgot to supply a message."
         * It is strdup()d so it can be indiscriminately free()d later.
         */
        pz_warning ("Could not read from %s: %s.", pz_module_get_datapath (module, "message.txt"),
                    strerror (errno));
        text = (char *)strdup ("Hi! I forgot to supply a message!");
    } else {
        /* This reads all the data in the file into [text]. You don't necessarily have to
         * understand how it works.
         */
        long len;
        fseek (fp, 0, SEEK_END);
        len = ftell (fp);
        fseek (fp, 0, SEEK_SET);
        text = malloc (len + 1);   /* Note malloc; thus, we need to free later. */
        fread (text, 1, len, fp);
        text[len] = 0;
    }

    /* And now, the GUI stuff. These three lines would have taken fourteen in PZ0. */

    /* First, we create our window, a "normal" one (fullscreen except the header).
     * "MyModule" is the title.
     */
    ret = pz_new_window ("MyModule", PZ_WINDOW_NORMAL);

    /* Then, we add a "widget" to it - just a combination of draw and event handlers.
     * You can also use TTK widgets by calling ttk_add_widget() directly.
     */
    pz_add_widget (ret, draw_mymodule, event_mymodule);

    /* If you wanted a timer, you could do it by replacing that line with:
     *   PzWidget *wid = pz_add_widget (ret, draw_mymodule, event_mymodule);
     *   pz_widget_set_timer (wid, 50 );/* 50 = 50 ms */
     * or even
     *   pz_widget_set_timer (pz_add_widget (ret, draw_mymodule, event_mymodule), 50);
     */

    /* Finalize the window and return it. For normal windows, finalization does nothing,
     * but it's good practice anyway since it's important for other types of window.
     */
    return pz_finish_window (ret);
}

11. Now make your event handler. Pay attention—the format of the event structure is rather different from legacy podzilla. (It's also much simpler.)

int event_mymodule (PzEvent *ev) 
{
    switch (ev->type) {
    case PZ_EVENT_BUTTON_UP:  /* button released, don't care which one */
        /* Close the window without caching. (Next time the user selects this menu item,
         * new_mymodule_window() will be called again.) If you want the exact same window
         * to pop up next time, without any new init code, you should use pz_hide_window().
         *
         * ev->wid is the widget the event was sent to, and ev->wid->win is the window that
         * widget is in.
         */
	pz_close_window (ev->wid->win);
	break;
    case PZ_EVENT_DESTROY:
        /* Our window is about to be freed - free anything we allocated ourselves. */
	ttk_free_surface (image);
	free (text);
	break;
 
    /* If you wanted to trap scroll events:
     * case PZ_EVENT_SCROLL:
     *     work with ev->arg = the distance (+ or -) that the user scrolled
     *     break;
     * Other events too - see the PZ API reference.
     */
 
    }
    /* Normal event return - handled, no clicky noise */
    return 0;
}

12. Finally, the draw handler. This looks complex, but it's not really that hard. The main complexity is in the parsing of multi-line text messages appropriately.

void draw_mymodule (PzWidget *wid, ttk_surface srf) 
{
    // Draw the message
    char *p = text;      /* Pointer to go through the text */
    int y = wid->y + 5;  /* We'll use this to keep track of where on the screen we're drawing. */
    while (*p) {
        /* <black magic> */
	char svch;
	if (strchr (p, '\n')) {
	    svch = *strchr (p, '\n');
	    *strchr (p, '\n') = 0;
	} else {
	    svch = 0;
	}
        /* </black magic> */
 
        /* The ttk_text call draws text. We're drawing with the font ttk_textfont (intended
         * for, you guessed it, ''text'') on srf at (wid->x + 5, y) with the color BLACK.
         */
	ttk_text (srf, ttk_textfont, wid->x + 5, y, ttk_makecol (BLACK), p);
 
       	p += strlen (p);
	y += ttk_text_height (ttk_textfont) + 1; /* Increment y to point to the next line. */
	*p = svch;
	if (*p) p++;
    }
    y += 3;
    /* Draw a line. */
    ttk_line (srf, wid->x + 5, y, wid->x + wid->w - 5, y, ttk_makecol (DKGREY));
    
    /* Draw the image, dead center. */
    ttk_blit_image (image, srf, wid->x + (wid->w - imgw) / 2, wid->y + (wid->h - imgh) / 2);
    
    /* Draw the message at the bottom of the screen using the menu font. */
#define MSG _("Press a button to quit.")
    ttk_text (srf, ttk_menufont, wid->x + (wid->w - ttk_text_width (ttk_menufont, MSG)) / 2,
	      wid->y + wid->h - ttk_text_height (ttk_menufont) - 5, ttk_makecol (BLACK), MSG);
}

13. Congratulations! You're done. Compile this thing by typing make IPOD=1 in the podzilla directory, or if you worked outside the PZ dir,

make -C /path/to/podzilla2/directory SUBDIRS=`pwd` IPOD=1

If you compiled it with podzilla, you'll find a file pods/mymodule.pod that you can copy to iPod/usr/lib. If you compiled outside the PZ dir, you'll have to make the pod yourself - type something like this.

/path/to/podfile/directory/pod -c mymodule.pod mymodule.o Module message.txt image.png

If you wanted to compile for the desktop, make in the PZ dir or

make -C /path/to/podzilla2/directory SUBDIRS=`pwd`

outside of it. If you compiled outside the directory, you'll need to make a symlink:

ln -s `pwd` /path/to/podzilla2/directory/xpods/mymodule

Then run ./podzilla, maybe with an argument like -photo or -mini to set the screen size and color depth.


Well done, congratulations on building your first podzilla application!

If you have a great addition to podzilla please consider submitting it to the project!

Scalable Apps

Different iPods have different screen sizes and depths — keep this in mind when writing your app. One podzilla binary works on all iPods.

Image:Genscreen.png

The screen size can be obtained via ttk_screen global's members, w and h, and bpp contains the bits per pixel (2 for monochrome iPods, 16 for Photo/Nano/Video).

/* iPod */
ttk_screen->bpp == 2
ttk_screen->w == 160
ttk_screen->h == 128
/* iPod mini */
ttk_screen->bpp == 2
ttk_screen->w == 138
ttk_screen->h == 110
/* iPod photo */
ttk_screen->bpp == 16
ttk_screen->w == 220
ttk_screen->h == 176
/* iPod nano */
ttk_screen->bpp == 16
ttk_screen->w == 176
ttk_screen->h == 132
/* iPod video (5g) */
ttk_screen->bpp == 16
ttk_screen->w == 320
ttk_screen->h == 240

Also, what may be more useful is the size of your window — this does not include the size of the header, so less calculations to do. (The height of the header is ttk_screen->wy.) Look at widget->win->w and ->h; ->color is a boolean flag indicating whether or not the window supports color.

Views
Personal tools