[LUPG Home] [Tutorials] [Related Material] [Essays] [Project Ideas] [Send Comments]
When windows are displayed on an X server, they are always ordered in some hierarchy - every window may have child windows, each child window may have its own child windows, etc. Lets see a few properties of this hierarchy, and how they effect the operations like drawing or events propagation.
On each screen, there is a root window. The root window always spans the entire screen size. This window cannot be destroyed, resized or iconified. When an application creates windows, it first has to create at least one top-level window. This window becomes a direct descendant of the root window, until it is first mapped on the screen. Before this window is mapped, the window manager is notified of the operation about to take place. The window manager then has the privilege of re-parenting the new top-level window. This is used to add a window that will contain the new window, and will be used to draw its frame, title bar, system menu, etc.
Once such a top-level window (which is actually not a top-level window after the re-parenting operation takes place) is created, the application can create child windows inside this window. A child can only be displayed inside its parent window - If it is moved outside, its being clipped by the border of its parent window. Any window may contain more than one child window, and if this is the case, these windows are being ordered in an internal stack. When a top-level window is being raised - all its descendant windows are being raised with it, while retaining their internal ordering. If a child window is raised - it is being raised only amongst its sibling windows.
Lets see how to create a child window inside a given window 'win'.
/* this variable will store the handle of the newly created child window. */
Window child_win;
/* these variables will store the window's width and height. */
int child_win_width = 50;
int child_win_height = 30;
/* these variables will store the window's location. */
/* position of the child window is top-left corner of the */
/* parent window. - 0,0. */
int child_win_x = 0;
int child_win_y = 0;
/* create the window, as specified earlier. */
child_win = XCreateSimpleWindow(display,
win,
child_win_x, child_win_y,
child_win_width, child_win_height,
child_win_border_width,
BlackPixel(display, screen_num),
WhitePixel(display, screen_num));
We have discussed events propagation earlier - If a window is being sent an event and it does not process the event - the event is being passed to the this window's parent window. If that parent window does not handle this event - it is being passed to the parent's parent window, and so on. This behavior does not make a lot of sense for a simple Xlib application, but it does make sense when higher-level graphic libraries are used. They usually associate functions with events occurring in specific windows. In such a case, it is useful to pass the event to the relevant window, with which a proper function is associated.
After we have seen how to create windows and draw on them, we take one step back, and look at how our windows are interacting with their environment - the full screen, and the other windows. First of all, our application needs to interact with the window manager. The window manager is responsible to decorating drawn windows (i.e. adding a frame, an iconify button, a system menu, a title bar), as well as to handling icons shown when windows are being iconified. It also handles ordering of windows on the screen, and other administrative tasks. We need to give it various hints as to how we want it to treat our application's windows.
Many of the parameters communicated to the window manager are passed using
data called "properties". These properties are attached by the X server to
different windows, and are stored in a format that makes it possible to read
them from different machines, that may use different architectures (remember
that an X client program may run on a remote machine). Properties may be of
various types - numbers, strings, etc. Most window manager hints functions
use text properties. A function named XStringListToTextProperty()
may be used to turn a normal C string into an X text property, that can later
be passed to various Xlib functions. Here is how to use it:
/* This variable will store the newly created property. */
XTextProperty window_title_property;
/* This is the string to be translated into a property. */
char* window_title = "hello, world";
/* translate the given string into an X property. */
int rc = XStringListToTextProperty(&window_title,
1,
&window_title_property);
/* check the success of the translation. */
if (rc == 0) {
fprintf(stderr, "XStringListToTextProperty - out of memory\n");
exit(1);
}
XStringListToTextProperty()
function accepts an array of C
strings, a count of the number of strings in the array (1 in our case), and
a pointer to an XTextProperty
variable. It concatenates the
list of strings and places it in the XTextProperty
variable.
It returns a non-zero value on success, or 0 on failure (e.g. not enough
memory to perform the operation).
The first thing would be to set the name for our window. This is done
using the XSetWMName()
function. This name may be used
by the window manager as the title of the window (in the title bar), in
a task list, etc. This function accepts 3 parameters: a pointer to the display,
a window handle, and an XTextProperty
containing the desired
title. Here is how it is used:
/* assume that window_title_property is our XTextProperty, and is */
/* defined to contain the desired window title. */
XSetWMName(display, win, &window_title_property);
in order to set the name of the iconized version of our window, we will use
the XSetWMIconName()
function in a similar way:
/* this time we assume that icon_name_property is an initialized */
/* XTextProperty variable containing the desired icon name. */
XSetWMIconName(display, win, &icon_name_property);
In various cases, we wish to let the window manager know that we want to
have a given size for our window, and to only let the user resize our window
in given quantities. For example, in a terminal application (like xterm),
we want our window to always contain complete rows and columns, so the text we
display won't be cut off in the middle. In other cases we do not want our
window to be resized at all (like in many dialog boxes), etc. We can
relay this information to the window manager, although it may simply ignore
our request. We need to first create a data structure to hold the information,
fill it with proper data, and use the XSetWMNormalHints()
function.
Here is how this may be done:
/* pointer to the size hints structure. */
XSizeHints* win_size_hints = XAllocSizeHints();
if (!win_size_hints) {
fprintf(stderr, "XAllocSizeHints - out of memory\n");
exit(1);
}
/* initialize the structure appropriately. */
/* first, specify which size hints we want to fill in. */
/* in our case - setting the minimal size as well as the initial size. */
win_size_hints->flags = PSize | PMinSize;
/* next, specify the desired limits. */
/* in our case - make the window's size at least 300x200 pixels. */
/* and make its initial size 400x250. */
win_size_hints->min_width = 300;
win_size_hints->min_height = 200;
win_size_hints->base_width = 400;
win_size_hints->base_height = 250;
/* pass the size hints to the window manager. */
XSetWMNormalHints(display, win, win_size_hints);
/* finally, we can free the size hints structure. */
XFree(win_size_hints);
There are some other window manager hints that may be set using the
XSetWMHints()
function. This function uses a XWMHints
structure to pass the data to the window manager. Here is how it
may be used:
/* pointer to the WM hints structure. */
XWMHints* win_hints = XAllocWMHints();
if (!win_hints) {
fprintf(stderr, "XAllocWMHints - out of memory\n");
exit(1);
}
/* initialize the structure appropriately. */
/* first, specify which hints we want to fill in. */
/* in our case - setting the state hint as well as the icon position hint. */
win_hints->flags = StateHint | IconPositionHint;
/* next, specify the desired hints data. */
/* in our case - make the window's initial state be iconized, */
/* and set the icon position to the top-left part of the screen. */
win_hints->initial_state = IconicState;
win_hints->icon_x = 0;
win_hints->icon_y = 0;
/* pass the hints to the window manager. */
XSetWMHints(display, win, win_hints);
/* finally, we can free the WM hints structure. */
XFree(win_hints);
In order to set an icon to be used by the window manager, when a user
iconifies our application, we use the XSetWMHints()
function
mentioned above. However, we first need to create a pixmap that contains
the icon's data. Pixmaps are the way that X servers manipulate images,
and will be explained fully later in this tutorial.
For now, we'll just show you how to set the icon for your application.
We assume you got a bitmap file representing this icon, stored using an
X bitmap format. An example of such a file is the
"icon.bmp" file that comes with this tutorial.
Anyway, here is the code:
/* include the definition of the bitmap in our program. */
#include "icon.bmp";
/* pointer to the WM hints structure. */
XWMHints* win_hints;
/* load the given bitmap data and create an X pixmap containing it. */
Pixmap icon_pixmap = XCreateBitmapFromData(display,
win,
icon_bitmap_bits,
icon_bitmap_width,
icon_bitmap_height);
if (!icon_pixmap) {
fprintf(stderr, "XCreateBitmapFromData - error creating pixmap\n");
exit(1);
}
/* allocate a WM hints structure. */
win_hints = XAllocWMHints();
if (!win_hints) {
fprintf(stderr, "XAllocWMHints - out of memory\n");
exit(1);
}
/* initialize the structure appropriately. */
/* first, specify which size hints we want to fill in. */
/* in our case - setting the icon's pixmap. */
win_hints->flags = IconPixmapHint;
/* next, specify the desired hints data. */
/* in our case - supply the icon's desired pixmap. */
win_hints->icon_pixmap = icon_pixmap;
/* pass the hints to the window manager. */
XSetWMHints(display, win, win_hints);
/* finally, we can free the WM hints structure. */
XFree(win_hints);
To conclude this section, we supply a simple-wm-hints.c, that creates a window, sets the window manager hints shown above, and runs into a very simple event loop, allowing the user to play with the window and see how these hints affect the behavior of the application. Try playing with the various hints, and try to run the program under different window managers, to see the differences in its behavior under each of them. This will teach you something about X programs portability.
One more thing we can do to our windows is manipulate them on the screen - resize them, move them, raise or lower them, iconify them and so on. A set of window operations functions are supplied by Xlib for this purpose.
The first pair of operations we can apply on a window is mapping it, or un-mapping it. Mapping a window causes the window to appear on the screen, as we have seen in our simple window program example. UN-mapping it causes it to be removed from the screen (although the window as a logical entity still exists). This gives the effect of making a window hidden (unmapped) and shown again (mapped). For example, if we have a dialog box window in our program, instead of creating it every time the user asks to open it, we can create the window once, in an unmapped mode, and when the user asks to open it, we simply map the window on the screen. When the user clicked the 'OK' or 'Cancel' button, we simply UN-map the window. This is much faster than creating and destroying the window, however, the cost is wasted resources, both on the client side, and on the X server side.
You will remember That the map operation can be done using the
XMapWindow()
operation. The UN-mapping operation can be done using
the XUnmapWindow()
operation. Here is how to apply them:
/* make the window actually appear on the screen. */
XMapWindow(display, win);
/* make the window hidden. */
XUnmapWindow(display, win);
The mapping operation will cause an Expose
event to be sent to
our application, unless the window is completely covered by other windows.
Another operation we might want to do with windows is to move them to
a different location. This can be done using the XMoveWindow()
function. It will accept the new coordinates of the window, in the
same fashion that XCreateSimpleWindow()
got them when the
window was created. The function is invoked like this:
/* move the window to coordinates x=400 and y=100. */
XMoveWindow(display, win, 400, 100);
Note that when the window is moved, it might get partially exposed or
partially hidden by other windows, and thus we might get Expose
events due to this operation.
Yet another operation we can do is to change the size of a window. This
is done using the XResizeWindow()
function:
/* resize the window to width=200 and height=300 pixels. */
XResizeWindow(display, win, 200, 300);
We can also combine the move and resize operations using the single
XMoveResizeWindow()
function:
/* move the window to location x=20 y=30, and change its size */
/* to width=100 and height=150 pixels. */
XMoveResizeWindow(display, win, 20, 30, 100, 150);
Until now we changed properties of a single window. We'll see that there are properties that relate to the window and other windows. One of them is the stacking order. That is, the order in which the windows are layered on top of each other. the front-most window is said to be on the top of the stack, while the back-most window is at the bottom of the stack. Here is how we manipulate our windows stacking order:
/* move the given window to the top of the stack. */
XRaiseWindow(display, win1);
/* move the given window to the bottom of the stack. */
XLowerWindow(display, win1);
One last operation we will show here is the ability to change a window
into an iconified mode and vice versa. This is done using the
XIconifyWindow()
function - to iconify the window,
and the XMapWindow()
to de-iconify it. To understand why there
is no inverse function for XIconifyWindow()
, we must realize that
when a window is iconified, what actually happens is that the window is being
unmapped, and instead its icon window is being mapped. Thus, to make the
original window reappear, we simply need to map it again. The icon is indeed
another window that is simply related strongly to our normal window - it is not
a different state of our window. Here is how to iconify a window and then
de-iconify it:
/* iconify our window. Make its icon window appear on the same */
/* screen as our window (assuming we created our window on the */
/* default screen). */
XIconifyWindow(display, win, DefaultScreen(display));
/* de-iconify our window. the icon window will be automatically */
/* unmapped by this operation. */
XMapWindow(display, win);
Just like we can set various attributes of our windows, we can also ask
the X server supply the current values of these attributes. For
example, we can check where a window is located on screen, what is its current
size, whether it is mapped or not, etc. The XGetWindowAttributes()
function may be used to get this information. Here is how it is used:
/* this variable will contain the attributes of the window. */
XWindowAttributes win_attr;
/* query the window's attributes. */
Status rc = XGetWindowAttributes(display, win, &win_attr);
XWindowAttributes
structure contains many fields - here
are some of them:
int x, y;
int width, height;
int border_width
Window root;
XQueryTree()
and
XTranslateCoordinates()
. These two functions do more than
we need, so we will concentrate on the relevant information only.
/* these variables will eventually hold the translated coordinates. */
int screen_x, screen_y;
/* this variable is here simply because it's needed by the */
/* XTranslateCoordinates function below. For its purpose, see the */
/* manual page. */
Window child_win;
/* this variable will store the ID of the parent window of our window. */
Window parent_win;
/* this variable will store the ID of the root window of the screen */
/* our window is mapped on. */
Window root_win;
/* this variable will store an array of IDs of the child windows of */
/* our window. */
Window* child_windows;
/* and this one will store the number of child windows our window has. */
int num_child_windows;
/* finally, make the query for the above values. */
XQueryTree(display, win,
&root_win,
&parent_win,
&child_windows, &num_child_windows);
/* we need to free the list of child IDs, as it was dynamically allocated */
/* by the XQueryTree function. */
XFree(child_windows);
/* next, we make the coordinates translation, from the coordinates system */
/* of the parent window, to the coordinates system of the root window, */
/* which happens to be the same as that of the screen, since the root */
/* window always spans the entire screen size. */
/* the 'x' and 'y' values are those previously returned by the */
/* XGetWindowAttributes function. */
XTranslateCoordinates(display, parent_win, root_win,
win_attr.x, win_attr.y, &screen_x, &screen_y,
&child_win);
/* at this point, screen_x and screen_y contain the location of our original */
/* window, using screen coordinates. */
As an example of how these operations all work, check out our window-operations.c program.
Up until now, all our painting operations were done using black and white. We will (finally) see now how to draw using colors.
In the beginning, there were not enough colors. Screen controllers could only support a limited number of colors simultaneously (16 initially, and then 256). Because of this, an application could not just ask to draw in a "light purple-red" color, and expect that color to be available. Each application allocated the colors it needed, and when all 16 or 256 color entries were in use, the next color allocation would fail.
Thus, the notion of "a color map" was introduced. A color map is a table whose size is the same as the number of simultaneous colors a given screen controller. Each entry contained the RGB (Red, Green and Blue) values of a different color (all colors can be drawn using some combination of red, green and blue). When an application wants to draw on the screen, it does not specify which color to use. Rather, it specifies which color entry of some color map to be used during this drawing. Change the value in this color map entry - and the drawing will use a different color.
In order to be able to draw using colors that got something to do with what the programmer intended, color map allocation functions were supplied. You could ask to allocate a color map entry for a color with a set of RGB values. If one already existed, you'd get its index in the table. If none existed, and the table was not full, a new cell would be allocated to contain the given RGB values, and its index returned. If the table was full, the procedure would fail. You could then ask to get a color map entry with a color that is closest to the one you were asking for. This would mean that the actual drawing on the screen would be done using colors similar to what you wanted, but not the same.
On today's more modern screens, where one runs an X server with support for 1 million colors, this limitation looks a little silly, but remember that there are still older computers with older graphics cards out there. Using color maps, support for these screens becomes transparent to you. On a display supporting 1 million colors, any color entry allocation request would succeed. On a display supporting a limited number of colors, some color allocation requests would return similar colors. It won't look as good, but your application would still work.
When you draw using Xlib, you can choose to use the standard color map of the screen your window is displayed on, or you can allocate a new color map and apply it to a window. In the latter case, each time the mouse moves onto your window, the screen color map will be replaced by your window's color map, and you'll see all the other windows on screen change their colors into something quite bizzar. In fact, this is the effect you get with X applications that use the "-install" command-line option.
In order to access the screen's standard color map, the
DefaultColormap
macro is defined:
Colormap screen_colormap = DefaultColormap(display, DefaultScreen(display));
this will return a handle for the color map used by default on the first
screen (again, remember that an X server may support several different screens,
each of which might have its own resources).
The other option, that of allocating a new color map, works as follows:
/* first, find the default visual for our screen. */
Visual* default_visual = DefaultVisual(display, DefaultScreen(display));
/* this creates a new color map. the number of color entries in this map */
/* is determined by the number of colors supported on the given screen. */
Colormap my_colormap = XCreateColormap(display,
win,
default_visual,
AllocNone);
Note that the window parameter is only used to allow the X server to create
the color map for the given screen. We can then use this color map for any
window drawn on the same screen.
Once we got access to some color map, we can start allocating colors.
This is done using the XAllocNamedColor()
and
XAllocColor()
functions. The first XAllocNamedColor()
accepts a color name (e.g. "red", "blue", "brown" and so on) and allocates
the the closest color that can be actually drawn on the screen. The
XAllocColor()
accepts an RGB color, and allocates the closest
color that can be drawn on the screen. Both functions use the
XColor
structure, that has the following relevant fields:
unsigned long pixel
unsigned short red
unsigned short green
unsigned short blue
Here is an example of using these functions:
/* this structure will store the color data actually allocated for us. */
XColor system_color_1, system_color_2;
/* this structure will store the exact RGB values of the named color. */
/* it might be different from what was actually allocated. */
XColor exact_color;
/* allocate a "red" color map entry. */
Status rc = XAllocNamedColor(display,
screen_colormap,
"red",
&system_color_1,
&exact_color);
/* make sure the allocation succeeded. */
if (rc == 0) {
fprintf(stderr,
"XAllocNamedColor - allocation of 'red' color failed.\n");
}
else {
printf("Color entry for 'red' - allocated as (%d,%d,%d) in RGB values.\n",
system_color_1.red, system_color_1.green, system_color_1.blue);
}
/* allocate a color with values (30000, 10000, 0) in RGB. */
system_color_2.red = 30000;
system_color_2.green = 10000;
system_color_2.blue = 0;
Status rc = XAllocColor(display,
screen_colormap,
&system_color_2);
/* make sure the allocation succeeded. */
if (rc == 0) {
fprintf(stderr,
"XAllocColor - allocation of (30000,10000,0) color failed.\n");
}
else {
/* do something with the allocated color... */
.
.
}
After we have allocated the desired colors, we can use them when drawing
text or graphics. To do that, we need to set these colors as the foreground
and background colors of some GC (Graphics Context), and then use this GC
to make the drawing. This is done using the XSetForeground()
and XSetBackground()
functions, as follows:
/* use the previously defined colors as the foreground and background */
/* colors for drawing using the given GC. assume my_gc is a handle to */
/* a previously allocated GC. */
XSetForeground(display, my_gc, screen_color_1.pixel);
XSetForeground(display, my_gc, screen_color_2.pixel);
As you see, this is rather simple. The actual drawing is done using the
same functions we have seen earlier. Note that in order to draw using many
different colors, we can do one of two things. We can either change the
foreground and/or background colors of the GC before any drawing function,
or we can use several different GCs to draw in different colors. The decision
as of which option to use is yours. Note that allocating many GCs will use
more resources of the X server, but this will sometime lead to more compact
code, and will might it easier to replace the drawn colors.
As an example to drawing using colors, look at the color-drawing.c program. This is a copy of the simple-drawing.c program, except that here we also allocate colors and use them to paint the rainbow...
One thing many so-called "Multi-Media" applications need to do, is display images. In the X world, this is done using bitmaps and pixmaps. We have already seen some usage of them when setting an icon for our application. Lets study them further, and see how to draw these images inside a window, along side the simple graphics and text we have seen so far.
One thing to note before delving further, is that Xlib supplies no means of manipulating popular image formats, such as gif, jpeg or tiff. It is left up to the programmer (or to higher level graphics libraries) to translate these image formats into formats that the X server is familiar with - x bitmaps, and x pixmaps.
An X bitmap is a two-color image stored in a format specific to the X window system. When stored in a file, the bitmap data looks like a C source file. It contains variables defining the width and height of the bitmap, an array containing the bit values of the bitmap (the size of the array = width * height), and an optional hot-spot location (will be explained later, when discussing mouse cursors).
A X pixmap is a format used to store images in the memory of an X server. This format can store both black and white images (such as x bitmaps) as well as color images. It is the only image format supported by the X protocol, and any image to be drawn on screen, should be first translated into this format.
In actuality, an X pixmap can be thought of as a window that does not
appear on the screen. Many graphics operations that work on windows, will
also work on pixmaps - just supply the pixmap ID instead of a window ID.
In fact, if you check the manual pages, you will see that all these
functions accept a 'Drawable', not a 'Window'. since both windows and pixmaps
are drawables, they both can be used to "draw on" using functions such
as XDrawArc()
, XDrawText()
, etc.
We have already seen how to load a bitmap from a file to memory, when we demonstrated setting an icon for an application. The method we showed earlier required the inclusion of the bitmap file in our program, using the C pre-processor '#include' directive. We will see here how we can access the file directly.
/* this variable will contain the ID of the newly created pixmap. */
Pixmap bitmap;
/* these variables will contain the dimensions of the loaded bitmap. */
unsigned int bitmap_width, bitmap_height;
/* these variables will contain the location of the hot-spot of the */
/* loaded bitmap. */
int hotspot_x, hotspot_y;
/* this variable will contain the ID of the root window of the screen */
/* for which we want the pixmap to be created. */
Window root_win = DefaultRootWindow(display);
/* load the bitmap found in the file "icon.bmp", create a pixmap */
/* containing its data in the server, and put its ID in the 'bitmap' */
/* variable. */
int rc = XReadBitmapFile(display, root_win,
"icon.bmp",
&bitmap_width, &bitmap_height,
&bitmap,
&hotspot_x, &hotspot_y);
/* check for failure or success. */
switch (rc) {
case BitmapOpenFailed:
fprintf(stderr, "XReadBitmapFile - could not open file 'icon.bmp'.\n");
break;
case BitmapFileInvalid:
fprintf(stderr,
"XReadBitmapFile - file '%s' doesn't contain a valid bitmap.\n",
"icon.bmp");
break;
case BitmapNoMemory:
fprintf(stderr, "XReadBitmapFile - not enough memory.\n");
break;
case BitmapSuccess:
/* bitmap loaded successfully - do something with it... */
.
.
break;
}
Once we got a handle to the pixmap generated from a bitmap, we can draw it
on some window, using the XCopyPlane()
function. This function
allows us to specify what drawable (a window, or even another pixmap) to draw
the given pixmap onto, and at what location in that drawable.
/* draw the previously loaded bitmap on the given window, at location */
/* 'x=100, y=50' in that window. we want to copy the whole bitmap, so */
/* we specify location 'x=0, y=0' of the bitmap to start the copy from, */
/* and the full size of the bitmap, to specify how much of it to copy. */
XCopyPlane(display, bitmap, win, gc,
0, 0,
bitmap_width, bitmap_height,
100, 50,
1);
As you can see, we could also copy a given rectangle of the pixmap, instead of
the whole pixmap. Also note the last parameter to the XCopyPlane
function (the '1' at the end). This parameter specifies which plane of the
source image we want to copy to the target window. For bitmaps, we always copy
plane number 1. This will become clearer when we discuss color depths below.
Sometimes we want to create an un-initialized pixmap, so we can later draw into it. This is useful for image drawing programs (creating a new empty canvas will cause the creation of a new pixmap on which the drawing can be stored). It is also useful when reading various image formats - we load the image data into memory, cerate a pixmap on the server, and then draw the decoded image data onto that pixmap.
/* this variable will store the handle of the newly created pixmap. */
Pixmap pixmap;
/* this variable will contain the ID of the root window of the screen */
/* for which we want the pixmap to be created. */
Window root_win = DefaultRootWindow(display);
/* this variable will contain the color depth of the pixmap to create. */
/* this 'depth' specifies the number of bits used to represent a color */
/* index in the color map. the number of colors is 2 to the power of */
/* this depth. */
int depth = DefaultDepth(display, DefaultScreen(display));
/* create a new pixmap, with a width of 30 pixels, and height of 40 pixels. */
pixmap = XCreatePixmap(display, root_win, 30, 40, depth);
/* just for fun, draw a pixel in the middle of this pixmap. */
XDrawPoint(display, pixmap, gc, 15, 20);
Once we got a handle to pixmap, we can draw it on some window, using the
XCopyArea()
function. This function allows us to specify
what drawable (a window, or even another pixmap) to draw the given pixmap
onto, and at what location in that drawable.
/* draw the previously loaded bitmap on the given window, at location */
/* 'x=100, y=50' in that window. we want to copy the whole bitmap, so */
/* we specify location 'x=0, y=0' of the bitmap to start the copy from, */
/* and the full size of the bitmap, to specify how much of it to copy. */
XCopyArea(display, bitmap, win, gc,
0, 0,
bitmap_width, bitmap_height,
100, 50);
As you can see, we could also copy a given rectangle of the pixmap, instead of
the whole pixmap.
One important note should be made - it is possible to create pixmaps of
different depths on the same screen. When we perform copy operations (a
pixmap onto a window, etc), we should make sure that both source and target
have the same depth. If they have a different depth, the operation would fail.
The exception to this is if we copy a specific bit plane of the source pixmap,
using the XCopyPlane()
function shown earlier. In such an event,
we can copy a specific plain to the target window - in actuality, setting
a specific bit in the color of each pixel copied. This can be used to
generate strange graphic effects in window, but is beyond the scope of out
tutorial.
Finally, when we are done using a given pixmap, we should free it, in order
to free resources of the X server. This is done using the
XFreePixmap()
function:
/* free the pixmap with the given ID. */
XFreePixmap(display, pixmap);
After freeing a pixmap - we must not try accessing it again.
To summarize this section, take a look at the draw-pixmap.c program, to see a pixmap being created using a bitmap file, and then tiled up on a window on your screen.
Often we see programs that modify the shape of the mouse pointer (also called the X pointer) when in certain states. For example, a busy application would often display a sand clock over its main window, to give the user a visual hint that they should wait. Without such a visual hint, the user might think that the application got stuck. Lets see how we can change the mouse cursor for our windows.
There are two methods for creating cursors. One of them is by using a set of pre-defined cursors, that are supplied by Xlib. The other is by using user-supplied bitmaps.
In the first method, we use a special font named "cursor", and the
function XCreateFontCursor()
. This function accepts
a shape identifier, and returns a handle to the generated cursor. The
list of allowed font identifiers is found in the include file
<X11/cursorfont.h>. Here are a few such cursors:
XC_arrow
XC_pencil
XC_watch
#include <X11/cursorfont.h> /* defines XC_watch, etc. */
/* this variable will hold the handle of the newly created cursor. */
Cursor watch_cursor;
/* create a sand watch cursor. */
watch_cursor = XCreateFontCursor(display, XC_watch);
The other methods of creating a cursor is by using a pair of pixmaps with
depth of one (that is, two color pixmaps). One pixmap defines the shape of
the cursor, while the other works as a mask, specifying which pixels of the
cursor will be actually drawn. The rest of the pixels will be transparent.
Creating such a cursor is done using the XCreatePixmapCursor()
function. As an example, we will create a cursor using the "icon.bmp" bitmap.
We will assume that it was already loaded into memory, and turned into a
pixmap, and its handle is stored in the 'bitmap' variable. We will want it
to be fully transparent. That is, only the parts of it that are black will
be drawn, while the white parts will be transparent. To achieve this effect,
we will use the icon both as the cursor pixmap and as the mask pixmap.
Try to figure out why...
/* this variable will hold the handle of the newly created cursor. */
Cursor icon_cursor;
/* first, we need to define foreground and background colors for the cursor. */
XColor cursor_fg, cursor_bg;
/* access the default color map of our screen. */
Colormap screen_colormap = DefaultColormap(display, DefaultScreen(display));
/* allocate black and while colors. */
Status rc = XAllocNamedColor(display,
screen_colormap,
"black",
&cursor_fg,
&cursor_fg);
if (rc == 0) {
fprintf(stderr, "XAllocNamedColor - cannot allocate 'black' ??!!??\n");
exit(1);
}
Status rc = XAllocNamedColor(display,
screen_colormap,
"white",
&cursor_bg,
&cursor_bg);
if (rc == 0) {
fprintf(stderr, "XAllocNamedColor - cannot allocate 'white' ??!!??\n");
exit(1);
}
/* finally, generate the cursor. make the 'hot spot' be close to the */
/* top-left corner of the cursor - location (x=5, y=4). */
icon_cursor = XCreatePixmapCursor(display, bitmap, bitmap,
&cursor_fg, &cursor_bg,
5, 4);
Finally, when we are done with a cursor and no longer need it, we can
release it using the XFreeCursor()
function:
XFreeCursor(display, icon_cursor);
After we have created a cursor, we can tell the X server to attach this
cursor to any of our windows. This is done using the
XDefineCursor()
, and causes the X server to change the mouse
pointer to the shape of that cursor, each time the mouse pointer moves into
and across that window. We can later detach this cursor from our window using
the XUndefineCursor()
function. This will cause the default cursor
to be shown when the mouse enter that windows.
/* attach the icon cursor to our window. */
XDefineCursor(display, win, icon_cursor);
/* detach the icon cursor from our window. */
XUndefineCursor(display, win);
As an example, look at our cursor.c program, and see how mouse cursors are set, changed and removed. Run the program, place the mouse pointer over the created window, and watch.
[LUPG Home] [Tutorials] [Related Material] [Essays] [Project Ideas] [Send Comments]
This document is copyright (c) 1999-2002 by guy keren.
The material in this document is provided AS IS, without any
expressed or implied warranty, or claim of fitness for a
particular purpose. Neither the author nor any contributers shell
be liable for any damages incured directly or indirectly by using
the material contained in this document.
permission to copy this document (electronically or on paper, for
personal or organization internal use) or publish it on-line is
hereby granted, provided that the document is copied as-is, this
copyright notice is preserved, and a link to the original document
is written in the document's body, or in the page linking to the
copy of this document.
Permission to make translations of this document is also granted,
under these terms - assuming the translation preserves the meaning
of the text, the copyright notice is preserved as-is, and a link
to the original document is written in the document's body, or in
the page linking to the copy of this document.
For any questions about the document and its license, please
contact the author.