text: /**
   * 2ine; an OS/2 emulator for Linux.
   *
   * Please see the file LICENSE.txt in the source's root directory.
   *
   *  This file written by Ryan C. Gordon.
   */
  
  #include "os2native.h"
  #include "pmwin.h"
  #include "SDL.h"
  
  #include "pmwin-lx.h"
  
  // NOTE: PM reference manual says OS/2 generally ignores the HAB you pass
  //  to functions, instead getting that info from the current thread, but
  //  other IBM platforms might not do that, so programs should always pass
  //  a valid HAB to everything. Things like WinCreateWindow() probably rely
  //  on the current thread's HAB without requiring the API call to specify
  //  it in any case.
  // So current approach: keep a pointer to the anchor block in the TIB, and
  //  the anchor block keeps a copy of it's HAB. When the anchor block is
  //  needed, we pull it from the TIB directly, and if it's in a function
  //  where a HAB is specified, we fail if the handles don't match.
  // If I later find out that OS/2 doesn't even care even if you give it a
  //  bogus HAB, we can relax this check.
  
  
  // !!! FIXME: You can send messages to HWNDs on a different thread, which
  // !!! FIXME:  means HWND values must be unique across threads. Technically,
  // !!! FIXME:  they have to be unique across processes, too.  :/
  
  
  #define FIRST_HWND_VALUE 10
  #define FIRST_HPS_VALUE 10
  
  // This is the keyname we use on an heavyweight SDL window to find our
  //  associated Window*.
  #define WINDATA_WINDOWPTR_NAME "2ine_windowptr"
  
  static SDL_atomic_t GHABCounter;
  static SDL_atomic_t GHMQCounter;
  
  typedef struct WindowClass
  {
      char *name;
      PFNWP window_proc;
      ULONG style;
      ULONG data_len;
      struct WindowClass *next;
  } WindowClass;
  
  // these get shared with all children, but they're owned by the topmost parent.
  typedef struct
  {
      SDL_Window *window;
      SDL_Renderer *renderer;
      SDL_Texture *texture;
  } HeavyWeightWindow;
  
  typedef struct Window
  {
      PFNWP window_proc;
      HeavyWeightWindow heavy;
      struct Window *parent;
      struct Window *children;
      struct Window *sibling;
      struct Window *owner;
      const char *window_class_name;  // Class at creation time. styles and window procs might change later, though! This is just to make debugging easier.
      char *text;
      void *data;
      size_t data_len;
      ULONG id;
      ULONG class_style;
      ULONG style;
      HWND hwnd;
      LONG x;
      LONG y;
      LONG w;
      LONG h;
      BOOL enabled;
  } Window;
  
  typedef struct MessageQueueItem
  {
      QMSG qmsg;
      struct MessageQueueItem *next;
  } MessageQueueItem;
  
  typedef struct
  {
      HMQ hmq;
      MessageQueueItem *head;
      MessageQueueItem *tail;
      MessageQueueItem *free_pool;
  } MessageQueue;
  
  typedef struct
  {
      HPS hps;
      Window *window;
      // !!! FIXME: this is gonna be WAY more complicated.
  } PresentationSpace;
  
  typedef struct
  {
      HAB hab;  // actual HAB value for this block, for comparison.
      ERRORID last_error;
      WindowClass *registered_classes;
      MessageQueue message_queue;
      Window *windows;
      size_t windows_array_len;
      PresentationSpace *pres_spaces;
      size_t pres_spaces_array_len;
  } AnchorBlock;
  
  #define SET_WIN_ERROR_AND_RETURN(anchor, err, rc) { anchor->last_error = (err); return (rc); }
  
  static Window desktop_window;  // !!! FIXME: initialize and upkeep on this.
  
  static inline AnchorBlock *getAnchorBlockNoHAB(void)
  {
      return (AnchorBlock *) LX_GETPOSTTIB()->anchor_block;
  } // getAnchorBlockNoHAB
  
  static AnchorBlock *getAnchorBlock(const HAB hab)
  {
      AnchorBlock *anchor = getAnchorBlockNoHAB();
      if (!anchor) {
          return NULL;
      } else if (anchor->hab != hab) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INVALID_HAB, NULL);
      }
      return anchor;
  } // getAnchorBlock
  
  static Window *getWindowFromHWND(const AnchorBlock *anchor, const HWND hwnd)
  {
      if (hwnd >= FIRST_HWND_VALUE) {
          const size_t idx = (size_t) (hwnd - FIRST_HWND_VALUE);
          if (idx >= anchor->windows_array_len) {
              return NULL;
          }
          Window *win = &anchor->windows[idx];
          return (win->hwnd == hwnd) ? win : NULL;
      } else if (hwnd == HWND_DESKTOP) {
          return &desktop_window;
      }
      return NULL;
  } // getWindowFromHWND
  
  static PresentationSpace *getPresentationSpaceFromHPS(const AnchorBlock *anchor, const HPS hps)
  {
      if (hps >= FIRST_HPS_VALUE) {
          const size_t idx = (size_t) (hps - FIRST_HPS_VALUE);
          if (idx >= anchor->pres_spaces_array_len) {
              return NULL;
          }
          PresentationSpace *ps = &anchor->pres_spaces[idx];
          return (ps->hps == hps) ? ps : NULL;
      }
      return NULL;
  } // getPresentationSpaceFromHPS
  
  static const char *messageName(const ULONG msg)
  {
      switch (msg) {
          #define MSGCASE(m) case m: return #m
          MSGCASE(WM_NULL);
          MSGCASE(WM_CREATE);
          MSGCASE(WM_DESTROY);
          MSGCASE(WM_ENABLE);
          MSGCASE(WM_SHOW);
          MSGCASE(WM_MOVE);
          MSGCASE(WM_SIZE);
          MSGCASE(WM_ADJUSTWINDOWPOS);
          MSGCASE(WM_CALCVALIDRECTS);
          MSGCASE(WM_SETWINDOWPARAMS);
          MSGCASE(WM_QUERYWINDOWPARAMS);
          MSGCASE(WM_HITTEST);
          MSGCASE(WM_ACTIVATE);
          MSGCASE(WM_SETFOCUS);
          MSGCASE(WM_SETSELECTION);
          MSGCASE(WM_PPAINT);
          MSGCASE(WM_PSETFOCUS);
          MSGCASE(WM_PSYSCOLORCHANGE);
          MSGCASE(WM_PSIZE);
          MSGCASE(WM_PACTIVATE);
          MSGCASE(WM_PCONTROL);
          MSGCASE(WM_COMMAND);
          MSGCASE(WM_SYSCOMMAND);
          MSGCASE(WM_HELP);
          MSGCASE(WM_PAINT);
          MSGCASE(WM_TIMER);
          MSGCASE(WM_SEM1);
          MSGCASE(WM_SEM2);
          MSGCASE(WM_SEM3);
          MSGCASE(WM_SEM4);
          MSGCASE(WM_CLOSE);
          MSGCASE(WM_QUIT);
          MSGCASE(WM_SYSCOLORCHANGE);
          MSGCASE(WM_SYSVALUECHANGED);
          MSGCASE(WM_APPTERMINATENOTIFY);
          MSGCASE(WM_PRESPARAMCHANGED);
          MSGCASE(WM_CONTROL);
          MSGCASE(WM_VSCROLL);
          MSGCASE(WM_HSCROLL);
          MSGCASE(WM_INITMENU);
          MSGCASE(WM_MENUSELECT);
          MSGCASE(WM_MENUEND);
          MSGCASE(WM_DRAWITEM);
          MSGCASE(WM_MEASUREITEM);
          MSGCASE(WM_CONTROLPOINTER);
          MSGCASE(WM_QUERYDLGCODE);
          MSGCASE(WM_INITDLG);
          MSGCASE(WM_SUBSTITUTESTRING);
          MSGCASE(WM_MATCHMNEMONIC);
          MSGCASE(WM_SAVEAPPLICATION);
          MSGCASE(WM_FLASHWINDOW);
          MSGCASE(WM_FORMATFRAME);
          MSGCASE(WM_UPDATEFRAME);
          MSGCASE(WM_FOCUSCHANGE);
          MSGCASE(WM_SETBORDERSIZE);
          MSGCASE(WM_TRACKFRAME);
          MSGCASE(WM_MINMAXFRAME);
          MSGCASE(WM_SETICON);
          MSGCASE(WM_QUERYICON);
          MSGCASE(WM_SETACCELTABLE);
          MSGCASE(WM_QUERYACCELTABLE);
          MSGCASE(WM_TRANSLATEACCEL);
          MSGCASE(WM_QUERYTRACKINFO);
          MSGCASE(WM_QUERYBORDERSIZE);
          MSGCASE(WM_NEXTMENU);
          MSGCASE(WM_ERASEBACKGROUND);
          MSGCASE(WM_QUERYFRAMEINFO);
          MSGCASE(WM_QUERYFOCUSCHAIN);
          MSGCASE(WM_OWNERPOSCHANGE);
          MSGCASE(WM_CALCFRAMERECT);
          MSGCASE(WM_WINDOWPOSCHANGED);
          MSGCASE(WM_ADJUSTFRAMEPOS);
          MSGCASE(WM_QUERYFRAMECTLCOUNT);
          MSGCASE(WM_QUERYHELPINFO);
          MSGCASE(WM_SETHELPINFO);
          MSGCASE(WM_ERROR);
          MSGCASE(WM_REALIZEPALETTE);
          MSGCASE(WM_RENDERFMT);
          MSGCASE(WM_RENDERALLFMTS);
          MSGCASE(WM_DESTROYCLIPBOARD);
          MSGCASE(WM_PAINTCLIPBOARD);
          MSGCASE(WM_SIZECLIPBOARD);
          MSGCASE(WM_HSCROLLCLIPBOARD);
          MSGCASE(WM_VSCROLLCLIPBOARD);
          MSGCASE(WM_DRAWCLIPBOARD);
          MSGCASE(WM_MOUSEMOVE);
          MSGCASE(WM_BUTTON1DOWN);
          MSGCASE(WM_BUTTON1UP);
          MSGCASE(WM_BUTTON1DBLCLK);
          MSGCASE(WM_BUTTON2DOWN);
          MSGCASE(WM_BUTTON2UP);
          MSGCASE(WM_BUTTON2DBLCLK);
          MSGCASE(WM_BUTTON3DOWN);
          MSGCASE(WM_BUTTON3UP);
          MSGCASE(WM_BUTTON3DBLCLK);
          MSGCASE(WM_CHAR);
          MSGCASE(WM_VIOCHAR);
          MSGCASE(WM_JOURNALNOTIFY);
          MSGCASE(WM_MOUSEMAP);
          MSGCASE(WM_VRNDISABLED);
          MSGCASE(WM_VRNENABLED);
          MSGCASE(WM_DDE_INITIATE);
          MSGCASE(WM_DDE_REQUEST);
          MSGCASE(WM_DDE_ACK);
          MSGCASE(WM_DDE_DATA);
          MSGCASE(WM_DDE_ADVISE);
          MSGCASE(WM_DDE_UNADVISE);
          MSGCASE(WM_DDE_POKE);
          MSGCASE(WM_DDE_EXECUTE);
          MSGCASE(WM_DDE_TERMINATE);
          MSGCASE(WM_DDE_INITIATEACK);
          MSGCASE(WM_QUERYCONVERTPOS);
          MSGCASE(WM_MSGBOXINIT);
          MSGCASE(WM_MSGBOXDISMISS);
          MSGCASE(WM_CTLCOLORCHANGE);
          MSGCASE(WM_QUERYCTLTYPE);
          MSGCASE(WM_CHORD);
          MSGCASE(WM_BUTTON1MOTIONSTART);
          MSGCASE(WM_BUTTON1MOTIONEND);
          MSGCASE(WM_BUTTON1CLICK);
          MSGCASE(WM_BUTTON2MOTIONSTART);
          MSGCASE(WM_BUTTON2MOTIONEND);
          MSGCASE(WM_BUTTON2CLICK);
          MSGCASE(WM_BUTTON3MOTIONSTART);
          MSGCASE(WM_BUTTON3MOTIONEND);
          MSGCASE(WM_BUTTON3CLICK);
          MSGCASE(WM_BEGINDRAG);
          MSGCASE(WM_ENDDRAG);
          MSGCASE(WM_SINGLESELECT);
          MSGCASE(WM_OPEN);
          MSGCASE(WM_CONTEXTMENU);
          MSGCASE(WM_CONTEXTHELP);
          MSGCASE(WM_TEXTEDIT);
          MSGCASE(WM_BEGINSELECT);
          MSGCASE(WM_ENDSELECT);
          MSGCASE(WM_PICKUP);
          MSGCASE(WM_SEMANTICEVENT);
          MSGCASE(WM_USER);
          #undef MSGCASE
          default: break;
      }
  
      return "???";
  } // messageName
  
  static WindowClass *findRegisteredClass(AnchorBlock *anchor, const char *classname)
  {
      const ULONG intclass = (ULONG) (size_t) classname;
      if ((intclass >= 0xffff0001) && (intclass <= 0xffff0070)) {
          // built-in class, like WC_BUTTON or whatever.
          switch (intclass) {
              // !!! FIXME: write me.
              case WC_FRAME:
              case WC_COMBOBOX:
              case WC_BUTTON:
              case WC_MENU:
              case WC_STATIC:
              case WC_ENTRYFIELD:
              case WC_LISTBOX:
              case WC_SCROLLBAR:
              case WC_TITLEBAR:
              case WC_MLE:
              case WC_APPSTAT:
              case WC_KBDSTAT:
              case WC_PECIC:
              case WC_DBE_KKPOPUP:
              case WC_SPINBUTTON:
              case WC_CONTAINER:
              case WC_SLIDER:
              case WC_VALUESET:
              case WC_NOTEBOOK:
              case WC_CIRCULARSLIDER:
              default:
                  return NULL;
          }
      } else {
          for (WindowClass *i = anchor->registered_classes; i; i = i->next) {
              if (strcmp(i->name, classname) == 0) {
                  return i;
              }
          }
      }
      return NULL;
  } // findRegisteredClass
  
  static ULONG currentSystemTicks(void)
  {
      FIXME("this isn't correct");  // see notes in processSDLEvent() for details.
      return SDL_GetTicks();
  } // currentSystemTicks
  
  HAB WinInitialize(ULONG flOptions)
  {
      TRACE_NATIVE("WinInitialize(%u)", (uint) flOptions);
  
      if (flOptions != 0) {
          return NULLHANDLE;  // reserved; must be zero.
      }
  
      LxPostTIB *posttib = LX_GETPOSTTIB();
      if (posttib->anchor_block != NULL) {
          return NULLHANDLE;  // fail if thread already has a HAB
      }
  
      if (!SDL_WasInit(SDL_INIT_VIDEO)) {
          if (SDL_Init(SDL_INIT_VIDEO) == -1) {
              fprintf(stderr, "SDL_INIT_VIDEO failed: %s\n", SDL_GetError());
              return NULLHANDLE;
          }
      }
  
      FIXME("OS/2 doesn't support multi-monitor");  // this might not be true, actually.
      SDL_Rect desktoprect;
      SDL_GetDisplayBounds(0, &desktoprect);
      desktop_window.hwnd = HWND_DESKTOP;
      desktop_window.w = desktoprect.w;
      desktop_window.h = desktoprect.h;
  
      AnchorBlock *anchor = calloc(1, sizeof (AnchorBlock));
      if (anchor == NULL) {
          return NULLHANDLE;
      }
  
      anchor->windows_array_len = 16;
      anchor->windows = (Window *) malloc(sizeof (Window) * anchor->windows_array_len);
      if (!anchor->windows) {
          free(anchor);
          return NULLHANDLE;
      }
      memset(anchor->windows, '\0', sizeof (Window) * anchor->windows_array_len);
  
      anchor->pres_spaces_array_len = 16;
      anchor->pres_spaces = (PresentationSpace *) malloc(sizeof (PresentationSpace) * anchor->pres_spaces_array_len);
      if (!anchor->pres_spaces) {
          free(anchor->windows);
          free(anchor);
          return NULLHANDLE;
      }
      memset(anchor->pres_spaces, '\0', sizeof (PresentationSpace) * anchor->pres_spaces_array_len);
  
      const int ihab = SDL_AtomicAdd(&GHABCounter, 1) + 1;
      if (ihab <= 0) {  // this is clearly a pathological program.
          free(anchor->pres_spaces);
          free(anchor->windows);
          free(anchor);
          return NULLHANDLE;
      }
  
      const HAB hab = (HAB) ihab;
      anchor->hab = hab;
      posttib->anchor_block = anchor;
  
      return hab;
  } // WinInitialize
  
  static MRESULT sendMessage(Window *win, ULONG msg, MPARAM mp1, MPARAM mp2)
  {
      PFNWP winproc = win->window_proc;
      if (!winproc) {
          winproc = WinDefWindowProc;
      }
      TRACE_EVENT("EVENT SEND { hwnd=%u, msg=%u (%s), mp1=%p, mp2=%p, proc=%p }", (uint) win->hwnd, (uint) msg, messageName(msg), mp1, mp2, winproc);
      return winproc(win->hwnd, msg, mp1, mp2);
  } // sendMessage
  
  static BOOL destroyMessageQueue(AnchorBlock *anchor)
  {
      MessageQueueItem *next = NULL;
      for (MessageQueueItem *i = anchor->message_queue.head; i; i = next) {
          next = i->next;
          free(i);
      }
      for (MessageQueueItem *i = anchor->message_queue.free_pool; i; i = next) {
          next = i->next;
          free(i);
      }
      TRACE_EVENT("HMQ %u destroyed", (uint) anchor->message_queue.hmq);
      memset(&anchor->message_queue, '\0', sizeof (MessageQueue));
      return TRUE;
  } // destroyMessageQueue
  
  static void destroyWindow(AnchorBlock *anchor, Window *win)
  {
      if (!win || (win->hwnd == NULLHANDLE)) {
          return;
      }
  
      // !!! FIXME: hide window first, and optionally send WM_ACTIVATE message if losing focus.
      // !!! FIXME: send WM_RENDERALLFMTS if we are clipboard owner and unrendered formats are in the clipboard.
  
      // send WM_DESTROY. This is a simple notification, and cannot prevent destruction.
      sendMessage(win, WM_DESTROY, 0, 0);
  
      // Next, destroy all child windows.
      Window *next = NULL;
      for (Window *i = win->children; i != NULL; i = next) {
          next = i->sibling;
          destroyWindow(anchor, i);
      }
  
      // !!! FIXME: see notes in SDK reference about disassociating/destroying HPS objects.
  
      // if this is a heavyweight window, destroy those parts.
      if (win->parent == &desktop_window) {
          SDL_DestroyTexture(win->heavy.texture);
          SDL_DestroyRenderer(win->heavy.renderer);
          SDL_DestroyWindow(win->heavy.window);
      }
  
      if (win->parent->children == win) {
          win->parent->children = win->sibling;
      } else {
          for (Window *i = win->parent->children; i != NULL; i = i->sibling) {
              assert(i != win);
              if (i->sibling == win) {
                  i->sibling = win->sibling;
                  break;
              }
          }
      }
      free(win->text);
      free(win->data);
      memset(win, '\0', sizeof (*win));
  } // destroyWindow
  
  static void destroyPresentationSpace(AnchorBlock *anchor, PresentationSpace *ps)
  {
      if (!ps || (ps->hps == NULLHANDLE)) {
          return;
      }
      FIXME("this will be more complicated later");
      memset(ps, '\0', sizeof (*ps));
  } // destroyPresentationSpace
  
  BOOL WinTerminate(HAB hab)
  {
      TRACE_NATIVE("WinTerminate(%u)", (uint) hab);
      AnchorBlock *anchor = getAnchorBlock(hab);
      if (!anchor) {
          return FALSE;
      }
  
      for (size_t i = 0; i < anchor->pres_spaces_array_len; i++) {
          destroyPresentationSpace(anchor, &anchor->pres_spaces[i]);
      }
      free(anchor->pres_spaces);
  
      for (size_t i = 0; i < anchor->windows_array_len; i++) {
          destroyWindow(anchor, &anchor->windows[i]);
      }
      free(anchor->windows);
  
      WindowClass *winclassnext = NULL;
      for (WindowClass *i = anchor->registered_classes; i; i = winclassnext) {
          winclassnext = i->next;
          free(i->name);
          free(i);
      }
  
      if (anchor->message_queue.hmq != NULLHANDLE) {
          destroyMessageQueue(anchor);
      }
  
      free(anchor);
      LX_GETPOSTTIB()->anchor_block = NULL;
  
      SDL_Quit();  // !!! FIXME: does this reference count?
  
      return TRUE;
  } // WinTerminate
  
  ERRORID WinGetLastError(HAB hab)
  {
      TRACE_NATIVE("WinGetLastError(%u)", (uint) hab);
      AnchorBlock *anchor = getAnchorBlock(hab);
      if (!anchor) {
          return PMERR_INVALID_HAB;
      }
      const ERRORID retval = anchor->last_error;
      SET_WIN_ERROR_AND_RETURN(anchor, NO_ERROR, retval);
  } // WinGetLastError
  
  HMQ WinCreateMsgQueue(HAB hab, LONG cmsg)
  {
      TRACE_NATIVE("WinCreateMsgQueue(%u, %d)", (uint) hab, (int) cmsg);
      AnchorBlock *anchor = getAnchorBlock(hab);
      if (!anchor) {
          return NULLHANDLE;
      } else if (anchor->message_queue.hmq != NULLHANDLE) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_MSG_QUEUE_ALREADY_EXISTS, NULLHANDLE);
      }
  
      // as of OS/2 Warp 4.0, cmsg is ignored (the queue is always dynamically allocated), so we ignore it too.
  
      assert(anchor->message_queue.head == NULL);
      assert(anchor->message_queue.tail == NULL);
      assert(anchor->message_queue.free_pool == NULL);
  
      const int ihmq = SDL_AtomicAdd(&GHMQCounter, 1) + 1;
      if (ihmq <= 0) {  // this is clearly a pathological program.
          return NULLHANDLE;
      }
  
      const HMQ hmq = (HMQ) ihmq;
      anchor->message_queue.hmq = hmq;
      TRACE_EVENT("HMQ %u created", (uint) hmq);
      return hmq;
  } // WinCreateMsgQueue
  
  static ERRORID postMessage(AnchorBlock *anchor, const QMSG *qmsg)
  {
      TRACE_EVENT("EVENT POST { hwnd=%u, msg=%u (%s), mp1=%p, mp2=%p, time=%u, ptl={%d,%d}, reserved=%u }", (uint) qmsg->hwnd, (uint) qmsg->msg, messageName(qmsg->msg), qmsg->mp1, qmsg->mp2, (uint) qmsg->time, (int) qmsg->ptl.x, (int) qmsg->ptl.y, (uint) qmsg->reserved);
  
      MessageQueueItem *item = anchor->message_queue.free_pool;
      if (item) {
          anchor->message_queue.free_pool = item->next;
      } else {
          item = (MessageQueueItem *) malloc(sizeof (*item));
          if (item == NULL) {
              return FALSE;
          }
      }
  
      memcpy(&item->qmsg, qmsg, sizeof (*qmsg));
      item->next = NULL;
  
      assert(!anchor->message_queue.head == !anchor->message_queue.tail);
      if (anchor->message_queue.tail) {
          anchor->message_queue.tail->next = item;
      } else {
          anchor->message_queue.head = item;
      }
      anchor->message_queue.tail = item;
  
      return TRUE;
  } // postMessage
  
  static inline Window *getWindowFromSDLWindow(SDL_Window *sdlwin)
  {
      return (Window *) sdlwin ? SDL_GetWindowData(sdlwin, WINDATA_WINDOWPTR_NAME) : NULL;
  } // getWindowFromSDLWindow
  
  
  #if 0
  static Window *hittestWindow(SDL_Window *sdlwin, int x, int y)
  {
      if (!sdlwin) {
          return NULL;
      }
  
      Window *win = getWindowFromSDLWindow(sdlwin);
      if (!win) {
          return NULL;
      }
  
      // (win) is currently the oldest parent, which is associated with the
      //  SDL_Window. We need to see which lightweight child window, if any,
      //  this event was meant for.
  
      // y coordinates are flipped on OS/2. zero is the bottom of the window.
      y = sdlwin->h - y;
  // !!! FIXME WRITE ME
      for (Window *i =
      if (win->class_style & CS_HITTEST) {
          sendMessage(win, WM_HITTEST,
      }
  
  } // findWindowFromSDL
  #endif
  
  static BOOL postTimestampedMsg(AnchorBlock *anchor, HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2, ULONG ticks)
  {
      const QMSG qmsg = { hwnd, msg, mp1, mp2, currentSystemTicks(), { 0, 0 }, 0 };
      return postMessage(anchor, &qmsg);
  } // postTimestampedMsg
  
  #if 0
  static BOOL postSimpleMsg(AnchorBlock *anchor, HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
  {
      return postTimestampedMsg(anchor, hwnd, msg, mp1, mp2, currentSystemTicks());
  } // postSimpleMsg
  
  #endif
  
  static BOOL postExposedMessages(AnchorBlock *anchor, Window *win, const ULONG ticks)
  {
      FIXME("this should WinInvalidateRect() the windows, not send paint messages");
      FIXME("this is a recursive hell");  // we need to start at the end of the sibling list, which is the bottom of the stack.
      BOOL retval = FALSE;
      if (win) {
          retval |= postExposedMessages(anchor, win->sibling, ticks);
          if (win->style & WS_VISIBLE) {
              FIXME("for CS_SYNCPAINT classes, this should send the WM_PAINT message right now instead of posting");
              retval |= postTimestampedMsg(anchor, win->hwnd, WM_PAINT, 0, 0, ticks);
              retval |= postExposedMessages(anchor, win->children, ticks);
          }
      }
      return retval;
  } // postExposedMessages
  
  
  static const char *sdlEventName(const SDL_EventType ev)
  {
      switch (ev) {
          #define EVCASE(m) case m: return #m
          EVCASE(SDL_QUIT);
          EVCASE(SDL_APP_TERMINATING);
          EVCASE(SDL_APP_LOWMEMORY);
          EVCASE(SDL_APP_WILLENTERBACKGROUND);
          EVCASE(SDL_APP_DIDENTERBACKGROUND);
          EVCASE(SDL_APP_WILLENTERFOREGROUND);
          EVCASE(SDL_APP_DIDENTERFOREGROUND);
          EVCASE(SDL_WINDOWEVENT);
          EVCASE(SDL_SYSWMEVENT);
          EVCASE(SDL_KEYDOWN);
          EVCASE(SDL_KEYUP);
          EVCASE(SDL_TEXTEDITING);
          EVCASE(SDL_TEXTINPUT);
          EVCASE(SDL_KEYMAPCHANGED);
          EVCASE(SDL_MOUSEMOTION);
          EVCASE(SDL_MOUSEBUTTONDOWN);
          EVCASE(SDL_MOUSEBUTTONUP);
          EVCASE(SDL_MOUSEWHEEL);
          EVCASE(SDL_JOYAXISMOTION);
          EVCASE(SDL_JOYBALLMOTION);
          EVCASE(SDL_JOYHATMOTION);
          EVCASE(SDL_JOYBUTTONDOWN);
          EVCASE(SDL_JOYBUTTONUP);
          EVCASE(SDL_JOYDEVICEADDED);
          EVCASE(SDL_JOYDEVICEREMOVED);
          EVCASE(SDL_CONTROLLERAXISMOTION);
          EVCASE(SDL_CONTROLLERBUTTONDOWN);
          EVCASE(SDL_CONTROLLERBUTTONUP);
          EVCASE(SDL_CONTROLLERDEVICEADDED);
          EVCASE(SDL_CONTROLLERDEVICEREMOVED);
          EVCASE(SDL_CONTROLLERDEVICEREMAPPED);
          EVCASE(SDL_FINGERDOWN);
          EVCASE(SDL_FINGERUP);
          EVCASE(SDL_FINGERMOTION);
          EVCASE(SDL_DOLLARGESTURE);
          EVCASE(SDL_DOLLARRECORD);
          EVCASE(SDL_MULTIGESTURE);
          EVCASE(SDL_CLIPBOARDUPDATE);
          EVCASE(SDL_DROPFILE);
          EVCASE(SDL_DROPTEXT);
          EVCASE(SDL_DROPBEGIN);
          EVCASE(SDL_DROPCOMPLETE);
          EVCASE(SDL_AUDIODEVICEADDED);
          EVCASE(SDL_AUDIODEVICEREMOVED);
          EVCASE(SDL_RENDER_TARGETS_RESET);
          EVCASE(SDL_RENDER_DEVICE_RESET);
          EVCASE(SDL_USEREVENT);
          #undef EVCASE
          default: break;
      }
      return "???";
  } // sdlEventName
  
  static const char *sdlWindowEventName(const SDL_WindowEventID ev)
  {
      switch (ev) {
          #define EVCASE(m) case m: return #m
          EVCASE(SDL_WINDOWEVENT_SHOWN);
          EVCASE(SDL_WINDOWEVENT_HIDDEN);
          EVCASE(SDL_WINDOWEVENT_EXPOSED);
          EVCASE(SDL_WINDOWEVENT_MOVED);
          EVCASE(SDL_WINDOWEVENT_RESIZED);
          EVCASE(SDL_WINDOWEVENT_SIZE_CHANGED);
          EVCASE(SDL_WINDOWEVENT_MINIMIZED);
          EVCASE(SDL_WINDOWEVENT_MAXIMIZED);
          EVCASE(SDL_WINDOWEVENT_RESTORED);
          EVCASE(SDL_WINDOWEVENT_ENTER);
          EVCASE(SDL_WINDOWEVENT_LEAVE);
          EVCASE(SDL_WINDOWEVENT_FOCUS_GAINED);
          EVCASE(SDL_WINDOWEVENT_FOCUS_LOST);
          EVCASE(SDL_WINDOWEVENT_CLOSE);
          EVCASE(SDL_WINDOWEVENT_TAKE_FOCUS);
          EVCASE(SDL_WINDOWEVENT_HIT_TEST);
          #undef EVCASE
          default: break;
      }
      return "???";
  } // sdlWindowEventName
  
  static BOOL processSDLEvent(AnchorBlock *anchor, const SDL_Event *sdlevent)
  {
      // !!! FIXME: we just drop events if we run out of memory, etc.
  
      // !!! FIXME: QMSG::time appears to be milliseconds since the system
      // !!! FIXME:  booted (presumably rolling over every 38 days). SDL's
      // !!! FIXME:  event timestamps (and SDL_GetTicks()) are milliseconds
      // !!! FIXME:  since SDL_Init(), which is more or less since app startup.
      // !!! FIXME:  We might need a way to map these, so they're consistent
      // !!! FIXME:  across processes.
      // !!! FIXME: Also, it's well known that SDL event timestamps are when
      // !!! FIXME:  SDL gets the event from the OS, not the timestamp of when
      // !!! FIXME:  the OS generated it, which might be a problem.
  
  /*
  sdfsdf
      HWND    hwnd;
      ULONG   msg;
      MPARAM  mp1;
      MPARAM  mp2;
      ULONG   time;
      POINTL  ptl;
      ULONG   reserved;
  sdfsdf
  */
  
  //SDL_GetWindowFromID(window) sdfsdf
  
      const ULONG ticks = (ULONG) sdlevent->common.timestamp;
  
      TRACE_EVENT("EVENT SDL { type=0x%X (%s) }", (uint) sdlevent->type, sdlEventName(sdlevent->type));
  
      switch (sdlevent->type) {
          case SDL_QUIT:
              return postTimestampedMsg(anchor, NULLHANDLE, WM_QUIT, 0, 0, ticks);
  
          case SDL_WINDOWEVENT:
              TRACE_EVENT("EVENT SDL WINDOW { type=0x%X (%s) }", (uint) sdlevent->window.event, sdlWindowEventName(sdlevent->window.event));
              switch (sdlevent->window.event) {
                  case SDL_WINDOWEVENT_SHOWN:
                  case SDL_WINDOWEVENT_EXPOSED: {
                      // maybe this doesn't matter in a world of compositing window managers, but it would be nice to completely avoid sending WM_PAINT to child windows that otherwise don't apply.
                      //  This only applies for heavyweight windows the OS is marking as exposed, not OS/2 apps marking OS/2 HWNDs as invalid for various reasons.
                      FIXME("SDL doesn't report invalid subregions, just that a window needs repainting");
                      Window *win = getWindowFromSDLWindow(SDL_GetWindowFromID(sdlevent->window.windowID));
                      if (!win) {
                          return FALSE;
                      }
                      return postExposedMessages(anchor, win, ticks);
                  }
  
                  default: break;  // not (currently) supported.
              }
              return FALSE;
  
  
  
  #if 0
                  case SDL_WINDOWEVENT_SHOWN:
                  case SDL_WINDOWEVENT_HIDDEN:
                  case SDL_WINDOWEVENT_MOVED,          /**< Window has been moved to data1, data2
                                       */
                  case SDL_WINDOWEVENT_RESIZED,        /**< Window has been resized to data1xdata2 */
                  case SDL_WINDOWEVENT_SIZE_CHANGED,   /**< The window size has changed, either as
                                           a result of an API call or through the
                                           system or user changing the window size. */
                  case SDL_WINDOWEVENT_MINIMIZED,      /**< Window has been minimized */
                  case SDL_WINDOWEVENT_MAXIMIZED,      /**< Window has been maximized */
                  case SDL_WINDOWEVENT_RESTORED,       /**< Window has been restored to normal size
                                           and position */
                  case SDL_WINDOWEVENT_ENTER,          /**< Window has gained mouse focus */
                  case SDL_WINDOWEVENT_LEAVE,          /**< Window has lost mouse focus */
                  case SDL_WINDOWEVENT_FOCUS_GAINED,   /**< Window has gained keyboard focus */
                  case SDL_WINDOWEVENT_FOCUS_LOST,     /**< Window has lost keyboard focus */
                  case SDL_WINDOWEVENT_CLOSE,          /**< The window manager requests that the window be closed */
                  case SDL_WINDOWEVENT_TAKE_FOCUS,     /**< Window is being offered a focus (should SetWindowInputFocus() on itself or a subwindow, or ignore) */
                  //case SDL_WINDOWEVENT_HIT_TEST        /**< Window had a hit test that wasn't SDL_HITTEST_NORMAL. */
  
  
  //    SDL_MOUSEMOTION    = 0x400, /**< Mouse moved */
  //    SDL_MOUSEBUTTONDOWN,        /**< Mouse button pressed */
  //    SDL_MOUSEBUTTONUP,          /**< Mouse button released */
  //    SDL_MOUSEWHEEL,             /**< Mouse wheel motion */
  //    SDL_KEYDOWN        = 0x300, /**< Key pressed */
  //    SDL_KEYUP,                  /**< Key released */
  //    SDL_TEXTINPUT,              /**< Keyboard text input */
  //    SDL_CLIPBOARDUPDATE = 0x900, /**< The clipboard changed */
  //    SDL_DROPFILE        = 0x1000, /**< The system requests a file open */
  //    SDL_DROPTEXT,                 /**< text/plain drag-and-drop event */
  //    SDL_DROPBEGIN,                /**< A new set of drops is beginning (NULL filename) */
  //    SDL_DROPCOMPLETE,             /**< Current set of drops is now complete (NULL filename) */
  
  
          case SDL_MOUSEBUTTONDOWN:
              
          case SDL_MOUSEBUTTONUP:
              if (sdlevent->button.clicks == 1) {}
  #endif
  
  
          default: return FALSE;  // not (currently) supported.
      }
  
      assert(!"shouldn't hit this");
      return TRUE;
  } // processSDLEvent
  
  static void pumpEvents(AnchorBlock *anchor)
  {
      SDL_Event sdlevent;
      BOOL queued = FALSE;
  
      // run through anything pending. If we don't post anything to the OS/2
      //  event queue, we need to block until we do, though, so then we'll use
      //  SDL_WaitEvent() in hopes that someday it's fully waitable.
      while (SDL_PollEvent(&sdlevent)) {
          queued |= processSDLEvent(anchor, &sdlevent);
      }
  
      while (!queued) {  // block until stuff shows up.
          while (!SDL_WaitEvent(&sdlevent)) {
              SDL_Delay(10);  // dunno what else to do here...
          }
          queued |= processSDLEvent(anchor, &sdlevent);
          while (SDL_PollEvent(&sdlevent)) {  // catch anything else pending.
              queued |= processSDLEvent(anchor, &sdlevent);
          }
      }
  } // waitForMessage
  
  static BOOL skipMessage(const PQMSG qmsg, HWND hwndFilter, ULONG msgFilterFirst, ULONG msgFilterLast)
  {
      if (hwndFilter && (qmsg->hwnd != hwndFilter)) {
          return TRUE;
      } else if (msgFilterFirst || msgFilterLast) {
          const ULONG msg = qmsg->msg;
          if (msgFilterFirst > msgFilterLast) {  // reject if between first and last.
              if ((msg >= msgFilterLast) && (msg <= msgFilterFirst)) return TRUE;
          } else {  // reject if not between first and last.
              if ((msg < msgFilterFirst) || (msg > msgFilterLast)) return TRUE;
          }
      }
      return FALSE;
  } // skipMessage
  
  BOOL WinGetMsg(HAB hab, PQMSG pqmsg, HWND hwndFilter, ULONG msgFilterFirst, ULONG msgFilterLast)
  {
      TRACE_NATIVE("WinGetMsg(%u, %p, %u, %u, %u)", (uint) hab, pqmsg, (uint) hwndFilter, (uint) msgFilterFirst, (uint) msgFilterLast);
      const BOOL bIsFiltering = (hwndFilter || msgFilterFirst || msgFilterLast);
      AnchorBlock *anchor = getAnchorBlock(hab);
      if (!anchor) {
          return FALSE;
      } else if (hwndFilter) {
          FIXME("fail with PMERR_INVALID_HWND if this is a bogus HWND.");
      }
  
      while (1) {
          MessageQueueItem *prev = NULL;
          MessageQueueItem *i = anchor->message_queue.head;
          if (bIsFiltering) {
              for (; i != NULL; prev = i, i = i->next) {
                  if (!skipMessage(&i->qmsg, hwndFilter, msgFilterFirst, msgFilterLast)) {
                      break;
                  }
              }
          }
  
          // Found an event? Remove from queue, return that info.
          if (i != NULL) {
              memcpy(pqmsg, &i->qmsg, sizeof (QMSG));
  
              TRACE_EVENT("EVENT GET { hwnd=%u, msg=%u (%s), mp1=%p, mp2=%p, time=%u, ptl={%d,%d}, reserved=%u }", (uint) pqmsg->hwnd, (uint) pqmsg->msg, messageName(pqmsg->msg), pqmsg->mp1, pqmsg->mp2, (uint) pqmsg->time, (int) pqmsg->ptl.x, (int) pqmsg->ptl.y, (uint) pqmsg->reserved);
  
              if (prev) {
                  prev->next = i->next;
              } else {
                  anchor->message_queue.head = i->next;
              }
  
              if (!anchor->message_queue.head) {
                  assert(anchor->message_queue.tail == i);
                  anchor->message_queue.tail = NULL;
              }
  
              i->next = anchor->message_queue.free_pool;
              anchor->message_queue.free_pool = i;
  
              return (pqmsg->msg != WM_QUIT) ? TRUE : FALSE;
          }
  
          pumpEvents(anchor);  // may block.
      }
  
      assert(!"shouldn't hit this.");
      return TRUE;  // shouldn't hit this.
  } // WinGetMsg
  
  MRESULT WinDispatchMsg(HAB hab, PQMSG pqmsg)
  {
      TRACE_NATIVE("WinDispatchMsg(%u, %p)", (uint) hab, pqmsg);
      AnchorBlock *anchor = getAnchorBlock(hab);
      if (anchor) {
          Window *win = getWindowFromHWND(anchor, pqmsg->hwnd);
          if (win) {
              return sendMessage(win, pqmsg->msg, pqmsg->mp1, pqmsg->mp2);
          }
      }
      return 0;
  } // WinDispatchMsg
  
  BOOL WinDestroyMsgQueue(HMQ hmq)
  {
      TRACE_NATIVE("WinDestroyMsgQueue(%u)", (uint) hmq);
      AnchorBlock *anchor = getAnchorBlockNoHAB();
      if (!anchor) {
          return FALSE;
      } else if ((hmq == NULLHANDLE) || (anchor->message_queue.hmq != hmq))  {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INVALID_HMQ, FALSE);
      }
  
      return destroyMessageQueue(anchor);
  } // WinDestroyMsgQueue
  
  BOOL WinRegisterClass(HAB hab, PSZ pszClassName, PFNWP pfnWndProc, ULONG flStyle, ULONG cbWindowData)
  {
      TRACE_NATIVE("WinRegisterClass(%u, '%s', %p, %u, %u)", (uint) hab, pszClassName, pfnWndProc, (uint) flStyle, (uint) cbWindowData);
  
      AnchorBlock *anchor = getAnchorBlock(hab);
      if (!anchor) {
          return FALSE;
      }
  
      WindowClass *winclass = findRegisteredClass(anchor, pszClassName);
      if (!winclass) {  // PM docs say this call can replace existing class.
          winclass = (WindowClass *) malloc(sizeof (WindowClass));
          if (!winclass) {
              SET_WIN_ERROR_AND_RETURN(anchor, PMERR_HEAP_OUT_OF_MEMORY, FALSE);
          } else if ((winclass->name = strdup(pszClassName)) == NULL) {
              free(winclass);
              SET_WIN_ERROR_AND_RETURN(anchor, PMERR_HEAP_OUT_OF_MEMORY, FALSE);
          }
          winclass->next = anchor->registered_classes;
          anchor->registered_classes = winclass;
      }
  
      winclass->window_proc = pfnWndProc;
      winclass->style = flStyle;
      winclass->data_len = cbWindowData;
  
      return TRUE;
  } // WinRegisterClass
  
  MRESULT WinSendMsg(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
  {
      // !!! FIXME: This needs to be able to send to windows in different
      // !!! FIXME:  threads (and processes!), so this will need some effort to
      // !!! FIXME:  support.
  
      TRACE_NATIVE("WinSendMsg(%u, %u, %p, %p)", (uint) hwnd, (uint) msg, mp1, mp2);
      AnchorBlock *anchor = getAnchorBlockNoHAB();
      if (anchor) {
          Window *win = getWindowFromHWND(anchor, hwnd);
          if (win) {
              return sendMessage(win, msg, mp1, mp2);
          }
      }
      return (MRESULT) 0;
  } // WinSendMsg
  
  BOOL WinPostMsg(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
  {
      // !!! FIXME: SDL docs say you can call this, but not WinSendMsg() if
      // !!! FIXME:  the thread doesn't have a message queue, but that has to
      // !!! FIXME:  be a typo, right? How do you post a message to a queue that
      // !!! FIXME:  doesn't exist? But one could call a window procedure
      // !!! FIXME:  directly...
  
      // !!! FIXME: This needs to be able to post to windows in different
      // !!! FIXME:  threads (and processes!), so this will need some effort to
      // !!! FIXME:  support.
  
      TRACE_NATIVE("WinPostMsg(%u, %u, %p, %p)", (uint) hwnd, (uint) msg, mp1, mp2);
      AnchorBlock *anchor = getAnchorBlockNoHAB();
      if (!anchor) {
          return FALSE;
      }
      if (anchor->message_queue.hmq == NULLHANDLE) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_NO_MSG_QUEUE, FALSE);
      }
  
      Window *win = getWindowFromHWND(anchor, hwnd);
      if (!win) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INVALID_HWND, FALSE);
      }
  
      int mousex, mousey;
      SDL_GetGlobalMouseState(&mousex, &mousey);  FIXME("track this ourselves?");
      const QMSG qmsg = { hwnd, msg, mp1, mp2, currentSystemTicks(), { mousex, mousey }, 0 };
      const ERRORID rc = postMessage(anchor, &qmsg);
      if (rc) {
          SET_WIN_ERROR_AND_RETURN(anchor, rc, FALSE);
      }
  
      return TRUE;
  } // WinPostMsg
  
  BOOL WinPostQueueMsg(HMQ hmq, ULONG msg, MPARAM mp1, MPARAM mp2)
  {
      TRACE_NATIVE("WinPostMsgQueue(%u, %u, %p, %p)", (uint) hmq, (uint) msg, mp1, mp2);
      // I assume this works like win32's PostThreadMessage(), in that there
      //  isn't a target HWND, so the target thread would have to deal with this
      //  message after a call to WinGetMsg() and not in a window procedure
      //  during a call to WinDispatchMsg().
  
      // (!!! FIXME: right now these are stored on the anchor block and not
      //             anywhere global I can reach it.)
      FIXME("need to be able to access other threads' message queues");
      return FALSE;
  } // WinPostQueueMsg
  
  HWND WinCreateWindow(HWND hwndParent, PSZ pszClass, PSZ pszName, ULONG flStyle, LONG x, LONG y, LONG cx, LONG cy, HWND hwndOwner, HWND hwndInsertBehind, ULONG id, PVOID pCtlData, PVOID pPresParams)
  {
      TRACE_NATIVE("WinCreateWindow(%u, '%s', '%s', %u, %d, %d, %d, %d, %u, %u, %u, %p, %p)", (uint) hwndParent, pszClass, pszName, (uint) flStyle, (int) x, (int) y, (int) cx, (int) cy, (uint) hwndOwner, hwndInsertBehind, (uint) id, pCtlData, pPresParams);
  
      AnchorBlock *anchor = getAnchorBlockNoHAB();
      if (!anchor) {
          return NULLHANDLE;
      }
  
      if (anchor->message_queue.hmq == NULLHANDLE) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_NO_MSG_QUEUE, NULLHANDLE);
      }
  
      const WindowClass *winclass = findRegisteredClass(anchor, pszClass);
      if (!winclass) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INVALID_PARM, NULLHANDLE);
      }
  
      Window *parent = getWindowFromHWND(anchor, hwndParent);
      if (!parent) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INVALID_HWND, NULLHANDLE);
      }
  
      Window *owner = NULL;
      if (hwndOwner != NULLHANDLE) {
          owner = getWindowFromHWND(anchor, hwndParent);
          if (!owner) {
              SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INVALID_HWND, NULLHANDLE);
          }
      }
  
      // make space for the new window struct in the anchor block
      Window *win = NULL;
      FIXME("optimize this");
      for (size_t i = 0; i < anchor->windows_array_len; i++) {
          if (anchor->windows[i].hwnd == NULLHANDLE) {
              win = &anchor->windows[i];
              break;
          }
      }
  
      if (!win) {
          const size_t len = anchor->windows_array_len;
          void *ptr = realloc(anchor->windows, sizeof (Window) * len * 2);
          if (!ptr) {
              SET_WIN_ERROR_AND_RETURN(anchor, PMERR_HEAP_OUT_OF_MEMORY, NULLHANDLE);
          }
          anchor->windows = (Window *) ptr;
          memset(&anchor->windows[len], '\0', sizeof (Window) * len);
          win = &anchor->windows[len];
          anchor->windows_array_len *= 2;
      }
  
      char *text = NULL;
      if (pszName) {
          // !!! FIXME: text isn't necessarily a string. It's window-class-specific data.
          text = strdup(pszName);
          if (!text) {
              SET_WIN_ERROR_AND_RETURN(anchor, PMERR_HEAP_OUT_OF_MEMORY, NULLHANDLE);
          }
      }
  
      void *data = NULL;
      if (winclass->data_len > 0) {
          data = malloc(winclass->data_len);
          if (!data) {
              free(text);
              SET_WIN_ERROR_AND_RETURN(anchor, PMERR_HEAP_OUT_OF_MEMORY, NULLHANDLE);
          }
      }
  
      if (hwndParent == HWND_DESKTOP) {  // toplevel? build a heavy weight window.
          Uint32 sdlflags = SDL_WINDOW_HIDDEN | SDL_WINDOW_BORDERLESS;
          // !!! FIXME   SDL_WINDOW_SKIP_TASKBAR  = 0x00010000,      /**< window should not be added to the taskbar */
          // !!! FIXME: text isn't necessarily a string. It's window-class-specific data.
          // The Y coordinate is the bottom of the window, counted from the bottom of the parent window (desktop, in this case) :/
          SDL_Window *window = SDL_CreateWindow(text ? text : "", x, (desktop_window.h - y) - cy, cx, cy, sdlflags);
          if (!window) {
              free(text);
              free(data);
              FIXME("error code?");
              SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INTERNAL_ERROR_1, NULLHANDLE);
          }
  
          SDL_SetWindowData(window, WINDATA_WINDOWPTR_NAME, win);
  
          SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_TARGETTEXTURE);
          if (!renderer) {
              free(text);
              free(data);
              SDL_DestroyWindow(window);
              FIXME("error code?");
              SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INTERNAL_ERROR_1, NULLHANDLE);
          }
  
          SDL_RendererInfo info;
          SDL_GetRendererInfo(renderer, &info);  FIXME("handle failure here");
  
          SDL_Texture *texture = SDL_CreateTexture(renderer, info.texture_formats[0], SDL_TEXTUREACCESS_TARGET, cx, cy);
          if (!texture) {
              free(text);
              free(data);
              SDL_DestroyRenderer(renderer);
              SDL_DestroyWindow(window);
              FIXME("error code?");
              SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INTERNAL_ERROR_1, NULLHANDLE);
          }
  
          SDL_SetRenderTarget(renderer, texture);
  
          win->heavy.window = window;
          win->heavy.renderer = renderer;
          win->heavy.texture = texture;
      } else {  // use same heavy as parent.
          win->heavy.window = parent->heavy.window;
          win->heavy.renderer = parent->heavy.renderer;
          win->heavy.texture = parent->heavy.texture;
      }
  
      win->parent = parent;
      win->children = NULL;
      win->hwnd = (HWND) (((size_t) (win - anchor->windows)) + FIRST_HWND_VALUE);
      win->window_class_name = winclass->name;  // this pointer lives until Anchor Block terminates.
      win->data = data;
      win->text = text;
      win->data_len = winclass->data_len;
      win->window_proc = winclass->window_proc;
      win->class_style = winclass->style;
      win->style = flStyle;
      win->id = id;
      win->owner = owner;
      win->sibling = NULL;  // we'll insert this later.
      win->x = win->y = win->w = win->h = 0;  // we'll set these later.
  
      // we're ready, send WM_CREATE. Note that pCtlData has to be a pointer (not
      //  an integer cast to a pointer), because OS/2 would dereference it, and the
      //  16 bits at the start of the data it points to tell you how many bytes are
      //  pointed at, so it could deal with 16-bit code. We don't (currently) care,
      //  so just pass it on.
      if (sendMessage(win, WM_CREATE, pCtlData, pPresParams) != FALSE) {
          // response to WM_CREATE was to cancel window creation!!
          destroyWindow(anchor, win);
          return NULLHANDLE;
      }
  
      FIXME("should I set swp.fl here?");
      SWP swp = { 0, cy, cx, y, x, hwndInsertBehind, win->hwnd, 0, 0 };
      MRESULT adjust = sendMessage(win, WM_ADJUSTWINDOWPOS, &swp, 0);
  
      (void) adjust; FIXME("adjust based on WM_ADJUSTWINDOWPOS message result");
  
      win->x = swp.x;
      win->y = swp.y;
      win->w = swp.cx;
      win->h = swp.cy;
      hwndInsertBehind = swp.hwndInsertBehind;
  
      Window *insertBehind = NULL;
      if ((hwndInsertBehind != HWND_TOP) && (hwndInsertBehind != HWND_BOTTOM)) {
          insertBehind = getWindowFromHWND(anchor, hwndInsertBehind);
          if (!insertBehind || (insertBehind->parent != parent)) {
              FIXME("should we fail or just insert at top/bottom?");
              hwndInsertBehind = HWND_BOTTOM;
              insertBehind = NULL;
          }
      }
  
      if (parent->children == NULL) {  // nothing to insert behind, just make it the only child.
          parent->children = win;
      } else if (hwndInsertBehind == HWND_TOP) {
          win->sibling = parent->children;
          parent->children = win;
      } else if (hwndInsertBehind == HWND_BOTTOM) {
          Window *prev = NULL;
          for (Window *i = parent->children; i != NULL; i = i->sibling) {
              prev = i;
          }
          prev->sibling = win;
      } else {  // insert behind a specific window.
          win->sibling = insertBehind->sibling;
          insertBehind->sibling = win;
      }
  
      if (hwndParent == HWND_DESKTOP) {
          SDL_SetWindowPosition(win->heavy.window, win->x, (parent->h - win->y) - win->h);
          SDL_SetWindowSize(win->heavy.window, win->w, win->h);
          if (win->style & WS_VISIBLE) {
              // !!! FIXME: mark window region invalid
              SDL_ShowWindow(win->heavy.window);
          }
      }
  
      return win->hwnd;
  } // WinCreateWindow
  
  HWND WinCreateStdWindow(HWND hwndParent, ULONG flStyle, PULONG pflCreateFlags, PSZ pszClientClass, PSZ pszTitle, ULONG styleClient, HMODULE hmod, ULONG idResources, PHWND phwndClient)
  {
      TRACE_NATIVE("WinCreateStdWindow(%u, %u, %p, '%s', '%s', %u, %u, %u, %p)", (uint) hwndParent, (uint) flStyle, pflCreateFlags, pszClientClass, pszTitle, (uint) styleClient, (uint) hmod, (uint) idResources, phwndClient);
  
      if (phwndClient) {
          *phwndClient = NULLHANDLE;
      }
  
      FRAMECDATA fcd = { sizeof (FRAMECDATA), *pflCreateFlags, hmod, idResources };
      HWND retval = WinCreateWindow(hwndParent, (PSZ) WC_FRAME, pszTitle, 0, 0, 0, 0, 0, NULLHANDLE, HWND_TOP, idResources, &fcd, NULL);
      if (!retval) {
          return NULLHANDLE;
      }
  
      if (pszClientClass) {
          HWND clientwin = WinCreateWindow(retval, pszClientClass, NULL, 0, 0, 0, 0, 0, retval, HWND_BOTTOM, FID_CLIENT, NULL, NULL);
          if (!clientwin) {
              WinDestroyWindow(retval);
              return NULLHANDLE;
          }
          if (phwndClient) {
              *phwndClient = clientwin;
          }
      }
  
      return retval;
  } // WinCreateStdWindow
  
  BOOL WinDestroyWindow(HWND hwnd)
  {
      TRACE_NATIVE("WinDestroyWindow(%u)", (uint) hwnd);
      AnchorBlock *anchor = getAnchorBlockNoHAB();
      if (!anchor) {
          return FALSE;
      }
  
      Window *win = getWindowFromHWND(anchor, hwnd);
      if (!win) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INVALID_HWND, FALSE);
      }
  
      destroyWindow(anchor, win);
  
      return TRUE;
  } // WinDestroyWindow
  
  MRESULT WinDefWindowProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
  {
      TRACE_NATIVE("WinDefWindowProc(%u, %u, %p, %p)", (uint) hwnd, (uint) msg, mp1, mp2);
  
      AnchorBlock *anchor = getAnchorBlockNoHAB();
      if (!anchor) {
          return (MRESULT) 0;
      }
      Window *win = getWindowFromHWND(anchor, hwnd);
      if (!win) {
          return (MRESULT) 0;
      }
  
      switch (msg) {
          // this whole block just returns zero without further processing.
          case WM_ACTIVATE:
          case WM_APPTERMINATENOTIFY:
          case WM_ADJUSTWINDOWPOS:
          case WM_CALCFRAMERECT:
          case WM_COMMAND:
          case WM_CONTROL:
          case WM_CREATE:
          case WM_CTLCOLORCHANGE:
          case WM_DESTROY:
          case WM_DRAWITEM:
          case WM_ENABLE:
          case WM_ERASEBACKGROUND:
          case WM_ERROR:
          case WM_FORMATFRAME:
          case WM_HSCROLL:
          case WM_INITDLG:
          case WM_INITMENU:
          case WM_JOURNALNOTIFY:
          case WM_MATCHMNEMONIC:
          case WM_MEASUREITEM:
          case WM_MENUEND:
          case WM_MINMAXFRAME:
          case WM_MOUSEMAP:
          case WM_MOVE:
          case WM_NEXTMENU:
          case WM_NULL:
          case WM_PCONTROL:
          case WM_PRESPARAMCHANGED:
          case WM_PSETFOCUS:
          case WM_PSIZE:
          case WM_PSYSCOLORCHANGE:
          case WM_QUERYACCELTABLE:
          case WM_QUIT:
          case WM_SAVEAPPLICATION:
          case WM_SEM1:
          case WM_SEM2:
          case WM_SEM3:
          case WM_SEM4:
          case WM_SETACCELTABLE:
          case WM_SETFOCUS:
          case WM_SETHELPINFO:
          case WM_SETSELECTION:
          case WM_SETWINDOWPARAMS:
          case WM_SHOW:
          case WM_SIZE:
          case WM_SUBSTITUTESTRING:
          case WM_SYSCOLORCHANGE:
          case WM_SYSCOMMAND:
          case WM_SYSVALUECHANGED:
          case WM_TIMER:
          case WM_TRACKFRAME:
          case WM_TRANSLATEACCEL:
          //case WM_TRANSLATEMNEMONIC:  // !!! FIXME: this is in the SDK reference but not the SDK headers.
          case WM_UPDATEFRAME:
          case WM_VRNDISABLED:
          case WM_VRNENABLED:
          case WM_VSCROLL:
  
          // the SDK reference manual doesn't list default actions for these messages, but it's probably just "return zero".
          case WM_MSGBOXINIT:
          case WM_MSGBOXDISMISS:
  
              return (MRESULT) 0;
  
          // this whole block just returns one without further processing.
          case WM_MENUSELECT:
              return (MRESULT) 1;
  
          // this block sends the message to the window owner (if one exists), or otherwise returns 0.
          case WM_BEGINDRAG:
          case WM_BEGINSELECT:
          case WM_BUTTON1CLICK:
          case WM_BUTTON1DBLCLK:
          case WM_BUTTON1MOTIONSTART:
          case WM_BUTTON1MOTIONEND:
          case WM_BUTTON1UP:
          case WM_BUTTON2CLICK:
          case WM_BUTTON2DBLCLK:
          case WM_BUTTON2MOTIONSTART:
          case WM_BUTTON2MOTIONEND:
          case WM_BUTTON2UP:
          case WM_BUTTON3CLICK:
          case WM_BUTTON3DBLCLK:
          case WM_BUTTON3MOTIONSTART:
          case WM_BUTTON3MOTIONEND:
          case WM_BUTTON3UP:
          case WM_CHAR:
          case WM_CHORD:
          case WM_CONTEXTMENU:
          case WM_ENDDRAG:
          case WM_ENDSELECT:
          case WM_OPEN:
          case WM_PACTIVATE:
          case WM_QUERYCTLTYPE:
          case WM_QUERYHELPINFO:
          case WM_QUERYTRACKINFO:
          case WM_SINGLESELECT:
          case WM_TEXTEDIT:
              return win->owner ? sendMessage(win->owner, msg, mp1, mp2) : (MRESULT) 0;
  
          // These messages actually do something specific in default processing...
          case WM_HITTEST:
              return HT_NORMAL;
  
          case WM_BUTTON1DOWN:
          case WM_BUTTON2DOWN:
          case WM_BUTTON3DOWN:
          case WM_CALCVALIDRECTS:
          case WM_CLOSE:
          case WM_CONTROLPOINTER:
          case WM_FOCUSCHANGE:
          case WM_HELP:
          case WM_MOUSEMOVE:
          case WM_PAINT:
          case WM_PPAINT:
          case WM_QUERYCONVERTPOS:
          case WM_QUERYWINDOWPARAMS:
          case WM_REALIZEPALETTE:
          case WM_WINDOWPOSCHANGED:
              fprintf(stderr, "Message that doesn't something other than return 0, %u, in WinDefWindowProc!\n", (uint) msg);
              break;
  
          default:
              fprintf(stderr, "Unhandled message %u in WinDefWindowProc!\n", (uint) msg);
              break;
      }
  
      return (MRESULT) 0;
  } // WinDefWindowProc
  
  HPS WinBeginPaint(HWND hwnd, HPS hps, PRECTL prclPaint)
  {
      TRACE_NATIVE("WinBeginPaint(%u, %u, %p)", (uint) hwnd, (uint) hps, prclPaint);
      AnchorBlock *anchor = getAnchorBlockNoHAB();
      if (!anchor) {
          return NULLHANDLE;
      }
      Window *win = getWindowFromHWND(anchor, hwnd);
      if (!win) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INVALID_HWND, NULLHANDLE);
      }
  
      PresentationSpace *ps = NULL;
      if (hps != NULLHANDLE) {
          ps = getPresentationSpaceFromHPS(anchor, hps);
          if (!ps) {
              SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INV_HPS, NULLHANDLE);
          }
          FIXME("check if this is already assigned to this drawable");
      } else {
          // make space for the new presentation struct in the anchor block
          FIXME("optimize this");
          FIXME("cut-and-paste from WinCreateWindow");
          for (size_t i = 0; i < anchor->pres_spaces_array_len; i++) {
              if (anchor->pres_spaces[i].hps == NULLHANDLE) {
                  ps = &anchor->pres_spaces[i];
                  break;
              }
          }
  
          if (!ps) {
              const size_t len = anchor->pres_spaces_array_len;
              void *ptr = realloc(anchor->pres_spaces, sizeof (PresentationSpace) * len * 2);
              if (!ptr) {
                  SET_WIN_ERROR_AND_RETURN(anchor, PMERR_HEAP_OUT_OF_MEMORY, NULLHANDLE);
              }
              anchor->pres_spaces = (PresentationSpace *) ptr;
              memset(&anchor->pres_spaces[len], '\0', sizeof (PresentationSpace) * len);
              ps = &anchor->pres_spaces[len];
              anchor->pres_spaces_array_len *= 2;
          }
          hps = (HPS) (((size_t) (ps - anchor->pres_spaces)) + FIRST_HPS_VALUE);
      }
  
      ps->hps = hps;
      ps->window = win;
  
      if (prclPaint) {
          FIXME("report the update region, not the whole window");
          prclPaint->xLeft = 0;
          prclPaint->yBottom = 0;
          prclPaint->xRight = win->w;
          prclPaint->yTop = win->h;
      }
  
      return hps;
  } // WinBeginPaint
  
  BOOL WinEndPaint(HPS hps)
  {
      TRACE_NATIVE("WinEndPaint(%u)", (uint) hps);
      AnchorBlock *anchor = getAnchorBlockNoHAB();
      if (!anchor) {
          return NULLHANDLE;
      }
      PresentationSpace *ps = getPresentationSpaceFromHPS(anchor, hps);
      if (!ps) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INV_HPS, NULLHANDLE);
      }
  
      Window *win = ps->window;
      SDL_Renderer *renderer = win->heavy.renderer;
      SDL_Texture *texture = win->heavy.texture;
      assert(SDL_GetRenderTarget(renderer) == texture);
      SDL_SetRenderTarget(renderer, NULL);
      SDL_RenderCopy(renderer, texture, NULL, NULL);
      SDL_RenderPresent(renderer);
      SDL_SetRenderTarget(renderer, texture);
  
      destroyPresentationSpace(anchor, ps);
      return TRUE;
  } // WinEndPaint
  
  static void os2ColorIndexToRGB(const LONG os2color, Uint8 *r, Uint8 *g, Uint8 *b)
  {
      assert(r != NULL); assert(g != NULL); assert(b != NULL);
      #define RETURNRGB(rr,gg,bb) { *r = rr; *g = gg; *b = bb; return; }
      switch (os2color) {
          case CLR_FALSE: FIXME("this should reverse on a printer"); RETURNRGB(0x00, 0x00, 0x00);
          case CLR_TRUE: FIXME("this should reverse on a printer"); RETURNRGB(0xFF, 0xFF, 0xFF);
          case CLR_DEFAULT: FIXME("Let users specify this on command line or system-wide .ini?"); RETURNRGB(0x00, 0x00, 0x00);
          case CLR_WHITE: RETURNRGB(0xFF, 0xFF, 0xFF);
          case CLR_BLACK: RETURNRGB(0x00, 0x00, 0x00);
          case CLR_BACKGROUND: FIXME("Let users specify this on command line or system-wide .ini?"); RETURNRGB(0xFF, 0xFF, 0xFF);
          case CLR_BLUE: RETURNRGB(0x00, 0x00, 0xFF);
          case CLR_RED: RETURNRGB(0xFF, 0x00, 0x00);
          case CLR_PINK: RETURNRGB(0xFF, 0x00, 0xFF);
          case CLR_GREEN: RETURNRGB(0x00, 0xFF, 0x00);
          case CLR_CYAN: RETURNRGB(0x00, 0xFF, 0xFF);
          case CLR_YELLOW: RETURNRGB(0xFF, 0xFF, 0x00);
          case CLR_NEUTRAL: FIXME("Let users specify this on command line or system-wide .ini?"); RETURNRGB(0x00, 0x00, 0x00);
          case CLR_DARKGRAY: RETURNRGB(0x80, 0x80, 0x80);
          case CLR_DARKBLUE: RETURNRGB(0x00, 0x00, 0x80);
          case CLR_DARKRED: RETURNRGB(0x80, 0x00, 0x00);
          case CLR_DARKPINK: RETURNRGB(0x80, 0x00, 0x80);
          case CLR_DARKGREEN: RETURNRGB(0x00, 0x80, 0x00);
          case CLR_DARKCYAN: RETURNRGB(0x00, 0x80, 0x80);
          case CLR_BROWN: RETURNRGB(0x80, 0x80, 0x00);
          case CLR_PALEGRAY: RETURNRGB(0xCC, 0xCC, 0xCC);
          default: break;
      }
  
      FIXME("unknown color index");
      RETURNRGB(0x00, 0x00, 0x00);
      #undef RETURNRGB
  } // os2ColorIndexToRGB
  
  static void calcHeavyweightRect(const Window *win, SDL_Rect* rect)
  {
      int x = 0, y = 0;
  
      rect->w = win->w;
      rect->h = win->h;
  
      while (win->parent != &desktop_window) {
          x += win->x;
          y += win->y;
          win = win->parent;
      }
  
      rect->x = x;
      rect->y = (win->h - y) - rect->h;
  } // calcHeavyweightRect
  
  BOOL WinFillRect(HPS hps, PRECTL prcl, LONG lColor)
  {
      TRACE_NATIVE("WinFillRect(%u, %p, %d)", (uint) hps, prcl, (int) lColor);
      AnchorBlock *anchor = getAnchorBlockNoHAB();
      if (!anchor) {
          return NULLHANDLE;
      }
      PresentationSpace *ps = getPresentationSpaceFromHPS(anchor, hps);
      if (!ps) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INV_HPS, NULLHANDLE);
      }
      Window *win = ps->window;
      SDL_Renderer *renderer = win->heavy.renderer;
      assert(SDL_GetRenderTarget(renderer) == win->heavy.texture);
  
      SDL_Rect cliprect;
      calcHeavyweightRect(win, &cliprect);
  
      SDL_Rect rect;
      rect.x = cliprect.x + prcl->xLeft;
      rect.y = cliprect.y + (cliprect.h - prcl->yTop);
      rect.w = prcl->xRight - prcl->xLeft;
      rect.h = prcl->yTop - prcl->yBottom;
  
      Uint8 r, g, b;
      os2ColorIndexToRGB(lColor, &r, &g, &b);
  
      //printf("fill { x=%d, y=%d, w=%d, h=%d, r=0x%X, g=0x%X, b=0x%X }\n", (int) rect.x, (int) rect.y, (int) rect.w, (int) rect.h, (uint) r, (uint) g, (uint) b);
      SDL_RenderSetClipRect(renderer, &cliprect);
      SDL_SetRenderDrawColor(renderer, r, g, b, 0xFF);
      SDL_RenderFillRect(renderer, &rect);
      SDL_RenderSetClipRect(renderer, NULL);
      return TRUE;
  } // WinFillRect
  
  BOOL WinIsWindow(HAB hab, HWND hwnd)
  {
      TRACE_NATIVE("WinIsWindow(%u, %u)", (uint) hab, (uint) hwnd);
      FIXME("this 'get the anchor, get the window, set errors' code is repeated a lot");
      AnchorBlock *anchor = getAnchorBlockNoHAB();
      if (!anchor) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INVALID_HWND, FALSE);
      }
  
      Window *win = getWindowFromHWND(anchor, hwnd);
      if (!win) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INVALID_HWND, FALSE);
      }
  
      return TRUE;
  } // WinIsWindow
  
  BOOL WinIsWindowEnabled(HWND hwnd)
  {
      TRACE_NATIVE("WinIsWindowEnabled(%u)", (uint) hwnd);
      AnchorBlock *anchor = getAnchorBlockNoHAB();
      if (!anchor) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INVALID_HWND, FALSE);
      }
  
      Window *win = getWindowFromHWND(anchor, hwnd);
      if (!win) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INVALID_HWND, FALSE);
      }
  
      return win->enabled;
  } // WinIsWindowEnabled
  
  BOOL WinIsWindowVisible(HWND hwnd)
  {
      TRACE_NATIVE("WinIsWindowVisible(%u)", (uint) hwnd);
      AnchorBlock *anchor = getAnchorBlockNoHAB();
      if (!anchor) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INVALID_HWND, FALSE);
      }
  
      Window *win = getWindowFromHWND(anchor, hwnd);
      if (!win) {
          SET_WIN_ERROR_AND_RETURN(anchor, PMERR_INVALID_HWND, FALSE);
      }
  
      return ((win->style & WS_VISIBLE) == WS_VISIBLE) ? TRUE : FALSE;
  } // WinIsWindowVisible
  
  
  // end of pmwin.c ...
  
  