/* * @OPENGROUP_COPYRIGHT@ * COPYRIGHT NOTICE * Copyright (c) 1990, 1991, 1992, 1993 Open Software Foundation, Inc. * Copyright (c) 1996, 1997, 1998, 1999, 2000 The Open Group * ALL RIGHTS RESERVED (MOTIF). See the file named COPYRIGHT.MOTIF for * the full copyright text. * * This software is subject to an open license. It may only be * used on, with or for operating systems which are themselves open * source systems. You must contact The Open Group for a license * allowing distribution and sublicensing of this software on, with, * or for operating systems which are not Open Source programs. * * See http://www.opengroup.org/openmotif/license for full * details of the license agreement. Any use, reproduction, or * distribution of the program constitutes recipient's acceptance of * this agreement. * * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS * PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY * WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY * OR FITNESS FOR A PARTICULAR PURPOSE * * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT * NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE * EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. */ /* * HISTORY */ #ifdef REV_INFO #ifndef lint static char rcsid[] = "$XConsortium: Synchronize.c /main/7 1995/07/14 11:31:08 drk $" #endif #endif /*********************************************************************** Calls: Summary: Processes all events generated by Xm, Xt, and X-server until the next quiescent condition. Then it returns control back to the calling routine. INPUTS: none OUTPUTS: none RETURNS: True = called XtDispatch at least once. ************************************************************************/ #include #include "testlib.h" #include "xislib.h" extern Widget SyncWidget; extern Boolean MonitorOn; extern Boolean SyncWidgetCreated; extern Boolean SyncWidgetPoppedup; /******************************************************************************* XIS SYNCHRONIZE *******************************************************************************/ /* XIS SYNCHRONIZE This routine replaces the need for XtAppMainLoop(). Fundamentally, it does the same thing as XtAppMainLoop() by iterating on calls to XtAppNextEvent() followed by XtDispatchEvent(). But this routine terminates at a controlled point in the event processing stream when it determines that all events that are going to be returned have indeed been returned and processed. In otherwords, it uses the idea that any one primitive input device input event (mouse button press/release, mouse motion, or key press/release) will result in 0 or more response events from the server up to some finite number. Once all the events related to this one primitive action have come back, the system will return to a quiesent (quiet) state. With this theory of operation in mind, it is intended that this function be used by calling it after each fundamental input synthesis command or action (such as move pointer, press mouse button, and press keyboard key) to cause Xt and ultimately Xm to react in whatever way they see fit and to return after the total toolkit-intrinsic-server reaction is complete. One would think that it would be as simple as calling XSync() to flush the output buffer of all unsent requests and just read the event input queue via XNextEvent() until there remains no additional incomming events. Afterall, the X Window System Protocol specification by Robert Sheifler in the last section (just before the appendicies) called Flow Control and Concurrency states: Whether or not a server is implemented with internal concurrency, the overall effect must be as if individual requests are executed to completion in some serial order, and requests from a given connection must be executed in delivery order (that is, the total execution order is a shuffle of the individual streams). The execution of a request includes validating all arguments, collecting all data for any reply, and generating and queueing all required events. Note that he says "queueing" and not necessarily sending of the events. In most multitasking systems, the communications drivers and/or pipe managers are usually run from a separate task so that the server code has no direct control over when the return replies and events are sent beyond flushing the buffer on demand which it rarely does for performance reasons. He reinforces this notion by going on to say: However, it [the execution of a request] does not include the actual transmission of the reply and the events. Thus unless we do something to force the flushing of the events from the server side (i.e., force the queued up ones to be sent across the wire), the replies and event packets which are recieved (by the client) will appear to show up in an asyncronous manner with respect to the requests which are sent out from server. Fortunately the spec goes on to say the following which clears this matter up: In addition, the effect of any other cause that can generate multiple events (for example, activation of a grab or pointer motion) must effectively generate and queue all required events indivisibly with respect to all other causes and requests. For a request from a given client, any events destined for that client that are caused by executing the request must be sent to the client before any reply or error is sent. And, knowing that an XSync() call is nothing more than an alias for a call of the protocol function GetInputFocus() which conveniently requires a synchronous reply (both send and reply packets are very short and simple which is ideal for XSync() which serves simply to "sync-up" both the client and server wire's send/receive queues), we see by the above statement that we are justified in using the XSync() command to reliably send back all events related to a given request. But we have greatly oversimplified the distributed model here by leaving out the effects introduced by a 2nd client - namely the window manager. Our physical model is as shown below: +-----------+ +-----------------+ | | | | | TEST | | WINDOW MANAGER | | CLIENT | | CLIENT | | | | | +-----+-----+ +--------+--------+ | | +------------+-------------+ (two way channel) | +-----+------+ | | | X-SERVER | | | +------------+ The effect of the window manager is to potentially place arbitrary delays between the test-client and the server via the structure control events (see section 8.4.7 of the Xlib spec), which include CirculateRequest, ConfigureRequest, MapRequest, and ResizeRequest events. These events are often times requested by window managers and act to divert any normal XMapRequest() calls, for example, to the window manager instead of passing them to the server to be processed. Actually, the request is sent to the server first which redirects it to the window manager client without further processing. Then the window manager takes these requests, and does something to them according to their behavior policy, and possible sends them back to the server in some alternate form. This gives us a worst-case data-flow model which looks something like this: +-----------+ | | | TEST | +------>| CLIENT | | | | | +-----+-----+ | | Request | V | +------------------+ Return | | | Events | | WINDOW MANAGER | | | CLIENT | | | | | +------------------+ | | Modified Request | V | +-----+------+ | | | +-------+ X-SERVER | | | +------------+ Each of these boxes represents 1 task. They may all be on one machine or they may all be on separate machines, or some combination thereof. Thus, in the worst case (3 machines), we must be aware of the availability of 3 independent computer nodes and 3 independent communication paths. Each node and communication path may vary dramatically from one another in terms of throughput performance. So to depend purely on a timeout method of syncronization would be tenuous at best and would certainly not be very forgiving of any intermediate system/channel load variations. To remedy the problem, we need to invent a modified form of the simple syncronization request and reply which works in the presence of a window manager (that may or may not be present). It turns out that there are very few requests which can in fact be redirected to the window manager. Though few in number, they are amoung the most often used commands during the setup of a typical widget. They include XMapWindow(), XUnmapWindow(), XConfigureWindow(), and XResizeWindow(). The window manager wants to know about any top level window's change configuration state (size, visibility state (in terms of mapped/unmapped), and stacking order) in order that it can do the correct thing with window border decoration, keyboard focus, and global stacking orders whenever one of these commands occurs. Once it gets the info about the request which was sent (but not acted on by the server) it turns around and re-sends it to the server in a way that conforms to its window management policy (by re-positioning it and re-sizing it accordingly). And finally, the server turns around and sends back the appropriate configure or map notify to the test-client which closes the loop. So to syncronize these three tasks, we must send one of these requests and wait for the resulting return event. This in effect, results in a sort of feedback self-test or "ping" operation on the data flow paths shown above. Furthermore, according to the Protocol Spec, if we wait for the resultant signiture sync event before continuing, we will have flushed out all the remaining events and window manager requests in the system. Unlike the Sync() command which waits for a syncronous reply to a simple X request, our sync operation will depend on the setting up of an invisible (never mapped) top level window (totally separate from our widgets under test) which we will use to periodically re-configure and wait for the resulting configuration event back from the server before proceeding. While waiting for our special sync event, we will get back all other events which were previously queued up in the system. By the way, we shouldn't have to call the simple sync as part of our more elaborate syncronizing operation since XNextEvent() and XtAppNextEvent() flush all requests internally prior to waiting for events to process. This solution is nice since it also satisfies the following additional 2 requirements: (1) that the server screen should in no way be altered from the way it would normally look when running motif without the input synthesis tools, and (2) that the method should work equally well with or without a window manager. Now I will cover how to use this syncronization method. The basic calling routine code path looks like this: 1. Create Widget 2. Send a primitive mouse/keyboard command 3. Process all events related to that command 4. Do another primitive mouse/keyboard command 5. Process all events related to that command etc. N. Destroy Widget This produces a chain of requests and events in the following order: C P S E E E... R P S E E E... R P S E E E... R etc D Where: C = Create Widget P = Primitive input synthesis command S = Send sync request E = Intermediate events R = Receive sync event D = Delete Widget The step which "Process all events" calls this routine. The basic code path of this routine is as follows: 1. Send out a syncronizing request 2. While (True) do the following 3 steps 3. XtAppNextEvent(xt_application_context,&event) - get next event 4. If done (received sync event), leave loop 5. XtDispatchEvent(&event) - let Xt and Xm process event 6. Return to caller For one input synthesis command, this produces a chain of requests and events in the following order: P S E E E... R At first look, it appears that all we have to do is to send the primitive sync request, then "wrap" a sync around its response by sending a sync request, and in the process of waiting for its response, scoop up all intermediate events generated from the original event. Then when we receive the sync event return control to the caller. However, this does not take into account the fact that an intermediate event when sent to XtDispatch() for processing by Xt and Xm may result in a request which generates more intermediate events. So now it appears that in order to catch ALL resultant events, we must resend a sync command after each call of XtDispatch() in case it sent any additional requests that produced events. Then when we get back the last sync event out of all of them that we send, then we should return control to the caller. Now the modified algorithm looks like this: 1. Send out a syncronizing request 2. While (True) do the following 4 steps 3. XtAppNextEvent(xt_application_context,&event) - get next event 4. If done (received last sync event), leave loop 5. XtDispatchEvent(&event) - let Xt and Xm process event 6. Send out another syncronizing request 7. Return to caller For one input synthesis command, this produces a chain of requests and events in the following order: P S E S E S E... R R R <- last sync response In reality, very few of the calls to XtDispatch() result in requests which generate more events. So monitoring the events would indicate that we end up flooding the data flow path with a stream of many unnecessary sync events. Another way of looking at the solution is this: we send out a request which guarantees the return of at least 1 event; thus we can call XtAppNextEvent() without worrying about getting blocked indefinitely. If nothing else arrives, we will at least get back our 1 sync event. From this point of view, we need to inject new sync requests into the system only upon the completion of our previous sync event but prior to calling the XtAppNextEvent() wait loop again in order to gaurantee no blocking will occur. The end of one ping starts the next ping in a sort of rythmic fashion which forms a kind of "heartbeart" for controlling the rest of event processing. And if you think about it, it turns out that our termination condition occurs when we receive two sync events in a row without any intermediate events in between. This indicates that the system is settled back to a quiet state and can no longer generate any additional events. Now the modified algorithm looks like this: 1. Send out a syncronizing request 2. While (True) do the following 4 steps 3. XtAppNextEvent(xt_application_context,&event) - get next event 4. If received sync event, 5. If no intermediate events since last sync event Leave loop 6. Else Send another sync request 7. Else XtDispatchEvent(&event) - let Xt and Xm process event 8. Return to caller This produces the following chain of requests and events: P S E E E... R-S E E E... R-S E E E... R-S R <- last sync response This algorithm plan covers us 95% of the time, but still we need one more refinement to make it complete. Some widgets (such as XmArrowButton) employ internal delays to give the effect of action-delay-action. In XmArrowButton this happens when a user presses the return key while the arrow button is in keyboard focus. The visual appearance of the button goes in immediately when the key is pressed (along with the invocation of the arm callbacks), then it waits for about 1/2 second, and then it gets redrawn unpressed (along with the invocation of the activate callbacks). Notice that the time when the user releases the return key is irrelevant to the visual release time of the arrow button. To cover this case, we need to sync on the callbacks directly. The first part of the processing with the "heartbeat" sync loop is done as previously stated. Once we receive the dual sync indicating completion of the first phase of this widget's response, we then enter a 2 second timeout loop where we wait for the timeout and other followup events, which ever comes first. As events come in, we process them and keep checking for the presence of all the correct callbacks. If events start coming in prior to the 2 second timeout and the correct callbacks occur, then the routine returns. Otherwise, if the 2 second timeout comes in first and another pass of the "heartbeat" sync loop does not shake out any new events, then the routine does a forced return, hoping that the calling routine will sense the absence of the correct callbacks. Notice that even in the timeout case, a "ping" operation is done to ensure that the load on the other systems and/or communication paths has not held up the events. */ typedef struct _sync_values { int serviced_sync; int done; } SyncValues; static SyncValues syncvalues; /***************************************************************************/ static void SendConfigureEvent() { static int border = 0; static XWindowChanges window_config; if (border) { (*xisTraceMsg)("Setting sync_window border_width to 0\n"); border = 0; } else { (*xisTraceMsg)("Setting sync_window border_width to 1\n"); border = 1; } window_config.border_width = border; XConfigureWindow(xisDisplay, xisSyncWindow, CWBorderWidth, &window_config); } /***************************************************************************/ static void HandleConfig(shell, data, event, keep_going) Widget shell; XtPointer data; XEvent *event; Boolean *keep_going; { SyncValues *syncvalues = (SyncValues *) data; if (event->type == ConfigureNotify && event->xconfigure.window == xisSyncWindow) { /** absorb one sync event **/ if (syncvalues->serviced_sync && (!XtAppPending(xisAppContext) & XtIMXEvent)) syncvalues->done = 1; else { /* Grab all the consecutive configure notify events for this window. This prevents us from getting in an infinite loop if we get one too many configure notify events in the queue. This fix will probably not work if there is more than one display connection in the application. */ while (XtAppPending(xisAppContext) & XtIMXEvent) { XEvent peekevent; XtAppPeekEvent(xisAppContext, &peekevent); if (peekevent.type == ConfigureNotify && peekevent.xconfigure.window == xisSyncWindow) { /* Its one of the events we want so pull it off of the queue */ XtAppNextEvent(xisAppContext, &peekevent); (syncvalues->serviced_sync)++; } /* Its a different kind of event, so quit this loop */ else break; } (syncvalues->serviced_sync)++; SendConfigureEvent(); } XSync(xisDisplay,False); } } /***************************************************************************/ static void CreateSyncWindow() { xisSyncWindow = XCreateSimpleWindow(xisDisplay, xisRootWindow, 400, 400, 10, 10, 0, 0, 0); XSelectInput(xisDisplay, xisSyncWindow, StructureNotifyMask | PropertyChangeMask | KeyPressMask); } /***************************************************************************/ void xisResetSyncWindow() { Arg args[10]; int argn; Widget shell; /* Create the empty shell widget. Make sure to give it some dimensions so that Xt doesn't complain about a zero sized widget. Make sure to also realize the widget. */ argn = 0; XtSetArg(args[argn], XmNwidth, 10); ++argn; XtSetArg(args[argn], XmNheight, 10); ++argn; XtSetArg(args[argn], XmNmappedWhenManaged, False); ++argn; shell = XtCreatePopupShell("SyncWindow", topLevelShellWidgetClass, Shell1, args, argn); XtRealizeWidget(shell); XDestroyWindow(xisDisplay, xisSyncWindow); xisSyncWindow = (Window) NULL; xisSyncWindow = XtWindow(shell); /* Add an event handler on the shell to look for configure notify events. */ XtAddEventHandler(shell, StructureNotifyMask, False, HandleConfig, (XtPointer) &syncvalues); } /***************************************************************************/ int xisSynchronize() { static char routine_name[] = "xisSynchronize"; int argn; Arg args[10]; XEvent event; int called_dispatch = 0; Widget shell; static short first_time = 1; xisUseSessionInfo(routine_name); if (first_time) { first_time = 0; CreateSyncWindow(); syncvalues.serviced_sync = 0; syncvalues.done = 0; } syncvalues.serviced_sync = 0; SendConfigureEvent(); syncvalues.done = 0; while (!syncvalues.done) { XSync(xisDisplay,False); XtAppNextEvent(xisAppContext,&event); /** read event **/ /***** If it is really necessary, then uncomment this: xisProcessObjects(); xisPrintEvent(&event); *****/ if (event.type == ConfigureNotify && event.xconfigure.window == xisSyncWindow) { HandleConfig((Widget) NULL, (XtPointer)(&syncvalues), &event, (Boolean *) NULL); } else if ((event.type == CirculateNotify || event.type == DestroyNotify || event.type == ReparentNotify || event.type == GravityNotify) && (event.xmap.window == xisSyncWindow)) { ; /* do nothing */ } else { syncvalues.serviced_sync = 0; called_dispatch = 1; switch (event.xany.type) { case ButtonPress: event.xbutton.send_event = 0; event.xbutton.state = xisState.mod_button_state & (~xisMouseButtonMask[event.xbutton.button]); if (!xisUseCurrentTime || (xisInform.event_code == EventMouseButtonMultiClick && xisInform.num_clicks > 1) ) xisLastButtonPressTime = event.xbutton.time; break; case ButtonRelease: event.xbutton.send_event = 0; event.xbutton.state |= xisState.mod_button_state | xisMouseButtonMask[event.xbutton.button]; if (xisUseSyntheticTime) event.xbutton.time = xisGetServerTime(0); else if (!xisUseCurrentTime || (xisInform.event_code == EventMouseButtonMultiClick && xisInform.num_clicks > 1) ) xisLastEventTime = event.xbutton.time; break; case KeyPress: case KeyRelease: event.xkey.send_event = 0; event.xkey.state = event.xkey.state | xisState.mod_button_state; break; case MotionNotify: event.xmotion.send_event = 0; event.xmotion.state = xisState.mod_button_state; if (xisUseSyntheticTime) event.xmotion.time = xisGetServerTime(0); break; case EnterNotify: case LeaveNotify: event.xcrossing.state = xisState.mod_button_state; if (xisUseSyntheticTime) event.xcrossing.time = xisGetServerTime(0); else xisLastEventTime = event.xcrossing.time; break; case PropertyNotify: if (xisUseSyntheticTime) event.xproperty.time = xisGetServerTime(0); else xisLastEventTime = event.xproperty.time; break; case SelectionClear: if (xisUseSyntheticTime) event.xselectionclear.time = xisGetServerTime(0); else xisLastEventTime = event.xselectionclear.time; break; case SelectionRequest: if (xisUseSyntheticTime) event.xselectionrequest.time = xisGetServerTime(0); else xisLastEventTime = event.xselectionrequest.time; break; case SelectionNotify: if (xisUseSyntheticTime) event.xselection.time = xisGetServerTime(0); else xisLastEventTime = event.xselection.time; break; default: break; } XtDispatchEvent(&event); } } /* End while(!done) */ if ((MonitorOn == True) && (SyncWidgetCreated == True) && (SyncWidgetPoppedup == True)) { XtPopdown (SyncWidget); SyncWidgetPoppedup = False; xisSynchronize(); } return(called_dispatch); } /* End xisSynchronize() */