Introduction to Microwindows Programming, Part 2
A lot has been happening since the first article (ELJ January/February 2001) in this series. In the last couple of months, we've worked hard to enhance Microwindows for the Linux PDA environment, and it sure has been fun! This time 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 web site 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. Another great site for the iPAQ is http://www.handhelds.org/. So let's get started!
Similar to the X Window System, Microwindows also sports a low-level, Xlib-like graphics API known as Nano-X. As we mentioned in the last article, 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 they 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 at which all drawing occurs. The Nano-X server doesn't allow any drawing outside a given Window ID, which is specified in every graphics request. But what about window titles, 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 that also go to user applications, for its own inspection in order to draw window decorations outside of the standard window area and manage mouse or pen-down operations outside 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, 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 located 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 simply 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:
GrSelectEvents(GR_ROOT_WINDOW_ID, <@cont_arrow>å<@$p>GR_EVENT_MASK_CHLD_UPDATE);
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, including 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.
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 in which it will draw the title bar, close box and borders. The second statement ``reparents'' the application window from the root window to this new window and sticks its 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:
GR_WM_PROPERTIES props; 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*/ GR_WM_PROPERTIES; }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.
GR_WM_PROPS_NOBACKGROUND: When this bit is set it prevents Microwindows from automatically erasing and filling the window background to the background window color. This should be set when the application will draw the entire window background. This reduces flicker over having the application draw the background again after the server does.
GR_WM_PROPS_NOFOCUS: This bit tells the server never to give focus to this window when the user pens down on it. This can be used to forbid user interaction with the window.
GR_WM_PROPS_NOMOVE: When this bit is set, the window manager won't allow the window to be moved by user interaction.
GR_WM_PROPS_NORAISE: 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.
GR_WM_PROPS_NODECORATE: If set, this bit tells the window manager to leave this top-level window completely 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 because the window manager would not have created a container window for it.
GR_WM_PROPS_NOAUTOMOVE: By default, the window manager will reposition a window in the next desirable (stacking) location and ignore the x- and y- position arguments passed by the application in GrNewWindow. This bit tells the window manager not to do this but instead to place the window in the location programmatically requested. 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.
GR_WM_PROPS_NOAUTORESIZE: 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.
GR_WM_PROPS_BORDER: When set, this bit tells the window manager to draw a single line border around the entire window.
GR_WM_PROPS_APPFRAME: This bit specifies that a 3-D frame should be drawn around the window, making the window look like a top-level window.
GR_WM_PROPS_CAPTION: This bit reserves space for a caption bar in which the window title, if present, will be displayed.
GR_WM_PROPS_CLOSEBOX: 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.
GR_WM_PROPS_MAXIMIZE: This bit tells the window manager that the application is maximized and should be drawn and positioned differently.
GR_WM_PROPS_APPWINDOW: This isn't really a bit, since it's defined to be 0. When specified, it causes the appearance of the window to 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 looks 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 3-D 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 are 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; GR_WM_PROPERTIES props; 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"; props.flags = GR_WM_FLAGS_PROPS|GR_WM_FLAGS_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, <@cont_arrow>å<@$p>"App Title" x, y, width, height, BLACK);
One of the fun things about writing graphics programs is being able to display graphical images to impress your friends or communicate information quickly 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.
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 the coordinates of x and y, with the images 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 (bpp), then the image colors will be translated to the truecolor values accordingly matching the display color resolution. On the other hand, if the display is palletized, with 1, 2, 4 or 8bpp, 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 uses only the first 24 colors for itself) 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 be 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:
GR_IMAGE_INFO info; image_id = GrLoadImageFromFile(image_path, 0); GrGetImageInfo(image_id, &info); GrDrawImageToFit(window_id, gc, x, y, info.width, info.height, image_id); GrFreeImage(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) and one for full-color image display. Monochrome image data is represented in the application program as a series of 16-bit short words. The format of the bits within each word is with zero padding on the right if the image width is not exactly divisible by 16. one-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 application 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 it's 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:
MWPF_PALETTE: 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.
MWPF_TRUECOLOR0888: Pixels are packed 32bpp, 4 bytes packed, each representing BGR truecolor values, with 8 bits for blue, green, red and the last byte 0.
MWPF_TRUECOLOR888: Pixels are packed 24bpp, BGR truecolor, 3 bytes packed, each byte representing 8 bits of a blue, green and red value.
MWPF_TRUECOLOR565: Pixels are packed 16bpp, truecolor, 2 bytes packed, with 5 bits for blue, 6 for green and 5 for red.
MWPF_TRUECOLOR332: Pixels are packed 8bpp, truecolor, with 3 bits for blue, 3 for green and 2 for red.
MWPF_PIXELVAL: 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.
MWPF_RGB: Pixels are packed RGB, 32bpp and converted to the backward BGR format used by most truecolor displays.
GrArea(window_id, gc, x, y, width, height, image_data, MWPF_TRUECOLOR565);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 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 in order to convert the image into a C source file with a filled-in image-header structure. The microwin/src/mwin/bmp directory contains examples of how to do this. This structure can also be used to specify a transparent color for transparent image drawing. Listing 1 is the structure for GR_IMAGE_HDR/MWIMAGEHDR.
Listing 1. GR_IMAGE_HDR/MWIMAGEHDR Structure
The compression flag allows upside-down-image decoding to be specified using the MWIMAGE_UPSIDEDOWN flag, as well as RGB vs. 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 at http://microwindows.org/. Have fun, and we'll see you next month!
When not at the office detailing plans for building New Age PDA applications technologies, Greg Haerr sits at home in front of his terminal enhancing Microwindows. Greg is CEO of Century Software and the chief maintainer of the Microwindows Project. He can be reached at greg@censoft.com.
email: greg@censoft.com