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?
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.
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.
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 ;
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.
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)) ;
}
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) ;
}
}
}
}
}
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.
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) ;
}
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 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;
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 */
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) ;
}
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.
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.
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
|