Here is something interesting to try: put *depth: 3
in your
.Xdefaults
file, and run your favorite X program. Next, you might
want to try *visual: DirectColor
.
If the program uses widgets you probably received an error:
X Error of failed request: BadMatch (invalid parameter attributes) Major opcode of failed request: 1 (X_CreateWindow) Serial number of failed request: 23 Current serial number in output stream: 31
While these examples may seem odd, the same thing happens for most X programs even if you used a display capable of a 3 bit depth, or one capable of using a DirectColor visual class, if that depth or that visual was not the default of the server. (A more likely scenario is trying to use *depth: 24 on a 24 bit capable display with a default 8 bit visual, or perhaps *depth: 8 on a 24 bit capable display with a default 24 bit visual.) In any case, most Xt programs fail to deal with non-default visuals.
BadMatch
error results. First, the depth must match the visual's depth. Second, the visual must be the same visual used to create the colormap. To meet these requirements we must carefully pick our depth, visual and colormap. Since one of the attributes of a visual is its depth, when we choose a visual we also choose a depth. A colormap is created with a visual, so once we have our visual we can easily get a compatible colormap. The real obstacle is picking an appropriate visual to suit our needs.
At initialization, each supported visual is given a unique ID on the display.
(To check which visuals your display supports you can use the xdpyinfo
program.)
One of the visuals is chosen as the default, and used when the root window is
created. We can choose our visual by ID, or, given a visual class and/or depth,
we can try to find an appropriate match. There are two X calls we use to obtain
information about supported visuals -- XMatchVisualInfo() and XGetVisualInfo().
XMatchVisualInfo() takes depth and visual class parameters, and if successful, returns an XVisualInfo **, a pointer to a structure containing a visual's attributes. XGetVisualInfo() takes a mask and template and returns a list of all matching visuals the display supports If our selected visual is not the same visual used by the root window a colormap must be created with that visual. Once we have the depth, visual and colormap we can create a window.
To create a window, call either XCreateWindow() or XCreateSimpleWindow(). One of the main differences between XCreateWindow() and XCreateSimpleWindow() is XCreateSimpleWindow() uses CopyFromParent for the depth, colormap and visual. XCreateWindow() requires the depth, visual and colormap as parameters, or, instead of actual values, CopyFromParent may still be used. CopyFromParent means the value should be the same value used to create the parent window. For the top window in your hierarchy (or your shell) CopyFromParent takes the value from the root window (set when the X server was initialized). For a shell to use a non-default visual we cannot use CopyFromParent for the depth, visual and colormap, we will need to specify the values.
The default converter for the visual resource checks the value against the six visual classes (StaticGray, GrayScale, StaticColor, PseudoColor, DirectColor, or TrueColor). If the class is a valid value (converted to an integer between 0 and 5 inclusive), XMatchVisualInfo() is called, and if it succeeds, returns the visual ID. If more than one visual with the same class exists, the one with the widget's depth is returned. If there are multiple visuals with that class and depth, it is undefined which one will be returned. The colormap converter does a string to integer conversion, and may or may not return a valid colormap ID.
When a shell widget is created, the core initialize method is called first, and the depth and colormap are set. (In the intrinsics, initialize procedures are called from super-to-subclass order.) Next, the shell initialize procedure is called. Unfortunately no check is made to ensure that the visual, depth and colormap are correctly matched. Since a visual is needed to create a colormap, and a colormap has already been chosen, a non-default visual will likely cause a BadMatch error when the window is realized.
*depth: 24in a resource file. If a 24 bit visual does not exist, even if the shell's depth is set correctly, the depth of any child widgets is still change from CopyFromParent to 24 and a BadMatch occurs. Using XtNapplicationDepth allows us to reserve setting the depth programmatically and avoid BatMatch errors.
The algorithm is as follows:
*applicationDepth: 24
and know that if a 24 bit visual ever exists, it will be found and used.
The first thing to do is to add to our application resources:
typedef struct _OurResourceStruct { int visualID; int applicationDepth; int visualClass; Boolean usePrivateColormap; } OurResourceStruct, *OurResourcePtr; OurResourceStruct ourResources; #define UNDEFINED_DEFAULT -1 #define TheOffset(a) XtOffset(OurResourcePtr, (a)) static XtResource AppResources[] = { {XtNvisualID, XtCVisualID, XtRInt, sizeof(int), TheOffset(visualID), XtRImmediate, (XtPointer)UNDEFINED_DEFAULT}, {XtNapplicationDepth, XtCApplicationDepth, XtRInt, sizeof(int), TheOffset(applicationDepth), XtRImmediate, (XtPointer)UNDEFINED_DEFAULT}, {XtNvisualClass, XtCVisualClass, XtRVisualClass, sizeof(int), TheOffset(visualClass), XtRImmediate, (XtPointer)UNDEFINED_DEFAULT}, {XtNusePrivateColormap, XtCUsePrivateColormap, XtRBoolean, sizeof(Boolean), TheOffset(usePrivateColormap), XtRImmediate, (XtPointer)FALSE}, };
Next we need to call XtGetApplicationResources() at startup to obtain their values. However the first argument to XtGetApplicationResources() is a widget, so what do we use? The answer, unfortunately, is that we need a mini-hack. First we create a shell, but don't realize it. (Because the window is not created until the realize stage, as long as you don't realize the widget, there is no chance of a BadMatch since XCreateWindow() has not been called.)
This allows us to get the resource values and modify them if necessary. We now come to the hack, and we have two choices. Once we have our modified resources we could destroy the shell and create a new shell using the new values. Or, we can set the XtNbackground and XtNborderPixel values to 0, a valid entry in any colormap, with XtSetValues(), and at the same time set our new resource values. This is not as bad as it may seem. 99 percent of the time a shell widget has a child using its entire window space making the shell's background color irrelevant. The border pixel color, while possibly useful, is easily traded for not having to destroy and create a new shell. My choice then is to use the second option.
shell = XtAppCreateShell(name, className, applicationShellWidgetClass, display, NULL, 0); XtGetApplicationResources(shell, &ourResources, AppResources, XtNumber(AppResources), NULL, 0);
After XtGetApplicationResources() is called, we have all the information we need to step through our algorithm.
success = FALSE; if (ourResources.visualID != UNDEFINED_DEFAULT) { XVisualInfo *vtemp; int vitems; vtemp.visualid = ourResources.visualID; vinfos = XGetVisualInfo(display, VisualIDMask, &vtemp, &vitems); if (vinfos != NULL) { /* ** Better only be one match! */ theVisual = vinfos[0].visual; theApplicationDepth = vinfos[0].depth; theVisualClass = vinfos[0].class; XFree(vinfos); success = TRUE; } }
screen = DefaultScreen(display); if (!success) { /* Step 2 */ if ( (ourResources.applicationDepth == UNDEFINED_DEFAULT) && (ourResources.visualClass == UNDEFINED_DEFAULT)) { theVisual = DefaultVisual(display, screen); theApplicationDepth = DefaultDepth(display, screen); theVisualClass = theVisual->class; } else { /* Step 3 */ if (ourResources.applicationDepth == UNDEFINED_DEFAULT) theApplicationDepth = DefaultDepth(display, screen); else theApplicationDepth = ourResources.applicationDepth; if (ourResources.visualClass == UNDEFINED_DEFAULT) theVisualClass = DefaultVisual(display, screen)->class; else theVisualClass = ourResources.visualClass; if (XMatchVisualInfo(display, screen, theApplicationDepth, theVisualClass, &theVisualInfo) != 0) { theVisual = theVisualInfo.visual; theApplicationDepth = theVisualInfo.depth; theVisualClass = theVisualInfo.class; } else { /* Step 4 */ XVisualInfo *visTemplate; XVisualInfo **visReturn; int numVis, n; visReturn = (XVisualInfo **)NULL; n = 0; /* ** See if we can find a visual at the depth they ask for. */ if (ourResources.applicationDepth != UNDEFINED_DEFAULT) { visTemplate.depth = ourResources.applicationDepth; visReturn = XGetVisualInfo(display, VisualDepthMask, &visTemplate, &numVis); /* ** If numVis > 1 you may want to have it pick your favorite visual. This is not ** necessary since the user still has finer control by setting XtNvisualClass ** or XtNvisualID. */ /* Step 5 */ if (visReturn != (XVisualInfo **)NULL) { visTemplate.class = theVisualClass; visReturn = XGetVisualInfo(display, VisualClassMask, &visTemplate, &numVis); if (visReturn != (XVisualInfo **)NULL) { int i; for (i=1;ivisReturn[n].depth) n = i; } } /* Step 6 */ if (visReturn == (XVisualInfo **)NULL) { theVisual = DefaultVisual(display, screen); theApplicationDepth = DefaultDepth(display, screen); theVisualClass = theVisual->class; } else { theVisual = visReturn[n].visual; theApplicationDepth = visReturn[n].depth; theVisualClass = visReturn[n].class; } } } } }
Finally, if the visual we end up with is not the default visual, or XtNusePrivateColormap is TRUE, create a private colormap. The resource XtNusePrivateColormap is necessary in case the default visual uses a read-write colormap. It is possible a user or programmer may want a colormap of the same class and depth as the default colormap, except with all the cells unallocated. Imagine an 8 bit PseudoColor default visual, and a background image that uses 255 colors. If you do not want your application to run in black and white when XAllocColor() fails, you need to create your own colormap.
if ( (theVisual->visualid == DefaultVisual(display, screen)->visualid) && !ourResources.usePrivateColormap) theColormap = DefaultColormap(display, screen); else theColormap = XCreateColormap(display, RootWindow(display, screen), theVisual, AllocNone);
Finally we use XtSetValues() to change the values in our shell.
n = 0; XtSetArg(warg[n], XtNdepth, theApplicationDepth); n++; XtSetArg(warg[n], XtNvisual, theVisualDepth); n++; XtSetArg(warg[n], XtNcolormap, theColormap); n++; XtSetArg(warg[n], XtNbackground, 0); n++; XtSetArg(warg[n], XtNborderPixel, 0); n++; XtSetValues(shell, warg, n);
Subsequent toplevel shells can be quickly initialized by setting the Arg list at creation.
The last problem is to make subshells use the same visual, depth and colormap as the toplevel shells. Remember, a shell's parent window is the root window with CopyFromParent as the default value for the visual, depth and colormap. To avoid making our values global we introduce the following function:
Widget XtGetShell(Widget _w) { while(_w && !XtIsShell(_w)) _w = XtParent(_w); return _w; }
Now when creating a subshell, use XtGetShell() on the subshell's parent widget. This will return the supershell that you can use XtGetValues() on to obtain the depth, visual and colormap. Set these in the Arg list of the popup at creation time. Matching sub- and supershells alleviates much of the colormap flashing for menus and dialogs. Our application can now exist beyond the default visual. Better yet, it does this under resource control.
First we take advantage of the fact that we can change the core instance's depth and colormap any time before the widget is realized (when the window is created). Second, all the appPlusShells share any created colormaps, and have the same visual and depth. While this seems limiting, it has the same effect as the original solution. It would be possible to allow each appPlusShell to have different visuals or separate colormaps, but problems, such as when to share a colormap, arise. In my own programs I have found it to be reasonable to have the entire application under the same visual and colormap.
If we share the visual and colormap, we need a common place to store the information. Since all the instances can access the class part of the appPlusShell, we use that. We initialize the fields in the widget class record, flagging them as being unset. The first time the initialize method is called, the new resource values are checked, and most of the original solution utilized. After we find the appropriate visual, depth and colormap we store these values in the appPlusShell class part of the widget. Each new instance checks the class part to see if the fields are initialized. If the fields are initialized, the values are copied from the class part to the instance fields, otherwise it is the first instance and we must run through our algorithm.
The other two procedures that need to be implemented are the realize method and the destroy method. In the realize method, we need to set the colormap, background_pixel and border_pixel values of the XSetWindowAttributes variable. We use the colormap we have set in the core instance record, and the pixel values are both set to 0.
static void realize(AppPlusShellWidget _apsw, XtValueMask *_xvm, XSetWindowAttributes *_xswa) { _xswa->colormap = _apsw->core.colormap; _xswa->background_pixel = _xswa->border_pixel = 0; *_xvm |= CWColormap | CWBorderPixel | CWBackPixel; (*appPlusShellWidgetClass->core_class.superclass->core_class.realize)((Widget)_apsw, _xvm, _xswa); }
The destroy method need not be implemented. It is possible to reference count our colormap and if we created it, release it when the count is 0. In practice, many users are prone to closing all the appPlusShells and then opening a new one. It is not necessary to go through the initialization routine again, so instead, I won't implement a destroy method. This provides the ability to use the values previously stored in the class part. Anything we have allocated is left to be free'd automatically when the connection to the display is closed.
Now that you have the visual of your choice, how can you use it effectively? In my column, I examine how different visuals can be gathered into a common color model.
In the mean time, the code presented in this article can be found as part of the freely available AppPlusShell package at ftp.x.org:/contrib/widgets/AppPlusS.tar.Z.