Embedded Linux Journal
Introduction to Microwindows Programming Part I

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

In the fast-changing world of embedded, handheld and wireless devices, there are many hardware and software design changes taking place. Many devices now feature 32-bit microprocessors from Intel, MIPS and Motorola, as well as larger LCD graphical displays. In order to leverage the significant results gained in the desktop arena the last ten years, many developers are turning to using desktop-like operating systems with these new embedded designs. One of the most promising emerging areas is running Linux in these environments, for a couple of good reasons: Linux on embedded systems brings with it the entire power of desktop computing, along with many solutions already running. Linux, being open source, allows any aspect of the solution to be fully understood and then customized for a particular application.

Microwindows Overview

The Microwindows Project is an open source project aimed at bringing the features of modern graphical windowing environments to smaller devices and platforms running Linux. Designed as a replacement for the X Window System, Microwindows provides similar functionality using much less RAM and file storage space, from 100k to 600k. The architecture allows for ease in adding different display, mouse, touchscreen and keyboard devices. Starting with Linux version 2.2, the kernel contains code to allow user applications to access graphical display memory as a framebuffer, which ends up being a memory-mapped region in a user process space that, when written to, controls the display appearance. This allows graphics applications to be written without having to have knowledge of the underlying graphics hardware, or having to use the X Window System. This is the way that Microwindows typically runs on embedded systems.

Microwindows fully supports the new Linux kernel framebuffer architecture, and currently has support for 1, 2, 4, 8, 16, 24, and 32 bits per pixel displays, with support for palettized and truecolor display color implementations, as well as grayscale. When programming applications, all colors are specified in the portable RGB format, and the system has routines to convert to the nearest available color, or shade of gray for monochromatic systems. Although Microwindows fully supports Linux, it's internal, portable architecture is based on a relatively simple screen device interface, and can run on many different RTOS's as well as bare hardware. This brings a big benefit: graphics programming by the customer can be shared between projects, and even run on different targets with different RTOS's, without having to rewrite the graphics side of the application.

The Microwindows system supports host platform emulation of the target platform graphically. That is, Microwindows applications for Linux can be developed and prototyped on the desktop, run and tested without having to cross-compile and run on the target platform. This is accomplished using Microwindows' X screen driver, rather than the framebuffer driver, where the target application is run on the desktop host and displayed within an X window. The driver can be told to exactly emulate the target platform's display in terms of bits per pixel and color depth. Thus, even though the desktop system is 24 bit color, it can display a 2bpp grayscale for previewing the target application. Since both the host and target are running Linux, most all operating system services are available on the desktop host.

Two APIs: Win32 and Nano-X

Microwindows was designed to attempt to bring applications to market quickly, with a minimum of effort. In order to accomplish this I felt that designing yet another graphics applications programming interface (API) would steepen the learning curve, thus discouraging interest and increasing time-to-market. Microwindows implements two popular graphics programming interfaces: the Microsoft Windows Win32/WinCE graphics display interface (GDI), used by all Windows CE and Win32 applications, and an Xlib-like interface, known as Nano-X, used at the lowest level by all Linux X widget sets. This allows the extremely large group of Windows programmer talent to be used in developing the graphical side of the application, as well as being familiar to the core group of Linux graphics programmers used to working with X.

Nano-X Programming

In this article we'll build a sample application using the Nano-X API, and discuss the issues associated with lower level Nano-X programming. The Nano-X API allows applications to be built using a client/server protocol over a network or local UNIX domain socket. This allows several applications, running on the embedded device or a remote host to connect to the Microwindows server for display. In this way, Nano-X programs operate much like clients using the X Window System. The Nano-X API is similar to X's Xlib library, being quite low level and concerned mostly with creating and destroying windows and basic graphics drawing functions. Because Microwindows was designed to be small, many options are settable using a configuration file supplied with the source package. I'll cover some of these options, so that we can first build a working Nano-X server.

First things first

The Microwindows source package, available at http://microwindows.org, must first be compiled to build a Nano-X server for your particular host or embedded target platform. Most all options are contained in a configuration file. After untar'ing the package, you must first cd to the microwin/src directory and edit the config file. Here's some of the most important options:

ARCH=LINUX-NATIVE
ARCH=LINUX-ARM
ARCH=LINUX-MIPS
ARCH=LINUX-POWERPC

Setting the ARCH option to LINUX-NATIVE tells the system to create programs for the host Linux system you're currently running on. If you want to cross-compile for a RISC processor target machine, set ARCH to one of the other options. Microwindows uses the Arch.rules file to hold the specific settings for each of these options. Image support is compiled into the Nano-X server by setting any of the following options:

HAVE_BMP_SUPPORT=Y
HAVE_GIF_SUPPORT=Y
HAVE_JPEG_SUPPORT=Y

If you set the option for JPEG images, you've also got to indicate the location of an external jpeg decompression library using LIBJPEG=/usr/lib/libjpeg.a, for example. This library comes precompiled on most systems, but is also available from the Microwindows web site. The next major configuration item to concern yourself with is whether to compile in support for scalable font support. By default, Microwindows includes support for compiled in, fixed size bitmap fonts specified in the drivers/genfont.c file. In addition, if you're going to want to display larger fonts, for instance to run an embedded browser, Microwindows can compile in support for TrueType or Adobe Type 1 fonts. When either of these options is compiled in, you can specify a certain font file and pixel size for font rendering and Microwindows will generate the appropriately sized font from that external font file. Recently, we've also added support for external Chinese fonts as well. All font characters can be specified using an 8-bit ASCII index, Unicode-16, or UTF-8, which is a byte-stream encoding for Unicode. The following options control compiling in font support:

HAVE_FREETYPE_SUPPORT=Y
HAVE_T1LIB_SUPPORT=Y
HAVE_HZK_SUPPORT=Y

The FreeType and T1lib external libraries are used to provide support for TrueType and Adobe Type 1 font support, respectively. These libraries must be previously compiled and their location specified in the config file as well. Both libraries are also available from the Microwindows web site.

Configuring the output screen device

Since Microwindows is capable of running on framebuffer systems as well within a window on top of X, a few configuration settings are required to specify options available for each of these screen drivers. If you're already running on a Linux desktop running X, it's best to build the system first using the X screen driver, then later create a framebuffer version for your embedded device. To configure the X screen driver, set the following options:

X11=Y
SCREEN_WIDTH=640
SCREEN_HEIGHT=480
SCREEN_PIXTYPE=MWPF_TRUECOLOR0888


These options tell Microwindows to run within a virtual 640x480 window on the X desktop, and to run programs using an output color model of 8 bits each for red, green and blue. By changing these settings, you can control the emulation of a target embedded device on your desktop. For instance, to emulate a 16 bits per pixel display, use SCREEN_PIXTYPE=MWPF_TRUECOLOR565. These MWPF constants are explained in more detail in the src/include/mwtypes.h header file. 

Setting up the display for framebuffer is a bit more complicated, since you've got to be sure that you're Linux system kernel is compiled for framebuffer support. Set the following options for most framebuffer systems:

X11=N
FRAMEBUFFER=Y
FBVGA=Y
VTSWITCH=Y
PORTRAIT_MODE=N

The FBVGA option compiles in support for a 16 color VGA planar mode screen driver. This option won't typically be used with embedded systems however. The VTSWITCH option allows Microwindows to run on the console framebuffer, but switch to another virtual console when an Alt-function key is pressed. For some embedded systems, this option must be turned off. Finally, the PORTRAIT_MODE option set to R or L builds a server that will run tilted either right or left, which is very suitable for systems like the new Compaq iPAQ PDA.

Linux kernel framebuffer support

If you get "Can't open /dev/fb0" when you run the Nano-X server, it typically means that you don't have open permission or your system kernel doesn't have a framebuffer driver compiled in. The simplest way to tell is that you should see a graphical penguin logo when you boot your system. If not, make sure that at least the following options are set in your /usr/src/linux/.config file:

CONFIG_FB=y
CONFIG_FB_VGA16=y
CONFIG_FBCON_VGA=y
CONFIG_FBCON_CFB4=y
CONFIG_FBCON_CFB8=y


If you have a supported graphics card other than standard old-fashioned VGA, you can select a different option other than CONFIG_FB_VGA16. Before rebuilding the kernel, make sure that you have you're original kernel backed up, with a backup entry in the lilo.conf file. As you can see, building a framebuffer kernel is not for the weak at heart, which is why I recommend using the X screen driver initially. Most embedded systems come configured running the framebuffer as standard.

The last major configuration item is telling Microwindows what device is used for mouse or touch screen input. Microwindows currently supports mice using the GPM utility, or directly using a serial port. The easiest is probably setting GPM support using GPMMOUSE=Y, and then running the gpm utility: 'gpm -R -t ps2' for a PS/2 mouse, for instance. To set up a mouse using a serial port, use SERMOUSE=Y and set the MOUSE_TYPE and MOUSE_PORT environment variables as documented in src/drivers/mou_ser.c.

Building a complete demo system

Luckily, we only have to set the options in the config file once, and they can then remain without being touched. There's also a bunch of sample config files for various platforms in the src directory. To build your Nano-X server, as well as all the sample demo applications, make sure you're in the microwin/src directory, and then type "make". All programs are created in the microwin/src/bin directory, and client linking libraries are put into the microwin/src/lib directory. To run demo applications, first run the Nano-X server (bin/nano-X) and then run the application:

bin/nano-X & sleep 1; bin/world

The sleep command is used to give the server a moment to inintialize before running the demonstration world plotting program on the display. The normal build also builds some of the win32 demonstration programs. These programs contain both the application and the server linked together, and don't require a separate Nano-X server. A fun demo to run is the bin/mine landmine program, or the bin/mdemo window demonstration.

Microwindows has a number of Nano-X demos, all in the microwin/src/demo/nanox directory. Currently, there's a terminal emulator nterm, sample window manager nanowm, as well as a font demonstration program. These samples are invaluable for learning about small Nano-X programs. In the next section, we'll build a Nano-X program from scratch.

Building a simple Nano-X application

We'll start out by building the simplest possible Nano-X application: one that draws a white box with a blue border. Here it is: (sample.c)

#define MWINCLUDECOLORS
#include <stdio.h>
#include "nano-X.h"

int main(int ac,char **av)
{
    GR_WINDOW_ID w;
    GR_EVENT event;

    if (GrOpen() < 0) {
        printf("Can't open graphics\n");
        exit(1);
    }

    w = GrNewWindow(GR_ROOT_WINDOW_ID, 20, 20, 100, 60,
        4, WHITE, BLUE);
    GrMapWindow(w);

    for (;;) {
        GrGetNextEvent(&event);
    }
    GrClose();
    return 0;
}

After configuring and testing the initial Microwindows installation, use "make install" to install the Nano-X server, client libraries and header files. Then compile link, and run our sample program by typing:

gcc sample.c -o sample -lnano-X
nano-X & sleep 1; sample


Type escape to exit, this special key will cause the server to exit.

The GrOpen() call tries to open a connection to the running Nano-X server. If the server isn't running, -1 is returned, and the application exits with an error message. Then, a 100 by 60 pixel window is created at location 20,20 on the screen with the GrNewWindow call. The border size is specified as four pixels wide, with a white interior and blue border color. Creating a window doesn't actually display it on the screen until the GrMapWindow call is made. The reason for this is that sometimes it's convenient to create a set of windows but display and remove them from the screen according to user actions. After mapping the window to the screen, the program enters an "event loop" which waits for the next mouse or keyboard event, although nothing is done with it.
Expose events

More complex Nano-X applications almost always follow this same logical structure. First windows are created, then mapped, and then the program waits in an event loop waiting for user action to take place. In our sample, we haven't actually written any explicit code to draw anything in the window. Before we try this, however, it's important to understand the concept of expose events. The Nano-X API provides a full suite of functions that draw lines, text, circles and images on the screen. When a window is obscured, Microwindows handles clipping the drawing so that windows above the drawn window aren't changed. However, when a previously obscured portion of a window becomes visible, that portion of the window must be redrawn. When this happens, the server sends an expose event to the application, requesting that it redraw it's window contents. The tricky part is that for good program design, the code that originally draws the window contents and the code that redraws the contents should be the same code, in the same place. Specifically, the expose event routine can draw the original contents in the first place. This works because Microwindows sends an expose event just after first mapping the window. Lets use this technique to draw some text in our sample program, but also redraw the text when the window is moved: (sample2.c)

#define MWINCLUDECOLORS
#include <stdio.h>
#include "nano-X.h"

int main(int ac,char **av)
{
    GR_WINDOW_ID w;
    GR_GC_ID gc;
    GR_EVENT event;

    if (GrOpen() < 0) {
        printf("Can't open graphics\n");
        exit(1);
    }

    w = GrNewWindow(GR_ROOT_WINDOW_ID, 20, 20, 100, 60,
        4, WHITE, BLUE);
    gc = GrNewGC();
    GrSetGCForeground(gc, BLACK);
    GrSetGCUseBackground(gc, GR_FALSE);

    GrSelectEvents(w, GR_EVENT_MASK_EXPOSURE);
    GrMapWindow(w);

    for (;;) {
        GrGetNextEvent(&event);
        switch (event.type) {
        case GR_EVENT_TYPE_EXPOSURE:
        GrText(w, gc, 10, 30, "Hello World", -1, GR_TFASCII);
        break;
    }
    GrClose();
    return 0;
}

In order to test our expose event code, we'll run Nano-X with the NanoWM window manager so that we can move windows around. Here's the command line we'll use:

bin/nano-X & sleep 1; bin/nanowm & sleep 1; sample2

In the sample2 program, we've added the GrSelectEvents function to tell Microwindows to send GR_EVENT_TYPE_EXPOSURE events to our client program. In order to keep client/server traffic down, the server will only send selected events to each client window. The "Hello World" text display is handled in only one place, which is our expose event routine. An expose event is generated immediately after the GrMapWindow call, so that the text displays even though the window hasn't actually moved yet. In the GrText call, you may be wondering what the gc parameter is. We'll now introduce the notion of graphics contexts.

Graphics contexts

When drawing objects such as lines or text, there are many parameters that can be specified for each drawing function call that affect the operation. Besides the minimum information required for each call, such as the line starting points, other "contextual" information is kept by the system so that it doesn't have to be specified for every drawing function. Items such as foreground and background color, XOR vs OR drawing modes, and others parameters are all kept in a server structure known as the graphics context. In our sample above, a new graphics context is created with GrNewGC, which creates a graphics context with standard settings. Then, an additional call is made to set the foreground text color using GrSetGCForeground. During the expose event processing the graphics context is passed along with the window id to specify all the text drawing parameters.

Drawing functions

Now that you've seen the very basics of creating programs, windows and graphics contexts, we can explain some of the other functions Microwindows provides in it's graphics arsenal. The following list is a short detail of the different drawing functions:

GrClearWindow                     Clear a window to it's background color
GrPoint                                   Draw a single point
GrLine                                    Draw a line
GrRect                                    Draw a rectangle outline
GrFillRect                                Draw a filled rectangle
GrEllipse                                 Draw an ellipse or circle outline
GrFillEllipse                             Draw a filled ellipse or circle
GrArc                                     Draw an arc outline or pie wedge
GrArcAngle                             Like GrArc, but uses floating point and angles
GrPoly                                     Draw a polygon outline
GrFillPoly                                 Draw a filled polygon
GrBitmap                                 Draw a bitmap image
GrDrawImageFromFile            Draw a BMP, GIF or JPEG file from disk
GrDrawImageToFit                  Draw a cached image and stretch to fit
GrArea                                     Draw from a memory array of pixels
GrCopyArea                            Copy a rectangular area from one window to another

Microwindows also supports a type of window that is never displayed on the screen, known as a pixmap. Pixmap windows are sometimes called offscreen windows and are never directly displayed on the screen, but instead copied from, using GrCopyArea. Pixmaps are useful because sometimes it's too CPU or time intensive to regenerate a window's contents during expose events, and normal windows never save their contents when obscured or unmapped. A pixmap is created using the GrNewPixmap function and can be used where ever a window is used.

Hopefully this introduction has helped you understand how a small system can be used to enable more sophisticated applications in the embedded Linux market. There's quite a bit more documentation available on the microwindows.org web site. In next month's article we'll dig deeper into the Microwindows API and discuss Microwindows' architecture and how it works.