/* 
 * tkUnixFocus.c --
 *
 *  This file contains platform specific procedures that manage
 *  focus for Tk.
 *
 * Copyright (c) 1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) Id
 */

#include "tkInt.h"
#include "tkPort.h"
#include "tkUnixInt.h"


/*
 *----------------------------------------------------------------------
 *
 * TkpChangeFocus --
 *
 *  This procedure is invoked to move the official X focus from
 *  one window to another.
 *
 * Results:
 *  The return value is the serial number of the command that
 *  changed the focus.  It may be needed by the caller to filter
 *  out focus change events that were queued before the command.
 *  If the procedure doesn't actually change the focus then
 *  it returns 0.
 *
 * Side effects:
 *  The official X focus window changes;  the application's focus
 *  window isn't changed by this procedure.
 *
 *----------------------------------------------------------------------
 */

int
TkpChangeFocus(winPtr, force)
    TkWindow *winPtr;    /* Window that is to receive the X focus. */
    int force;      /* Non-zero means claim the focus even
         * if it didn't originally belong to
         * topLevelPtr's application. */
{
    TkDisplay *dispPtr = winPtr->dispPtr;
    Tk_ErrorHandler errHandler;
    Window window, root, parent, *children; 
    unsigned int numChildren, serial; 
    TkWindow *winPtr2;
    int dummy;

    /*
     * Don't set the X focus to a window that's marked
     * override-redirect.  This is a hack to avoid problems with menus
     * under olvwm: if we move the focus then the focus can get lost
     * during keyboard traversal.  Fortunately, we don't really need to
     * move the focus for menus: events will still find their way to the
     * focus window, and menus aren't decorated anyway so the window
     * manager doesn't need to hear about the focus change in order to
     * redecorate the menu.
     */

    serial = 0;
    if (winPtr->atts.override_redirect) {
  return serial;
    }

    /*
     * Check to make sure that the focus is still in one of the windows
     * of this application or one of their descendants.  Furthermore,
     * grab the server to make sure that the focus doesn't change in the
     * middle of this operation.
     */

    XGrabServer(dispPtr->display);
    if (!force) {
  /*
   * Find the focus window, then see if it or one of its ancestors
   * is a window in our application (it's possible that the focus
   * window is in an embedded application, which may or may not be
   * in the same process.
   */

  XGetInputFocus(dispPtr->display, &window, &dummy);
  while (1) {
      winPtr2 = (TkWindow *) Tk_IdToWindow(dispPtr->display, window);
      if ((winPtr2 != NULL) && (winPtr2->mainPtr == winPtr->mainPtr)) {
    break;
      }
      if ((window == PointerRoot) || (window == None)) {
    goto done;
      }
      XQueryTree(dispPtr->display, window, &root, &parent, &children,
        &numChildren);
      if (children != NULL) {
    XFree((void *) children);
      }
      if (parent == root) {
    goto done;
      }
      window = parent;
  }
    }

    /*
     * Tell X to change the focus.  Ignore errors that occur when changing
     * the focus:  it is still possible that the window we're focussing
     * to could have gotten unmapped, which will generate an error.
     */

    errHandler = Tk_CreateErrorHandler(dispPtr->display, -1, -1, -1,
      (Tk_ErrorProc *) NULL, (ClientData) NULL);
    if (winPtr->window == None) {
  panic("ChangeXFocus got null X window");
    }
    XSetInputFocus(dispPtr->display, winPtr->window, RevertToParent,
      CurrentTime);
    Tk_DeleteErrorHandler(errHandler);

    /*
     * Remember the current serial number for the X server and issue
     * a dummy server request.  This marks the position at which we
     * changed the focus, so we can distinguish FocusIn and FocusOut
     * events on either side of the mark.
     */

    serial = NextRequest(winPtr->display);
    XNoOp(winPtr->display);

    done:
    XUngrabServer(dispPtr->display);

    /*
     * After ungrabbing the server, it's important to flush the output
     * immediately so that the server sees the ungrab command.  Otherwise
     * we might do something else that needs to communicate with the
     * server (such as invoking a subprocess that needs to do I/O to
     * the screen); if the ungrab command is still sitting in our
     * output buffer, we could deadlock.
     */

    XFlush(dispPtr->display);
    return serial;
}
