/* 
 *  @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 <Xm/Xm.h>
#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() */



