Contents Previous Next

18 The Clipboard


This chapter describes a way for the application to interact with other applications. Data is placed on the clipboard, where it can be accessed by other windows on the desktop regardless of the applications with which they are associated.

Imagine a group of people in a room; the only way for them to communicate is by writing messages on paper, placing the paper on a clipboard, and passing the clipboard around. A single person acts as the moderator and holds the clipboard at all times. If someone wants to post a note, she writes the message on a slip of paper and hands the message to the moderator. The note is now available for anyone to read. However, those who read the message do not remove the message from the clipboard; rather, they copy what was written. There is no guarantee that anyone will want to look at any particular message, but it is there nonetheless and will remain there until someone writes a new one.

This scenario is the concept behind the Motif clipboard: a data transfer mechanism that enables widgets to make data available for other widgets, including those in separate applications. Information of any size or type can be passed using the clipboard interface. The most common example of this data transfer model is cut and paste, a method by which the user can move or copy text between windows. Here, the user interacts with a Text widget that contains some text that she wishes to transfer to another Text widget. The user first selects the text she wants to transfer by clicking the left mouse button and dragging it across the entire area to be copied. Then, she moves the pointer to the target widget and pastes the text by clicking the middle mouse button. This is the default cut and paste user model; the user may override it using resources or keyboard equivalents. The actual method for performing this task is not the point of discussion here.

This action causes the text to appear to be copied to the new window. However, the text does not actually move; it is copied to the clipboard, from which the second widget then copies it into its own window. The original data may have been changed or destroyed since it was sent to the clipboard, but that is of no concern to the second widget.

An object that wishes to place data on the clipboard or read data from it is called a client of the clipboard (one of the people in our imaginary room). Since only one client may access the clipboard at a time, whether it is storing or retrieving data, requesting access to the clipboard implies "locking" it. If another widget already has locked the clipboard, the client must wait and ask for it again later (after the current holder has "unlocked" it).

Now, imagine that the people in the room have all sorts of items besides text messages they wish to make available for copy. Some may have pictures, records, tapes--anything. Their "cargo" must be deliverable by the moderator to anyone who requests it. To deal with this situation, the moderator must know what type of cargo she will be handling. Therefore, certain information must be registered with the moderator before cargo may be sent or received through the clipboard mechanism. Once a particular cargo type is registered, anyone may post or request such cargo to or from the moderator.

In the Motif toolkit, different types of cargo are referred to as formats. With respect to the X server and client applications, text messages are the most commonly used format of clipboard messages and are therefore registered by default. There are also other types that are automatically registered, such as integers. A complete list is given in Section #sclipformat. Application-specific data structures must be registered separately, perhaps on a per-application basis. Once a new data type is registered, even clients that exist on other computer architectures where data is not represented identically (e.g., due to byte swapping) can use that data type, since the clipboard registration handles the proper data conversion.

There are some situations where it is impractical to place complete information on the clipboard. Some people's cargo may be "too heavy" for the clipboard to hold indefinitely. Other people may have perishables that don't last very long. Still others may have information that varies with the state of the world. For these cases, the person with the special cargo may choose to leave only some information about their cargo rather than the cargo itself. This information might include its weight, type, name and/or reference number, for example. Potential recipients may then examine the clipboard and inquire about the cargo without having to get it or even look at it. Only in the event that someone else wishes to obtain the cargo is the original owner called upon to provide it.

In the Motif world, this scenario describes clipboard data that is available by name. For example, if a client wishes to place an entire file on the clipboard, it might choose to register the file by name without providing the actual contents unless someone requests it. This may save a lot of time and resources, since it's possible that no one will request it. Referencing data this way is very cheap and is not subject to expiration or obsolescence.

When posting messages by name, the client must provide the clipboard with a callback function that returns the actual data. This callback function may be called by the Motif toolkit at any time, provided another client requests the data. If the data is time-dependent or subject to other criteria (someone removed or changed the file), the callback routine may respond accordingly.

The Motif clipboard functions are based on X's Inter-Client Communications Conventions Manual (ICCCM). Knowledge of these conventions will aid greatly in your understanding of how these functions are implemented. However, knowledge of the implementation is not required in order to understand the concepts involved here or to be able to use the clipboard effectively through Motif's application interface. This chapter does not address many of the issues involved with the ICCCM and the lower-level Xlib properties that implement them. Rather, it only addresses the highest level of interaction provided by the Motif toolkit.

Also note that the clipboard is one of three commonly used mechanisms to support interclient communication. There are also the primary and secondary selections, which are similar in nature, but are handled differently at the application and user level. The Motif toolkit supports convenience routines that interact with clipboard selections only. To use the other selection mechanisms, you must use X Toolkit Intrinsics functions that were discussed in Volume Four, X Toolkit Intrinsics Programming Manual . Note, however, that the Text widget supports both mechanisms.

18.1 Simple Clipboard Copy and Retrieval

To introduce the application programmer's interface (API) for the clipboard functions, we demonstrate how to handle simple copy and retrieval of text. The cut and paste functions provided by the Text widgets handle copy and retrieval from the clipboard in the manner we are about to describe; they also support interaction with the primary and secondary selection mechanisms. However, as pointed out in Chapter 14, Text Widgets, these functions are usually reserved for interactive actions taken by the user. Fortunately, Motif provides many convenience functions that facilitate the task of dealing with the clipboard for Text widgets. This section discusses the techniques used by the Text widget when it interacts with the clipboard.

Let's begin with the short program in the source code This program creates two PushButtons that have complementary callback routines: to_clipbd() copies text to the clipboard and from_clipbd() retrieves text from the clipboard. For this example, the text copied to the clipboard is arbitrary; we happen to use a string that represents the number of times the Copy to Clipboard button is pressed. 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.

   /* copy_retrieve.c -- simple copy and retrieve program.  Two
    * pushbuttons: the first places text in the clipboard, the other
    * receives text from the clipboard.  This just demonstrates the
    * API involved.
    */
   #include <Xm/CutPaste.h>
   #include <Xm/RowColumn.h>
   #include <Xm/PushB.h>

   static void to_clipbd(), from_clipbd();

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

       XtSetLanguageProc (NULL, NULL, NULL);

       /* Initialize toolkit, application context and toplevel shell */
       toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0,
           &argc, argv, NULL, NULL);

       /* manage two buttons in a RowColumn widget */
       rowcol = XtVaCreateWidget ("rowcol", xmRowColumnWidgetClass,
           toplevel, NULL);

       /* button1 copies to the clipboard */
       button = XtVaCreateManagedWidget ("button1",
           xmPushButtonWidgetClass, rowcol,
           XtVaTypedArg, XmNlabelString, XmRString,
               "Copy To Clipboard", 18, /* strlen() + 1 */
           NULL);
       XtAddCallback (button, XmNactivateCallback, to_clipbd, "text");

       /* button2 retrieves text stored in the clipboard */
       button = XtVaCreateManagedWidget ("button2",
           xmPushButtonWidgetClass, rowcol,
           XtVaTypedArg, XmNlabelString, XmRString,
               "Retrieve From Clipboard", 24, /* strlen() + 1 */
           NULL);
       XtAddCallback (button, XmNactivateCallback, from_clipbd, NULL);

       /* manage RowColumn, realize toplevel shell and start main loop */
       XtManageChild (rowcol);
       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   /* copy data to clipboard. */
   static void
   to_clipbd(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       unsigned long item_id = 0;  /* clipboard item id */
       int           status;
       XmString      clip_label;
       char          buf[32];
       static int    cnt;
       Display      *dpy = XtDisplayOfObject (widget);
       Window        window = XtWindowOfObject (widget);
       char         *data = (char *) client_data;

       sprintf (buf, "%s-%d", data, ++cnt); /* make each copy unique */

       clip_label = XmStringCreateLocalized ("to_clipbd");

       /* start a copy -- retry till unlocked */
       do
           status = XmClipboardStartCopy (dpy, window,
               clip_label, CurrentTime, NULL, NULL, &item_id);
       while (status == ClipboardLocked);

       XmStringFree (clip_label);

       /* copy the data (buf) -- pass "cnt" as private id for kicks */
       do
           status = XmClipboardCopy (dpy, window, item_id, "STRING",
               buf, (long) strlen (buf)+1, cnt, NULL);
       while (status == ClipboardLocked);

       /* end the copy */
       do
           status = XmClipboardEndCopy (dpy, window, item_id);
       while (status == ClipboardLocked);

       printf ("Copied
   }

   static void
   from_clipbd(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       int         status, private_id;
       char        buf[32];
       Display    *dpy = XtDisplayOfObject (widget);
       Window      window = XtWindowOfObject (widget);

       do
           status = XmClipboardRetrieve (dpy, window,
               "STRING", buf, sizeof (buf), NULL, &private_id);
       while (status == ClipboardLocked);

       if (status == ClipboardSuccess)
           printf ("Retrieved
   }
The program uses the header file <Xm/CutPaste.h> to include the appropriate function declarations and various constants. Don't let the name of the file confuse you. CutPaste.h is derived from the phrase "cut and paste," which historically has been used to describe clipboard-type operations. The to_clipbd() callback routine uses the following clipboard functions to copy data to the clipboard:
   XmClipboardStartCopy()
   XmClipboardCopy()
   XmClipboardEndCopy()
Copying data to the clipboard is a three-phase process. Each of the functions locks the clipboard so that other clients cannot access it. Since locking the clipboard is done on a per-window basis, the object that locks the clipboard should have an associated window, which means that gadgets may not work. Gadgets happen to work in some cases because of their window-based widget parents. However, some of the clipboard functions use XtWindow() rather than XtWindowOfObject() to get the window of an object. These functions do not work for gadgets. When the clipboard is locked, only requests from objects with the same window ID can access the clipboard. Each time an object requests a lock on the clipboard, a counter is incremented so that matching unlock requests can be honored.

XmClipboardStartCopy() sets up internal storage for the copy to take place, XmClipboardCopy() sends the data to the clipboard, and XmClipboardEndCopy() frees the internal supporting structures. When copying data to the clipboard, including copies by name, all three functions must be used.

The from_clipbd() callback routine uses XmClipboardRetrieveCopy() to re­trieve data from the clipboard. Only a single call is needed for the retrieval of short items, as in this example. However, a three-step process similar to that for copying data to the clipboard is required for the incremental retrieval of large amounts of data. We will cover these functions shortly.

18.1.1 Copying Data

The syntax of the functions that copy data to the clipboard is outlined below. Due to the intricacies involved in providing data to the clipboard, these functions take a larger number of parameters than you might expect from the simple examples given so far. Later examples should clarify the intended usage of these functions and their corresponding parameters. Each of the routines takes a pointer to the Display and the Window associated with the object making the clipboard request. These parameters may be derived from any widget or gadget using XtDisplayOfObject() and XtWindowOfObject().

XmClipboardStartCopy() takes the following form:

   int
   XmClipboardStartCopy(display, window, label, timestamp,
                        widget, callback, item_id)
       Display        *display;
       Window          window;
       XmString        label;
       Time            timestamp;
       Widget          widget;
       XmCutPasteProc  callback;
       long           *item_id;
The widget and callback parameters are only used when registering data by name (see Section #sclipbyname). Although the label parameter is currently unused, its purpose is to label the data so that certain applications can view the contents of the clipboard. The timestamp identifies the server time when the cut took place (CurrentTime is the typical value). The item_id parameter is filled in by the toolkit and is returned to the client for use in subsequent clipboard function calls. This value identifies the item's entry in the clipboard.

XmClipboardCopy() has the following form:

   int
   XmClipboardCopy(display, window, item_id, format_name,
                   buffer, length, private_id, data_id)
       Display        *display;
       Window          window;
       long            item_id;
       char           *format_name;
       XtPointer       buffer;
       unsigned long   length;
       int             private_id;
       int            *data_id;

XmClipboardCopy() copies the data in buffer to the clipboard. The format of the data is described by the format_name parameter. This value is not a type, but a string ­describing the type. For example, "STRING" indicates that the data is a text string. The length parameter is the size of the data. Text strings, can use strlen (data).

The item_id parameter is the ID returned by XmClipboardStartCopy(). The data_id parameter returns the format ID. You may pass NULL for this parameter if you are not interested in the value, however you may need it for other functions. For example, you will need it if you wish to withdraw an item from the clipboard. We will discuss this issue later when we talk about registration by name. The private_id parameter is an arbitrary number that is application-defined. The value is passed back to various functions, including those that handle calling by name, so we will address it further in Section #sclipbyname.

When copying is done, XmClipboardEndCopy() is called to free the internal data structures associated with the clipboard item. The routine takes the following form:

   int
   XmClipboardEndCopy(display, window, item_id)
       Display *display;
       Window   window;
       long     item_id;
The item_id parameter is the ID returned by the call to XmClipboardStartCopy().

The clipboard copy functions return one of three status values: ClipboardSuccess, ClipboardLocked, or ClipboardFail. If the client is successful in gaining access to the clipboard, the routine returns ClipboardSuccess. If another client is already accessing the clipboard, the clipboard is locked and the client can loop repeatedly to attempt to gain access.

Motif keeps a stack of items that have been placed on the clipboard using any of the clipboard functions. As of Release 1.1, the stack depth is set to two. If a third item is added, the older of the other two is removed. Once a copy to the clipboard is complete, you can undo it using XmClipboardUndoCopy(), which takes the following form:

   int
   XmClipboardUndoCopy(display, window)
       Display *display;
       Window   window;
Calling XmClipboardUndoCopy() twice undoes the last undo. Thus, undoing a copy simply swaps the two elements on the clipboard stack. You can remove an item that you have placed on the clipboard using XmClipboardWithdrawFormat(). This routine is discussed in Section #sclipbyname.

18.1.2 Retrieving Data

In the source code we retrieved the data stored on the clipboard using the function XmClipboardRetrieve(). This function takes the following form:

   int
   XmClipboardRetrieve(display, window, format_name, buffer,
                       length, num_bytes, private_id)
       Display        *display;
       Window          window;
       char           *format_name;
       char           *buffer;
       unsigned long   length;
       unsigned long  *num_bytes;
       int            *private_id;
When using XmClipboardRetrieve(), you must provide buffer space to retrieve the data. In our example, we know that the data is not very large, so we declared buffer to have 32 bytes, which is more than adequate. The length parameter tells the clipboard how much space is available in buffer. The num_bytes parameter is the address of an unsigned long variable. This value is filled in by XmClipboardRetrieve() to indicate how much data it gave us. The private_id parameter is the address of an int; its value is the same as the private_id parameter passed to XmClipboardCopy(). You can pass NULL as this parameter if you are not interested in it.

If the routine is successful in retrieving the data, it returns ClipboardSuccess. If the clipboard is locked, the function returns ClipboardLocked. A rare internal error may cause the function to return ClipboardFail. If the routine does not succeed, you can choose to loop repeatedly to attempt to retrieve data.

One problem with XmClipboardRetrieve() occurs when there is more data in the clipboard than buffer space to contain it. In this case, the function copies only length bytes into buffer and sets num_bytes to the number of bytes it copied, which should be the same value as length if not enough space is available. If this situation arises, the function returns ClipboardTruncate to indicate that it did not copy everything that is available. Since we cannot just arbitrarily specify a larger data space without knowing how much data there is, we have two choices: query the clipboard to find out how much data there is or copy the data incrementally. There are advantages and disadvantages to each method. Let's start by discussing incremental retrieval.

To do an incremental retrieval, we need to introduce two functions: XmClipboardStartRetrieve() and XmClipboardEndRetrieve(). These functions are similar to the start and end copy functions discussed earlier. XmClipboardStartRetrieve() takes the following form:

   int
   XmClipboardStartRetrieve(display, window, timestamp)
       Display  *display;
       Window    window;
       Time      timestamp;
This function locks the clipboard and notes the timestamp . Data placed on the clipboard after this time is considered invalid and the function returns ClipboardFailed. The constant CurrentTime is typically used as this value. It is also common to provide the timestamp found in an event structure when available. This technique is typically used when the clipboard retrieval is initiated as a result of an action or callback routine where an event structure is available. XmClipboardStartRetrieve() also allocates internal data structures to support the incremental retrieval operation. Once the function is called, multiple calls to XmClipboardRetrieve() can be made until it returns ClipboardSuccess. While the routine returns ClipboardTruncate , more data needs to be read and you should continue to call the function. Be careful to save the data that has already been retrieved before the next call to the function, or you may overwrite the old data and lose information.

Once all of the data has been retrieved, call XmClipboardEndRetrieve(), which takes the following form:

   int
   XmClipboardEndRetrieve(display, window)
       Display   *display;
       Window     window;
This function unlocks the clipboard and frees the internal data structures. the source code shows a callback routine that retrieves data from the clipboard incrementally. The from_clipbd_incr() routine could replace the from_clipbd() callback routine in Example 17-1.
   static void
   from_clipbd_incr(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       int           status;
       unsigned      total_bytes;
       unsigned long received;
       char         *data = NULL, buf[32];
       Display      *dpy = XtDisplayOfObject (widget);
       Window        window = XtWindowOfObject (widget);

       do
           status = XmClipboardStartRetrieve (dpy, window, CurrentTime);
       while (status == ClipboardLocked);

       /* initialize data to contain at least one byte. */
       data = XtMalloc (1);
       total_bytes = 1;
       do  {
           /* retrieve data from clipboard -- if locked, try again */
           status = XmClipboardRetrieve (dpy, window, "STRING",
               buf, sizeof (buf), &received, NULL);

     /* reallocate data to contain enough space for everything */
     if (!(data = XtRealloc (data, total_bytes + received))) {
         XtError ("Can't allocate space for data");
         break; /* XtError may or may not return */
     }

     /* copy buf into data.  strncpy() does not NULL terminate */
     strncpy (&data[total_bytes-1], buf, received);
     total_bytes += received;
       } while (status == ClipboardTruncate);

       if (data)
           data[total_bytes] = 0; /* NULL terminate */

       if (status == ClipboardSuccess)
           printf ("Retrieved

       status = XmClipboardEndRetrieve (dpy, window);
   }
The callback routine works regardless of the amount of data held by the clipboard. If the client placed an entire file on the clipboard, the routine would read all of it in 32-byte increments. It is probably wise to use a larger block size when retrieving data incrementally; the constant BUFSIZ is a good default choice. BUFSIZ is defined in <stdio.h>.

The primary advantage of using the incremental retrieval method is that you do not need to allocate a potentially large amount of memory at one time. By segmenting memory, you can reuse some of it, or even discard it as each increment is read. This technique is especially useful if you are scanning for specific data and you have no intention of actually saving everything that you retrieve.

18.1.3 Querying the Clipboard for Data Size


The problem with incremental retrieval is that numerous round trips to the server may be necessary in order to obtain the entire contents of the clipboard. If you intend to save every bit of information you retrieve, the most economical way to handle the retrieval is by reading everything in one fell swoop. A single call to XmClipboardRetrieve() is more convenient than the three-step process involving locking the clipboard.

However, as pointed out earlier, we have a problem since we do not know how much data there is to read. The solution to the problem is to determine exactly how much data there is by using XmClipboardInquireLength(). This routine has the following form:

   int
   XmClipboardInquireLength(display, window, format_name, length)
       Display       *display;
       Window         window;
       char          *format;
       unsigned long *length;
The function returns the amount of data being held by the clipboard under the specified format_name. In Example 17-3, we are looking for data in the "STRING" format. If any data on the clipboard is in this format, the function returns ClipboardSuccess and the length parameter is set to the number of bytes being held. If there is no data on the clipboard in the specified format, the function returns ClipboardNoData. If length is not set to a value other than 0, the data cannot be read from the clipboard.

If XmClipboardInquireLength() is successful, then the number of bytes specified by length can be allocated and the data can be retrieved in one call to XmClipboardRetrieve(). the source code shows a callback routine that retrieves data from the clipboard after querying the size of the data. The from_clipbd_query() routine could replace the from_clipbd() callback routine in Example 17-1.

   static void
   from_clipbd_query(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       int         status, recvd, length;
       char       *data;
       Display    *dpy = XtDisplayOfObject (widget);
       Window      window = XtWindowOfObject (widget);

       do
           status = XmClipboardInquireLength (dpy, window, "STRING", &length);
       while (status == ClipboardLocked);

       if (length == 0)
           printf ("No data on clipboard in specified format.0);

       data = XtMalloc (length+1);

       do
           status = XmClipboardRetrieve (dpy, window,
               "STRING", data, length+1, &recvd, NULL);
       while (status == ClipboardLocked);

       if (status != ClipboardSuccess || recvd != length) {
           printf ("Failed to receive all clipboard data0);
     XtFree (data);
       }
       else
           printf ("Retrieved
   }

18.2 Copy by Name

As discussed earlier, there are cases where data should not be c­opied to the clipboard until it is requested. It is possible to copy data by name, so that the owner of the data is notified through a callback function when the data is needed by the clipboard. Since copying large amounts of data may be expensive, time-consuming, or even impossible due to other constraints in an application, copying data by name may be the only option available. The technique is especially advantageous if the data is never requested, since time and resources are saved.

The procedure for copying data by name is quite similar to the procedure for normal copying. The application first calls XmClipboardStartCopy(), but unlike a normal copy operation, the callback and widget parameters are specified. These values indicate that the data is to be copied by name. The callback parameter specifies the routine that is called when the data is requested by another client. The widget parameter specifies the widget that receives the messages requesting the data. Since the toolkit handles the messages, any valid widget ID can be used.

XmClipboardCopy() is then called with a buffer value of NULL. XmClipboardEndCopy() is called as usual. When a client requests the data from the clipboard, the callback routine provided to XmClipboardStartCopy() is called and the application provides the actual data using XmClipboardCopyByName().

You can use the convenience function XmClipboardBeginCopy() instead of XmClipboardStartCopy(). The only difference between the two routines is that the convenience function does not take a timestamp parameter; it simply uses CurrentTime as the timestamp value.

The program shown in the source code demonstrates copying data to the clipboard by name.

   /* copy_by_name.c -- demonstrate clipboard copies "by-name".
    * Copying by name requires that the copy *to* clipboard
    * functions use the same window as the copy *from* clipboard
    * functions.  This is a restriction placed on the API by the
    * toolkit, not by the ICCCM.
    */
   #include <Xm/CutPaste.h>
   #include <Xm/RowColumn.h>
   #include <Xm/PushB.h>

   static void to_clipbd(), from_clipbd();
   Widget toplevel;

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

       XtSetLanguageProc (NULL, NULL, NULL);

       /* Initialize toolkit, application context and toplevel shell */
       toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0,
           &argc, argv, NULL, NULL);

       /* manage two buttons in a RowColumn widget */
       rowcol = XtVaCreateWidget ("rowcol",
           xmRowColumnWidgetClass, toplevel,
           NULL);

       /* button1 copies to the clipboard */
       button = XtVaCreateManagedWidget ("button1",
           xmPushButtonWidgetClass, rowcol,
           XtVaTypedArg, XmNlabelString, XmRString,
               "Copy To Clipboard", sizeof (char *),
           NULL);
       XtAddCallback (button, XmNactivateCallback, to_clipbd, NULL);

       /* button2 retrieves text stored in the clipboard */
       button = XtVaCreateManagedWidget ("button2",
           xmPushButtonWidgetClass, rowcol,
           XtVaTypedArg, XmNlabelString, XmRString,
               "Retrieve From Clipboard", sizeof (char *),
           NULL);
       XtAddCallback (button, XmNactivateCallback, from_clipbd, NULL);

       /* manage RowColumn, realize toplevel shell and start main loop */
       XtManageChild (rowcol);
       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   static void
   copy_by_name(widget, data_id, private_id, reason)
   Widget widget;
   int *data_id;
   int *private_id;
   int *reason;
   {
       Display      *dpy = XtDisplay (toplevel);
       Window        window = XtWindow (toplevel);
       static int    cnt;
       int           status;
       char          buf[32];

       printf ("Copy by name called0reason: %s, private_id: %d, data_id: %d0,
           *reason == XmCR_CLIPBOARD_DATA_REQUEST? "request" : "delete",
           *private_id, *data_id);

       if (*reason == XmCR_CLIPBOARD_DATA_REQUEST) {
           sprintf (buf, "stuff-%d", ++cnt); /* make each copy unique */

           do
               status = XmClipboardCopyByName (dpy, window, *data_id, buf,
                   strlen (buf)+1, *private_id = cnt);
           while (status != ClipboardSuccess);
       }
   }

   /* copy data to clipboard */
   static void
   to_clipbd(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       unsigned long item_id = 0;  /* clipboard item id */
       int           status;
       XmString      clip_label;
       Display      *dpy = XtDisplay (toplevel);
       Window        window = XtWindow (toplevel);

       clip_label = XmStringCreateLocalized ("to_clipbd");

       /* start a copy.  retry till unlocked */
       do
           status = XmClipboardBeginCopy (dpy, window,
               clip_label, widget, copy_by_name, &item_id);
       while (status == ClipboardLocked);

       /* copy by name by passing NULL as the "data", copy_by_name() as
        * the callback and "widget" as the widget.
        */
       do
           status = XmClipboardCopy (dpy, window, item_id, "STRING",
               NULL, 8L, 0, NULL);
       while (status == ClipboardLocked);

       /* end the copy */
       do
           status = XmClipboardEndCopy (dpy, window, item_id);
       while (status == ClipboardLocked);
   }

   static void
   from_clipbd(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       int           status;
       unsigned      total_bytes;
       unsigned long received;
       char         *data = NULL, buf[32];
       Display      *dpy = XtDisplay (toplevel);
       Window        window = XtWindow (toplevel);

       do
           status = XmClipboardStartRetrieve (dpy, window, CurrentTime);
       while (status == ClipboardLocked);

       /* initialize data to contain at least one byte. */
       data = XtMalloc (1);
       total_bytes = 1;
       do  {
           buf[0] = 0;
           /* retrieve data from clipboard -- if locked, try again */
           status = XmClipboardRetrieve (dpy, window, "STRING",
                   buf, sizeof (buf), &received, NULL);
           if (status == ClipboardNoData) {
               puts ("No data on the clipboard");
               break;
           }
           /* reallocate data to contain enough space for everything */
           if (!(data = XtRealloc (data, total_bytes + received))) {
               XtError ("Can't allocate space for data");
               break; /* XtError may or may not return */
           }
           /* copy buf into data.  strncpy() does not NULL terminate */
           strncpy (&data[total_bytes-1], buf, received);
           total_bytes += received;
       } while (status == ClipboardTruncate);
       data[total_bytes-1] = 0; /* NULL terminate */

       if (status == ClipboardSuccess)
           printf ("Retrieved
               data, total_bytes);

       status = XmClipboardEndRetrieve(dpy, window);
   }
Just as in Example 17-1, the function to_clipbd() is used to initiate copying data to the clipboard. However, rather than passing actual data, we use:
   status = XmClipboardBeginCopy (dpy, window,
       clip_label, widget, copy_by_name, &item_id);
Passing a valid widget and a callback routine indicates that the copy-by-name method is being used. Here, the data is provided through the given callback routine when it is requested, rather than being provided immediately. The item_id parameter is filled in by the clipboard function to identify the particular data element. The parameter is then used in the call to copy data:
   status = XmClipboardCopy (dpy, window, item_id, "STRING",
       NULL, 8L, 0, NULL);
Passing NULL as the data also indicates that the data is passed by name. The value 8L is passed as the size parameter to indicate how much data will be sent if the data is requested. This value is important in case other clients query the clipboard to find out how much data is available to copy.

The callback function copy_by_name() is called either when someone requests the data from the clipboard or when another client copies new data (by name or with actual data) to the clipboard. In the first case, the data must be copied to the clipboard; in the second case, the clipboard is telling the client that it can now free its data. The callback function is an XmCutPasteProc, which takes the following form:

   typedef void
   (*XmCutPasteProc) (Widget, * int, * int, * int)
       Widget widget;
       int *data_id;
       int *private_id;
       int *reason;
The widget parameter is the same as that passed to XmClipboardStartCopy(). The data_id arguemnt is the ID of the data item that is returned by XmClipboardCopy(), and private_id is the private data passed to XmClipboardCopy(). The reason parameter takes the value XmCR_CLIPBOARD_DATA_REQUEST, which indicates that the data must be copied to the clipboard, or XmCR_CLIPBOARD_DATA_DELETE , which indicates that the client can delete the data from the clipboard. Although the last three parameters are pointers to integer values, the values are read-only and changing them has no effect.

The purpose of the function is either to send the appropriate data to the clipboard or to free the data. The value of reason determines which action is taken. Since no data is passed to the clipboard until this callback function is called, either the data must be stored locally (in the application) or the function must be able to generate it dynamically. The example makes no assumptions or suggestions about how to create the data, since it is entirely subject to the nature of the data and/or the application.

Once the data is obtained, it is sent to the clipboard using XmClipboardCopyByName(). This function does not need to lock the clipboard since the clipboard is already being locked by the window that called XmClipboardRetrieve(). At this point in time, both routines are accessing the clipboard. If the same application is both retrieving the data and copying the data, the XmClipboardRetrieve() and XmClipboardCopyByName() routines must use the same window for their respective window parameters because otherwise deadlock will occur and the application will hang. There may be cases where you should copy data to the clipboard incrementally. The data may be large enough that allocating one large data space to handle the entire copy is unreasonable; its size may warrant sending it in smaller chunks. Moreover, data may be generated by a slow mechanism such as a database library. If the database only returns data in specific block sizes, then you need not buffer them all up and send to the clipboard with one call; you can send each block as it comes through.

Incremental copying requires multiple calls to XmClipboardCopyByName(). Since XmClipboardCopyByName() does not lock the clipboard, you need to do that yourself by calling XmClipboardLock(). However, you only need to call it once no matter how much data is transferred. When you are through copying the data, you need to call XmClipboardUnlock(). In some cases, you may need to stop sending data before the copy is complete. For example, if the database is not responding to your application or there are other extenuating circumstances, you may want to terminate the copy operation using XmClipboardCancelCopy(), which has the following form:

   int
   XmClipboardCancelCopy(display, window, item_id)
       Display *display;
       Window   window;
       long     item_id;
When using XmClipboardCancelCopy, you should not unlock the clipboard using XmClipboardUnlock().

If you have copied data by name to the clipboard under a specific data format, you may withdraw it by calling XmClipboardWithdrawFormat(). The function takes the following form:

   int
   XmClipboardWithdrawFormat(display, window, data_id)
       Display *display;
       Window   window;
       int      data_id;
Despite the name of the procedure, its main purpose is not to remove a format specification, but to remove a data element in that format from the clipboard. The data_id parameter is the same value that is returned by XmClipboardCopy() when the data is initially copied by name. If the specified window holds the clipboard data but it is in a different format than that specified by data_id, then the data is not removed from the clipboard.

18.3 Clipboard Data Formats

As discussed in the introduction, the clipboard can contain data in arbitrary formats. While the most commonly used format is text, other formats include integers, pixmaps, and arbitrary data structures. Since all applications on the desktop have access to the clipboard, any of them may register a new format and place items of that type on the clipboard.

When registering a new format, you must also register a corresponding format name and the format length in bits (8, 16, and 32). Determining the type of data on the clipboard is much easier when there is a descriptive name associated with it. The length allows applications to send and receive data without suffering from byte-swapping problems due to differing computer architectures.

To register a new format, use XmClipboardRegisterFormat(), which takes the following form:

   int
   XmClipboardRegisterFormat(display, format_name, format_length)
       Display       *display;
       char          *format_name;
       unsigned long  format_length;
The function may return ClipboardBadFormat if the format name is NULL or the format length is other than 8, 16, or 32. The format length may be specified as 0, in which case Motif will attempt to look up the default length for the given name. shows the format lengths for some predefined format names.

tab(@), linesize(2); l | l lfCW | nfCW . Format Name@Format Length
_
"TARGETS"@32 "MULTIPLE"@32 "TIMESTAMP"@32 "STRING"@8 "LIST_LENGTH"@32 "PIXMAP"@32 "DRAWABLE"@32 "BITMAP"@32 "FOREGROUND"@32 "BACKGROUND"@32 "COLORMAP"@32 "ODIF"@8 "OWNER_OS"@8 "FILE_NAME"@8 "HOST_NAME"@8 "CHARACTER_POSITION"@32 "LINE_NUMBER"@32 "COLUMN_NUMBER"@32 "LENGTH"@32 "USER"@8 "PROCEDURE"@8 "MODULE"@8 "PROCESS"@32 "TASK"@32 "CLASS"@8 "NAME"@8 "CLIENT_WINDOW"@32
_ Although these format names are known, they are not necessarily registered automatically with the server; you may still need to register the one(s) you want to use. If you are specifying your own data structure as a format, you should choose an appropriate name for it and use 32 as the format size.

The following code fragment shows how you can register a data format and then copy data in that format to the clipboard:

   unsigned long item_id;
   int           data_id;
   Display      *dpy = XtDisplay (widget);
   Window        window = XtWindow (widget);
   XmString      label = XmStringCreateLocalized ("my data");

   /* register our own data structure with clipboard. */
   XmClipboardRegisterFormat (dpy, "MY_DATA_STRUCT", 32);

   /* use the copy-by-name method to transfer data to clipboard */
   do
       status = XmClipboardStartCopy (dpy, window, label, CurrentTime,
           my_data_callback, widget, &item_id);
   while (status == ClipboardLocked);

   XmStringFree (label); /* don't need this anymore */

   /* MY_DATA_SIZE is presumed to be the amount of data to transfer */
   do
       status = XmClipboardCopy (dpy, window, item_id, "MY_DATA+STRUCT",
           NULL, MY_DATA_SIZE, 0, &data_id); /* save the data_id! */
   while (status == ClipboardLocked);

   do
       status = XmClipboardEndCopy (dpy, window, item_id);
   while (status == ClipboardLocked);
Once the "MY_DATA_STRUCT" format has been registered with the server, we follow the standard procedure for copying data to the clipboard. Here, we chose to use the copy-by-name method discussed earlier. Note that we save the value of the data_id returned by XmClipboardCopy(). This value is used so that we may withdraw the data later using XmClipboardWithdrawFormat() if necessary. Note that formats are never removed from the clipboard; only data can be removed from the clipboard. Once a particular format is registered with the clipboard, it is there until the server goes down. If you plan on retrieving data held by the clipboard, you may wish to inquire about the format of the data it is holding. To do so, you must use two functions together: XmClipboardInquireCount() and XmClipboardInquireFormat(). They take the following form:
   int
   XmClipboardInquireCount(display, window, count, max_length)
       Display *display;
       Window   window;
       int     *count;
       int     *max_length;
   int
   XmClipboardInquireFormat(display, window, index,
                            format_name_buf, buffer_len, copied_len)
       Display         *display;
       Window           window;
       int              index;
       char            *format_name_buf;
       unsigned long    buffer_len;
       unsigned long   *copied_len;
XmClipboardInquireCount() returns the number of formats the clipboard knows about for the data item it is currently holding. Also returned is the string length of the longest format name. You can iterate through the formats starting from 1 (one) through count by calling XmClipboardInquireFormat(). The iteration number is passed as the index parameter. You should use this value to ensure that you can read all the format types in your search for the desired format.

Although there is only one data item stored on the clipboard at any one time, that item may have multiple formats associated with it. While this is unusual, it is possible to handle this case by providing different formats to successive calls to XmClipboardCopy() or XmClipboardCopyByName().

18.4 The Primary Selection and the Clipboard


Since text is the most commonly used format in the clipboard, there is a natural interaction between the clipboard and windows that contain text. In most situations, it is usual (even expected) that when the user selects text, the selection should be placed on the clipboard, which is known as a copy operation. Retrieving text from the clipboard and placing it in another window is known as a paste operation. In some cases, after the data is pasted from the clipboard, the original window deletes the data it copied, which is classified as a cut operation. The clipboard uses what is commonly referred to as the cut and paste model.

The low-level implementation of the clipboard mechanism uses the X Toolkit selection mechanism. This model has additional properties that provide for more detailed communication between the clients involved. For example, cutting text from a Text widget and placing it in another widget involves more communication between the widgets than that of the clipboard copy and retrieval mechanism. When the text that was selected in the first widget is pasted in the other, the first widget may be notified to delete the selected text. This type of communication can be handled either automatically by the Text widgets or through low-level X calls where the corresponding windows of the widgets send real events called client messages to one another.

18.4.1 Clipboard Functions With Text Widgets


In most cases, you should not need to access the clipboard functions to perform simple text copy and retrieval (cut and paste) for Text widgets. If you need to access the clipboard above and beyond the normal selection mechanisms provided by the Text widgets, there are a number of convenience routines that deal with selections automatically. We present a brief overview of these functions here; see Chapter 14, Text Widgets, for detailed information.

The XmTextCut(), XmTextCopy(), and XmTextPaste() routines handles cutting, copying, and pasting operations for the Text widget. There are also corresponding functions for the TextField widget. XmTextCut() and XmTextCopy() take the following form:

   Boolean
   XmTextCut(widget, time)
       Widget widget;
       Time   time;
   Boolean
   XmTextCopy(widget, time)
       Widget widget;
       Time   time;
If there is text selected in the Text widget referred to by the widget parameter, the selected text is placed on the clipboard. For XmTextCut(), the selected text is also deleted from the Text widget, while for XmTextCopy() it is not. The functions return True if all of these things happen successfully. If False is returned, it is usually because the Text widget does not have any selected text.

The time parameter controls when the operation takes place and may be set to any server timestamp value. For example, if you are calling this function from a callback routine, you may wish to use the time field from the event pointer in the callback structure provided by the Motif toolkit. The value CurrentTime can also be used, but there is no guarantee that this value will prevent any race conditions between other clients wanting to use the clipboard. Although race conditions are not likely, the possibility does exist. The result of the race condition is that one widget may appear to have cut or copied selected text to the clipboard when in fact another Text widget got there first.

XmTextPaste() takes the following form:

   Boolean
   XmTextPaste(widget)
       Widget widget;
XmTextPaste() gets the current data from the clipboard and places it in the Text widget. The routine returns False if there is no data on the clipboard.

XmTextCut() and XmTextCopy() only work if there is a current selection in the specified Text widget, which may be dependent on whether or not the user has made a selection. However, you can force a selection in a Text widget using XmTextSetSelection(). This routine takes the following form:

   void
   XmTextSetSelection(widget, first, last, time)
       Widget          widget;
       XmTextPosition  first;
       XmTextPosition  last;
       Time            time;
XmTextSetSelection() selects the text between the specified positions in the Text widget. Once the text has been selected, either XmTextCut() or XmTextCopy() may be called to place the selection on the clipboard.

Although XmTextGetSelection() does not deal with the clipboard directly, it provides a convenient way to get the current selection from the corresponding Text widget. This routine takes the following form:

   char *
   XmTextGetSelection(widget)
       Widget widget;
Note that the text returned by the routine is allocated data and must be freed by the caller using XtFree(). The function returns NULL if the specified widget does not own the text selection.

To deselect the current selection in a Text widget, you can use XmTextClearSelection(), which takes the following form:

   void
   XmTextClearSelection(widget, time)
       Widget widget;
       Time   time;

18.4.2 The Owner of the Selection

Sometimes, if you have a large number of Text widgets, you may need to know which of the widgets has the text selection. You can determine this by using the Xlib function XGetSelectionOwner():

   Window
   XGetSelectionOwner(display, selection)
       Display *display;
       Atom     selection;
The display parameter can be taken from any widget using XtDisplay(). The selection argument represents the Atom associated with the kind of selection you are looking for. For example, you can determine the Text widget that has the current clipboard selection with the following calls:
   Display *dpy = XtDisplay (widget);
   Atom clipboard_atom = XmInternAtom (dpy, "CLIPBOARD", False);
   Window win = XGetSelectionOwner (dpy, clipboard_atom);
   Widget text_w = XtWindowToWidget (dpy, win);

18.5 Implementation Issues

The Motif clipboard mechanism relies on an underlying X mechanism referred to as properties. As you know, windows are data structures maintained by the X server; each window can have an arbitrary list of properties associated with it. Each property consists of a name (called an atom), an arbitrary amount of data, and a format. Property formats are not at all the same thing as the higher-level Motif formats--they simply indicate whether the data is a list of 8-bit, 16-bit, or 32-bit quantities, so that the server can perform byte-swapping, if appropriate. Properties are the underlying mechanism for all interclient communication, including interaction between applications and window managers, and interapplication interaction such as the transfer of selections.

In order to simplify communication over the network, property names are not passed as ­arbitrary-length strings, but as defined integers known as atoms. A number of standard properties (such as those used for communication between applications and window managers) are predefined and interned, or made known to, and cached by the server. However, ­application-defined atoms can also be interned with the server by calling the Xlib function XInternAtom() or the Motif function XmInternAtom(). Atoms are not only used to name properties, but to name any string data that may need to be passed back and forth between a client and the server.

We started this chapter with the analogy that the Motif toolkit is the moderator of the clipboard. In reality, the clipboard itself is a property (called CLIPBOARD) that is automatically maintained by the X server. A property is uniquely identified by both an atom and a window, which means that it is possible for there to be multiple copies of a given property. However, there should be only one CLIPBOARD property active at one time, based on conventions about the use of properties set forth in a document called the Inter-Client Communication Conventions Manual (ICCCM) and followed by the deeper layers of X software. Reprinted as Appendix L of Volume Zero, X Protocol Reference Manual. Among these conventions are that certain properties should only be set by application top-level windows and that only one window should own the CLIPBOARD property at any one time. When an application makes a call to XmClipboardCopy(), the data is actually stored in the CLIPBOARD property of the window that was identified in the call to XmClipboardCopy().

The format of the data stored in a property is defined by another property. The standard formats are based on those recommended by the ICCCM. For example, the FONT format might suggest that an application wants the font that the data string is rendered in, rather than the data string itself. At present, Motif does not support this functionality. You have to remember that formats (or targets, as they are referred to in the ICCCM) are not really things that have any functionality. They are simply names that are translated into integer atoms. The meaning of the formats to an application depends entirely on convention. At present, most applications only support the STRING format. But eventually, conventions will doubtless be articulated for doing far more with the selection mechanism.

A further complication that needs some mention is how the Motif clipboard implementation relates to the underlying X Toolkit implementation of selections. The ICCCM actually defines three separate properties that can be used for selections: PRIMARY, SECONDARY, and CLIPBOARD. Standard Xt applications, including all of the clients distributed by MIT, use the PRIMARY property for storing selections.

The SECONDARY property is designed for quicker, more transient selections. An application that makes use of this property usually copies data directly to another window instantly when the owner finishes copying data to the property. The Motif Text widget uses the SECONDARY property when the META key is down while the middle button is clicked and dragged. As soon as the selection is complete, the selected data is immediately sent to the window that has the input focus, which may be the same window.

In the standard MIT implementation, the CLIPBOARD property is used by an independent client called xclipboard. Keep in mind that a property stays around only as long as the window with which it is associated. When you terminate a client and close its windows, any data stored in a property on one of the client's windows is lost. If the CLIPBOARD property is associated with a client that is kept around between invocations of other applications, it embodies a consistent repository for information to be passed between applications.

The ICCCM blesses the use of both the PRIMARY and CLIPBOARD selection properties. However, you should be aware that the difference between the Motif use of the CLIPBOARD property and the use of the PRIMARY selection property by other Xt applications makes interoperability questionable, unless you take care to handle the PRIMARY selection in your application. The X Toolkit mechanisms for handling selections are described in Volume Four, X Toolkit Intrinsics Programming Manual. The Motif Text widgets support both the Xt mechanism, which uses the PRIMARY selection, and the Motif clipboard, depending on the interaction. You should probably do the same for your application.

While you can manipulate properties and atoms directly using Xlib, the higher-level API provided by Motif and Xt should insulate you from many of the details and ensure that your applications interoperate well with others. Eventually, toolkits and applications will doubtless support numerous extensions of the current clipboard and selection mechanisms.

18.6 Summary

The clipboard provides a convenient mechanism that allows applications to interact with one another in a way that is independent of the application, operating system, and system architecture. The clipboard is one of two common mechanisms used to handle data transfer between objects. The primary selection is still regarded as the most common method for data transfer between applications, mostly because it is the standard cut and paste method used to move textual data between terminal emulators like xterm. A secondary selection method is also available, but is not very widely used.

The Motif toolkit tries to compensate for the de facto standard use of the primary selection method by integrating both the primary and clipboard selections into the same set of functions. Although users seem to be oblivious to the differences, this technique has the unfortunate side effect of confusing programmers.


Contents Previous Next