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.
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.
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.
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.
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 XmNbaseHeightThe 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.
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.
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.
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.
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:
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.
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:
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.
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.
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:
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.
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 filenamethe 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.
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.
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] functionOnly 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:
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.
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.
These exercises are designed to help you understand
the material that was presented in this chapter.