Making use of the ICCCM Pt1: Requestor Side Selections

Introduction

This is the first installment on a series about the ICCCM (Inter-Client Communication Conventions Manual) which is a manual for client communication in the X environment. The ICCCM is very important to us because it defines some ways in which clients can communicate, without which it would be very difficult to share anything between clients, especially when you consider that clients are quite often not even on the same machine as the one they want to talk to.

I will be covering a certain amount of the ICCCM in each issue, hopefully covering enough of it that you can finish reading the ICCCM and understand what it is saying. I won't cover each and every detail, but I will cover certain sections which should pave the way for a better understanding. I will be covering Xlib only, later I may do another series on some of the toolkits and utility libraries like Xmu.

I am also covering this information as I learn it, if you're an expert and you see any errors let me know and I will fix them in the next issue. I have spent hours of research and trial and terror on this subject and I believe I can cut down some of that time for those reading this article. And since nobody who knows this subject better is writing anything on it, you will just have to take what you can get :P

You will also want to read the ICCCM, probably more then twice, because it gets into more detail when it comes to targets and types. I have only given an introduction and don't plan on replacing the ICCCM, only helping you make use of it.

This article is on the requestor side of a PRIMARY selection transfer; I will explain the owner's side next time. The PRIMARY selection makes use of most of the things used in all selections, the information should carry over well.


Properties and Atoms

The ICCCM makes extreme use of properties and atoms, which is why the first chapter is devoted to their explanation. But that chapter was rather confusing the first time I read it, so I will try to explain it better.

Atoms are 32 bit numbers with the top most bits set to 0, meaning it is an int (on 32 bit machines) which left 3 bits will always be 0. This number is used to represent a string, which in turn represents some idea. Instead of sending this string across the wire repeatedly, you do it once for each client, then you send the number instead.

Atoms are created with XInternAtom which takes 3 arguments, a Display, a char*, and a Boolean value. The Display is of course the display you want to use the Atom for, the char* is a string which is what the Atom will represent, and the Boolean value is whether or not to only return the Atom if it already exists. The first time XInternAtom is called for that display, with that string, a new number will be created and sent back, but only if the 3rd argument is False. Further calls to that display with that string will return the same number as the first. Atoms exist until the last connection to the server is lost, so don't go creating billions of them :P

In short, the Atom is like a pointer to a string I want to pass around between clients. The string lives in the server and all clients will get a pointer to that string. I send the pointer to the other client, it checks to see if that pointer is the same as the one it has, if it is we know we are talking about the same thing without having to say everything.

There are some predefined Atoms which you do not need to call XInternAtom to get the Atom value, but you can. The predefined Atom will be the string with XA_ prepended to it, for instance PRIMARY has a predefined Atom called XA_PRIMARY. All predefined Atoms are defined in Xatom.h, you will need to include it if you want to use them. You could also call XInternAtom on the string "PRIMARY" and get the same value as XA_PRIMARY already is. This works on any predefined Atom.

Atom atom = XInternAtom(display, "IDEA", False);

Properties are represented by Atoms and belong to a certain window. Several windows can have the same property, which is represented by the same Atom, and the data for each will not be corrupted by the other. Each property has a type which is also represented by an Atom, and tells the program what kind of data to expect to be inside the property. A property also has a format which is a positive integer value of 0, 8, 16, or 32 which says how many bits are in the data type. And finally the property contains the data in bytes, which has to be cast into whichever actual type the data represents.

So, a property is an array of bytes, and some data to tell us what those bytes are, pointed to by an Atom, on a particular window.

Properties are created by using XChangeProperty which allows you to replace, append, or prepend data to the property. You can only add data to a property if it is of the same type as the property.... in other words, if your trying to append or prepend data to the property, the 'type' and 'format' arguments must be the same as they are inside the property.

You retrieve a property's values with XGetWindowProperty and delete a property either with XDeleteProperty, or with XGetWindowProperty with the 'delete' argument set to True.

If your still confused it will become clearer as I continue since they are used repeatedly in the ICCCM.


Selections

Everyone should definitely read section 2 of the ICCCM since selections are so obviously important to the user. I have seen quite a few applications, which allowed text IO that did not make use of selections, and this greatly lowers their usefulness on an X desktop. I have also learned in the course of writing this article and the example programs, that some applications, which do selections, do not do them right. For example, tk applications do the MULTIPLE targets wrong, Xemacs doesn't do it at all. And many applications do not accept targets which all clients are supposed to, for instance TIMESTAMP.

But selections are not only used for cut and paste jobs. There are also times when a client wants to be known to do something in particular, and other clients need to be able to call upon it to do those things. In the words of the ICCCM itself "Selections are the primary mechanism that X Version 11 defines for the exchange of information between clients."

A selection can only be owned by one window, they are global to the server and are represented by Atoms. Other clients that want to communicate through this selection must ask for ownership if they want to own the selection, or ask for conversion if they are a requestor.


PRIMARY

The first example of selections a person is likely to see when they begin to use X is when they select some text in one window, and then insert it into another usually with the 2nd button. Most of the time this is using the PRIMARY selection which I will describe here. There is also a SECONDARY selection which works in the same way programatically, but I have never seen an example.

The PRIMARY is not only the most common selection used, it is also a good example because it makes use of a great deal of the selection mechanism, including parameterized targets, and the way the whole thing works. Other selections will work in much the same way as this one.


The Requestor

In this article I will explain what the requestor needs to do. The owner does more work, and there is a lot to this deal so I needed to split somewhere. Next issue will be about the owner.

There are three different aspects to the requestor side of this mechanism that need be addressed. Two of which should most certainly be included in ANY requestor, but one can be omitted if you're only after one thing.

There is the simplest example, when the data being transferred is rather short and only one target is specified. In this example, all the requestor need do is 1) ask for the conversion, 2) wait for the SelectionNotify event, 3) retrieve the data from the newly created property, and 4) delete that property so the owner knows we got it.

You start this transference by asking for the conversion of the PRIMARY selection. To do a conversion you need a total of three different Atoms, the selection (PRIMARY for this one), the target, and a property for the owner to put the data into. My target is going to be STRING, which has a predefined Atom XA_STRING so I only need to make one call to XInternAtom to create the property Atom, I will use the string "TEST_PROP" in this example.

Since the object is to send less data across the wire, we should only call XInternAtom one time in the entire running of the program and use the returned Atom in reference from then on. So, I have created a file for the purpose of event handling which will have all the necessary Atoms declared as static globals to that file. Then I create a function called once from main which will initialize these values.

This code will introduce you to the concept of selections and to the retrieval of data from properties.

/* Begin initialization code */
static Atom	selection_property;

void init_eventhandler(void)
{
    selection_property = XInternAtom (dpy, "TEST_PROP", False);
}
/* end initialization code */

You know need to ask for the selection by attempting to have it converted. You need some sort of event to set this off because XConvertSelection requires a timestamp as one of its arguments which should not be CurrentTime. Since in most cases the event that triggers a selection conversion is a button press from the 2nd mouse button, that is the event I used here.

/* Begin conversion.  The event loop goes by the return of the
** function handling the event, return of 0 results in death. */
int buttonPress(XButtonEvent ev)
{
    if (ev.button == 2)
    {
        printf("trying to convert selection\n");
        XConvertSelection(dpy, XA_PRIMARY, XA_STRING, selection_property,
                          ev.window, ev.time);
    }
    return 1;
}
/* end conversion */

Now the owner (if there is one) will receive a SelectionRequest event and will either send a SelectionNotify event with our 4th argument as the property, or it will return a property of None if it was unable to convert. We will also receive a property of None if there is no owner of this selection. If the conversion was possible, the owner will create a new property on our window represented by the Atom we passed as the 4th argument. This property will contain the data from the converted selection. All we need do now is get it.

/* Begin data retrieval */

int selection_notify(XSelectionEvent ev)
/* This function is passed the event that was
** triggered if it is a SelectionNotify type. */
{
	char	*buffer, *buff_p;
	int	format;
	Atom	type_return;

	unsigned char	*data;
	unsigned long	nitems, bytes_left, total;


	if (ev.property == None)
		return 1; /* The selection failed. */

	XGetWindowProperty(dpy, ev.requestor, selection_property,
			   0L, 4096L, False, AnyPropertyType, &type_return,
			   &format, &nitems, &bytes_left, &data);

	if (format != 8 || nitems == 0)
	{
	    /* We have no idea what language this guy is talkin */
            /* So don't touch it or the owner will think we do */
		return 1;
	}

	buff_p = buffer = (char*)malloc(nitems);
	memcpy(buffer, data, nitems);
	XFree(data);

	for (total = nitems; bytes_left;)
	{
		buff_p += nitems;
		XGetWindowProperty(dpy, ev.requestor, selection_property,
				   (total / 4L), 4096L, False,
				   AnyPropertyType, &type_return,
				   &format, &nitems, &bytes_left,
				   &data);

		total += nitems;
		realloc(buffer, total);
		memcpy(buff_p, data, nitems);
		XFree(data);
	}

	XDeleteProperty(dpy, ev.requestor, selection_property);
	/* we are done, tell the owner */
        XFlush(dpy);

	printf("%s", buffer);
	free(buffer);

	return 1;
}

We go through the loop because some servers may lock if we try and request too much data at once. There is also no way of knowing how much data is in that property until we have asked for at least some of its data, and we do not know how much data can be transferred at once as that is server dependant. 4096 or 4k seems like a reasonable request to me, so if there is any left we just loop until we have gotten it all. Also, we must divide the offset by 4 because it will be multiplied by 4 on the other side (don't ask me why, it does seem ridiculous but it works that way). If you have the program print out the nitems and bytes_left you will find that if there is anything left after the first call, nitmes / 4 == 4096 in this situation. When we are finished, we must delete the property and flush the display to tell the owner that we successfully retrieved the data.

Also, since the type of the property can be anything, and is not necessarily the same type as our target (types are more general then targets) and the function will not succeed if we send the wrong type, we must use AnyPropertyType which will allow us to get the property without knowing (since we don't know do we).


INCR

In a slightly more complicated situation, the data selected is too large to be transferred all at once. The best way to deal with this, and the only one which wouldn't be covered by the above requestor code, is to use the INCR mechanism (ICCCM section 2.7.2). In this situation, the owner will respond to the conversion by sending a property with the type being INCR. The property will contain what the ICCCM calls "a lower bound of the bytes in the selection" which I have not found a purpose for. To instigate further data transfer the requestor must delete the property, flush the display, and then wait for the property value to change. Then you retrieve the property, delete/flush, and wait some more until the property is changed to a '0 length property' which simply put means the data is empty and the num_items return will be 0. The requestor then deletes that property and that is the end.

/* Need another non-predefined atom */
static Atom	selection_property, incr_type;

void init_eventhandler(void)
{
    selection_property = XInternAtom (dpy, "TEST_PROP", False);
    incr_type = XInternAtom(dpy, "INCR", False);
}
/* end initializer */

/* The conversion request is the same, what we get is the only thing that
** has changed. */

/* Begin retrieval code */
/* This is probably not the best way to do this since I lock the client
** during the selection transfer */
static void incr_loop(Window win, Atom property)
{
    int		format;
    Atom	type_return, type = None;

    unsigned char	*data;
    unsigned long	nitems, bytes_left, total;

    XEvent	ev;

    while (1)
    {
        XNextEvent(dpy, &ev);

        printf("Type: %d, %d\n", PropertyNotify, ev.type);
        

         if (ev.type != PropertyNotify)
             continue;

        if (ev.xproperty.state == PropertyNewValue)
        {
            XGetWindowProperty(dpy, win, property,
                               0L, 4096L, False, AnyPropertyType, &type_return,
                               &format, &nitems, &bytes_left, &data);

            if (format != 8 || (type != None && type != type_return))
            {
                XDeleteProperty(dpy, win, property);
                XFlush(dpy);
                XFree(data);
                continue;
            }

            if (nitems == 0)
            {
                XDeleteProperty(dpy, win, property);
                XFlush(dpy);
                XFree(data);
                break;
            }


            printf("nitems: %d, bytes_left: %d\n", nitems, bytes_left);
            printf("%s", data);
            XFree(data);

            for (total = nitems;
                 bytes_left;)
            {
                XFlush(dpy);
                printf("nitems: %d, bytes_left: %d\n", nitems, bytes_left);
		XGetWindowProperty(dpy, win, property,
				   (total/4L), 4096L, False,
				   AnyPropertyType, &type_return,
				   &format, &nitems, &bytes_left,
				   &data);
                printf("nitems: %d, bytes_left: %d\n", nitems, bytes_left);

                total += nitems;
                printf("%s", data);
		
		XFree(data);
            }

            XDeleteProperty(dpy, win, property);
            XFlush(dpy);
        }
    }
}


int selection_notify(XSelectionEvent ev)
/* This function is passed the event that was
** triggered if it is a SelectionNotify type. */
{
	char	*buffer, *buff_p;
	int	format;
	Atom	type_return;

	unsigned char	*data;
	unsigned long	nitems, bytes_left, total;


	if (ev.property == None)
		return 1; /* The selection failed. */

	XGetWindowProperty(dpy, ev.requestor, ev.property,
			   0L, 4096L, False, AnyPropertyType, &type_return,
			   &format, &nitems, &bytes_left, &data);

	if (type_return == incr_type)
        {
            XDeleteProperty(dpy, ev.requestor, ev.property);
            XFlush(dpy);
            incr_loop(ev.requestor, ev.property);
            return 1;
        }
	/* the rest is the same as the last example to get non-INCR types */


}

In the INCR mechanism, the first time data is sent through the property it will specify the type of data present, future properties MUST be of this same type or else I guess ignored (the ICCCM does not say what to do if they are not).

As you can see, it is much the same but now we are waiting for PropertyNotify events and getting the data in chunks. The final property will be empty and as soon as we have deleted it the transfer is finished, we must then go on with normal life.


MULTPILE

In the final situation, we the requestor are able to get data in a variety of types, or we want some extra information from the owner. Owners are supposed to be able to respond to at least the 'TARGETS' and 'TIMESTAMP' targets, and should also respond to target 'MULTIPLE'. If we want to pass more then one target we use the 'MULTIPLE' target.

The MULTIPLE target is an example of a parameterized target (ICCCM section 2.2). When you ask for a conversion to the MULTIPLE target you MUST pass a property and never None (passing None in other situations just labels you as an obsolete client). Inside the property you pass is an array of Atoms which represent target and property pairs. The target comes first followed by the property to put the data in. The owner then converts what it can, deletes the PROPERTIES of the targets it cannot convert to, and replaces them with None. Then the owner sends the SelectioNotify event once all conversions has been performed or refused. Targets are processed in the order they are passed in the array because some targets (like DELETE) can have side effects (ICCCM section 2.6.3) which can change everything.

The requestor waits for the SelectionNotify event and then gets the data from all the properties that have been converted, deleting each one, and finally deleting the original property telling the owner that everything went ok.

This example will introduce you to the creation of properties.


/* We now need a whole bunch of atoms so we need a new init function */
static void	read_property(Window, Atom, Atom, int);

static Atom	selection_property,
		multiple, *atoms, incr_property;


void init_eventhandler(void)
{
    char	*names[] = {
        "HOST_NAME", "U0",
        "FILE_NAME", "U1",
        "LINE_NUMBER", "U2",
        "NAME", "U3",
        "USER", "U4",
        "PROCEDURE", "U5",
        "CLASS", "U6",
        "POSTSCRIPT", "U7",
        "TARGETS", "U8",
        "STRING", "U9"
    };
    
    selection_property = XInternAtom (dpy, "TEST_PROP", False);
    multiple = XInternAtom(dpy, "MULTIPLE", False);
    incr_property = XInternAtom(dpy, "INCR", False);

    atoms = (Atom*)malloc(20 * sizeof(Atom));

    if (!XInternAtoms(dpy, names, 20, False, atoms))
    {
        fprintf(stderr, "Couldn't get the Atoms.\n");
        exit(1);
    }
   
}
/* end init */

Notice the new function? XInternAtoms creates an array of Atoms from an array of strings and returns 0 if it fails.

/* Begin retrieval */
int selection_notify(XSelectionEvent ev)
/* This function is passed the event that was
** triggered if it is a SelectionNotify type. */
{
	char	*buffer, *buff_p;
	int	format, i;
	Atom	type_return, *atoms;

	unsigned char	*data;
	unsigned long	nitems, bytes_left, total;


	if (ev.property == None)
		return 1; /* The selection failed. */

	printf("Property: %s\n", XGetAtomName(dpy, ev.property));

        XGetWindowProperty(dpy, ev.requestor, ev.property,
                           0L, 4096L, False, AnyPropertyType,
                           &type_return, &format, &nitems,
                           &bytes_left, &data);

        atoms = (Atom*)data;
        printf("-----Property Contents----\n");
        for (i = 0; i < 20; i += 2)
            if (atoms[i] != None)
                read_property(ev.requestor, atoms[i], atoms[i+1], i);

        XFree(data);
        XDeleteProperty(dpy, ev.requestor, ev.property);
        XFlush(dpy);
        
        return 1;
}

static void read_property(Window	win,
                          Atom 		target,
                          Atom 		property,
                          int 		locator)
{
    char	*buffer, *buff_p;
    int		format, i;
    Atom	type_return, *atoms;

    unsigned char	*data;
    unsigned long	nitems, bytes_left, total;

    if (property == None)
    {
        /* TK applications seem to be broken in this way
        ** so, if you don't check, you liable to explode. */
        fprintf(stderr, "Broken owner!\nTarget: %s\n",
                XGetAtomName(dpy, target));
        return;
    }

   /* Get the property, same deal as the first example */

    switch (locator)
    {
		/* this simply outputs the value in the correct manner */
        case 0:
        case 2:
        case 6:
        case 8:
        case 10:
        case 14:
            if (format != 8)
                break;
            printf("%s: %s\n", XGetAtomName(dpy, target), buffer);
            break;
        case 12:
            
            printf("CLASS: %s, %s\n", buffer,
                   (buff_p = (buffer + strlen(buffer) + 1)));
            break;
        case 4:
            printf("LINE_NUMBER\n");
            printf("Format: %d\n", format);
            break;
        case 18:
            printf("STRING:\n%s\n\n\n", buffer);
            break;
        case 16:
            printf("Targets:\n");
            for (i = 0, atoms = (Atom*)buffer; i < total; i++, atoms++)
                if (*atoms == None) printf("None\n");
                else
                    fprintf(stderr, "\t%s\n", XGetAtomName(dpy, *atoms));
            break;

        default:
            printf("You miscounted!\n");
            printf("locator: %d\n", locator);
    }

    if (type_return == incr_property)
        printf("INCR selection\n");
    XDeleteProperty(dpy, win, property);
    XFlush(dpy);
    free(buffer);
}
/* end retrival */

And that's that. I leave putting it all together and reading the LINE_NUMBER target correctly (it is of type == SPAN described in ICCCM section 2.7.4) to you as an exercise. I have also put together a small example package which shows each of the above 3 examples at work. Compile them on your Unix system (or win32 if you have client libs) and try to paste some selections. The selection gets put into stdout for the most part and adds some extra information you can learn from. All but 'incr_sel' can be stepped through with a debugger, but incr_sel will hang if you try (must need special synchronization between clients and never exits the while if the selection never finishes.)


Till Next Time

Hopefully by the time the next CScene is ready to be published I will have the next issue for you at least. It will cover the owner side of a PRIMARY selection and will cover all the aspects of selections we did here, on the other side. The owner does more work then a requestor, including the creation of all properties, and the conversion of all data. Expect that article to be much longer.


(c)Noah Roberts (jik-)
I hereby grant, free of change, the right to copy and redistribute this work in any form as long as this copyright notice is left intact on the copy.

Last modified: Tue Mar 16 19:27:20 PST 1999


This page is Copyright © 1999 By C Scene. All Rights Reserved