Embeded Linux Journal
Introduction to Microwindows Programming, Part II

by Gregory Haerr, CEO, Century Software
Chief Maintainer, The Microwindows Project

A lot's been happening since the first article in this series. In the last couple of months, we've worked hard to enhance Microwindows for the Linux PDA environment, and it's sure been fun! This month I'll cover some of the recent additions to the Microwindows Project, and show how to take advantage of these new features with specific programming examples. Many of the enhancements have been written to take advantage of the Compaq iPAQ handheld PDA. In addition, a complete working Linux kernel for the iPAQ, along with a screentop operating environment, precompiled binaries, cross-development libraries, and toolchains for Microwindows, is freely available from the Century Software website at http://embedded.centurysoftware.com. You can use this runtime and development environment along with the information in this article to develop your own applications for the iPAQ, or any other embedded system running Linux, for that matter. Another great site for the iPAQ is http://www.handhelds.org. So let's get started!

New Nano-Window Manager
Similar to Linux's X Window System, Microwindows also sports a low-level, Xlib-like graphics API known as Nano-X. As we mentioned last month, this client-side library starts a connection with the Nano-X Server, and directs the graphics draw requests from the application to the server, where the draw requests are formatted onto the kernel framebuffer, which is mapped into the server's address space. The basic data structure used for all drawing is the Window ID, which reserves a portion of the physical display memory as a destination for all drawing to occur onto. The Nano-X server doesn't allow any drawing outside of a given Window ID, which is specified in every graphics request. But what about window titles, and close boxes, borders and all that good stuff? How does that portion of a window get drawn? Like X, Nano-X uses a separate application, called a window manager, to accomplish this.

The Nano-X Window Manager is a completely normal Nano-X application, except that it selects certain events to receive that it needs to know about in order to simulate drawing window decorations outside of the standard window area, and managing mouse or pen down operations outside of the main window area. After the Nano-X server is started, it's usual to start the new window manager, called nanowm, next. The idea is that the look-and-feel of the operating environment, including what a window caption looks like, what font to use when drawing the title text, and whether to run an application full-screen or not, is actually calculated and performed by the window manager. In this way, application code can stay the same for PDAs and WebPADs, for instance, but running a different window manager could manage the screen real-estate differently between the systems.

The source code to the window manager is in microwin/src/demos/nanowm. Let's get a basic overview of how it functions, and at the same time discuss the ways a client application can control its own look-and-feel and behavior. The window manager relies on a neat trick to know whenever an application creates a new "top level" window in the system. Technically, a top-level window is just a child window of the initial root window created by the server at startup. By requesting update events, any application can be notified when a given window is updated. So one of the first things the window manager executes is:


This specifies that the Nano-X server should send GR_EVENT_TYPE_UPDATE events to the window manager whenever any children of the root window are updated, which includes when they are created. Here are the subtypes for update events:

GR_UPDATE_MAP                 Sent when a window is initially displayed
GR_UPDATE_UNMAP           Sent when a window is hidden
GR_UDPATE_MOVE              Sent when a window is moved
GR_UPDATE_SIZE                 Sent when a window is resized
GR_UPDATE_UNMAPTEMP Sent instead of UNMAP during user move or resize operations
GR_UPDATE_ACTIVATE       Sent when a top-level window is activated/deactivated
GR_UPDATE_DESTROY        Sent when a window is destroyed

The window manager waits for initial window mappings for children of the root window, and when it gets one, it uses another cute trick, called window reparenting. Initially, the application window's parent is the root window, but the window manager executes the following:

container_window_id = GrNewWindow(GR_ROOT_WINDOW_ID, x, y, width, height,
    0, BLACK, BLACK);
GrReparentWindow(app_window_id, container_window_id, 1, 12);

The first statement creates a new, top-level "container window," which is the window it will use to draw the title bar, close box and borders in. The second statement "reparents" the application window from the root window to this new window, and sticks it inset as a child window at 1,12 offset, which is 1 pixel in for the left border, and 12 pixels down to leave room for the caption. The GrReparentWindow call effectively redirects the application window to start a new life as a child of the window manager's decoration window. Next, the window manager reads the application-specified window properties and uses them to determine the style that the application window should appear as. This is where different window managers can interpret various properties to make applications appear differently. The window properties are read using the call:

GrGetWMProperties(app_window_id, &props);

The GR_WM_PROPERTIES structure is declared in nano-X.h as follows:

typedef struct {
    GR_WM_PROPS flags;     /* Which fields are valid in structure*/
    GR_WM_PROPS props;.    /* Window property bits*/
    GR_CHAR *title;        /* Window title*/
    GR_COLOR background;   /* Window background color*/
    GR_SIZE bordersize;    /* Window border size*/

The flags field is used only when setting property bits. The actual window property bits that can be specified by an application can be divided into two categories, general properties and decoration styles. Following are descriptions of each of the window property bits:

General Window Properties

When set, don't automatically erase and fill the window background to the background window color. This should be set when the application will draw the entire window background, to reduce flicker instead of having the server draw the background once before the application does.

This bit tells the server to never give focus to this window, when the user pens down on it. This can be used to disallow user interaction with the window.

When set, the window manager won't allow the window to be moved by user interaction.

This bit, when set, tells the window manager not to bring the window up to the forefront as is the default when the user pens down on the window.

If set, this tells the window manager to completely leave this top-level window alone, and not to bother reparenting or tracking this window. No window ornaments, borders or captions will be drawn, nor can the window be moved using the pen, since the window manager won't have created a container window for it.

By default, the window manager will reposition a window to the next desirable (stacking) location, and ignore the x, y position arguments passed by the application in GrNewWindow. This bit tells the window manager not to do this, but to instead place the window where it was programmatically asked for. When applications don't set this bit, different window managers can create maximized or bordered container windows, depending on the size of the screen display, for instance.

Like NOAUTOMOVE, this bit tells the window manager not to auto-resize the application window, but to instead use the application-specified size. Otherwise, the window manager may limit the size of the window to the visible screen, for instance.

Window Decoration Styles

When set, this bit tells the window manager to draw a single line border around the entire window.

This bit specifies that a 3-D frame should be drawn around the window, making the window look like a top-level window.

This bit reserves space for a caption bar in which the window title, if present, will be displayed.

When set along with CAPTION, the window manager will draw a close box and interpret presses in the area by sending GR_EVENT_TYPE_CLOSEREQ events to the application.

This bit tells the window manager that the application is maximized, and should be drawn and positioned differently.

This isn't really a bit, since it's defined to be 0. When specified, it specifies that the appearance of the window should be left up to the window manager. Most applications will specify this style, which allows different window managers, or a window manager running in different modes, to determine different look-and-feels for the application. We use this bit in our applications so that the same application runs say, maximized on a PDA, but with a 3D border with a caption and closebox on a WebPAD. The window manager substitutes real decoration style bits for this pseudo-bit after determining the size of the display area and the size of the screen area.

As you can see, there's quite a few options that can be used to communicate between an application and the window manager. Typically, this is the code that an application executes when creating its main window and the window properties:

GR_WINDOW_ID app_window_id;

app_window_id = GrNewWindow(GR_ROOT_WINDOW_ID, x, y, width, height,
    0, BLACK, 0);
props.props = GR_WM_PROPS_APPWINDOW;
props.title = "App Title";
GrSetWMProperties(app_window_id, &props);

Since this sequence is used so often, there's a new helper function GrNewWindowEx that can be used to do the same thing with less code:

app_window_id = GrNewWindowEx(GR_WM_PROPS_APPWINDOW, "App Title",
    x, y, width, height, BLACK);

New Image Support
One of the fun things about writing graphics programs is being able to display graphical images on the display, to impress your friends or quickly communicate information to the user. There are several new graphics image formats supported by Microwindows, known as decoders, as well as some spiffy routines for caching images in the server, and displaying them in different locations and sizes on the screen. I'll cover the Microwindows image subsystem in this section, and show the various ways that images can be manipulated.

Graphical images can be stored on a disk or flash filesystem in various formats, as well as stored internally by the Microwindows server. There are function calls that allow color images to be decoded from the filesystem and displayed once, as well as more complex calls that will decode an image for caching in the server for display numerous times, for maximum speed. In addition, an application program can programmatically create an image bit-by-bit, so-to-speak, and have the server display that as well. I'll cover the filesystem-based image decoding routines first.

Image Decoding
Currently, Microwindows supports images in BMP, JPEG, GIF, PNG, PPM and XPM formats. These decoders all live in microwin/src/engine/devimage.c, and are automatically called based on the internal image file data, so that an image type doesn't need to be known in advance in order to be displayed. If the size of an image is known this call will decode the image and display it at once:

GrDrawImageFromFile(window_id, gc, x, y, width, height, image_path, 0);

The image at image_path will be displayed at x, y stretched or shrunk to the width and height specified. A width and height of -1 will display the image unmodified. If the display is a truecolor display, which is 16, 24 or 32 bits-per-pixel, then the image colors will be translated to the truecolor values according matching the display color resolution. On the other hand, if the display is palletized, with 1, 2, 4 or 8 bpp, then the image colors will be translated to the nearest colors in the system palette and displayed. If the palette is not full, which will be the case when using an 8bpp system, since Microwindows only uses the first 24 colors for itself, then each new image color will be added to the system palette until the system palette is full. The GdResetPalette() routine can be used to reset the system palette's reusable entries.

If the size of the image is not known, or if the image will need to displayed quickly many times, the server can decode the image and retain its data for subsequent display or query. The following code sequence loads and caches an image on the server, gets the image width/height information, displays the image, and then frees its resources:

image_id = GrLoadImageFromFile(image_path, 0);
GrGetImageInfo(image_id, &info);
GrDrawImageToFit(window_id, gc, x, y, info.width, info.height, image_id);

When an image is computed at runtime, rather than being loaded from the filesystem, Microwindows offers two routines, one for monochrome image data (such as font data), as well as routines for full-color image display. Monochrome image data is represented by the applications program with each bit from left to right stored in subsequent 16-bit short words, hi bit first, and right-padded on the end to a short word. 1-bits are displayed in the GC foreground color with the background color displayed if GrSetUseBackground is TRUE for 0 bits:

GrBitmap(window_id, gc, x, y, width, height, imagebits);

In-core color image data can be passed to the server using either raw device-dependent data formatting, or by filling in a screen-independent image header structure. If the applications program can calculate the image data in the format required by the screen driver, then the GrArea call can be used to pass raw image data to the display. This call allows the programmer to pass a parameter describing the raw image format, which the server will use to translate the data if not the same as the screen format. Generally, the translation is too slow for most images, so this method should only be used when the image data can be computed to match the screen pixel format, which is available via the GrGetScreenInfo call. Following are the allowable pixel formats:

Pixels are 1, 2, 4 or 8 bits each, packed into a byte. Each pixel value corresponds to an index into the current system palette.

Pixels are packed 32 bits per pixel, 4 bytes packed, each representing BGR truecolor values, with 8 bits for blue, green, red, and the last byte 0.

Pixels are packed 24 bits per pixel, BGR truecolor, 3 bytes packed, each byte representing 8 bits of a blue, green and red value.

Pixels are packed 16 bits per pixel, truecolor, 2 bytes packed, with 5 bits for red, 6 for green, and 5 for red.

Pixels are packed 8 bits per pixel, truecolor, with 3 bits for red, 3 for green, and 2 for red.

Pixels are packed according to the Microwindows config file SCREEN_PIXTYPE compile-time option. By default, this is MWPF_TRUECOLOR0888, but can be changed so that PIXELVALs internal to the server require less space than 32 bits each.

Pixels are packed RGB, 32 bits per pixel, and converted to the backwards BGR format used by most truecolor displays.

The Compaq iPAQ uses a 16bpp display, so an application could display 16bpp preformatted image data by using the following call:

GrArea(window_id, gc, x, y, width, height, image_data,

Finally, for a screen-device-independent method of displaying images, the GR_IMAGE_HDR /MWIMAGEHDR structure can be used to define an image. This method is also used when it is desired to compile in an image into an application, so that no external filesystem access is required to display an encoded image. To make this work, the image must first be converted into BMP format, and then the bin/convbmp utility executed to convert the image into a C source file with a filled-in image header structure. The microwin/src/mwin/bmp directory contains examples for how to do this. This structure can also be used to specify a transparent color for transparent image drawing. Following is the structure for the GR_IMAGE_HDR/MWIMAGEHDR structure:

typedef struct {
    int width;          /* image width in pixels*/
    int height;         /* image height in pixels*/
    int planes;         /* image planes, set to 1*/
    int bpp;            /* image bpp, 1, 4, 8, 24 or 32*/
    int bytesperpixel;  /* # bytes per pixel*/
    int compression;    /* compression algorithm, usually 0*/
    int palsize;        /* image palette size, 0 for truecolor*/
    long transcolor;    /* image transparent color or -1 if none*/
    MWPALENTRY *palette;/* image palette*/
    MWUCHAR *imagebits; /* image bits, dword right aligned*/

The compression flag allows upside-down image decoding to be specified using the MWIMAGE_UPSIDEDOWN flag, as well as RGB versus BGR color packing using the MWIMAGE_RGB flag.

The image header data structure is filled out with the appropriate values, with the image data bits corresponding to the bits-per-pixel specified, in all cases right-aligned to a 32-bit boundary. The image can then be displayed using the following:

extern MWIMAGEHDR car_image; /* generated by convbmp program in external .c file*/
GrDrawImageBits(window_id, gc, x, y, &car_image);

In order to keep Microwindows small, the various image decoders are specified for inclusion at compile time in the config file, using HAVE_BMP_SUPPORT, HAVE_JPEG_SUPPORT and similar options. Make sure you include the decoders that you think you'll need or you won't be seeing your favorite images.

Well, we've zipped through some of the new features that have been added to Microwindows in the last several months, and hopefully this will speed your learning process and allow you to have fun implementing state-of-the art applications on embedded Linux devices. Remember, Microwindows is open source, and we're happy to accept contributions, bug reports and enhancements. If you've got any other questions, make sure you drop a line on the mailing list on the web page, http://microwindows.org. Have fun and we'll see you next month!