
/* Copyright (c) Mark J. Kilgard, 1994, 1997, 2001.  */

/* This program is freely distributable without licensing fees
   and is provided without guarantee or warrantee expressed or
   implied. This program is -not- in the public domain. */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#ifndef _WIN32
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#endif

#include "glutint.h"

/* Some Mesa versions define GLX_VERSION_1_2 without defining
   GLX_VERSION_1_1. */
#if defined(GLX_VERSION_1_2) && !defined(GLX_VERSION_1_1)
# define GLX_VERSION_1_1 1
#endif

GLUTwindow *__glutCurrentWindow = NULL;
GLUTwindow **__glutWindowList = NULL;
int __glutWindowListSize = 0;
#if !defined(_WIN32)
GLUTstale *__glutStaleWindowList = NULL;
#endif
GLUTwindow *__glutMenuWindow = NULL;

void (*__glutFreeOverlayFunc) (GLUToverlay *);
XVisualInfo *(*__glutDetermineVisualFromString) (char *string, Bool * treatAsSingle,
  Criterion * requiredCriteria, int nRequired, int requiredMask, void** fbc) = NULL;

static Criterion requiredWindowCriteria[] =
{
  {LEVEL, EQ, 0},
  {TRANSPARENT, EQ, 0}
};
static int numRequiredWindowCriteria = sizeof(requiredWindowCriteria) / sizeof(Criterion);
static int requiredWindowCriteriaMask = (1 << LEVEL) | (1 << TRANSPARENT);

static void
cleanWindowWorkList(GLUTwindow * window)
{
  GLUTwindow **pEntry = &__glutWindowWorkList;
  GLUTwindow *entry = __glutWindowWorkList;

  /* Tranverse singly-linked window work list look for the
     window. */
  while (entry) {
    if (entry == window) {
      /* Found it; delete it. */
      *pEntry = entry->prevWorkWin;
      return;
    } else {
      pEntry = &entry->prevWorkWin;
      entry = *pEntry;
    }
  }
}

#if !defined(_WIN32)

static void
cleanStaleWindowList(GLUTwindow * window)
{
  GLUTstale **pEntry = &__glutStaleWindowList;
  GLUTstale *entry = __glutStaleWindowList;

  /* Tranverse singly-linked stale window list look for the
     window ID. */
  while (entry) {
    if (entry->window == window) {
      /* Found it; delete it. */
      *pEntry = entry->next;
      free(entry);
      return;
    } else {
      pEntry = &entry->next;
      entry = *pEntry;
    }
  }
}

#endif

static GLUTwindow *__glutWindowCache = NULL;

GLUTwindow *
__glutGetWindow(Window win)
{
  int i;

  /* Does win belong to the last window ID looked up? */
  if (__glutWindowCache && (win == __glutWindowCache->win ||
      (__glutWindowCache->overlay && win ==
        __glutWindowCache->overlay->win))) {
    return __glutWindowCache;
  }
  /* Otherwise scan the window list looking for the window ID. */
  for (i = 0; i < __glutWindowListSize; i++) {
    if (__glutWindowList[i]) {
      if (win == __glutWindowList[i]->win) {
        __glutWindowCache = __glutWindowList[i];
        return __glutWindowCache;
      }
      if (__glutWindowList[i]->overlay) {
        if (win == __glutWindowList[i]->overlay->win) {
          __glutWindowCache = __glutWindowList[i];
          return __glutWindowCache;
        }
      }
    }
  }
#if !defined(_WIN32)
  {
    GLUTstale *entry;

    /* Scan through destroyed overlay window IDs for which no
       DestroyNotify has yet been received. */
    for (entry = __glutStaleWindowList; entry; entry = entry->next) {
      if (entry->win == win) {
        return entry->window;
      }
    }
  }
#endif
  return NULL;
}

/* CENTRY */
int GLUTAPIENTRY
glutGetWindow(void)
{
  if (__glutCurrentWindow) {
    return __glutCurrentWindow->num + 1;
  } else {
    return 0;
  }
}
/* ENDCENTRY */

void
__glutSetWindow(GLUTwindow * window)
{
  /* It is tempting to try to short-circuit the call to
     glXMakeCurrent if we "know" we are going to make current
     to a window we are already current to.  In fact, this
     assumption breaks when GLUT is expected to integrated with
     other OpenGL windowing APIs that also make current to
     OpenGL contexts.  Since glXMakeCurrent short-circuits the
     "already bound" case, GLUT avoids the temptation to do so
     too. */
  __glutCurrentWindow = window;

  MAKE_CURRENT_LAYER(__glutCurrentWindow);

#if !defined(_WIN32)
  /* We should be careful to force a finish between each
     iteration through the GLUT main loop if indirect OpenGL 
     contexts are in use; indirect contexts tend to have  much
     longer latency because lots of OpenGL extension requests
     can queue up in the X protocol stream.  We accomplish this
     by posting GLUT_FINISH_WORK to be done. */
  if (!__glutCurrentWindow->isDirect) {
    __glutPutOnWorkList(__glutCurrentWindow, GLUT_FINISH_WORK);
  }
#endif

  /* If debugging is enabled, we'll want to check this window
     for any OpenGL errors every iteration through the GLUT
     main loop.  To accomplish this, we post the
     GLUT_DEBUG_WORK to be done on this window. */
  if (__glutDebug) {
    __glutPutOnWorkList(__glutCurrentWindow, GLUT_DEBUG_WORK);
  }
}

/* CENTRY */
void GLUTAPIENTRY
glutSetWindow(int win)
{
  GLUTwindow *window;

  if (win < 1 || win > __glutWindowListSize) {
    __glutWarning("glutSetWindow attempted on bogus window.");
    return;
  }
  window = __glutWindowList[win - 1];
  if (!window) {
    __glutWarning("glutSetWindow attempted on bogus window.");
    return;
  }
  __glutSetWindow(window);
}
/* ENDCENTRY */

static int
getUnusedWindowSlot(void)
{
  int i;

  /* Look for allocated, unused slot. */
  for (i = 0; i < __glutWindowListSize; i++) {
    if (!__glutWindowList[i]) {
      return i;
    }
  }
  /* Allocate a new slot. */
  __glutWindowListSize++;
  if (__glutWindowList) {
    GLUTwindow **new_list = (GLUTwindow **)
      realloc(__glutWindowList,
        __glutWindowListSize * sizeof(GLUTwindow *));
    if (!new_list) {
      free(__glutWindowList);
    }
    __glutWindowList = new_list;
  } else {
    /* XXX Some realloc's do not correctly perform a malloc
       when asked to perform a realloc on a NULL pointer,
       though the ANSI C library spec requires this. */
    __glutWindowList = (GLUTwindow **)
      malloc(sizeof(GLUTwindow *));
  }
  if (!__glutWindowList) {
    __glutFatalError("out of memory.");
    return -1;
  }
  __glutWindowList[__glutWindowListSize - 1] = NULL;
  return __glutWindowListSize - 1;
}

static XVisualInfo *
getVisualInfoCI(unsigned int mode)
{
  static int bufSizeList[] =
  {16, 12, 8, 4, 2, 1, 0};
  XVisualInfo *vi;
  int list[32];
  int i, n = 0;

  /* Should not be looking at display mode mask if
     __glutDisplayString is non-NULL. */
  assert(!__glutDisplayString);

  list[n++] = GLX_BUFFER_SIZE;
  list[n++] = 1;
  if (GLUT_WIND_IS_DOUBLE(mode)) {
    list[n++] = GLX_DOUBLEBUFFER;
  }
  if (GLUT_WIND_IS_STEREO(mode)) {
    list[n++] = GLX_STEREO;
  }
  if (GLUT_WIND_HAS_DEPTH(mode)) {
    list[n++] = GLX_DEPTH_SIZE;
    list[n++] = 1;
  }
  if (GLUT_WIND_HAS_STENCIL(mode)) {
    list[n++] = GLX_STENCIL_SIZE;
    list[n++] = 1;
  }
  list[n] = (int) None; /* terminate list */

  /* glXChooseVisual specify GLX_BUFFER_SIZE prefers the
     "smallest index buffer of at least the specified size".
     This would be reasonable if GLUT allowed the user to
     specify the required buffe size, but GLUT's display mode
     is too simplistic (easy to use?). GLUT should try to find
     the "largest".  So start with a large buffer size and
     shrink until we find a matching one that exists. */

  for (i = 0; bufSizeList[i]; i++) {
    /* XXX Assumes list[1] is where GLX_BUFFER_SIZE parameter
       is. */
    list[1] = bufSizeList[i];
    vi = glXChooseVisual(__glutDisplay,
      __glutScreen, list);
    if (vi) {
      return vi;
    }
  }
  return NULL;
}

static XVisualInfo *
getVisualInfoRGB(unsigned int mode)
{
  int list[32];
  int n = 0;

  /* Should not be looking at display mode mask if
     __glutDisplayString is non-NULL. */
  assert(!__glutDisplayString);

  /* XXX Would a caching mechanism to minize the calls to
     glXChooseVisual? You'd have to reference count
     XVisualInfo* pointers.  Would also have to properly
     interact with glutInitDisplayString. */

  list[n++] = GLX_RGBA;
  list[n++] = GLX_RED_SIZE;
  list[n++] = 1;
  list[n++] = GLX_GREEN_SIZE;
  list[n++] = 1;
  list[n++] = GLX_BLUE_SIZE;
  list[n++] = 1;
  if (GLUT_WIND_HAS_ALPHA(mode)) {
    list[n++] = GLX_ALPHA_SIZE;
    list[n++] = 1;
  }
  if (GLUT_WIND_IS_DOUBLE(mode)) {
    list[n++] = GLX_DOUBLEBUFFER;
  }
  if (GLUT_WIND_IS_STEREO(mode)) {
    list[n++] = GLX_STEREO;
  }
  if (GLUT_WIND_HAS_DEPTH(mode)) {
    list[n++] = GLX_DEPTH_SIZE;
    list[n++] = 1;
  }
  if (GLUT_WIND_HAS_STENCIL(mode)) {
    list[n++] = GLX_STENCIL_SIZE;
    list[n++] = 1;
  }
  if (GLUT_WIND_HAS_ACCUM(mode)) {
    list[n++] = GLX_ACCUM_RED_SIZE;
    list[n++] = 1;
    list[n++] = GLX_ACCUM_GREEN_SIZE;
    list[n++] = 1;
    list[n++] = GLX_ACCUM_BLUE_SIZE;
    list[n++] = 1;
    if (GLUT_WIND_HAS_ALPHA(mode)) {
      list[n++] = GLX_ACCUM_ALPHA_SIZE;
      list[n++] = 1;
    }
  }

/* GLX_SAMPLES_SGIS has the same value as GLX_SAMPLES_ARB. */
# ifndef GLX_SAMPLES_ARB
#  define GLX_SAMPLES_ARB                      100001
# endif

  if (GLUT_WIND_IS_MULTISAMPLE(mode)) {
    if (
#ifdef _WIN32
        !has_WGL_ARB_multisample
#else
        !__glutIsSupportedByGLX("GLX_SGIS_multisample") &&
        !__glutIsSupportedByGLX("GLX_ARB_multisample")
#endif
       )  {
      return NULL;
    }
    /* Accept either the SGIS or ARB GLX multisample extensions. */
    list[n++] = GLX_SAMPLES_ARB;
    list[n++] = 2;
  }

  list[n] = (int) None; /* terminate list */

  return glXChooseVisual(__glutDisplay,
    __glutScreen, list);
}

XVisualInfo *
__glutGetVisualInfo(unsigned int mode)
{
  /* XXX GLUT_LUMINANCE not implemented for GLUT 3.0. */
  if (GLUT_WIND_IS_LUMINANCE(mode)) {
    return NULL;
  }

  if (GLUT_WIND_IS_RGB(mode)) {
    return getVisualInfoRGB(mode);
  } else {
    return getVisualInfoCI(mode);
  }
}

XVisualInfo *
__glutDetermineVisual(
  unsigned int displayMode,
  Bool * treatAsSingle,
  XVisualInfo * (getVisualInfo) (unsigned int))
{
  XVisualInfo *vis;

  /* Should not be looking at display mode mask if
     __glutDisplayString is non-NULL. */
  assert(!__glutDisplayString);

  *treatAsSingle = GLUT_WIND_IS_SINGLE(displayMode);
  vis = getVisualInfo(displayMode);
  if (!vis) {
    /* Fallback cases when can't get exactly what was asked
       for... */
    if (GLUT_WIND_IS_SINGLE(displayMode)) {
      /* If we can't find a single buffered visual, try looking
         for a double buffered visual.  We can treat a double
         buffered visual as a single buffer visual by changing
         the draw buffer to GL_FRONT and treating any swap
         buffers as no-ops. */
      displayMode |= GLUT_DOUBLE;
      vis = getVisualInfo(displayMode);
      *treatAsSingle = True;
    }
    if (!vis && GLUT_WIND_IS_MULTISAMPLE(displayMode)) {
      /* If we can't seem to get multisampling (ie, not Reality
         Engine class graphics!), go without multisampling.  It
         is up to the application to query how many multisamples
         were allocated (0 equals no multisampling) if the
         application is going to use multisampling for more than
         just antialiasing. */
      displayMode &= ~GLUT_MULTISAMPLE;
      vis = getVisualInfo(displayMode);
    }
  }
  return vis;
}

static void GLUTCALLBACK
defaultDisplay(void)
{
  /* XXX Remove the warning after GLUT 3.0. */
  __glutWarning("The following is a new check for GLUT 3.0; update your code.");
  __glutFatalError(
    "redisplay needed for window %d, but no display callback.",
    __glutCurrentWindow->num + 1);
}

static void GLUTCALLBACK
__glutDefaultReshape(int width, int height)
{
  GLUToverlay *overlay;

  /* Adjust the viewport of the window (and overlay if one
     exists). */
  MAKE_CURRENT_WINDOW(__glutCurrentWindow);
  glViewport(0, 0, (GLsizei) width, (GLsizei) height);
  overlay = __glutCurrentWindow->overlay;
  if (overlay) {
    MAKE_CURRENT_OVERLAY(overlay);
    glViewport(0, 0, (GLsizei) width, (GLsizei) height);
  }
  /* Make sure we are current to the current layer (application
     should be able to count on the current layer not changing
     unless the application explicitly calls glutUseLayer). */
  MAKE_CURRENT_LAYER(__glutCurrentWindow);
}

XVisualInfo *
__glutDetermineWindowVisual(Bool * treatAsSingle, Bool * visAlloced, void **fbc)
{
  if (__glutDisplayString) {

    /* __glutDisplayString should be NULL except if
       glutInitDisplayString has been called to register a
       different display string.  Calling glutInitDisplayString
       means using a string instead of an integer mask determine
       the visual to use. Using the function pointer variable
       __glutDetermineVisualFromString below avoids linking in
       the code for implementing glutInitDisplayString (ie,
       glut_dstr.o) unless glutInitDisplayString gets called by
       the application. */

    assert(__glutDetermineVisualFromString);
    *visAlloced = False;
    *fbc = NULL;
    return __glutDetermineVisualFromString(__glutDisplayString, treatAsSingle,
      requiredWindowCriteria, numRequiredWindowCriteria, requiredWindowCriteriaMask, fbc);
  } else {
    *visAlloced = True;
    *fbc = NULL;
    return __glutDetermineVisual(__glutDisplayMode,
      treatAsSingle, __glutGetVisualInfo);
  }
}

/* ARGSUSED5 */  /* Only Win32 uses gameMode parameter. */
GLUTwindow *
__glutCreateWindow(GLUTwindow * parent,
  int x, int y, int width, int height, int gameMode)
{
  GLUTwindow *window;
  XSetWindowAttributes wa;
  unsigned long attribMask;
  int winnum;
  int i;
#if defined(GLX_VERSION_1_1) && defined(GLX_SGIX_fbconfig)
  GLXFBConfigSGIX fbc;
#else
  void *fbc;
#endif

#if defined(_WIN32)
  WNDCLASS wc;
  int style;
  int pixelFormat;

  if (!GetClassInfo(GetModuleHandle(NULL), "GLUT", &wc)) {
    __glutOpenWin32Connection(NULL);
  }
#else
  if (!__glutDisplay) {
    __glutOpenXConnection(NULL);
  }
#endif
  if (__glutGameModeWindow) {
    __glutFatalError("cannot create windows in game mode.");
  }
  winnum = getUnusedWindowSlot();
  window = (GLUTwindow *) malloc(sizeof(GLUTwindow));
  if (!window) {
    __glutFatalError("out of memory.");
  }
  window->num = winnum;

#if !defined(_WIN32)
  window->vis = __glutDetermineWindowVisual(&window->treatAsSingle,
    &window->visAlloced, (void**) &fbc);
  if (!window->vis) {
    __glutFatalError(
      "visual with necessary capabilities not found.");
  }
  __glutSetupColormap(window->vis, &window->colormap, &window->cmap);
#endif
  window->eventMask = StructureNotifyMask | ExposureMask;

  attribMask = CWBackPixmap | CWBorderPixel | CWColormap | CWEventMask;
  wa.background_pixmap = None;
  wa.border_pixel = 0;
  wa.colormap = window->cmap;
  wa.event_mask = window->eventMask;
  if (parent) {
    if (parent->eventMask & GLUT_HACK_STOP_PROPAGATE_MASK) {
      wa.event_mask |= GLUT_HACK_STOP_PROPAGATE_MASK;
    }
    attribMask |= CWDontPropagate;
    wa.do_not_propagate_mask = parent->eventMask & GLUT_DONT_PROPAGATE_FILTER_MASK;
  } else {
    wa.do_not_propagate_mask = 0;
  }

  /* Stash width and height before Win32's __glutAdjustCoords
     possibly overwrites the values. */
  window->width = width;
  window->height = height;
  window->forceReshape = True;
  window->ignoreKeyRepeat = False;

#if defined(_WIN32)
  __glutAdjustCoords(parent ? parent->win : NULL,
    &x, &y, &width, &height);
  if (parent) {
    style = WS_CHILD;
  } else {
    if (gameMode) {
      /* Game mode window should be a WS_POPUP window to
         ensure that the taskbar is hidden by it.  A standard
         WS_OVERLAPPEDWINDOW does not hide the task bar. */
      style = WS_POPUP | WS_MAXIMIZE;
    } else {
      /* A standard toplevel window with borders and such. */
      style = WS_OVERLAPPEDWINDOW;
    }
  }
  window->win = CreateWindow("GLUT", "GLUT",
    WS_CLIPSIBLINGS | WS_CLIPCHILDREN | style,
    x, y, width, height, parent ? parent->win : __glutRoot,
    NULL, GetModuleHandle(NULL), 0);
  window->hdc = GetDC(window->win);
  /* Must set the XHDC for fake glXChooseVisual & fake
     glXCreateContext & fake XAllocColorCells. */
  XHDC = window->hdc;
  window->vis = __glutDetermineWindowVisual(&window->treatAsSingle,
    &window->visAlloced, &fbc);
  if (!window->vis) {
    __glutFatalError(
      "pixel format with necessary capabilities not found.");
  }
  pixelFormat = window->vis->num;
  if (pixelFormat == 0) {
    __glutFatalError("ChoosePixelFormat failed during window create.");
  }
  if (!SetPixelFormat(window->hdc, pixelFormat, &window->vis->pfd)) {
    __glutFatalError("SetPixelFormat failed during window create.");
  }
  __glutSetupColormap(window->vis, &window->colormap, &window->cmap);
  /* Make sure subwindows get a windowStatus callback. */
  if (parent) {
    PostMessage(parent->win, WM_ACTIVATE, 0, 0);
  }
  window->renderDc = window->hdc;
#else /* X Window System */
  window->win = XCreateWindow(__glutDisplay,
    parent == NULL ? __glutRoot : parent->win,
    x, y, width, height, 0,
    window->vis->depth, InputOutput, window->vis->visual,
    attribMask, &wa);
#endif
  window->renderWin = window->win;
#if defined(GLX_VERSION_1_1) && defined(GLX_SGIX_fbconfig)
  if (fbc) {
    window->ctx = glXCreateContextWithConfigSGIX(__glutDisplay, fbc,
      GLX_RGBA_TYPE_SGIX, None, __glutTryDirect);
  } else
#endif
  {
#ifdef _WIN32
  window->ctx = wglCreateContext(window->hdc);
#else
    window->ctx = glXCreateContext(__glutDisplay, window->vis,
      None, __glutTryDirect);
#endif
  }
  if (!window->ctx) {
    __glutFatalError(
      "failed to create OpenGL rendering context.");
  }
  window->renderCtx = window->ctx;
#if !defined(_WIN32)
  window->isDirect = glXIsDirect(__glutDisplay, window->ctx);
  if (__glutForceDirect) {
    if (!window->isDirect) {
      __glutFatalError("direct rendering not possible.");
    }
  }
#endif

  window->parent = parent;
  if (parent) {
    window->siblings = parent->children;
    parent->children = window;
  } else {
    window->siblings = NULL;
  }
  window->overlay = NULL;
  window->children = NULL;
  window->display = defaultDisplay;
  window->reshape = __glutDefaultReshape;
  window->mouse = NULL;
  window->motion = NULL;
  window->passive = NULL;
  window->entry = NULL;
  window->keyboard = NULL;
  window->keyboardUp = NULL;
  window->windowStatus = NULL;
  window->visibility = NULL;
  window->special = NULL;
  window->specialUp = NULL;
  window->buttonBox = NULL;
  window->dials = NULL;
  window->spaceMotion = NULL;
  window->spaceRotate = NULL;
  window->spaceButton = NULL;
  window->tabletMotion = NULL;
  window->tabletButton = NULL;
#ifdef _WIN32
  window->joystick = NULL;
  window->joyPollInterval = 0;
#endif
  window->tabletPos[0] = -1;
  window->tabletPos[1] = -1;
  window->shownState = 0;
  window->visState = -1;  /* not VisibilityUnobscured,
                             VisibilityPartiallyObscured, or
                             VisibilityFullyObscured */
  window->entryState = -1;  /* not EnterNotify or LeaveNotify */

  window->desiredConfMask = 0;
  window->buttonUses = 0;
  window->cursor = GLUT_CURSOR_INHERIT;

  /* Setup window to be mapped when glutMainLoop starts. */
  window->workMask = GLUT_MAP_WORK;
#ifdef _WIN32
  if (gameMode) {
    /* When mapping a game mode window, just show
       the window.  We have already created the game
       mode window with a maximize flag at creation
       time.  Doing a ShowWindow(window->win, SW_SHOWNORMAL)
       would be wrong for a game mode window since it
       would unmaximize the window. */
    window->desiredMapState = GameModeState;
  } else {
    window->desiredMapState = NormalState;
  }
#else
  window->desiredMapState = NormalState;
#endif
  window->prevWorkWin = __glutWindowWorkList;
  __glutWindowWorkList = window;

  /* Initially, no menus attached. */
  for (i = 0; i < GLUT_MAX_MENUS; i++) {
    window->menu[i] = 0;
  }

  /* Add this new window to the window list. */
  __glutWindowList[winnum] = window;

  /* Make the new window the current window. */
  __glutSetWindow(window);

  __glutDetermineMesaSwapHackSupport();

  if (window->treatAsSingle) {
    /* We do this because either the window really is single
       buffered (in which case this is redundant, but harmless,
       because this is the initial single-buffered context
       state); or we are treating a double buffered window as a
       single-buffered window because the system does not appear
       to export any suitable single- buffered visuals (in which
       the following are necessary). */
    glDrawBuffer(GL_FRONT);
    glReadBuffer(GL_FRONT);
  }
  return window;
}

#ifdef _WIN32
int GLUTAPIENTRY
__glutCreateWindowWithExit(const char *title, void (__cdecl *exitfunc)(int))
{
  __glutExitFunc = exitfunc;
  return glutCreateWindow(title);
}
#endif

/* CENTRY */
int GLUTAPIENTRY
glutCreateWindow(const char *title)
{
  static int firstWindow = 1;
  GLUTwindow *window;
#if !defined(_WIN32)
  XWMHints *wmHints;
#endif
  Window win;
  XTextProperty textprop;

  if (__glutGameModeWindow) {
    __glutFatalError("cannot create windows in game mode.");
  }
  window = __glutCreateWindow(NULL,
    __glutSizeHints.x, __glutSizeHints.y,
    __glutInitWidth, __glutInitHeight,
    /* not game mode */ 0);
  win = window->win;
  /* Setup ICCCM properties. */
  textprop.value = (unsigned char *) title;
  textprop.encoding = XA_STRING;
  textprop.format = 8;
  textprop.nitems = (unsigned long)strlen(title);
#if defined(_WIN32)
  SetWindowText(win, title);
  if (__glutIconic) {
    window->desiredMapState = IconicState;
  }
#else
  wmHints = XAllocWMHints();
  wmHints->initial_state =
    __glutIconic ? IconicState : NormalState;
  wmHints->flags = StateHint;
  XSetWMProperties(__glutDisplay, win, &textprop, &textprop,
  /* Only put WM_COMMAND property on first window. */
    firstWindow ? __glutArgv : NULL,
    firstWindow ? __glutArgc : 0,
    &__glutSizeHints, wmHints, NULL);
  XFree(wmHints);
  XSetWMProtocols(__glutDisplay, win, &__glutWMDeleteWindow, 1);
#endif
  firstWindow = 0;
  return window->num + 1;
}

int GLUTAPIENTRY
glutCreateSubWindow(int win, int x, int y, int width, int height)
{
  GLUTwindow *window;

  window = __glutCreateWindow(__glutWindowList[win - 1],
    x, y, width, height, /* not game mode */ 0);
#if !defined(_WIN32)
  {
    GLUTwindow *toplevel;

    toplevel = __glutToplevelOf(window);
    if (toplevel->cmap != window->cmap) {
      __glutPutOnWorkList(toplevel, GLUT_COLORMAP_WORK);
    }
  }
#endif
  return window->num + 1;
}
/* ENDCENTRY */

void
__glutDestroyWindow(GLUTwindow * window,
  GLUTwindow * initialWindow)
{
  GLUTwindow **prev, *cur, *parent, *siblings;

  /* Recursively destroy any children. */
  cur = window->children;
  while (cur) {
    siblings = cur->siblings;
    __glutDestroyWindow(cur, initialWindow);
    cur = siblings;
  }
  /* Remove from parent's children list (only necessary for
     non-initial windows and subwindows!). */
  parent = window->parent;
  if (parent && parent == initialWindow->parent) {
    prev = &parent->children;
    cur = parent->children;
    while (cur) {
      if (cur == window) {
        *prev = cur->siblings;
        break;
      }
      prev = &(cur->siblings);
      cur = cur->siblings;
    }
  }
  /* Unbind if bound to this window. */
  if (window == __glutCurrentWindow) {
    UNMAKE_CURRENT();
    __glutCurrentWindow = NULL;
  }
  /* Invalidate glutExtensionSupported string cache if needed. */
  __glutInvalidateExtensionStringCacheIfNeeded(window->ctx);
  /* Begin tearing down window itself. */
  if (window->overlay) {
    __glutInvalidateExtensionStringCacheIfNeeded(window->overlay->ctx);
    __glutFreeOverlayFunc(window->overlay);
  }
  XDestroyWindow(__glutDisplay, window->win);
  glXDestroyContext(__glutDisplay, window->ctx);
  if (window->colormap) {
    /* Only color index windows have colormap data structure. */
    __glutFreeColormap(window->colormap);
  }
  /* NULLing the __glutWindowList helps detect is a window
     instance has been destroyed, given a window number. */
  __glutWindowList[window->num] = NULL;

  /* Cleanup data structures that might contain window. */
  cleanWindowWorkList(window);
#if !defined(_WIN32)
  cleanStaleWindowList(window);
#endif
  /* Remove window from the "get window cache" if it is there. */
  if (__glutWindowCache == window) {
    __glutWindowCache = NULL;
  }

  if (window->visAlloced) {
    /* Only free XVisualInfo* gotten from glXChooseVisual. */
    XFree(window->vis);
  }

  if (window == __glutGameModeWindow) {
    /* Destroying the game mode window should implicitly
       have GLUT leave game mode. */
    __glutCloseDownGameMode();
  }

  free(window);
}

/* CENTRY */
void GLUTAPIENTRY
glutDestroyWindow(int win)
{
  GLUTwindow *window = __glutWindowList[win - 1];

  if (__glutMappedMenu && __glutMenuWindow == window) {
    __glutFatalUsage("destroying menu window not allowed while menus in use");
  }
#if !defined(_WIN32)
  /* If not a toplevel window... */
  if (window->parent) {
    /* Destroying subwindows may change colormap requirements;
       recalculate toplevel window's WM_COLORMAP_WINDOWS
       property. */
    __glutPutOnWorkList(__glutToplevelOf(window->parent),
      GLUT_COLORMAP_WORK);
  }
#endif
  __glutDestroyWindow(window, window);
  XFlush(__glutDisplay);
}
/* ENDCENTRY */

void
__glutChangeWindowEventMask(long eventMask, Bool add)
{
  if (add) {
    /* Add eventMask to window's event mask. */
    if ((__glutCurrentWindow->eventMask & eventMask) !=
      eventMask) {
      __glutCurrentWindow->eventMask |= eventMask;
      __glutPutOnWorkList(__glutCurrentWindow,
        GLUT_EVENT_MASK_WORK);
    }
  } else {
    /* Remove eventMask from window's event mask. */
    if (__glutCurrentWindow->eventMask & eventMask) {
      __glutCurrentWindow->eventMask &= ~eventMask;
      __glutPutOnWorkList(__glutCurrentWindow,
        GLUT_EVENT_MASK_WORK);
    }
  }
}

void GLUTAPIENTRY
glutDisplayFunc(GLUTdisplayCB displayFunc)
{
  /* XXX Remove the warning after GLUT 3.0. */
  if (!displayFunc) {
    __glutFatalError("NULL display callback not allowed in GLUT 3.0; update your code.");
  }
  __glutCurrentWindow->display = displayFunc;
}

void GLUTAPIENTRY
glutMouseFunc(GLUTmouseCB mouseFunc)
{
  if (__glutCurrentWindow->mouse) {
    if (!mouseFunc) {
      /* Previous mouseFunc being disabled. */
      __glutCurrentWindow->buttonUses--;
      __glutChangeWindowEventMask(
        ButtonPressMask | ButtonReleaseMask,
        __glutCurrentWindow->buttonUses > 0);
    }
  } else {
    if (mouseFunc) {
      /* Previously no mouseFunc, new one being installed. */
      __glutCurrentWindow->buttonUses++;
      __glutChangeWindowEventMask(
        ButtonPressMask | ButtonReleaseMask, True);
    }
  }
  __glutCurrentWindow->mouse = mouseFunc;
}

void GLUTAPIENTRY
glutMotionFunc(GLUTmotionCB motionFunc)
{
  /* Hack.  Some window managers (4Dwm by default) will mask
     motion events if the client is not selecting for button
     press and release events. So we select for press and
     release events too (being careful to use reference
     counting).  */
  if (__glutCurrentWindow->motion) {
    if (!motionFunc) {
      /* previous mouseFunc being disabled */
      __glutCurrentWindow->buttonUses--;
      __glutChangeWindowEventMask(
        ButtonPressMask | ButtonReleaseMask,
        __glutCurrentWindow->buttonUses > 0);
    }
  } else {
    if (motionFunc) {
      /* Previously no mouseFunc, new one being installed. */
      __glutCurrentWindow->buttonUses++;
      __glutChangeWindowEventMask(
        ButtonPressMask | ButtonReleaseMask, True);
    }
  }
  /* Real work of selecting for passive mouse motion.  */
  __glutChangeWindowEventMask(
    Button1MotionMask | Button2MotionMask | Button3MotionMask,
    motionFunc != NULL);
  __glutCurrentWindow->motion = motionFunc;
}

void GLUTAPIENTRY
glutPassiveMotionFunc(GLUTpassiveCB passiveMotionFunc)
{
  __glutChangeWindowEventMask(PointerMotionMask,
    passiveMotionFunc != NULL);

  /* Passive motion also requires watching enters and leaves so
     that a fake passive motion event can be generated on an
     enter. */
  __glutChangeWindowEventMask(EnterWindowMask | LeaveWindowMask,
    __glutCurrentWindow->entry != NULL || passiveMotionFunc != NULL);

  __glutCurrentWindow->passive = passiveMotionFunc;
}

void GLUTAPIENTRY
glutEntryFunc(GLUTentryCB entryFunc)
{
  __glutChangeWindowEventMask(EnterWindowMask | LeaveWindowMask,
    entryFunc != NULL || __glutCurrentWindow->passive);
  __glutCurrentWindow->entry = entryFunc;
  if (!entryFunc) {
    __glutCurrentWindow->entryState = -1;
  }
}

void GLUTAPIENTRY
glutWindowStatusFunc(GLUTwindowStatusCB windowStatusFunc)
{
  __glutChangeWindowEventMask(VisibilityChangeMask,
    windowStatusFunc != NULL);
  __glutCurrentWindow->windowStatus = windowStatusFunc;
  if (!windowStatusFunc) {
    /* Make state invalid. */
    __glutCurrentWindow->visState = -1;
  }
}

static void GLUTCALLBACK
visibilityHelper(int status)
{
  if (status == GLUT_HIDDEN || status == GLUT_FULLY_COVERED) {
    __glutCurrentWindow->visibility(GLUT_NOT_VISIBLE);
  } else {
    __glutCurrentWindow->visibility(GLUT_VISIBLE);
  }
}

void GLUTAPIENTRY
glutVisibilityFunc(GLUTvisibilityCB visibilityFunc)
{
  __glutCurrentWindow->visibility = visibilityFunc;
  if (visibilityFunc) {
    glutWindowStatusFunc(visibilityHelper);
  } else {
    glutWindowStatusFunc(NULL);
  }
}

void GLUTAPIENTRY
glutReshapeFunc(GLUTreshapeCB reshapeFunc)
{
  if (reshapeFunc) {
    __glutCurrentWindow->reshape = reshapeFunc;
  } else {
    __glutCurrentWindow->reshape = __glutDefaultReshape;
  }
}
