[ / main / writing / xintro ]

    X11 Programming Intro



©1996 Jeff Weeks and Codex software

This is a very basic introduction to the X Window System. It'll cover enough territory to allow you to open a window, draw to your window, and handle input. This, however, covers stock X11 functions. For really fast graphics you'll probably want to check out the MIT SHM or XFree86 DGA extentions.




    Overview of X11 Programming




While I'm sure you've seen all the toolkits for X11, they are no replacement for knowing the unlying workings of the system. Learn low level X11 programming and you'll be able to do anything; Even write your own toolkit.




X11 comes with a bunch of standard header files, but the most common are the following. These will be the ones you'll almost always use, maybe with the occasional extra.

      #include <X11/Xlib.h>
      #include <X11/Xutil.h>
      #include <X11/Xos.h>

These files are typically found in /usr/include/X11, but don't rely on them being there, as they are in different places on different systems occasionally. You're best best is to do like the above and specify relative include paths, using the -I option of the compiler to specify include search paths.




X11 can be very complicated with its built in networking and the likes, and so you might occasionally feel like the code is a bit overkill, but I assure you, it is worth it. Instead of a simple Window variable, you must also define the Display and Screen you're writting to, as well as a graphics context:

      Display *dis;
      int screen;
      Window win;
      GC gc;

The display is your communications device to the X Server. The screen refers to which screen of the display to use. You can override the default display and screen by setting the DISPLAY environment variable, but I won't get into that right now. That's not a programming topic, but rather a networking topic. Your window variable will be used with most calls to the X11 graphics system, so it's usually left global, as are all the other variables. The graphics context specifies how things are displayed in the window. They're analogous to the VGA registers, I suppose; You can use masks, and the likes.




So, how do we finally get a window up and running. Well, here's a function that'll do just that:

    void init_x() {
      /* get the colors black and white */
      unsigned long black,white;
      /* Always specify 0 as the display name.  This will use the environment
         variable DISPLAY to choose what display to use and garauntees your
         program will work on any network.
      */
      dis=XOpenDisplay((char *)0);
      screen=DefaultScreen(dis);
      black=BlackPixel(dis,screen);   /* get colour black */
      white=WhitePixel(dis,screen);   /* get colour white */
      /* once the display is initialized, create the window.
         This window will be have be 200 pixels across and 300 down.
         It will have the foreground white and background black.  Check
         out the man page for XCreateSimpleWindow for different ways to
         open windows.
      */
      win=XCreateSimpleWindow(dis,DefaultRootWindow(dis),0,0,
                              200, 300, 5, white, black);
      /* here is where some properties of the window can be set.
         The third and fourth items indicate the name which appears
         at the top of the window and the name of the minimized window
         respectively.
      */
      XSetStandardProperties(dis,win,"My Window","HI!",None,NULL,0,NULL);
      /* this routine determines which types of input are allowed for
         your program.  Check the header files for a list of all available
         input masks.
      */
      XSelectInput(dis, win, ExposureMask|ButtonPressMask|KeyPressMask);
      /* create the Graphics Context */
      gc=XCreateGC(dis, win, 0,0);
      /* here is another routine to set the foreground and background
         colors currently in use in the window.
      */
      XSetBackground(dis,gc,white);
      XSetForeground(dis,gc,black);
      /* clear the window and bring it to the top */
      XClearWindow(dis, win);
      XMapRaised(dis, win);
    };

The man pages are a huge wealth of knowledge when you know what to look for. Try checking out the above functions to figure out some of the parameters I've left out. The header files are also a lot of help when you're looking for things such as mask and character definitions.




Now, you've opened your window, remember to close it when you're done with it. Here's how:

    void close_x() {
      XFreeGC(dis, gc);
      XDestroyWindow(dis,win);
      XCloseDisplay(dis);
    }



    Handling Input




X11's input and output routines are fairly advanced. Remember how you specified input masks? Well, that allows you to, effectively, ignore events you don't want to hear about. Oh yeah, that's another important piece of information; X11 is event driven. That's really nothing new, but X11's method may be a little new to you. It maintains a list of events that have happened and allows you to query this list and remove events off the queue based upon their type.




So, how do you remove events off this list? Well, first of all you have to have some place to put them. This is where the XEvent variable comes in. I would recomend defining one global event. Quite frankly, the XEvent structure is huge, and so it's wasteful to use more than one :)




One of the more inportant events is the Exposure event (remember above in init_x() where we specified the ExposureMask in XSelectInput?). This tells us that a previously hidden part of the window has been exposed and must be redrawn. That's rather interesting in itself; The programmer must repaint their windows.




Here is code snippet which gives an example of using XEvent's. Not only does it check of the Exposure event but also the keypress event, so it can exit upon the 'q' keypress.

      XEvent event;    /* the XEvent */
      char key;        /* a key code */
      /* look for events forever... */
      while(1) {
        /* get the next event into our event variable.
           Note:  only events we set the mask for are returned.  Again,
           check the man pages for more ways to get event input.
        */
        XNextEvent(dis, &event);
        /* check if the window is exposed and redraw it if need be */
        if(event.type == Expose && event.xexpose.count == 0) {
          redraw();
        }
        /* Check for a keypress */
        if(event.type == KeyPress) {
          key = XKeycodeToKeysym(dis, event.xkey.keycode, 0);
          if(key == 'q') {
            close_x();
          }
          printf("You pressed the %c key!\n",key);
        }
        /* even check for a button press */
        if(event.type == ButtonPress) {
          printf("You pressed a button at (%i,%i)\n",
                 event.xbutton.x,event.xbutton.y);
        }
      }
    };



The printf statements will, of course, print to the terminal you ran the X program from. To use actual fonts in your window you'll have to use the X11 font functions.




    Drawing in the Window




Now that you have a windows up and running, and it can accept input, the next step is to respond with output! Here are some basics to get you started:

    XDrawLine(dis,win,gc, x1,y1, x2,y2);
    XDrawArc(dis,win,gc,x,y, width, height, arc_start, arc_stop);
    XDrawRectangle(dis, win, gc, x, y, width, height)
    XFillArc(dis,win, gc, x, y, width, height, arc_start, arc_stop);
    XFillRectangle(dis,win,gc, x, y, width, height);

Each of these functions use the current foreground and background colours so you'll have to change them through XSetForeground and XSetBackground like we did in init_x()




    Placing Text in the Window




Placing text in a window is very similar to drawing in a window. The routine to remember is:

      XDrawString(dis,win,gc,x,y, string, strlen(string));

Very easy and so I won't spend any more time here.




    Working with Colours




Colour use and manipulations is a fairly large topic in itself and so I will have to skip some things, unfortunately. We'll start out with the easiest situation; Over 8bpp.




If you have a colour depth above 8 bits per pixel (over 256 colours) then you can express your colours as words with R, G and B components inside! For example, a 24bit colour component has 8 bits of red, then 8 bits of green, then the last 8 bits for blue. Check out my discussion on high colour depths in my DJGPP graphics programming chapter for a more in depth discussion.




What's a little more difficult, however, are 8bpp modes. In these modes you have a palette of colours called a colourmap. Lucky for you, X11 comes with a huge variety of predefined colours, in human readable form. If you wish to use one of these, all you have to do is search for your colour like this:

    void get_colors() {
      XColor tmp;
      XParseColor(dis,DefaultColormap(dis,screen), "chartreuse", &tmp);
      XAllocColor(dis,DefaultColormap(dis,screen),&tmp);
      chartreuse=tmp.pixel;
    };



These colours are usually found in /usr/X11/lib/X11/rgb.txt if you wish to look through it. One thing to note here; With only 256 colours, your chances of getting the exact colour you asked for is slim. This is the motivation for using your own private colour map. A private colour map is like having your own 256 colours, as apposed to using the global 256 colours that every application must share. Here's how you would create your own private colour map:

    void create_colormap() {
      int i;
      Colormap cmap;
      XColor tmp[255];
      for(i=0;i<255;i++) {
        tmp[i].pixel = i;
        tmp[i].flags = DoRed | DoGreen | DoBlue;
        tmp[i].red   = i*256;
        tmp[i].blue  = i*256;
        tmp[i].green = i*256;
      }
      cmap=XCreateColormap(dis,RootWindow(dis,screen),
                           DefaultVisual(dis,screen),AllocAll);
      XStoreColors(dis, cmap, tmp,255);
      XSetWindowColormap(dis,win,cmap);
    }



Basically what's happening here is you first define all your colours in an XColor array (that's what my for loop does) and then you pass this array to XCreateColourmap which will return a single variable containing all your defined colours. You, ofcourse, must tell the X server what you've done and so next I call XSetWindowColormap. Be sure to add XFreeColormap(dis,cmap) to your close_x() function if you use this method. I should mention that colormaps do not work in colour depths greater than 8bpp. You should use DefaultVisual() to determine what colour depth you have, and adapt your functions accordingly.




One thing I should probably mention. This page was originally written by bhammond@blaze.cba.uga.edu but I have subsequently rewritten it myself and will, ofcourse, accept any questions you have. Just mail me with whatever problems you've encountered. Mr. Hammond also wrote some source to go along with this tutorial that you can get here.