Contents Previous Next

17 Interacting With the Window Manager


This chapter provides additional information on the relationship between shell widgets and the Motif window manager (mwm). It discusses shell widget resources and describes how to use functions in the Motif toolkit to add and modify window manager protocols.

This chapter provides technical details about how Motif applications can interact with the window manager. It discusses when and how to interpret special window manager events and client messages, how to set shell resources that act as hints to the window manager, and how to add protocols for communication between the application and the window manager. In the course of the discussion, we cover the major features of the X Toolkit Intrinsics' WMShell widget class, which handles basic window manager communication, and Motif's VendorShell widget, which handles window manager events that are specific to the Motif window manager (mwm).

The material in this chapter is advanced; you should typically not interfere with the predefined interactions between an application and the Motif window manager. When you do so, you risk interfering with the uniform look and feel that is at the heart of a graphical user interface such as Motif. However, the material in this chapter should provide you with an understanding of some important concepts that may allow you to make your applications more robust. This chapter also discusses the use of protocols and client messages for window manager communication. These techniques can be used for communication between instances of the same application or between suites of cooperating applications.

17.1 Interclient Communication

The X Window System is designed so that any user-interface style can be imposed on the display. The X libraries (Xlib and Xt) provide the mechanisms for applications to decide for themselves how to display information and how to react to user-generated actions. It is left up to graphical user interface specifications such as Motif to standardize most of these decisions. However, in order to preserve a baseline of interoperability, there are certain standards that an application must conform to if it is to be considered a "good citizen" of the desktop. These standards are referred to as interclient communication conventions. While X makes no suggestions about the way an application should look or act, it does have a lot to say about how it interacts with other applications on the user's display.

One such convention is that all applications must negotiate the sizes and positions of their windows with the window manager, rather than with one another. The window manager is, in essence, the ultimate ruler of the desktop. While it is mostly benevolent, its primary function is to prevent anarchy on the display. Communication with the window manager has various forms. Applications can talk directly to the window manager, or the window manager may initiate a conversation with an application. When the user selects a item from the window menu or issues other window manager commands, he or she initiates communication between the window manager and the application. Much of the communication between the window manager and the application is carried on in terms of properties and protocols.

A property is an arbitrary-length piece of data associated with a window. It is stored on the server identified by a unique integer value called an Atom. Atoms are used to avoid the overhead of passing property names as arbitrary-length strings. See Volume One, Xlib Programming Manual, and Volume Four, X Toolkit Intrinsics Programming Manual, for a detailed discussion of properties and atoms. An application sets properties on its windows as a way of communicating with the window manager or other applications. Some properties are referred to as "window manager hints" because the window manager doesn't have to obey them. For example, an application can specify the preferred size of its top-level window, but the window manager might use this value only in the absence of any other instructions from the user.

A window manager protocol is an agreed-upon procedure for the exchange of messages between the window manager and an application. Protocols are implemented with ClientMessage events; the window manager sends an event to the application, and the application takes the appropriate action. For example, a protocol exchange occurs when the user selects Close from the window menu to close an application window.

There are low-level Xlib routines for setting and getting the value of window properties. However, the various shell widgets provided by Xt and Motif define resources that access most of the predefined properties of interest in window manager/application interaction. These resources are the preferred interface to window properties.

The WMShell widget defines many of the generic properties that are used for communication with the window manager. For example, you can use WMShell resources to specify an icon pixmap and resize increment values. The VendorShell widget class is defined by Xt as the widget class in which a vendor can define appearance and behavior resources specific to its own window manager. As such, this widget class is customized by every vendor of Xt-compatible toolkits. In the case of Motif, the VendorShell class provides resources that control the layout and operation of the Motif window manager decorations, and it supports the Motif window manager protocols.

You never instantiate WMShell or VendorShell widgets; they exist only as supporting classes for other shells, such as TopLevelShells, ApplicationShells, and DialogShells. However, you frequently need to set WMShell and VendorShell resources on other types of shell widgets. Remember that the MenuShell widget is not a subclass of VendorShell and WMShell, so it does not have the same provisions for window manager interaction. You can use the XtIsVendorShell() macro defined in <X11/Intrinsic.h>, to determine if a widget is a subclass of VendorShell. Similarly, the XtIsWMShell() macro indicates whether or not a widget is a subclass of WMShell. Once you have a handle to a shell widget, you can specify both generic and Motif-resources for it.

17.2 Shell Resources

As discussed in Chapter 3, Overview of the Motif Toolkit, the WMShell widget class handles standard window manager/application communications as established by the Inter-Client Communications Conventions Manual (ICCCM). This document, which can be found in Appendix L of Volume Zero, X Protocol Reference Manual, describes the standards set forth by the X Consortium for all interclient communication. Such conventions are necessary because the window manager and a client application are two separate programs. Applications and window managers need to follow these standards to maintain order in the X world.

To give you an idea of the kinds of properties in which the window manager is interested, shows a partial list of properties that are handled automatically by shells. tab(@), linesize(2); l | l lfCWw(1.5i) | lw(3.5i). Atom@Meaning
_
WM_NAME@T{ The name of the window T} WM_CLASS@T{ The class name of the window T} WM_NORMAL_HINTS@T{ Information about the size of the window T} WM_ICON_NAME@T{ The name of the icon for the window T} WM_HINTS@T{ Information about the icon pixmap, icon position, and input model for the window T}
_ Xlib provides functions for modifying the values of these atoms on a window so that you can change the visual appearance, size, position, or functionality of the window. See Volume Zero, X Protocol Reference Manual, for complete details on the properties that can be set on windows; see Volume One, Xlib Programming Manual , for details on how to set or get these properties. However, the job of the WMShell is to hide this interface from the programmer by providing resources that accomplish the same tasks. The next few sections describe how most of the common resources can be used. While we do not cover all of the WMShell resources here, most of the ones we have omitted are intuitive, so they do not require a great deal of explanation. See the WMShell reference page in Volume Six B, Motif Reference Manual, for a complete list of resources.

17.2.1 Shell Positions

You can position a shell at a specific location on the screen using the XmNx and XmNy resources. In addition, you can set the XmNx and XmNy resources of the immediate child of a shell widget to position the shell. This feature exists because Motif dialogs are designed to make their shell widgets invisible to the programmer. It is typically easier to set these resources directly on the child of a shell, as you are more likely to have a handle to that widget. The following code fragment shows how you can position a MessageDialog in the center of the screen:

   Widget dialog, parent;
   Dimension width, height;
   Screen screen = XtScreen (parent);
   Position x, y;

   dialog = XmCreateMessageDialog (parent, "dialog", NULL, 0);

   /* get width and height of dialog */
   XtVaGetValues (dialog,
       XmNwidth, &width,
       XmNheight, &height,
       NULL);

   /* center the dialog on the screen */
   x = (WidthOfScreen (screen) / 2) - (width / 2);
   y = (HeightOfScreen (screen) / 2) - (height / 2);
   XtVaSetValues (dialog,
       XmNx, x,
       XmNy, y,
       NULL);
You can position a dialog in this way because the Motif BulletinBoard widget passes positional information to its shell parent. See Chapter 5, Introduction to Dialogs, and Chapter 7, Custom Dialogs, for further discussion. In most cases, you shouldn't be setting the XmNx and XmNy resources for a dialog because it is the job of the window manager to position shells. The user can also have some say in how placement should be handled. For example, if the user has set the interactivePlacement resource for mwm to True, he gets to place the window himself when it first appears. If you set the position of the window, then you are interfering with the positioning method preferred by the user.

17.2.2 Shell Sizes

In some situations, an application may want to prevent one of its windows from growing or shrinking beyond certain geometrical limits. For example, an application might want to keep a dialog box from getting so small that some of its elements are clipped. A paint application might want to restrict its top-level window from growing larger than the size of its canvas. An application can also constrain the increments by which the user can interactively resize the window. For example, xterm only allows itself to be resized in character-size increments, where the character size is defined by the font being used.

The WMShell defines the following resources that can be used to constrain the size of a ­window:

   XmNminWidth
   XmNmaxWidth
   XmNminHeight
   XmNmaxHeight
   XmNwidthInc
   XmNheightInc
   XmNbaseWidth
   XmNbaseHeight
The XmNminWidth, XmNmaxWidth, XmNminHeight, and XmNmaxHeight resources specify the minimum and maximum width and height for the shell. The XmNwidthInc and XmNheightInc resources control the pixel incrementals by which the window changes when it is being resized by the user. When mwm provides visual feedback during a resize operation, it specifies the width and height in terms of these increments, rather than pixels. The XmNbaseWidth and XmNbaseHeight resources specify the base values that are used when calculating the preferred size of the shell.

the source code demonstrates incremental resizing. The application displays a shell widget that contains a PushButton. When you click on the button, it displays the size of the window in pixels, but when you resize the window, the mwm feedback window displays the size in terms of XmNwidthInc and XmNheightInc . XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.

   /* resize_shell.c -- demonstrate the max and min heights and widths.
    * This program should be run to really see how mwm displays the
    * size of the window as it is resized.
    */
   #include <Xm/PushB.h>

   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget toplevel, button;
       XtAppContext app;
       extern void getsize();

       XtSetLanguageProc (NULL, NULL, NULL);

       toplevel = XtVaAppInitialize (&app, "Demos",
           NULL, 0, &argc, argv, NULL,
           XmNminWidth,     75,
           XmNminHeight,    25,
           XmNmaxWidth,     150,
           XmNmaxHeight,    100,
           XmNbaseWidth,    5,
           XmNbaseHeight,   5,
           XmNwidthInc,     5,
           XmNheightInc,    5,
           NULL);

       /* Pushbutton's callback prints the dimensions of the shell. */
       button = XtVaCreateManagedWidget ("Print Size",
           xmPushButtonWidgetClass, toplevel, NULL);
       XtAddCallback (button, XmNactivateCallback, getsize, toplevel);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   void
   getsize(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       Widget shell = (Widget) client_data;
       Dimension width, height;

       XtVaGetValues (shell,
           XmNwidth, &width,
           XmNheight, &height,
           NULL);
       printf ("Width = %d, Height = %d0, width, height);
   }
In our example, we arbitrarily specify the minimum and maximum extents of the shell. The width and height increments are each set to five, so the user can only resize the window in five-pixel increments. As the window is resized, the feedback window displays the size according to these incremental units, rather than using pixel values. If you run resize_shell, you can press the PushButton to print the size of the shell in pixels and compare that size with the size reported by the window manager. If you are going to specify the various size resources for a shell, it only makes sense to hard-code the values as we have done here. If you specify the resources in an app-defaults file, the user can override the settings, which defeats the whole point of setting them.

The problem with specifying minimum and maximum extents is that most real applications contain many components whose sizes cannot be computed easily, making it difficult to determine exactly how large or small the window should be. If the fonts and strings for PushButtons, Labels, and ToggleButtons can be set in a resource file, the equation becomes far too difficult to calculate before the window is actually created and displayed. Incremental width and height values are even more difficult to estimate because there are margins, border widths, and other resources to consider.

However, all is not lost. If you need to constrain the size of an application, you should consider whether the application's default initial size can be considered either its maximum or minimum size. If so, you can allow the window to come up using default size and trap for ConfigureNotify events on the shell widget. You can then use the default width and height reported in that event as your minimum or maximum size, as demonstrated in the source code XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.

   /* set_minimum.c -- demonstrate how to set the minimum size of a
    * window to its initial size.  This method is useful if your program
    * is initially displayed at its minimum size, but it would be too
    * difficult to try to calculate ahead of time what the initial size
    * would be.
    */
   #include <Xm/PushB.h>

   void getsize(), configure();

   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget toplevel, button;
       XtAppContext app;

       XtSetLanguageProc (NULL, NULL, NULL);

       toplevel = XtVaAppInitialize (&app, "Demos",
           NULL, 0, &argc, argv, NULL,
           XmNmaxWidth,     150,
           XmNmaxHeight,    100,
           XmNbaseWidth,    5,
           XmNbaseHeight,   5,
           XmNwidthInc,     5,
           XmNheightInc,    5,
           NULL);

       /* Add an event handler to trap the first configure event */
       XtAddEventHandler (toplevel, StructureNotifyMask, False, configure, NULL);

       /* Pushbutton's callback prints the dimensions of the shell. */
       button = XtVaCreateManagedWidget ("Print Size",
           xmPushButtonWidgetClass, toplevel, NULL);
       XtAddCallback (button, XmNactivateCallback, getsize, toplevel);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   void
   getsize(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       Widget shell = (Widget) client_data;
       Dimension width, height;

       XtVaGetValues (shell,
           XmNwidth, &width,
           XmNheight, &height,
           NULL);
       printf ("Width = %d, Height = %d0, width, height);
   }

   void
   configure(shell, client_data, event)
   Widget shell;
   XtPointer client_data;
   XEvent *event;
   {
       XConfigureEvent *cevent = (XConfigureEvent *) event;

       if (cevent->type != ConfigureNotify)
           return;
       printf ("Width = %d, Height = %d0, cevent->width, cevent->height);
       XtVaSetValues (shell,
           XmNminWidth, cevent->width,
           XmNminHeight, cevent->height,
           NULL);
       XtRemoveEventHandler (shell, StructureNotifyMask, False, configure, NULL);
   }
We use XtAddEventHandler() to add an event handler to the top-level shell for events that satisfy the StructureNotifyMask , which includes ConfigureNotify events indicating the window's dimensions. The configure() function is called when the window is initially sized, so we can use the width and height fields of the XConfigureEvent structure as values for the XmNminWidth and XmNminHeight resources for the shell. To prevent the event handler from being called each time the window is resized, the event handler removes itself using XtRemoveEventHandler().

One problem with this technique occurs when the user has the interactivePlacement resource for mwm set to True. This specification allows the user to set the initial size and position of an application. However, once the user sets the initial size, she will never be able to make the window any smaller. Although interactive placement adheres to the constraints we have set, it cannot enforce a minimum size because we have not set one. Unfortunately, there is no way to allow interactive placement without allowing the user to resize the window.

The Shell widget class defines the XmNallowShellResize resource that is inherited by all of its subclasses. This resource specifies whether or not the shell allows itself to be resized when its widget children are resized, but it does not affect whether the user can resize the window. For example, if the number of items in a List widget grows, the widget tries to increase its own size, which causes a rippling effect that eventually reaches the top-level window. If XmNallowShellResize is True for this shell, it grows, subject to the window manager's approval, of course. However, if the resource is False, the shell does not even consult the window manager because it knows that it doesn't want to resize. This resource only prevents the shell from resizing after it has been realized, so it does not interfere with the initial sizing of the shell.

17.2.3 The Shell's Icon

Shells can be in one of three states: normal, iconic, or withdrawn. When a shell is in its normal state, the user can interact with the user-interface elements in the expected way. If a shell is withdrawn, it is still active, but the user cannot interact with it directly. When a shell is iconic, its window is not mapped to the screen, but instead it displays a smaller image, or icon, that represents the entire window. The application is still running in this state, but the program does not expect any user interaction. The icon window usually displays a visual image that suggests some connection to the window from which it came. Some window managers, like mwm, also allow a label to be attached to the icon's window.

The XmNiconPixmap resource specifies the pixmap that is used when an application is in an iconic state. the source code shows a simple application that sets its icon pixmap. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.

   #include <Xm/Xm.h>
   #include <X11/bitmaps/mailfull>

   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget toplevel;
       XtAppContext app;
       Pixmap bitmap;

       XtSetLanguageProc (NULL, NULL, NULL);

       toplevel = XtVaAppInitialize (&app, "Demos",
           NULL, 0, &argc, argv, NULL,
           XmNwidth, 100, /* size is irrelevant -- toplevel is iconified */
           XmNheight, 100, /* it just can't be 0, or Xt complains */
           XmNiconic,     True,
           NULL);

       bitmap = XCreatePixmapFromBitmapData (XtDisplay (toplevel),
           RootWindowOfScreen (XtScreen (toplevel)),
           mailfull_bits, mailfull_width, mailfull_height, 1, 0, 1);

       XtVaSetValues (toplevel,
           XmNiconPixmap, bitmap,
           NULL);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }
The program creates an ApplicationShell and sets the XmNiconic resource to True to cause the application to appear iconified. The bitmap variable is initialized to contain the bitmap described by the file /usr/include/X11/bitmaps/mailfull, and the XmNiconPixmap resource for the shell is set to the bitmap.

When we set the XmNiconPixmap and XmNiconic resources, we are actually sending hints to the window manager that we would like the icon window to display the given pixmap and that we would like to be in the iconic state. These requests are called hints because the window manager does not have to comply with the requests. However, if the icon pixmap or iconic state is ignored, it is most likely a bug in the window manager, or an incomplete implementation of one, which is often the case for older versions of many window managers, including mwm (Version 1.0).

One workaround for a window manager that ignores the icon pixmap is to set the XmNiconWindow resource. This resource sets the entire icon window, rather than just its image. In environments where the user may not be running the most up-to-date window manager, it may be best to create the icon window directly and then paint an image in that window. the source code contains a routine that demonstrates this technique. This routine creates a shell's icon window and can be called repeatedly to dynamically update its image.

   void
   SetIconWindow(shell, image)
   Widget shell;
   Pixmap image;
   {
       Window window, root;
       unsigned int width, height, border_width, depth;
       int x, y;
       Display *dpy = XtDisplay (shell);

       /* Get the current icon window associated with the shell */
       XtVaGetValues (shell, XmNiconWindow, &window, NULL);

       if (!window) {
           /* If there is no window associated with the shell, create one.
            * Make it at least as big as the pixmap we're
            * going to use.  The icon window only needs to be a simple window.
            */
           if (!XGetGeometry (dpy, image, &root, &x, &y,
                   &width, &height, &border_width, &depth) ||
               !(window = XCreateSimpleWindow (dpy, root, 0, 0, width, height,
                   (unsigned)0, CopyFromParent, CopyFromParent))) {
         XtVaSetValues (shell, XmNiconPixmap, image, NULL);
               return;
           }
           /* Now that the window is created, set it ... */
           XtVaSetValues (shell, XmNiconWindow, window, NULL);
       }
       /* Set the window's background pixmap to be the image. */
       XSetWindowBackgroundPixmap (dpy, window, image);
       /* cause a redisplay of this window, if exposed */
       XClearWindow (dpy, window);
   }
SetIconWindow() takes two parameters: a shell and an image. If the icon window for shell has not yet been set, we create a window using XCreateSimpleWindow(). The size of the window is set to the size of the image, which is retrieved with XGetGeometry(). This function is used to get the size of the image, but it can be used on windows as well. In the unlikely event that one of these routines fails, we fall back to using XmNiconPixmap to specify the image and hope the window manager understands it. Otherwise, we set the XmNiconWindow resource to the window we just created.

We use the image pixmap to set the window's background pixmap, which saves us the hassle of rendering it using XCopyArea() or XCopyPlane(). If the shell widget already has an icon window, XSetWindowBackgroundPixmap() is still called so that the specified image is displayed. The final call to XClearWindow() causes the icon to be repainted. This call isn't necessary if the window has just been created, but it is necessary if the window is merely updated with a new image.

The XmNiconX and XmNiconY resources can be used to set the position of the icon window on the screen. However, you probably shouldn't set these resources arbitrarily without a really good reason. Most window managers deal with positioning icon windows, or leave the positioning for the user to specify, so it is best not to interfere.

The XmNtitle and XmNiconName resources specify the titles used for the application window and the icon window, respectively. These resources are set to regular character strings, not compound strings. These values are typically both set to the name of the program, argv[0], by default. The values also affect the WM_NAME property for the top-level window, which is important for session managers and other applications that monitor all top-level windows on a desktop. These programs look for the WM_NAME property to provide menus or buttons that allow the user to control the desktop in a GUI-like fashion, rather than through tty-like shells such as xterm and csh. It is best to let the user set the XmNtitle and XmNiconName resources, especially since Xt provides command-line options such as -name that can be used to set the title of an application.

17.3 VendorShell Resources

The VendorShell widget class is subclassed from WMShell, so all of the shell widget classes subclassed from VendorShell can use the resources described in the previous section. All of the Motif shells except for MenuShell are subclassed from VendorShell. The VendorShell is designed to be implemented by individual vendors so that they can define resources specific to their own window manager. For example, mwm has some window manager features that are not found in other window managers. You need to be familiar with the Motif window manager in order to understand the discussion that follows.

17.3.1 Window Manager Decorations

The frame around an application's main window belongs to the window manager; the controls and window menu in it are not part of the application. The mwm window manager decorations for an application window are shown in the figure.

figs.eps/V6a.16.01.eps.png
Motif window manager decorations


The user can set mwm resources to control which of these items are available for particular windows on the desktop. Also, mwm automatically controls which elements are visible for certain windows, in order to maintain compatibility with the Motif Style Guide. As such, we discourage you from modifiying the decorations that are available on specific windows. Nevertheless, the VendorShell does provide the XmNmwmDecorations resource for use in exceptional cases. The resource can be set to an integer value that is made up of any of the following values:

MWM_DECOR_BORDER

This value enables the window manager borders for the frame. These borders are decorative only; they are not resize handles. Except for non-rectangular windows or programs like a clock, all Motif-style applications should have decorative borders.
MWM_DECOR_RESIZEH

This value enables the resize handles for the frame. If the resize handles are displayed, the decorative borders are forced to be displayed.
MWM_DECOR_TITLE

This value enables the title bar for the window.
MWM_DECOR_MENU

This value enables the window menu button on the title bar. If this item is on, the title bar is forced to be displayed.
MWM_DECOR_MAXIMIZE

This value makes the maximize button visible. When this button is selected, the window is expanded to the largest size possible. The size of the window is constrained by the values for XmNmaxWidth and XmNmaxHeight. If these resources are not set, the window is expanded to the size of the screen.
MWM_DECOR_MINIMIZE

This value makes the minimize button visible. This button does not shrink the window, but rather iconifies it. This item is turned off by default for TransientShell widgets (dialogs), since they cannot be iconified separately from their parent shells.
MWM_DECOR_ALL

This value can be used to enable all of the window manager decorations.

All of these values are defined in <Xm/MwmUtil.h >, which must be included before any of them may be used. The values are bitmasks, so they are meant to be ORed together. For example, if you have a customized dialog that you do not want to have resize handles, you can turn them off as shown in the following code fragment:

   Widget dialog_shell;
   int decor;

   XtVaGetValues (dialog_shell, XmNmwmDecorations, &decor, NULL);
   decor &= ~MWM_DECOR_RESIZEH;
   XtVaSetValues (dialog_shell, XmNmwmDecorations, decor, NULL);
While the programmatic interface is available to make changes in the form described above, you really don't have to resort to this level of complexity. If you want to do something that is allowed by the Motif Style Guide, chances are that the Motif toolkit provides a more convenient way of doing it. For example, you can turn off the resize handles for a Motif dialog by setting the XmNnoResize resource to True, as shown in the following code:
   Widget dialog;
   Arg args[5];
   int n = 0;

   XtSetArg (args[n], XmNnoResize, True); n++;
   dialog = XmCreateFileSelectionDialog (parent, "dialog", args, n);
If Motif doesn't provide a convenience routine or a resource for doing what you want, chances are good that you shouldn't be doing it. On the other hand, you don't have to use the convenience method; if it seems appropriate, you can use the methods described here.

17.3.2 Window Menu Functions

The contents of the window menu can be modified using the XmNmwmFunctions resource defined by the VendorShell. This resource acts like XmNmwmDecorations, in that the value is an integer that may be set to one or more of the following values:

MWM_FUNC_RESIZE

This value enables the Size item in the window menu. If this value isn't set, the resize handles for the window manager frame are disabled.
MWM_FUNC_MOVE

This value enables the Move menu item. Disabling this item does not affect the window manager frame decorations for the window.
MWM_FUNC_MINIMIZE

This value enables the Minimize menu item. Disabling this item causes the minimize button to be disabled as well.
MWM_FUNC_MAXIMIZE

This value enables the Maximize menu item. Disabling this item causes the corresponding window frame decoration to be disabled.
MWM_FUNC_CLOSE

This value enables the Close menu item. Disabling this item does not affect the window manager decorations for the window.

MWM_FUNC_ALL

This value causes all of the standard items in the menu to be displayed and all the default functionality of the window manager to work.

It is important to remember that the user can specify these window menu functions, as well as new functions, in an .mwmrc file. (See Volume Three, X Window System User's Guide, Motif Edition.) While your settings override any user specifications, you should only modify the window menu functions if it is absolutely necessary. A common misuse of this functionality is to disable the Close button. We strongly discourage disabling this button, as users expect it to be in the window menu. Rather than disable the button, you should link its functionality to another control in your application that has the same meaning. For example, if you are using a standard Motif dialog that provides OK and Cancel buttons, you can link the Close menu item to the Cancel button. We explain how to connect the functionality of these components in the next section.

17.4 Handling Window Manager Messages

A protocol is a set of rules that governs communication and data transfer. When the window manager sends a message to an application that follows a predefined protocol, the client application should respond accordingly. The ICCCM defines a number of protocols for window managers and applications to follow. One such protocol involves the Close item in the window menu. When the user selects this item, the window manager sends the application a protocol message, and the application must comply. The message is delivered through the normal event-handling mechanisms provided by Xlib. The event that corresponds to this message is called a ClientMessage event. The message itself is an Atom, which is merely a unique integer that is used as an identifier. (The actual value is unimportant, since you only need to reference the value through the preprocessor macro, WM_PROTOCOLS.) The protocol itself takes the form of other atoms, depending on the nature of the message. lists the atoms that are used as values for WM_PROTOCOLS client messages. Although this table is currently complete, it is expected to grow in future editions of the ICCCM. tab(@), linesize(2); l | l lfCW | l. Atom@Meaning
_
WM_TAKE_FOCUS@The window is getting the input focus. WM_DELETE_WINDOW@The window is about to be deleted. WM_SAVE_YOURSELF@The application should save its internal state.
_ the source code demonstrates how to use the WM_DELETE_YOURSELF protocol to link the Close item on the window menu with the Cancel button in a dialog. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4. XmStringCreateLocalized() is only available in Motif 1.2; XmStringCreateSimple() is the corresponding function in Motif 1.1.

   /* wm_delete.c -- demonstrate how to bind the Close button in the
    * window manager's system menu to the "cancel" button in a dialog.
    */
   #include <Xm/MessageB.h>
   #include <Xm/PushB.h>
   #include <Xm/Protocols.h>

   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget toplevel, button;
       XtAppContext app;
       void activate();

       XtSetLanguageProc (NULL, NULL, NULL);

       toplevel = XtVaAppInitialize  (&app, "Demos",
           NULL, 0, &argc, argv, NULL, NULL);

       button = XtCreateManagedWidget ("Push Me", xmPushButtonWidgetClass,
           toplevel, NULL, 0);
       XtAddCallback (button, XmNactivateCallback, activate, NULL);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   /* Create and popup an ErrorDialog indicating that the user may have
    * done something wrong.  The dialog contains an OK and Cancel button,
    * but he can still choose the Close button in the titlebar.
    */
   void
   activate(w, client_data, call_data)
   Widget w;
   XtPointer client_data;
   XtPointer call_data;
   {
       Widget dialog, shell;
       void response();
       XmString t = XmStringCreateLocalized ("Warning: Delete All Files?");
       Atom WM_DELETE_WINDOW;
       Arg args[5];
       int n;

       /* Make sure the VendorShell associated with the dialog does not
        * react to the user's selection of the Close system menu item.
        */
       n = 0;
       XtSetArg (args[n], XmNmessageString, t); n++;
       XtSetArg (args[n], XmNdeleteResponse, XmDO_NOTHING); n++;
       dialog = XmCreateWarningDialog (w, "notice", args, n);
       XmStringFree (t);

       /* add callback routines for ok and cancel -- desensitize help */
       XtAddCallback (dialog, XmNokCallback, response, NULL);
       XtAddCallback (dialog, XmNcancelCallback, response, NULL);
       XtSetSensitive (XmMessageBoxGetChild (dialog,
           XmDIALOG_HELP_BUTTON), False);

       XtManageChild (dialog);

       /* Add a callback for the WM_DELETE_WINDOW protocol */
       shell = XtParent (dialog);
       WM_DELETE_WINDOW = XmInternAtom
           (XtDisplay (w), "WM_DELETE_WINDOW", False);
       XmAddWMProtocolCallback (shell, WM_DELETE_WINDOW, response, dialog);
   }

   /* callback for the OK and Cancel buttons in the dialog -- may also be
    * called from the WM_DELETE_WINDOW protocol message sent by the wm.
    */
   void
   response (widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       XmAnyCallbackStruct *cbs = (XmAnyCallbackStruct *) call_data;
       Widget dialog;

       if  (cbs->reason == XmCR_OK)
           puts ("Yes");
       else
           puts ("No");

       if  (cbs->reason == XmCR_PROTOCOLS)
           /* we passed the dialog as client data for the protocol callback */
           dialog =  (Widget) client_data;
       else
           dialog = widget;

       XtDestroyWidget (dialog);
   }
When you run the application and click on the button, a dialog is displayed. All the application does is print "Yes" or "No" to standard output based on whether the OK or Cancel button is pressed. However, if you select Close from the window menu for the dialog, the dialog disappears, and the "No" message is printed.

When the user selects the Close item on the window menu, the application is sent a ClientMessage event by the window manager indicating that the window is about to be deleted. The value associated with the WM_PROTOCOLS message is WM_DELETE_WINDOW. The application is now responsible for complying with the protocol in some way.

At the highest level of abstraction, the VendorShell resource XmNdeleteResponse can be used to control what the application does in response to the user's selection of the Close button. The default behavior for a dialog is that the window is dismissed; the value XmUNMAP is used, and the window is unmapped from the screen. By setting XmNdeleteReponse to XmDESTROY, the window is destroyed; this value is the default for ApplicationShells. However, if the resource is set to XmDO_NOTHING , the application declares that it is going to handle the action itself.

In the source code we use this value to handle the WM_DELETE_WINDOW protocol ourselves by setting up a callback routine that is called whenever the protocol is sent. But before we can set up the callback, we have to get the atom associated with the WM_DELETE_WINDOW protocol. We retrieve the atom using XmInternAtom(), which takes the following form:

   Atom
   XmInternAtom(display, atom_name, dont_create)
       Display *display;
       char    *atom_name;
       Boolean  dont_create;
If the atom name described by the string atom_name exists, then the Atom is returned. If it does not exist and if dont_create is True, the function returns None. Otherwise, the routine creates and returns the atom. This function is identical to XInternAtom(), with the exception that the Motif version maintains an internal cache of previously-accessed atoms. Since creating and returning atoms causes a round trip to the server, it is a nice performance improvement to have that cache available for frequently-accessed atoms.

Once we have the protocol atom, we can add a callback routine to respond to the client message event generated by that protocol. The function XmAddWMProtocolCallback() is used to install a callback routine invoked whenever the window manager sends a WM_PROTOCOLS client message to the application. If the protocol sent in the client message matches the protocol passed to XmAddWMProtocolCallback(), the associated function is called. In the source code we use the response() routine as the callback for the dialog buttons and the protocol. As a result, the Close item invokes the same callback as the OK and Cancel buttons.

The form of this callback routine is the same as any other Motif callback. The final parameter is a Motif-defined callback structure of some kind, where the reason field specifies why the callback was called. This field is provided because the same callback function may be invoked by more than one widget. In our example, the response() function's callback structure may have one of three different values for reason: XmCR_OK for the OK button, XmCR_CANCEL for the Cancel button, or XmCR_PROTOCOLS for the Close button in the window menu. In Motif 1.1, XmCR_PROTOCOLS was not publicly defined, but this problem has been fixed in Motif 1.2. When the callback is invoked for the protocol message, the event field of the callback structure is an XClientMessageEvent.

The widget parameter passed to response() also varies depending on whether the routine is called from the dialog or from the Close button. When either OK or Cancel is pressed, the widget is the dialog itself. But the protocol callback routines are really processed by special protocol widgets that are attached to VendorShells. A shell can actually have any number of widget children, as long as only one of them is managed at a time. In the case of the Motif VendorShell, these other widgets are not managed but are used to process and manage protocols that are exchanged between the window manager and the application. When the protocol callback is invoked, the widget field is one of the special widgets, but this widget has no intrinsic meaning, so it can be ignored. We know that the activation of the WM_DELETE_WINDOW protocol causes a protocol widget to be passed as the widget parameter. Therefore, we pass a handle to the dialog widget as the client data to XmAddWMProtocolCallback() so that we have access to the dialog.

The purpose, of course, is to destroy the window, but our function could just as easily veto the operation and render the Close button inoperable. However, this technique is really not appropriate, as users expect to be able to use the Close button to remove a window. If the Close button is not going to unmap the window for some good reason, like an error, you should report the error in another dialog. If you are going to modify the default behavior of standard user-interface controls, you should keep the user informed about what you are doing.

17.4.1 Adding New Protocols

In general, you can attach a callback routine to any of the published protocols using the mechanisms we just described. You may also assign new protocols to send yourself special messages that are pertinent only to your application, as protocol messages can be passed from application to application, not just between the window manager and other clients. Handling arbitrary protocols is basically a matter of following these simple steps:

For the case of WM_DELETE_WINDOW, the second step has already been taken care of by the VendorShell, since it is an established and standardized ICCCM protocol. The VendorShell has already registered interest in the protocol so it can react to it in the method described by its XmNdeleteResponse resource. However, other protocols (customized or not) may not be registered. Since it doesn't hurt to register a protocol with a window more than once, it's always a good practice to register the protocol using XmAddWMProtocols(), which takes the following form:
   void
   XmAddWMProtocols(shell, protocols, num_protocols)
       Widget shell;
       Atom  *protocols;
       int    num_protocols;
This function takes a list of protocols, so you can use it to add as many protocols as you like at one time.

17.4.2 Saving Application State

A session manager is an application that acts something like a window manager. However, rather than controlling only the windows on a screen, it monitors the actual applications running on that screen. Frequently, session managers allow the user to start, terminate, or even restart any program automatically, through a variety of interface controls. Session managers may even cause a program to "sleep" by terminating all its keyboard and mouse input, so as far as the program is concerned, the user is just not interacting with it.

At the moment, there are not many full session managers available, so much of the possible functionality is uncharted. This section discusses one aspect of proposed session manager behavior and how it might be implemented. This behavior concerns the ability of an application running under the session manager to restart itself at the point where it left off in a previous session.

If the session manager decides that it might terminate (which might result in the entire X connection terminating), it may send a request to all its applications to save their internal state so they can be restarted later. In this case, the session manager sends a WM_SAVE_YOURSELF protocol message. According to the ICCCM, client applications that can save their current state and restart from that state should register the atom WM_SAVE_YOURSELF on the WM_PROTOCOLS property on one of their top-level windows.

The ICCCM states that after sending the WM_SAVE_YOURSELF message to the application, the session manager waits until the program updates its WM_COMMAND property on the same window that received the protocol message. The application is not permitted to interact with the user in any way at this time. You cannot prompt for filenames or ask if the user wants to save state. The callback routine must save its current state somehow, possibly in a predefined file that can be made known to the user through documentation, rather than a run-time message. It must then update the WM_COMMAND property to reflect the parameters that started the program, as well as any additional parameters that might be required to restart it.

For example, say your application is called wm_save and you want to be able to restart it from a previously-saved file. In this case, your application might parse the following command-line option:

   % wm_save -restart filename
the source code contains a code fragment that demonstrates how you might implement this functionality. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.
   /* wm_save.c -- demonstrate how to save the state of an application
    * from a WM_SAVE_YOURSELF session manager protocol.  This is not a
    * real program -- just a template.
    */
   #include <Xm/Xm.h>
   #include <Xm/Protocols.h>
   #include <stdio.h>

   /* save the original argc and argv for possible WM_SAVE_YOURSELF messages */
   int save_argc;
   char **save_argv;

   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget toplevel;
       XtAppContext app;
       Atom WM_SAVE_YOURSELF;
       void save_state();
       char *restart_file;
       int i;

       /* save argc and argv values */
       save_argv = (char **) XtMalloc (argc * sizeof(char *));
       for (i = save_argc = 0; i < argc; i++)
           /* we don't need to save old -restart options */
           if (!strcmp (argv[i], "-restart")) 
               i++; /* next arg is filename */
           else
               save_argv[save_argc++] =
                   strcpy (XtMalloc (strlen(argv[i]) + 1), argv[i]);

       XtSetLanguageProc (NULL, NULL, NULL); 

       /* initialize toolkit normally; argv has its Xt-specific args stripped */
       toplevel = XtVaAppInitialize (&app, "Demos",
           NULL, 0, &argc, argv, NULL,
           XmNwidth, 100,
           XmNheight, 100,
           NULL);

       /* get the WM_SAVE_YOURSELF protocol atom and register it with the
        * toplevel window's WM_PROTOCOLS property.  Also add a callback.
        */
       WM_SAVE_YOURSELF =
           XmInternAtom (XtDisplay (toplevel), "WM_SAVE_YOURSELF", False);
       XmAddWMProtocols (toplevel, &WM_SAVE_YOURSELF, 1);
       XmAddWMProtocolCallback (toplevel, WM_SAVE_YOURSELF,
           save_state, toplevel);

       /* create widgets... */

       /* now check to see if we are restarting from a previously run state */
       for (i = 0; i < argc; i++) {
           if (!strcmp (argv[i], "-restart")) {
               /* restarting from a previously saved state */
               restart_file = argv[++1];
           }

           /* possibly process other args here, too */
       }

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   /* called if WM_SAVE_YOURSELF client message was sent... */
   void
   save_state(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       Widget toplevel = (Widget) client_data;
       extern char *SaveStateAndReturnFileName();  /* hypothetical function */
       char *filename = SaveStateAndReturnFileName ();
       puts("save_state()");

       save_argv = (char **) XtRealloc (save_argv,
           (save_argc+2) * sizeof (char *));

       save_argv[save_argc++] = "-restart";
       save_argv[save_argc++] = filename;

       XSetCommand (XtDisplay (toplevel), XtWindow (toplevel),
           save_argv, save_argc); /* notice the order of these args! */
   }
This program registers the WM_SAVE_YOURSELF protocol using XmAddWMProtocols() before it specifies the callback routine. If the session manager sends a WM_SAVE_YOURSELF message to this program then the save_state() function is called, which causes the program to save its internal state using the function SaveStateAndReturnFileName(). This is a hypothetical function that you would write yourself to save the state of the program and return the filename that contains the state information. The callback routine also adds the -restart flag and the new filename to the saved argv from the beginning of the program. The function XSetCommand() is used to set the WM_COMMAND property on the window associated with the top-level shell, which fulfills the program's obligation to the session manager.

For more information about session managers and the save-yourself communication protocol, see Volume Zero, X Protocol Reference Manual. For more details on XSetCommand() and other Xlib-based functions that set and get window manager properties on top-level windows, see Volume One, Xlib Programming Manual, and Volume Two, Xlib Reference Manual.

17.5 Customized Protocols

The previous section demonstrated how similar one protocol message is to the next in the way they are added to a program. Adding a completely new protocol is not difficult either. The only changes we have to make are those that would otherwise interfere with the standard protocols and properties that are registered with the X protocol and ICCCM. To avoid conflicts, the convention is to begin the name of nonstandard atoms and window properties with at least an underscore, and possibly a more detailed prefix that identifies the atom as a private protocol or property. Accordingly, Motif provides the property _MOTIF_WM_MESSAGES as a private atom specifically for Motif-based applications that wish to send private messages to themselves or one another. Private does not mean that no one else can see the messages; it just implies that the protocol is not publicly available for other third-party applications to use, so don't expect other programs on the desktop to participate in the protocol.

the source code demonstrates how to register your own protocol with the shell and set up a callback routine that is invoked when that protocol is delivered. Like the source code this program is a skeletal frame only; it does not have any real functionality. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.

   /* wm_protocol.c -- demonstrate how to add your own protocol to a
    * shell.  The nature of the protocol isn't important; however, it
    * must be registered with the _MOTIF_WM_MESSAGES property on the
    * shell.  We also add a menu item to the window manager frame's
    * window menu to allow the user to activate the protocol, if desired.
    */
   #include <Xm/Xm.h>
   #include <Xm/Protocols.h>
   #include <stdio.h>

   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget toplevel;
       XtAppContext app;
       Atom MOTIF_MSGS, MY_PROTOCOL;
       void my_proto_callback();
       char buf[64];

       toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0,
           &argc, argv, NULL,
           XmNwidth, 100,
           XmNheight, 100,
           NULL);

       /* get the MOTIF_MSGS and MY_PROTOCOL atoms */
       MY_PROTOCOL = XmInternAtom (XtDisplay (toplevel),
           "_MY_PROTOCOL", False);
       MOTIF_MSGS = XmInternAtom (XtDisplay (toplevel),
           "_MOTIF_WM_MESSAGES", False);

       /* Add MY_PROTOCOL to the _MOTIF_WM_MESSAGES VendorShell-defined
        * property on the shell.  Add a callback for this protocol.
        */
       XmAddProtocols (toplevel, MOTIF_MSGS, &MY_PROTOCOL, 1);
       XmAddProtocolCallback (toplevel,
           MOTIF_MSGS, MY_PROTOCOL, my_proto_callback, NULL);

       /* allow the user to activate the protocol through the window manager's
        * window menu on the shell.
        */
       sprintf (buf, "MyProtocol _P Ctrl<Key>P f.send_msg %d", MY_PROTOCOL);
       XtVaSetValues (toplevel, XmNmwmMenu, buf, NULL);

       /* create widgets... */

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   /* called if _MY_PROTOCOL was activated, a client message was sent... */
   void
   my_proto_callback(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       puts ("My protocol got activated!");
   }
This program is set up to receive the protocol _MY_PROTOCOL. If the message is sent, the function my_proto_callback() is called, passing the appropriate client data and callback structure as before. However, since we just made up the protocol, the only way it can be delivered is by the window manager if (and only if) the user selects the new menu item that we attached to the window menu, as shown in the figure.

figs.eps/V6a.16.02.eps.png
Output of wm_protocol.c


The menu item is added using the XmNmwmMenu resource in the call to XtVaSetValues(). The syntax of the value for the string used by the XmNmwmMenu resource is described completely in the mwm documentation in Volume Six B, Motif Reference Manual. Briefly, each of the arguments refers to a single entry in the menu that is always added after the last standard protocol in the menu, which is usually the Close button. The syntax for the resource is:

   label [mnemonic] [accelerator] function
Only the label and the window manager function (mwm-specific) are required. The label is always first; if a space needs to be embedded in the label, precede it by two backslashes. The next token is parsed as a mnemonic if it starts with an underscore. If an accelerator is given, the Motif toolkit parses this string and creates a corresponding accelerator text string for the menu. Finally, the parser looks for a window manager function as described by the mwm documentation. These include f.move, f.raise and f.send_msg, for example. We use f.send_msg to tell mwm to send the specified client message to the application.

It is possible to deactivate a protocol on the window menu using XmDeactivateWMProtocol(). Deactivation makes a protocol insensitive (unselectable). Protocols may be reactivated by XmActivateWMProtocol(); new protocols are automatically activated when they are added. XmActivateProtocol() and XmDeactivateProtocol() perform an analogous function for non-window manager protocols.

But what can you do with your own private protocol? These protocols can come in handy if you want to attach any application-specific functionality to a window so that it can communicate with similar applications on the desktop. For example, larger application suites that contain multiple programs might need to communicate with one another through this protocol. If a suite of painting, drawing, and desktop publishing products wanted to pass document information to one another, they could pass messages using their own protocol. Whether or not you allow the window manager (and thus the user) to participate in the protocol can be controlled by whether you make the protocol handle available in the window menu, as shown in the figure.

Advanced work with protocols is getting beyond the scope of this book. Further progress requires Xlib-level code that you can research on your own by reading portions of Volume One, Xlib Programming Manual. However, if you are interested in providing this kind of functionality, you might consider the following design approach:

Remember, since this is your own private protocol, you can do whatever you like in the correspondence process. If you wanted, you could specify that the receiving window would always test for a newly-defined property on its window, and if that property is set, obtain further information from the primary selection. Using this process, you could write your own data transfer methods. However, whatever you come up with is strictly private, so no other application can participate in your protocol unless you tell the developer of the other application what to do.

You can place whatever information you like in properties: a string, an integer, or a data structure. Just make sure that it's not per-process information like a file descriptor. This type of data cannot be shared among separate processes. You should also try not to make the information host-specific because you are not guaranteed that both clients are going to be running on the same computer, although they will be running on the same server. It is also a good idea to avoid protocols that involve continuous chatting between programs. Protocols are not a good method for doing interactive talk programs because the network can't handle that kind of traffic. To do this kind of communication, it is typically better to establish your own TCP or STREAM connection between the two applications. You should attempt to be as network-portable as possible, but this is your own personal protocol, so you can do anything you like.

17.6 Summary

The best applications can still function adequately without a window manager. For portability reasons, you should not assume that the user is running mwm. Except for dealing with WM_DELETE_WINDOW protocol messages to handle the window menu's Close button, you should avoid interfering with the interaction between your applications and the window manager. Despite this advice, many developers believe they know better and attempt to redesign Motif on a per-application basis. If you attempt to go this route, be aware of the guidelines provided by the Motif Style Guide and the ICCCM.

Client messages can be an extremely powerful tool for a large application with many top-level windows that need to interact with each other. They can also be useful for larger groups of similar applications by the same vendor that need to talk to one another. The secret to making a private protocol work is establishing a good communication channel and being able to transfer a lot of information without having to transfer a lot of data.

17.7 Exercises

These exercises are designed to help you understand the material that was presented in this chapter.


Contents Previous Next