motifdeveloper.com
Sponsored by IST Limited
Formerly MW3: Motif on the World Wide Web [MW3 Logo]
Last Updated
November 18, 2002
 


X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation
 

motifdeveloper.com
Home
About the site
Bulletin Board
News Archive
OpenGroup News
Events
Search
Frequently Asked Questions
The Motif FAQ
X/Motif FAQs
General FAQs
Ask Antony
Latest Q & A
All Q & As
Submit Question
Tips & Pointers
Code Examples
Software
OpenMotif
Widget Sets
GUI Toolkits & Libraries
Motif suppliers
Non-commercial S/W
Commercial Software
Multimedia
Miscellaneous
Organizations
Docs & Pubs
X/Motif Newsgroups
Security
Internationalization
Feedback
Feedback Form
Contributors
 

How can I coordinate events between two processes?

11-Dec-00 16:00 GMT
Updated 20-Dec-00

Question: I have motif widgets contained in different processes. I would like to be able to receive callbacks from push button widgets in one process, based upon where the keyboard focus is, in the other process. How can I do this? - there is no common toplevel widget for all of these processes that I have running on the screen.

I would also like to be able to return the focus to one process, based on a function keypress. Is this possible, even if the keyboard focus is on another widget, which is in another process?

Introduction

Callbacks go off in the client space of a given process. This means that it is not directly possible to use the base callback mechanisms as a communication scheme between two processes.

However, this does not mean to say that what you require cannot be done. There is one part of the system which is common to all of the widget applications on the screen, and that is the X data manipulated by the server.

There are two basic communication mechanisms you can adopt here.

  • Firstly, you can communicate between the applications by storing data behind a Window.
  • Secondly, you can send messages between applications, using the X event system as the delivery mechanism.

Communicating using Window Properties

For the first case, storing data behind a Window, the obvious place to store is behind the root Window of the screen. The basic scheme of operations is as follows:

  • Define an Atom, known to both of your processes, which will be used to identify some Property (a user-defined piece of data) that is to be stored on the given Window.
  • Define a second Atom, known to both of your processes, which the system requires to define the logical type of the data being stored.
  • Register interest in PropertyChange events for the Window concerned.

To communicate between the two processes, all you need to do here is to change the Window Property value when the callback fires off in the first process. The second process installs the PropertyChange handler, and it is automatically notified after the first process has altered the Property value. It can then read the Property to work out the parameters of the application event, and then perform whatever tasks it requires to achieve in its own address space.

The key routines you need to consider are:

  • XInternAtom() - store ("intern") a string on the X server. You are returned an Atom (an identifier) for the stored string. Both processes will call this, so they share the same view of the Atom.
  • XChangeProperty() - modify the Property associated with a Window. Called by the process "sending" the data.
  • XGetWindowProperty() - fetch the Property associated with a Window. Called by the process "receiving" the data.
  • XSelectInput() - used to request Property change events for the communicating (root) Window. This is called by the "receiving" process.
  • XDeleteProperty() - used to tidy up afterwards

The exact format of the Property data (what you decide to store behind the Window) is application specific, and you would have to define this yourself. It could be a simple integer - button n has been pressed, command n is to be executed - right through to a complex data structure defining complete application state.

As an example, suppose we decide to have a simple protocol, where the first process wants to issue a simple command (with an optional pair of arguments) to the second process.

Defining the Window Data

We would define a simple data structure to represent the data we want to send to the second process, as follows:

    typedef struct Command_s
    {
	int command ;  /* CallbackGoneOff, PleaseSetTheFocus, etc */
		       /* You define these in some common header  */
	int argA ;     /* x coordinate, say */
	int argB ;     /* y coordinate, say */
    } Command_t, *Command_p ;

Defining Application-Specific Atoms

Then, in each process, we would need to define the Atoms representing our data. This is trivial:

    extern Display *display;
    Atom MyAtom     = XInternAtom (display, "MyAtom",     False);
    Atom MyDataType = XInternAtom (display, "MyDataType", False);

You can also use XmInternAtom(), although this is marked for deprecation from Motif 2.0 onwards.

Storing (sending) the Application Data

Now we need a routine to store our application data behind some Window we are using to communicate. This is likely to be the root Window, but we'll pass the Window into the routine so that it is flexible in this respect:

    void StoreApplicationData(Display *display, Window theWindow, Command_p command)
    {
	XChangeProperty (display,
		 theWindow,  /* Probably the root window, but it need not be           */
		 MyAtom,     /* Our data hook.                                         */
		 MyDataType, /* The logical type of our data. Could be a built-in type */
		 8,          /* As it happens, our structure contains integers.        */
		 PropModeReplace,
		 (unsigned char *) command,
		 sizeof(Command_t)) ;
    }

Registering interest in Property Change

The other process needs to receive the data. Firstly, it needs to declare an interest in receiving PropertyChange events on the common (EG, root) Window:

    void initDataReception(Display *display, Window theWindow)
    {
	XSelectInput (display, theWindow, PropertyChangeMask) ;
    }

Now we simply need to make sure we process the incoming data. This requires a hand-modified X loop, something along the lines of the following:

    void newMainLoop(XtAppContext app, Display *display, Window theWindow)
    {
	XEvent event;

	while (TRUE) {
	    XtAppNextEvent (app, &event) ;

	    switch (event.type) {
		case PropertyNotify:
		    if (event.xproperty.window == theWindow) {
			/* Quick Check */

			if (event.xproperty.atom == MyAtom) {
			    HandleIncomingData(display, theWindow) ;
			}
		    }
	    }
	}
    }

Fetching (receiving) the Application Data

The HandleIncomingData() routine will be application specific, but it will need to perform the basic task of fetching the incoming command. Something like the following:

    void HandleIncomingData(Display *display, Window theWindow)
    {
	Command_t      *command ;
	Atom           type ;
	int            format ;
	unsigned long  nitems, left ;

	if (XGetWindowProperty (display,
		    theWindow,
		    MyAtom,
		    0,
		    sizeof(Command_t),
		    FALSE,
		    MyDataType,
		   &type,
		   &format,
		   &nitems,
		   &left,
		   (unsigned char **) &command) == Success) {
	    if (type == MyDataType) {
		/* Process the command */
		...
	    }
	}
    }

The only remaining issue is deciding on the communication endpoint. This, as I say, is often simply the root Window of the screen. You can fetch this in both processes using the following code:

    Window fetchEndPoint(Display *display)
    {
	return DefaultRootWindow (display) ;
    }

There are alternatives: there is no reason why the communication point should not be the top level Window of one of the processes involved. This, however, results in a side issue: how do you communicate this ID to all of the processes in the group. There is nothing preventing you from implementing a multi-level scheme, whereby you store the Window ID into an Atom on the root Window, which each of the processes (except the first) fetch, and thereafter read/write using the same mechanisms above but using this Window ID instead.

So how does all this link into your required scheme of operations. Well, when a callback does go off in one process, simply call the StoreApplicationData() routine from within the callback. The data that is transmitted you will need to define yourself (what the command element means, etc). However, the basic mechanisms outlined here will make sure that the other process will receive notification, provided that it has registered interest using the routines above. What the process does in response to the incoming data is entirely up to you: it could indeed call something like XmProcessTraversal(), XtSetKeyboardFocus(), XSetInputFocus() or similar in order to move the focus around as required.

If you need to send data back to the originating process, simply define a new communication endpoint - a new Atom/Type - and store the reverse data into this. Register interest in PropertyChange back in the first process using exactly the schemes given here.

Tidying Up

A Property stored on a Window exists until the Window is destroyed, or until a client explicitly deletes the Property. The lifetime of the Property therefore will outlast the client which stored it if the Window where it is lodged is the root Window of the Display.

This means that we should explicitly take steps to tidy up if our application group terminates, so that the data is not left lying around - this is likely to confuse another group of applications which start up later. The routine we need to call here is XDeleteProperty(), and in the context of the example code should read something like:

    void TidyUp(Display *display, Window theWindow)
    {
	XDeleteProperty (display, theWindow, MyAtom) ;
    }

Communicating using Events

As an alternative, if you do know the ID of a Window, and that Window is associated with some widget in your application, you can send events directly to it which are of an application-specific nature. The XClientMessageEvent is specifically designed for this purpose. In this case, the routines we need to consider are:

  • XSendEvent() - send an event to a Window
  • XtAddEventHandler() - register interest in an event type for a widget's Window

The Client Message Data Structure

The XClientMessageEvent is defined in as follows:

    typedef struct {
	int      type;
	unsigned long serial;   /* # of last request processed by server      */
	Bool     send_event;    /* true if this came from a SendEvent request */
	Display *display;       /* Display the event was read from        */
	Window   window;
	Atom     message_type;
	int      format;
	union {
		 char b[20];
		 short s[10];
		 long l[5];
	} data;
    } XClientMessageEvent;

Registering interest in incoming Client Messages

In order to register interest in the reception of a ClientMessage, we add the following kind of code: the client_message_handler() routine we define ourselves, and it will be called whenever a non-maskable event arrives. Client messages are non-maskable, which means that the routine will have to check the type of the incoming message, and filter out uninteresting ones.

    extern Display *display;
    extern void     client_message_handler(Widget, XtPointer, XEvent *, Boolean *);

    XtAddEventHandler (display,
	       NoEventMask,            /* ClientMessage events are non-maskable */
	       TRUE,
	       client_message_handler, /* We define this */
	       NULL);                  /* or application-specific data */

Sending Client Messages

To send data to a process, we use XSendEvent() something along the lines of the following:

    void SendApplicationData(Display *display, Window theWindow, Command_p command)
    {
	XClientMessageEvent client ;

	client.display      = display ;   /* Could be remote */
	client.window       = theWindow ; /* Could be remote */
	client.type         = ClientMessage ;
	client.format       = 8 ;
	client.message_type = MyAtom ;    /* As in the Window Property example */

	/* You define this bit : what the data is, and how it is represented */
	client.data.s[0]    = command->command ;
	client.data.s[1]    = command->argA ;
	client.data.s[2]    = command->argB ;

	/* Send the data off to the other process */
	XSendEvent (display, theWindow, True, XtAllEvents, (XEvent*)&client) ;
				       /* modified 20-Dec-00 */
	XFlush(display) ;
    }

Receiving Client Messages

The only remaining issue is the incoming client_message_handler(); it has to unpack the incoming data, making sure it handles events of the right type. Something along the lines of:

    void client_message_handler(Widget    widget,
		    XtPointer client_data,
		    XEvent   *event,
		    Boolean  *continue_to_dispatch)
    {
	if (event->type == ClientMessage) {
	    XClientMessageEvent *me = (XClientMessageEvent *) event ;

	    if (event->message_type == MyAtom) {
		Command_t command ;

		/* You define this bit : what the data is, how it is represented */
		/* Make sure the send and receive routines share the protocol    */

		command.command = me->data.s[0] ;
		command.argA    = me->data.s[1] ;
		command.argB    = me->data.s[2] ;

		/* Handle the command */
		...
	    }
	}
    }

Of course, this scheme could be used in conjunction with the previous one: store the widget's Window ID on the root Window behind some property, so that processes in the group know where to issue an XSendEvent call.

Note that all of the above code has been written blind by hand: there is bound to be the odd typographical error, and I have not attempted to compile the code in any way. It is, however, the general scheme of things, and the right sort of routines, which you should be considering. For the exact syntax and meaning of each of the above functions, you are referred to your favorite Xlib or Xt Programming/Reference manuals.

Related Information

You might like to look at other inter-client mechanisms like the X Selections system, and the Uniform Transfer Model of Motif 2.1. These are really higher-level compared to the mechanisms outlined here, and are much more widget related, as opposed to general application data transfer, which just happens to use a Window as the communication end point. However, depending upon the mechanism you are prepared to adopt in order to initiate the data transfer, you might well find that these mechanisms are more appropriate to the task at hand.

Each of these mechanisms are in fact built on top of each other - the Uniform Transfer Model subsumes the Motif Clipboard, Drag-and-Drop, X Selection mechanisms; the Motif Clipboard in turn is implemented through the X Selection mechanisms; the X Selection Mechanisms use Atoms and Window properties just as we have outlined in the specimen codes above. There are thus many entry points to the inter-client data transfer systems of X and Motif: you simply have to decide which is appropriate to your given needs.

Bibliography

For reference, you might like to take a look at the following book:

    X Window Systems Programming and Applications with Xt,
    Douglas A. Young,
    Published by Prentice Hall,
    ISBN 0-13-972167-3
    Buy it from Amazon.com or Amazon.co.uk

This book contains several useful chapters on this subject, and examples of programs which communicate using X Window Properties and Xt Event mechanisms.

The book is not X11R6 compliant: you would need to check out the examples for deprecation, and so forth. It is, however, eminently useful as a readable starting point.

As an alternative, and you have the Motif sources, the Cut/Paste and Drag/Drop systems have several examples of the use of XChangeProperty(), and you might find that perusing these sources is helpful.

 


Sponsored by X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation

 

Goto top of page

 

[IST Limited]
IST Limited is the
proud sponsor of motifdeveloper.com.
 

Thanks to all the contributors to these pages.

Privacy Policy

 
"Motif" is a registered trademark of The Open Group. All other trademarks are the property of their respective owners.

Some articles/papers here have their own copyright notice. All other information is copyright ©1999-2008 IST Limited

[CommerceOne] MW3 site originally by Ken Sall of Commerce One (formerly Century Computing).