X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation |
In this chapter:
Chapter: 20
Interacting With the Window Manager
This chapter provides additional information on the relationship between shell widgets and the window manager. In particular, it focuses on the Motif window manager, mwm, although the information given pertains equally to the CDE desktop manager, as well as other ICCCM-compliant managers. 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), and the CDE desktop which is derived from it. In the discussions which follow, wherever the text refers to mwm, you should assume that the CDE desktop manager dtwm is also included unless otherwise stated. The majority of the differences between the CDE desktop and the Motif window manager mwm are concerned with consistency of visuals across platforms in any case, and do not overly affect the way in which clients communicate using the standardized ICCCM mechanisms.
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.
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 inter-operability, 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.1An 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, SessionShells, and DialogShells2. 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.
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 can be found in Appendix L of Volume 0, X Protocol Reference Manual, 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, Table 20-1 shows a partial list of properties that are handled automatically by shells.
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.3However, 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 6B, Motif Reference Manual, for a complete list of resources.
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: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.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);
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:
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.XmNminWidth XmNmaxWidth XmNminHeight XmNmaxHeight XmNwidthInc XmNheightInc XmNbaseWidth XmNbaseHeightExample 20-1 demonstrates incremental resizing. The application displays a shell widget that contains a PushButton. When you click on the button, it displays the sizeof the window in pixels, but when you resize the window, the mwm feedback window displays the size in terms of XmNwidthInc and XmNheightInc.4
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./* 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 (int argc, char *argv[]) { Widget toplevel, button; XtAppContext app; void getsize(Widget, XtPointer, XtPointer); XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, 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 = XmCreatePushButton (toplevel, "Print Size", NULL, 0); XtManageChild (button); XtAddCallback (button, XmNactivateCallback, getsize, (XtPointer) toplevel); XtRealizeWidget (toplevel); XtAppMainLoop (app); } void getsize (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 = %d\n", width, height); }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 Example 20-2.5
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()./* 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(Widget, XtPointer, XtPointer); void configure(Widget, XtPointer, XEvent *, Boolean *); main (int argc, char *argv[]) { Widget toplevel, button; XtAppContext app; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, 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 = XmCreatePushButton (toplevel, "Print Size", NULL, 0); XtManageChild (button); XtAddCallback (button, XmNactivateCallback, getsize, (XtPointer) toplevel); XtRealizeWidget (toplevel); XtAppMainLoop (app); } void getsize (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 = %d\n", width, height); } void configure (Widget shell, XtPointer client_data, XEvent *event, Boolean *unused) { XConfigureEvent *cevent = (XConfigureEvent *) event; if (cevent->type != ConfigureNotify) return; printf ("Width = %d, Height = %d\n", cevent->width, cevent->height); XtVaSetValues (shell, XmNminWidth, cevent->width, XmNminHeight, cevent->height, NULL); XtRemoveEventHandler (shell, StructureNotifyMask, False, configure, NULL); }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.
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. Example 20-3 shows a simple application that sets its icon pixmap.6
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.#include <Xm/Xm.h> #include <X11/bitmaps/mailfull> main (int argc, char *argv[]) { Widget toplevel; XtAppContext app; Pixmap bitmap; XtSetLanguageProc (NULL, NULL, NULL); /* size is irrelevant -- toplevel is iconified */ /* it just can't be 0, or Xt complains */ toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, XmNwidth, 100, XmNheight, 100, XmNiconic, True, NULL); bitmap = XCreatePixmapFromBitmapData (XtDisplay (toplevel) RootWindowOfScreen (XtScreen (toplevel)), (char *) mailfull_bits, mailfull_width mailfull_height, 1, 0, 1); XtVaSetValues (toplevel, XmNiconPixmap, bitmap, NULL); XtRealizeWidget (toplevel); XtAppMainLoop (app); }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 work around 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. Example 20-4 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.
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.void SetIconWindow (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); }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.
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.
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 Figure 20-1.
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 modifying 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:
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 bit masks, 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:
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_shell; int decor; XtVaGetValues (dialog_shell, XmNmwmDecorations, &decor, NULL); decor &= ~MWM_DECOR_RESIZEH; XtVaSetValues (dialog_shell, XmNmwmDecorations, decor, NULL);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.Widget dialog; Arg args[5]; int n = 0; XtSetArg (args[n], XmNnoResize, True); n++; dialog = XmCreateFileSelectionDialog (parent, "dialog", args, n);
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: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 Motif Volume 3, 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.
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. Table 20-2 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.
Example 20-5 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.7
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./* 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 (int argc, char *argv[]) { Widget toplevel, button; XtAppContext app; void activate(Widget, XtPointer, XtPointer); XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); button = XmCreatePushButton (toplevel, "Push Me", NULL, 0); XtManageChild (button); 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 (Widget w, XtPointer client_data, XtPointer call_data) { Widget dialog, shell; void response(Widget, XtPointer, XtPointer); 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 (XtNameToWidget (dialog, "Help"), False); XtManageChild (dialog); /* Add a callback for the WM_DELETE_WINDOW protocol */ shell = XtParent (dialog); WM_DELETE_WINDOW = XInternAtom (XtDisplay (w), "WM_DELETE_WINDOW", False); XmAddWMProtocolCallback (shell, WM_DELETE_WINDOW, response, (XtPointer) 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 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 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 Example 20-5, 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 XInternAtom(), which takes the following form:
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.Atom XInternAtom (Display *display, char *atom_name, Boolean dont_create)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 Example 20-5, 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. 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.8When 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.
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:
- Create an atom or retrieve one from the X server using XInternAtom().
- Register the atom on the shell with XmAddWMProtocols(), so the event-handling mechanism can recognize it if it should arrive.
- Install a callback routine that is invoked when the protocol is sent to the application using XmAddWMProtocolCallback().
This function takes a list of protocols, so you can use it to add as many protocols as you like at one time.void XmAddWMProtocols (Widget shell, Atom *protocols, int num_protocols)
Session Management
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.This section discusses one aspect of 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. The implementation focuses initially on the functionality inherent in the protocols defined by the ICCCM, and proceeds to a discussion of the new features provided by the X11R6 SessionShell.
Session Management in X11R5
Under the scheme drafted prior to X11R6, if the session manager decides that it should 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 further stated that after sending the WM_SAVE_YOURSELF message to the application, the session manager should wait until the program updates its WM_COMMAND property on the same window that received the protocol message. The application was not permitted to interact with the user in any way at this time. You were not supposed to prompt for filenames or ask if the user wants to save state. The callback routine saved its current state somehow, possibly in a predefined file that could be made known to the user through documentation, rather than a run-time message. It then updated 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:
Example 20-6 contains a code fragment that demonstrates how you would implement this functionality which is compatible with the X11R5 model.9% wm_save -restart filename
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./* 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 (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 { char *copy = XtMalloc (strlen (argv[i]) + 1); save_argv[save_argc++] = strcpy (copy, argv[i]); } } XtSetLanguageProc (NULL, NULL, NULL); /* initialize toolkit: argv has its Xt-specific args stripped */ toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, 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 = XInternAtom (XtDisplay (toplevel), "WM_SAVE_YOURSELF", False); XmAddWMProtocols (toplevel, &WM_SAVE_YOURSELF, 1); XmAddWMProtocolCallback (toplevel, WM_SAVE_YOURSELF, save_state, (XtPointer) 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[++i]; } /* possibly process other args here, too */ } XtRealizeWidget (toplevel); XtAppMainLoop (app); } /* called if WM_SAVE_YOURSELF client message was sent... */ void save_state (Widget widget, XtPointer client_data, XtPointer call_data) { Widget toplevel = (Widget) client_data; /* hypothetical function */ extern char *SaveStateAndReturnFileName(); char *filename = SaveStateAndReturnFileName (); puts("save_state()"); save_argv = (char **) XtRealloc ((char *) save_argv, (save_argc+2) * sizeof (char *)); save_argv[save_argc++] = "-restart"; save_argv[save_argc++] = filename; /* notice the order of XSetCommand() args! */ XSetCommand (XtDisplay (toplevel), XtWindow (toplevel), save_argv, save_argc); }For more information about session managers and the save-yourself communication protocol, see Volume 0, 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 1, Xlib Programming Manual, and Volume 2, Xlib Reference Manual.
Session Management in X11R6
X11R6 introduces the SessionShell widget class, which is specifically designed to encapsulate the interaction between an application and the session manager. The SessionShell is a subclass of the ApplicationShell; the ApplicationShell is now considered obsolete.Using the SessionShell, handling the interaction between the session manager and the application no longer requires direct programming using the lower level window manager protocols. It is simply a matter of setting some new resources, and providing new callbacks where appropriate.
The SessionShell widget class is fully described in the Programmer's Supplement for Release 6 of the X Window System. Only the basics will be described here, in order to give sufficient information to describe the X11R6 equivalent of the techniques described in the previous X11R5 section. You are referred to the Supplement for further details.
Connecting to the Session Manager
The connection between the application and the session manager is established automatically when you create a SessionShell. The internals of the communicating process, and the messages passed backwards and forwards between the application and the session manager in performing the initial handshake are beyond the scope of this chapter. For the purposes of this chapter, it is simply sufficient to know that calling XtVaOpenApplication() or XtOpenApplication(), passing the sessionShellWidgetClass as a parameter, will establish the connection for us as a side effect of creating our first toplevel shell.SessionShell Resources
The most important information which is passed between the session manager and the application is the name of the command, and arguments, which are to be used by the session manager to restart the application. This is specified using the XtNrestartCommand resource. The resource is represented internally by an array of strings, and is initialized by default from the argv, and argc parameters of the application which are passed to the XtOpenApplication() or XtVaOpenApplication() call when creating the SessionShell. For many applications, this default behavior may be considered sufficient to recover the current state of the application. However, for many applications, the internal state which changes due to various options or actions taken by the user may well result in the need to modify the notional parameters passed to the application if that internal state is to be recovered. Consider an editor, which edits files either passed to it on the command line, and which dynamically opens files as a result of menu or other actions. We may need to update the XtNrestartCommand resource as each new file is opened in the application.Recovering application state often involves considerably more than simply constructing an array of command line arguments. We may need to take into account the current directory in which the application was running at the time, or consider the state of environment variables which affect application behavior, or even explicitly supply the path to the application program. The SessionShell also supports these aspects of application recovery through resources.
The application environment can be explicitly set using the XtNenvironment resource; this is specified through a NULL-terminated array of strings. The default value is NULL, and is not initialized in any way from the current environment settings: you must explicitly program into the resource any environment which the application requires.
The current working directory can be specified through the XtNcurrentDirectory resource. Again, this resource has a default value of NULL, and must be explicitly set if required.
An explicit path to the application command can be specified through the XtNprogramPath resource. Unlike XtNrestartCommand, this resource is not initialized from the parameters passed to XtOpenApplication(), and the default value is NULL.
As well as specifying the restart behavior of our application, we can also inform the session manager of any commands we may care to execute in order to tidy up the current application before it exits. The XtNshutdownCommand resource specifies a command and arguments to be called by the session manager after our application terminates.
A unique handle on the interaction between the session manager and the application is available through the XtNsessionID resource. The exact syntax of the resource will not be covered here - you are referred to the Supplement for more details of this. As far as we are concerned in the examples which follow, it will be used simply to inform the session manager that we wish to restart our application in the same logical session in which it is currently running. We do this simply by copying the value from the SessionShell (where it was set up by the session manager) into part of the XtNrestartCommand array.
There is one other interesting resource which may be of use. This is the XtNcloneCommand, which can be used to inform the session manager how the application should be started in the general case. By default, if no XtNcloneCommand is specified, the session manager will clone a new application using the XtNrestartCommand value. Think of the difference between restart and clone as this: a restart command informs the session manager how to recover as near as possible the current application state, but the clone command just starts the application in the normal initial state.
Example 20-7 shows a specimen routine which resets the application restart parameters. It can be considered as a logical equivalent of the save_state() routine from Example 20-6. We ignore for the moment the problem of how this routine gets to be called in the new scheme of things: this is covered in the following section on SessionShell callbacks.
void set_session_restart (Widget w, XtPointer client_data, XtPointer call_data) { Widget toplevel = (Widget) client_data; /* hypothetical function */ extern char *SaveStateAndReturnFileName(); char *filename = SaveStateAndReturnFileName (); puts("set_session_restart()"); save_argv = (char **) XtRealloc ((char *) save_argv, (save_argc+3) * sizeof (char *)); save_argv[save_argc++] = "-restart"; save_argv[save_argc++] = filename; save_argv[save_argc] = (char *) 0; /* NULL terminated */ XtVaSetValues (toplevel, XtNrestartCommand, save_argv, NULL); }
SessionShell Callbacks
In the X11R5 model, we have to program the interchange between the session manager and the application using protocols. In X11R6, we use SessionShell callbacks. Furthermore, unlike the X11R5 model, we are allowed to interact with the user for whatever confirmation or information we require. User interaction is however strictly controlled in the sense that it should occur only at specific points in the interactions between the session manager and the application.There are six session management callbacks which can be used to program the various stages of the interaction between the session manager and the application. Not all need to be programmed for the interactions to work - it all depends on the degree of sophistication and error recovery required by the application to hand.
The most important callback is the XtNsaveCallback. This is used to perform actual application state save. It should also initialise the XtNrestartCommand resource if the application state has changed since the last time the session manager issued a request. The code in Example 20-7 is entirely typical: it simply resets the XtNrestartCommand value to reflect the current save file name. Note that the XtNsaveCallback is not supposed to interact with the user. We would ensure that the XtNsaveCallback is active simply by registering the routine using normal Xt means:
Once an application has saved its state, it may or may not require notification from the session manager that the message has been received and understood - that is, the session manager has managed to process all changes to the XtNrestartCommand resources for all the participating applications in the current session. The XtNsaveCompleteCallback can be used if this part of the interaction is important. For a typical application, it is not.extern Widget sessionShell; /* The application top level */ XtAddCallback (sessionShell, XtNsaveCallback, set_session_restart, NULL);If the session manager or the application decides to terminate the save state request, any handling of clean-up operations required should be programmed using an XtNcancelCallback. Again, a typical application would not be over-concerned: it is unlikely that you would want to unwind any save operations.
The session manager can request that the application kills itself: it would do this when the session is closing down. The application can catch this request using an XtNdieCalback. It should not attempt to interact with the user or save state in this callback, but simply exit as cleanly as possible.
When the program does want to interact with the user, it should register an XtNinteractCallback. Typically, this would be used to prompt the user for the name of a file into which the application state is to be saved, or indeed to request user confirmation as to whether she really does want the current application state saved at all. The XtNinteractCallback does not create any graphical interface for the user interaction - the programmer should create the message dialogs as appropriate inside the callback.
The last SessionShell callback available to the programmer is the XtNerrorCallback, which would be used by mission-critical applications that need to be exactly informed of errors in the session manager interaction.
All of the SessionShell callbacks receive as callback data an XtCheckPointToken. This is a pointer to a data structure, the XtCheckPointTokenRec, defined as follows:
The exact meaning of each of the elements is fully described in the Supplement. For our purposes, we will confine ourselves to the following elements: save_success, request_cancel, cancel_shutdown, interact_style, and interact_dialog_type.typedef struct _XtCheckpointTokenRec { int save_type; int interact_style; Boolean shutdown; Boolean fast; Boolean cancel_shutdown; int phase; int interact_dialog_type; Boolean request_cancel; Boolean request_next_phase; Boolean save_success; int type; Widget widget; } XtCheckpointTokenRec, *XtCheckpointToken;The save_success element indicates whether the application was able to successfully save its state. This should be set to TRUE or FALSE during the XtNsaveCallback depending on circumstances.
The request_cancel element should be set to TRUE if the application wants to abort the current save operation for any reason.
The cancel_shutdown element should be set to TRUE if the application wants to abort the current shutdown operation for any reason.
The interact_style element is set by the session manager to inform the program whether or not it is allowed to interact with the user. The possible values are:
The program should not attempt to interact with the user if the value is SmInteractStyleNone, and should only interact with the user in the case of an internal error if the style is SmInteractStyleErrors.SmInteractStyleNone SmInteractStyleAny SmInteractStyleErrorsThe interact_dialog_type is set by the programmer, and indicates back to the session manager whether any popup dialogs which will be created by the program are for the purposes of warning the user of an error, or if the dialog is an ordinary one for collecting user information or confirmation. Possible values are:
SmDialogError SmDialogNormal
Tokens
The session management system works by passing a logical token - an identifier represented in the client by an XtCheckpointToken - between the manager and the various applications participating in the session. Each application in turn holds the token as it attempts to save its state. This token must be returned to the session manager at the termination of each deferred save callback. That is, if you pop up a dialog to request information from the user using an interact callback, the callbacks associated with this dialog should return the token, not the interact callback itself. The routine XtSessionReturnToken() is used to perform this task, and has the following functional signature:The token parameter is simply the data passed through to the given session callback. It is also possible to fetch a token. What this means in the context of a session management interaction is simply whether or not the session manager is currently in the process of talking to (Checkpointing) the application. The routine XtSessionGetToken() returns a token depending upon whether a current checkpoint operation is in force. It is formally defined as follows:void XtSessionReturnToken (XtCheckpointToken token)The routine returns NULL if the session manager does not have a checkpoint operation in force.XtCheckpointToken XtSessionGetToken (Widget sessionShell)10It is very important that you remember to return the token in your session management deferred callbacks, otherwise the session manager can hang awaiting a non-existent reply.
An Example
The code in Example 20-8 is a simple application which sets up various session callbacks in order to save its state. The application does nothing more than display a spinbox: the state to be saved is the current value of the spinbox.
The output of this program is given in Figure 20-2./* session.c - outlines the interactions with the session manager */ #include <stdio.h> #include <Xm/Xm.h> #include <Xm/RowColumn.h> #include <Xm/SSpinB.h> #include <Xm/MessageB.h> Widget toplevel, spin; /* The command by which the session manager will restart this application */ char *restart_command[6] = { NULL, "-xtsessionID", NULL, "-value", NULL, NULL }; /* The "OK" button is pressed in the popup interaction dialog */ /* This does not perform save-yourself actions - */ /* it informs the session manager that we need to do so */ static void msg_ok_callback( Widget w, XtPointer client_data, XtPointer call_data) { XtCheckpointToken token = (XtCheckpointToken) client_data; /* Asks the session manager to call the save_callback */ token->save_success = True; /* Return the token */ XtSessionReturnToken (token); } /* The "Cancel" button is pressed in the popup interaction dialog */ static void msg_cancel_callback( Widget w, XtPointer client_data, XtPointer call_data) { XtCheckpointToken token = (XtCheckpointToken) client_data; /* Tells the session manager not to call the save_callback */ token->request_cancel = True; token->save_success = False; /* Return the token */ XtSessionReturnToken (token); } /* Interacts with the user during session shell operations */ static void interact_callback( Widget w, XtPointer client_data, XtPointer call_data) { static Widget message = (Widget) 0; XtCheckpointToken token = (XtCheckpointToken) call_data; XmString xms; Arg args[8]; int n; if (token->cancel_shutdown || token->interact_style == None) { token->save_success = False; return; } if (message == (Widget) 0) { n = 0; xms = XmStringCreateLocalized ("Save Changes Before Quitting?"); XtSetArg (args[n], XmNmessageString, xms); n++; message = XmCreateQuestionDialog (toplevel, "message", args, n); XtUnmanageChild (XtNameToWidget (message, "Help")); XtAddCallback (XtNameToWidget (message, "OK"), XmNactivateCallback, msg_ok_callback, call_data); XtAddCallback (XtNameToWidget (message, "Cancel"), XmNactivateCallback, msg_cancel_callback, call_data); } XtManageChild (message); /* Don't return the token: we are still interacting with the user */ /* The token is returned at a deferred time in the message dialog */ /* callbacks */ } /* Performs the session manager save-yourself actions */ /* That is, it sets up the XtNrestartCommand array as appropriate */ static void save_callback( Widget w, XtPointer client_data, XtPointer call_data) { XtCheckpointToken token = (XtCheckpointToken) call_data; int spin_value; char buf[20]; XtVaGetValues (spin, XmNposition, &spin_value, NULL); (void) sprintf (buf, "%d", spin_value); restart_command[4] = buf; XtVaSetValues (toplevel, XtNrestartCommand, restart_command, NULL); if (token->interact_style != SmInteractStyleNone) { if (token->interact_style == SmInteractStyleAny) token->interact_dialog_type = SmDialogNormal; else token->interact_dialog_type = SmDialogError; XtAddCallback (toplevel, XtNinteractCallback, interact_callback, NULL); } } /* Signals to the session manager that save completed successfully */ /* In this example, there is nothing to do */ static void save_complete_callback( Widget w, XtPointer client_data, XtPointer call_data) { XtCheckpointToken token = (XtCheckpointToken) call_data; } /* Kills this application in response to session manager request */ static void die_callback( Widget w, XtPointer client_data, XtPointer call_data) { XtDestroyWidget (toplevel); exit (0); } main (int argc, char *argv[]) { XtAppContext app; Arg args[16]; int i, n; String smcid; int spin_value = 0; /* Parse the command-line arguments for -value nnn. */ for (i = 1; i < argc; i++) { if ((strcmp (argv[i], "-value") == 0) && (i < argc - 1)) { spin_value = atoi (argv[++i]); } } XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, args, n); /* Set up the restart command */ XtVaGetValues (toplevel, XtNsessionID, &smcid, NULL); restart_command[0] = argv[0]; restart_command[2] = XtNewString (smcid); restart_command[4] = "0"; XtVaSetValues (toplevel, XtNrestartCommand, restart_command, NULL); /* Set up the session manager callbacks */ XtAddCallback (toplevel, XtNsaveCallback, save_callback, NULL); XtAddCallback (toplevel, XtNcancelCallback, save_complete_callback, NULL); XtAddCallback (toplevel, XtNsaveCompleteCallback, save_complete_callback, NULL); XtAddCallback (toplevel, XtNdieCallback, die_callback, NULL); n = 0; XtSetArg (args[n], XmNspinBoxChildType, XmNUMERIC); n++; XtSetArg (args[n], XmNcolumns, 2); n++; XtSetArg (args[n], XmNeditable, FALSE); n++; XtSetArg (args[n], XmNposition, spin_value); n++; XtSetArg (args[n], XmNminimumValue, 0); n++; XtSetArg (args[n], XmNmaximumValue, 99); n++; XtSetArg (args[n], XmNwrap, TRUE); n++; spin = XmCreateSimpleSpinBox (toplevel, "spin", args, n); XtManageChild (spin); XtRealizeWidget (toplevel); XtAppMainLoop (app); }
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 non-standard 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.Example 20-9 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 Example 20-6, this program is a skeletal frame only; it does not have any real functionality.11
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 Figure 20-3./* 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 (int argc, char *argv[]) { Widget toplevel; XtAppContext app; Atom MOTIF_MSGS, MY_PROTOCOL; void my_proto_callback(Widget, XtPointer, XtPointer); char buf[64]; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, XmNwidth, 100, XmNheight, 100, NULL); /* get the MOTIF_MSGS and MY_PROTOCOL atoms */ MY_PROTOCOL = XInternAtom (XtDisplay (toplevel), "_MY_PROTOCOL", False); MOTIF_MSGS = XInternAtom (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 widget, XtPointer client_data, XtPointer call_data) { puts ("My protocol got activated!"); }
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 6B, 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:
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.label [mnemonic] [accelerator] functionIt 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 Figure 20-2.
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 1, Xlib Programming Manual. However, if you are interested in providing this kind of functionality, you might consider the following design approach:
When an application is interested in communicating via a private protocol, it should place a property on its top-level windows that express this interest. For example, let's call this atom _MYAPP_CLIENT_PROP. The atom can be added to the WM_PROTOCOLS property already on the window using XmAddWMProtocol(), just as we did earlier.An application can also choose to use XChangeProperty() to actually use the atom as the property itself; XChangeProperty() adds a new property to a window's list of existing properties.
An application interested in seeking out other windows that have expressed interest in _MYAPP_CLIENT_PROP can call XQueryTree() to start at the root window and search all of its immediate children for those windows that have that property. The function XGetWindowProperty() can be used to test for the existence of the property itself.
When an application finds a window that contains the property, it can use XSendEvent() to send an XClientMessageEvent to that window. When sending a client message, the application can either do what the Motif toolkit does and send a WM_PROTOCOLS message, or it can just send the _MYAPP_CLIENT_PROP atom itself. If the program uses the first technique, the data.l[0] field of the XClientMessageEvent data structure contains the value WM_PROTOCOLS, and the data.l[1] field contains _MYAPP_CLIENT_PROP.If the receiving window is part of a Motif application that has registered a callback function for this protocol, the function is invoked.12
If the sending application wishes to send any additional data to the receiving application, it should either add or replace the receiving window's _MYAPP_CLIENT_PROP property and upgrade or change its value. 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.
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.
Exercises
These exercises are designed to help you understand the material that was presented in this chapter.
- Write a program that always places its error dialogs in the center of the screen.
- Whenever a shell changes from normal state to iconic state, the window manager changes the shell's WM_STATE property. Write a program that gets the PropertyNotify event generated from this state change so that you can track when a shell is iconified and de-iconified. Use XtAddEventHandler() to register a routine that tracks for the event in the same way we tracked for ConfigureNotify events in set_minimum.c
- Write a program so that when the user selects the Close button from a window menu, the shell iconifies itself if it is a TopLevelShell, and destroys itself if it is a DialogShell.
1 Atoms are used to avoid the overhead of passing property names as arbitrary-length strings. See Volume 1, Xlib Programming Manual, and Volume 4, X Toolkit Intrinsics Programming Manual for a detailed discussion of properties and atoms.2 The ApplicationShell widget class is considered deprecated in X11R6, and is superseded by the SessionShell.
3 See Volume 0, Xlib Programming Manual, for complete details on the properties that can be set on windows; see Volume 1, X Toolkit Intrinsics Programming Manual for details on how to set or get these properties.
4 XtVaAppInitialize() is considered deprecated in X11R6.
5 XtVaAppInitialize() is considered deprecated in X11R6.
6 XtVaAppInitialize() is considered deprecated in X11R6.
7 XtVaAppInitialize() is considered deprecated in X11R6. XmMessageBoxGetChild() is deprecated from Motif 2.0. XmInternAtom() is marked for deprecation from Motif 2.0.
8 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.
9 XtVaAppInitialize() is considered deprecated in X11R6. XInternAtom() is marked for deprecation from Motif 2.0.
10 The Solaris (and other) manual pages for this routine list the function in the form:
This is a bug: there is no type parameter.XtCheckpointToken XtSessionGetToken (Widget sessionShell, int type)11 XtVaAppInitialize() is considered deprecated in X11R6. XmInternAtom() is marked for deprecation in Motif 2.0.
12 Whether or not the receiving application is a Motif application, it can set up its own event handler to trap for the client message.
|