implemented SUPER key/modifier support on windows
[freeglut] / src / mswin / fg_main_mswin.c
index dccb8dd..e9d455d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * freeglut_main_mswin.c
+ * fg_main_mswin.c
  *
  * The Windows-specific mouse cursor related stuff.
  *
@@ -63,7 +63,7 @@ struct GXKeyList gxKeyList;
 #endif /* _WIN32_WCE */
 
 #ifdef _DEBUG
-/* 
+/*
  * WM_ message to string, for debugging
  * This is taken from the 8.0 SDK, so Windows 8 API and everything earlier is included
  */
@@ -184,7 +184,7 @@ static struct WM_MESSAGE_MAP allMessages[] =
 
 
 
-#   if(_WIN32_WINNT >= 0x0500)
+#   if(_WIN32_WINNT >= 0x0500) && defined(WM_NCXBUTTONDOWN)
         DEFINE_MESSAGE(WM_NCXBUTTONDOWN),
         DEFINE_MESSAGE(WM_NCXBUTTONUP),
         DEFINE_MESSAGE(WM_NCXBUTTONDBLCLK),
@@ -241,7 +241,7 @@ static struct WM_MESSAGE_MAP allMessages[] =
             DEFINE_MESSAGE(WM_UNINITMENUPOPUP),
             DEFINE_MESSAGE(WM_MENUCOMMAND),
 
-#           if(_WIN32_WINNT >= 0x0500)
+#           if(_WIN32_WINNT >= 0x0500) && defined(WM_CHANGEUISTATE)
                 DEFINE_MESSAGE(WM_CHANGEUISTATE),
                 DEFINE_MESSAGE(WM_UPDATEUISTATE),
                 DEFINE_MESSAGE(WM_QUERYUISTATE),
@@ -272,7 +272,7 @@ static struct WM_MESSAGE_MAP allMessages[] =
 #   if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400)
         DEFINE_MESSAGE(WM_MOUSEWHEEL),
 #   endif
-#   if (_WIN32_WINNT >= 0x0500)
+#   if (_WIN32_WINNT >= 0x0500) && defined(WM_XBUTTONDOWN)
         DEFINE_MESSAGE(WM_XBUTTONDOWN),
         DEFINE_MESSAGE(WM_XBUTTONUP),
         DEFINE_MESSAGE(WM_XBUTTONDBLCLK),
@@ -364,7 +364,7 @@ static struct WM_MESSAGE_MAP allMessages[] =
         DEFINE_MESSAGE(WM_MOUSEHOVER),
         DEFINE_MESSAGE(WM_MOUSELEAVE),
 #   endif
-#   if(WINVER >= 0x0500)
+#   if(WINVER >= 0x0500) && defined(WM_NCMOUSEHOVER)
         DEFINE_MESSAGE(WM_NCMOUSEHOVER),
         DEFINE_MESSAGE(WM_NCMOUSELEAVE),
 #   endif /* WINVER >= 0x0500 */
@@ -398,7 +398,7 @@ static struct WM_MESSAGE_MAP allMessages[] =
         DEFINE_MESSAGE(WM_PRINTCLIENT),
 #   endif /* WINVER >= 0x0400 */
 
-#   if(_WIN32_WINNT >= 0x0500)
+#   if(_WIN32_WINNT >= 0x0500) && defined(WM_APPCOMMAND)
         DEFINE_MESSAGE(WM_APPCOMMAND),
 #   endif /* _WIN32_WINNT >= 0x0500 */
 
@@ -474,7 +474,7 @@ fg_time_t fgPlatformSystemTime ( void )
     /* Check if we just wrapped */
     if (currTime32 < lastTime32)
         timeEpoch++;
-    
+
     lastTime32 = currTime32;
 
     return currTime32 | timeEpoch << 32;
@@ -542,7 +542,7 @@ static void fghPlatformOnWindowStatusNotify(SFG_Window *window, GLboolean visSta
     {
         SFG_Window *saved_window = fgStructure.CurrentWindow;
 
-        /* On win32 we only have two states, window displayed and window not displayed (iconified) 
+        /* On win32 we only have two states, window displayed and window not displayed (iconified)
          * We map these to GLUT_FULLY_RETAINED and GLUT_HIDDEN respectively.
          */
         INVOKE_WCB( *window, WindowStatus, ( visState ? GLUT_FULLY_RETAINED:GLUT_HIDDEN ) );
@@ -575,7 +575,17 @@ static int fgPlatformGetModifiers (void)
         ( ( ( GetKeyState( VK_LCONTROL ) < 0 ) ||
             ( GetKeyState( VK_RCONTROL ) < 0 )) ? GLUT_ACTIVE_CTRL  : 0 ) |
         ( ( ( GetKeyState( VK_LMENU    ) < 0 ) ||
-            ( GetKeyState( VK_RMENU    ) < 0 )) ? GLUT_ACTIVE_ALT   : 0 );
+            ( GetKeyState( VK_RMENU    ) < 0 )) ? GLUT_ACTIVE_ALT   : 0 ) |
+               ( ( ( GetKeyState( VK_LWIN     ) < 0 ) ||
+            ( GetKeyState( VK_RWIN     ) < 0 )) ? GLUT_ACTIVE_SUPER : 0 );
+}
+
+/* Check whether a button (VK_*BUTTON) is currently depressed. Returns
+ * non-zero (not necessarily 1) if yes. */
+static SHORT fgGetKeyState(int vKey)
+{
+    /* MSDN says: "If the high-order bit is 1, the key is down; otherwise, it is up". */
+    return GetKeyState(vKey) & 0xFF00;
 }
 
 static LRESULT fghWindowProcKeyPress(SFG_Window *window, UINT uMsg, GLboolean keydown, WPARAM wParam, LPARAM lParam)
@@ -584,7 +594,7 @@ static LRESULT fghWindowProcKeyPress(SFG_Window *window, UINT uMsg, GLboolean ke
                          rControl = 0, rShift = 0, rAlt = 0;
 
     int keypress = -1;
-    
+
     /* if keydown, check for repeat */
     /* If repeat is globally switched off, it cannot be switched back on per window.
      * But if it is globally switched on, it can be switched off per window. This matches
@@ -593,65 +603,67 @@ static LRESULT fghWindowProcKeyPress(SFG_Window *window, UINT uMsg, GLboolean ke
      */
     if( keydown && ( fgState.KeyRepeat==GLUT_KEY_REPEAT_OFF || window->State.IgnoreKeyRepeat==GL_TRUE ) && (HIWORD(lParam) & KF_REPEAT) )
         return 1;
-    
+
     /* Remember the current modifiers state so user can query it from their callback */
     fgState.Modifiers = fgPlatformGetModifiers( );
 
     /* Convert the Win32 keystroke codes to GLUTtish way */
-#   define KEY(a,b) case a: keypress = b; break;
+#   define FG_KEY(a,b) case a: keypress = b; break;
 
     switch( wParam )
     {
-        KEY( VK_F1,     GLUT_KEY_F1        );
-        KEY( VK_F2,     GLUT_KEY_F2        );
-        KEY( VK_F3,     GLUT_KEY_F3        );
-        KEY( VK_F4,     GLUT_KEY_F4        );
-        KEY( VK_F5,     GLUT_KEY_F5        );
-        KEY( VK_F6,     GLUT_KEY_F6        );
-        KEY( VK_F7,     GLUT_KEY_F7        );
-        KEY( VK_F8,     GLUT_KEY_F8        );
-        KEY( VK_F9,     GLUT_KEY_F9        );
-        KEY( VK_F10,    GLUT_KEY_F10       );
-        KEY( VK_F11,    GLUT_KEY_F11       );
-        KEY( VK_F12,    GLUT_KEY_F12       );
-        KEY( VK_PRIOR,  GLUT_KEY_PAGE_UP   );
-        KEY( VK_NEXT,   GLUT_KEY_PAGE_DOWN );
-        KEY( VK_HOME,   GLUT_KEY_HOME      );
-        KEY( VK_END,    GLUT_KEY_END       );
-        KEY( VK_LEFT,   GLUT_KEY_LEFT      );
-        KEY( VK_UP,     GLUT_KEY_UP        );
-        KEY( VK_RIGHT,  GLUT_KEY_RIGHT     );
-        KEY( VK_DOWN,   GLUT_KEY_DOWN      );
-        KEY( VK_INSERT, GLUT_KEY_INSERT    );
+        FG_KEY( VK_F1,     GLUT_KEY_F1        );
+        FG_KEY( VK_F2,     GLUT_KEY_F2        );
+        FG_KEY( VK_F3,     GLUT_KEY_F3        );
+        FG_KEY( VK_F4,     GLUT_KEY_F4        );
+        FG_KEY( VK_F5,     GLUT_KEY_F5        );
+        FG_KEY( VK_F6,     GLUT_KEY_F6        );
+        FG_KEY( VK_F7,     GLUT_KEY_F7        );
+        FG_KEY( VK_F8,     GLUT_KEY_F8        );
+        FG_KEY( VK_F9,     GLUT_KEY_F9        );
+        FG_KEY( VK_F10,    GLUT_KEY_F10       );
+        FG_KEY( VK_F11,    GLUT_KEY_F11       );
+        FG_KEY( VK_F12,    GLUT_KEY_F12       );
+        FG_KEY( VK_PRIOR,  GLUT_KEY_PAGE_UP   );
+        FG_KEY( VK_NEXT,   GLUT_KEY_PAGE_DOWN );
+        FG_KEY( VK_HOME,   GLUT_KEY_HOME      );
+        FG_KEY( VK_END,    GLUT_KEY_END       );
+        FG_KEY( VK_LEFT,   GLUT_KEY_LEFT      );
+        FG_KEY( VK_UP,     GLUT_KEY_UP        );
+        FG_KEY( VK_RIGHT,  GLUT_KEY_RIGHT     );
+        FG_KEY( VK_DOWN,   GLUT_KEY_DOWN      );
+        FG_KEY( VK_INSERT, GLUT_KEY_INSERT    );
+               FG_KEY( VK_LWIN,   GLUT_KEY_SUPER_L   );
+               FG_KEY( VK_RWIN,   GLUT_KEY_SUPER_R   );
 
     /* handle control, alt and shift. For GLUT, we want to distinguish between left and right presses.
      * The VK_L* & VK_R* left and right Alt, Ctrl and Shift virtual keys are however only used as parameters to GetAsyncKeyState() and GetKeyState()
      * so when we get an alt, shift or control keypress here, we manually check whether it was the left or the right
      */
-#define ASYNC_KEY_EVENT(winKey,glutKey,keyStateVar)\
-    if (!keyStateVar && GetAsyncKeyState ( winKey ))\
+#define FG_KEY_EVENT(winKey,glutKey,keyStateVar)\
+    if (!keyStateVar && fgGetKeyState ( winKey ))\
     {\
         keypress   = glutKey;\
         keyStateVar = 1;\
     }\
-    else if (keyStateVar && !GetAsyncKeyState ( winKey ))\
+    else if (keyStateVar && !fgGetKeyState ( winKey ))\
     {\
         keypress   = glutKey;\
         keyStateVar = 0;\
     }
     case VK_CONTROL:
-        ASYNC_KEY_EVENT(VK_LCONTROL,GLUT_KEY_CTRL_L,lControl);
-        ASYNC_KEY_EVENT(VK_RCONTROL,GLUT_KEY_CTRL_R,rControl);
+        FG_KEY_EVENT(VK_LCONTROL,GLUT_KEY_CTRL_L,lControl);
+        FG_KEY_EVENT(VK_RCONTROL,GLUT_KEY_CTRL_R,rControl);
         break;
     case VK_SHIFT:
-        ASYNC_KEY_EVENT(VK_LSHIFT,GLUT_KEY_SHIFT_L,lShift);
-        ASYNC_KEY_EVENT(VK_RSHIFT,GLUT_KEY_SHIFT_R,rShift);
+        FG_KEY_EVENT(VK_LSHIFT,GLUT_KEY_SHIFT_L,lShift);
+        FG_KEY_EVENT(VK_RSHIFT,GLUT_KEY_SHIFT_R,rShift);
         break;
     case VK_MENU:
-        ASYNC_KEY_EVENT(VK_LMENU,GLUT_KEY_ALT_L,lAlt);
-        ASYNC_KEY_EVENT(VK_RMENU,GLUT_KEY_ALT_R,rAlt);
+        FG_KEY_EVENT(VK_LMENU,GLUT_KEY_ALT_L,lAlt);
+        FG_KEY_EVENT(VK_RMENU,GLUT_KEY_ALT_R,rAlt);
         break;
-#undef ASYNC_KEY_EVENT
+#undef KEY_EVENT
 
     case VK_DELETE:
         /* The delete key should be treated as an ASCII keypress: */
@@ -707,7 +719,7 @@ static LRESULT fghWindowProcKeyPress(SFG_Window *window, UINT uMsg, GLboolean ke
             keypress = GLUT_KEY_F4;
     }
 #endif
-    
+
     if( keypress != -1 )
         if (keydown)
             INVOKE_WCB( *window, Special,
@@ -744,9 +756,11 @@ SFG_Window* fghWindowUnderCursor(SFG_Window *window)
 
         /* Get mouse position at time of message */
         DWORD mouse_pos_dw = GetMessagePos();
-        POINT mouse_pos = {GET_X_LPARAM(mouse_pos_dw), GET_Y_LPARAM(mouse_pos_dw)};
+        POINT mouse_pos;
+        mouse_pos.x = GET_X_LPARAM(mouse_pos_dw);
+        mouse_pos.y = GET_Y_LPARAM(mouse_pos_dw);
         ScreenToClient( window->Window.Handle, &mouse_pos );
-        
+
         hwnd = ChildWindowFromPoint(window->Window.Handle, mouse_pos);
         if (hwnd && hwnd!=window->Window.Handle)   /* can be NULL if mouse outside parent by the time we get here, or can be same as parent if we didn't find a child */
         {
@@ -864,7 +878,7 @@ LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
         /* Update visibility state of the window */
         if (wParam==SIZE_MINIMIZED)
             fghPlatformOnWindowStatusNotify(window,GL_FALSE,GL_FALSE);
-        else if (wParam==SIZE_RESTORED && !window->State.Visible)
+        else if ((wParam==SIZE_RESTORED || wParam == SIZE_MAXIMIZED) && !window->State.Visible)
             fghPlatformOnWindowStatusNotify(window,GL_TRUE,GL_FALSE);
 
         /* Check window visible, we don't want do anything when we get a WM_SIZE because the user or glutIconifyWindow minimized the window */
@@ -878,7 +892,7 @@ LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
             width  = LOWORD(lParam);
             height = HIWORD(lParam);
 #endif /* defined(_WIN32_WCE) */
-            
+
             /* Update state and call callback, if there was a change */
             fghOnReshapeNotify(window, width, height, GL_FALSE);
         }
@@ -925,21 +939,23 @@ LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
             if (!IsIconic(window->Window.Handle))
             {
                 RECT windowRect;
-                
+
                 /* lParam contains coordinates of top-left of client area.
                  * Get top-left of non-client area of window, matching coordinates of
-                 * glutInitPosition and glutPositionWindow, but not those of 
+                 * glutInitPosition and glutPositionWindow, but not those of
                  * glutGet(GLUT_WINDOW_X) and glutGet(GLUT_WINDOW_Y), which return
                  * top-left of client area.
                  */
                 GetWindowRect( window->Window.Handle, &windowRect );
-            
+
                 if (window->Parent)
                 {
                     /* For child window, we should return relative to upper-left
                      * of parent's client area.
                      */
-                    POINT topleft = {windowRect.left,windowRect.top};
+                    POINT topleft;
+                    topleft.x = windowRect.left;
+                    topleft.y = windowRect.top;
 
                     ScreenToClient(window->Parent->Window.Handle,&topleft);
                     windowRect.left = topleft.x;
@@ -1019,7 +1035,7 @@ LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
             {
                 TRACKMOUSEEVENT tme;
 
-                /* Cursor just entered window, set cursor look */ 
+                /* Cursor just entered window, set cursor look */
                 fgSetCursor ( window, window->State.Cursor ) ;
 
                 /* If an EntryFunc callback is specified by the user, also
@@ -1078,7 +1094,7 @@ LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
     case WM_PAINT:
     {
         RECT rect;
-        
+
         /* As per docs, upon receiving WM_PAINT, first check if the update region is not empty before you call BeginPaint */
         if (GetUpdateRect(hWnd,&rect,FALSE))
         {
@@ -1237,7 +1253,7 @@ LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
                 SetCapture ( window->Window.Handle ) ;
             setCaptureActive = 1; /* Set to false in WM_CAPTURECHANGED handler */
         }
-        else if (!GetAsyncKeyState(VK_LBUTTON) && !GetAsyncKeyState(VK_MBUTTON) && !GetAsyncKeyState(VK_RBUTTON))
+        else if (!fgGetKeyState(VK_LBUTTON) && !fgGetKeyState(VK_MBUTTON) && !fgGetKeyState(VK_RBUTTON))
           /* Make sure all mouse buttons are released before releasing capture */
           ReleaseCapture () ;
 
@@ -1267,7 +1283,7 @@ LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
     {
         int wheel_number = 0;   /* Only one scroll wheel on windows */
 #if defined(_WIN32_WCE)
-        int modkeys = LOWORD(wParam); 
+        int modkeys = LOWORD(wParam);
         short ticks = (short)HIWORD(wParam);
         /* commented out as should not be needed here, mouse motion is processed in WM_MOUSEMOVE first:
         xPos = LOWORD(lParam);  -- straight from docs, not consistent with mouse nutton and mouse motion above (which i think is wrong)
@@ -1275,7 +1291,7 @@ LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
         */
 #else
         /* int modkeys = GET_KEYSTATE_WPARAM( wParam ); */
-        short ticks = GET_WHEEL_DELTA_WPARAM( wParam );
+        short ticks = HIWORD( wParam );
         /* commented out as should not be needed here, mouse motion is processed in WM_MOUSEMOVE first:
         window->State.MouseX = GET_X_LPARAM( lParam );
         window->State.MouseY = GET_Y_LPARAM( lParam );
@@ -1284,10 +1300,10 @@ LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
 
         window = fghWindowUnderCursor(window);
 
-               fgState.MouseWheelTicks += ticks;
+        fgState.MouseWheelTicks += ticks;
         if ( abs ( fgState.MouseWheelTicks ) >= WHEEL_DELTA )
-               {
-                       int direction = ( fgState.MouseWheelTicks > 0 ) ? 1 : -1;
+        {
+            int direction = ( fgState.MouseWheelTicks > 0 ) ? 1 : -1;
 
             if( ! FETCH_WCB( *window, MouseWheel ) &&
                 ! FETCH_WCB( *window, Mouse ) )
@@ -1297,7 +1313,7 @@ LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
             fgState.Modifiers = fgPlatformGetModifiers( );
 
             while( abs ( fgState.MouseWheelTicks ) >= WHEEL_DELTA )
-                       {
+            {
                 if( FETCH_WCB( *window, MouseWheel ) )
                     INVOKE_WCB( *window, MouseWheel,
                                 ( wheel_number,
@@ -1307,7 +1323,7 @@ LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
                                 )
                     );
                 else  /* No mouse wheel, call the mouse button callback twice */
-                               {
+                {
                     /*
                      * Map wheel zero to button 3 and 4; +1 to 3, -1 to 4
                      *  "    "   one                     +1 to 5, -1 to 6, ...
@@ -1326,13 +1342,13 @@ LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
                                 ( button, GLUT_UP,
                                   window->State.MouseX, window->State.MouseY )
                     );
-                               }
+                }
 
-                               fgState.MouseWheelTicks -= WHEEL_DELTA * direction;
-                       }
+                fgState.MouseWheelTicks -= WHEEL_DELTA * direction;
+            }
 
             fgState.Modifiers = INVALID_MODIFIERS;
-               }
+        }
         /* Per docs, should return zero */
         lRet = 0;
     }
@@ -1486,50 +1502,60 @@ LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
         break;
 
 #ifdef WM_TOUCH
-       /* handle multi-touch messages */
-       case WM_TOUCH:
-       {
-               unsigned int numInputs = (unsigned int)wParam;
-               unsigned int i = 0;
-               TOUCHINPUT* ti = (TOUCHINPUT*)malloc( sizeof(TOUCHINPUT)*numInputs);
-
-               if (fghGetTouchInputInfo == (pGetTouchInputInfo)0xDEADBEEF) {
-                   fghGetTouchInputInfo = (pGetTouchInputInfo)GetProcAddress(GetModuleHandle("user32"),"GetTouchInputInfo");
-                   fghCloseTouchInputHandle = (pCloseTouchInputHandle)GetProcAddress(GetModuleHandle("user32"),"CloseTouchInputHandle");
-               }
-
-               if (!fghGetTouchInputInfo) { 
-                       free( (void*)ti );
-                       break;
-               }
-
-               if (fghGetTouchInputInfo( (HTOUCHINPUT)lParam, numInputs, ti, sizeof(TOUCHINPUT) )) {
-                       /* Handle each contact point */
-                       for (i = 0; i < numInputs; ++i ) {
-
-                               POINT tp;
-                               tp.x = TOUCH_COORD_TO_PIXEL(ti[i].x);
-                               tp.y = TOUCH_COORD_TO_PIXEL(ti[i].y);
-                               ScreenToClient( hWnd, &tp );
-
-                               ti[i].dwID = ti[i].dwID * 2;
-
-                               if (ti[i].dwFlags & TOUCHEVENTF_DOWN) {
-                                       INVOKE_WCB( *window, MultiEntry,  ( ti[i].dwID, GLUT_ENTERED ) );
-                                       INVOKE_WCB( *window, MultiButton, ( ti[i].dwID, tp.x, tp.y, 0, GLUT_DOWN ) );
-                               } else if (ti[i].dwFlags & TOUCHEVENTF_MOVE) {
-                                       INVOKE_WCB( *window, MultiMotion, ( ti[i].dwID, tp.x, tp.y ) );
-                               } else if (ti[i].dwFlags & TOUCHEVENTF_UP)   { 
-                                       INVOKE_WCB( *window, MultiButton, ( ti[i].dwID, tp.x, tp.y, 0, GLUT_UP ) );
-                                       INVOKE_WCB( *window, MultiEntry,  ( ti[i].dwID, GLUT_LEFT ) );
-                               }
-                       }
-               }
-               fghCloseTouchInputHandle((HTOUCHINPUT)lParam);
-               free( (void*)ti );
-               lRet = 0; /*DefWindowProc( hWnd, uMsg, wParam, lParam );*/
-               break;
-       }
+    /* handle multi-touch messages */
+    case WM_TOUCH:
+    {
+        unsigned int numInputs = (unsigned int)wParam;
+        unsigned int i = 0;
+        TOUCHINPUT* ti = (TOUCHINPUT*)malloc( sizeof(TOUCHINPUT)*numInputs);
+
+        if (fghGetTouchInputInfo == (pGetTouchInputInfo)0xDEADBEEF) {
+            fghGetTouchInputInfo = (pGetTouchInputInfo)GetProcAddress(GetModuleHandle("user32"),"GetTouchInputInfo");
+            fghCloseTouchInputHandle = (pCloseTouchInputHandle)GetProcAddress(GetModuleHandle("user32"),"CloseTouchInputHandle");
+        }
+
+        if (!fghGetTouchInputInfo) {
+            free( (void*)ti );
+            break;
+        }
+
+        if (fghGetTouchInputInfo( (HTOUCHINPUT)lParam, numInputs, ti, sizeof(TOUCHINPUT) )) {
+            /* Handle each contact point */
+            for (i = 0; i < numInputs; ++i ) {
+
+                POINT tp;
+                tp.x = TOUCH_COORD_TO_PIXEL(ti[i].x);
+                tp.y = TOUCH_COORD_TO_PIXEL(ti[i].y);
+                ScreenToClient( hWnd, &tp );
+
+                ti[i].dwID = ti[i].dwID * 2;
+
+                if (ti[i].dwFlags & TOUCHEVENTF_DOWN) {
+                    INVOKE_WCB( *window, MultiEntry,  ( ti[i].dwID, GLUT_ENTERED ) );
+                    INVOKE_WCB( *window, MultiButton, ( ti[i].dwID, tp.x, tp.y, 0, GLUT_DOWN ) );
+                } else if (ti[i].dwFlags & TOUCHEVENTF_MOVE) {
+                    INVOKE_WCB( *window, MultiMotion, ( ti[i].dwID, tp.x, tp.y ) );
+                } else if (ti[i].dwFlags & TOUCHEVENTF_UP)   {
+                    INVOKE_WCB( *window, MultiButton, ( ti[i].dwID, tp.x, tp.y, 0, GLUT_UP ) );
+                    INVOKE_WCB( *window, MultiEntry,  ( ti[i].dwID, GLUT_LEFT ) );
+                }
+            }
+        }
+        fghCloseTouchInputHandle((HTOUCHINPUT)lParam);
+        free( (void*)ti );
+        lRet = 0; /*DefWindowProc( hWnd, uMsg, wParam, lParam );*/
+        break;
+    }
+#endif
+
+#ifdef WM_INPUT
+    case WM_INPUT:
+        /* Added by Jinrong Xie <stonexjr at gmail.com> for SpaceNavigator support on Windows. Dec 2014 */
+        if (fgHasSpaceball())
+        {
+            fgSpaceballHandleWinEvent(hWnd, wParam, lParam);
+        }
+        break;
 #endif
     default:
         /* Handle unhandled messages */
@@ -1541,251 +1567,224 @@ LRESULT CALLBACK fgPlatformWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
 }
 
 
-/* Step through the work list */
-void fgPlatformProcessWork(SFG_Window *window)
+/* deal with work list items */
+void fgPlatformInitWork(SFG_Window* window)
 {
-    unsigned int workMask = window->State.WorkMask;
-    /* Now clear it so that any callback generated by the actions below can set work again */
-    window->State.WorkMask = 0;
-
-    if (workMask&~GLUT_DISPLAY_WORK)    /* Display work is the common case, skip all the below at once */
-    {
-    /* This is before the first display callback: call a few callbacks to inform user of window size, position, etc
-     * we know this is before the first display callback of a window as for all windows GLUT_INIT_WORK is set when
-     * they are opened, and work is done before displaying in the mainloop.
-     */
-    if (workMask & GLUT_INIT_WORK)
-    {
-        RECT windowRect;
+    RECT windowRect;
 
-        /* Notify windowStatus/visibility */
-        fghPlatformOnWindowStatusNotify(window, window->State.Visible, GL_TRUE);
+    /* Notify windowStatus/visibility */
+    fghPlatformOnWindowStatusNotify(window, window->State.Visible, GL_TRUE);
 
-        /* get and notify window's position */
-        GetWindowRect(window->Window.Handle,&windowRect);
-        fghOnPositionNotify(window, windowRect.left, windowRect.top, GL_TRUE);
+    /* get and notify window's position */
+    GetWindowRect(window->Window.Handle,&windowRect);
+    fghOnPositionNotify(window, windowRect.left, windowRect.top, GL_TRUE);
 
-        /* get and notify window's size */
-        GetClientRect(window->Window.Handle,&windowRect);
-        fghOnReshapeNotify(window, windowRect.right-windowRect.left, windowRect.bottom-windowRect.top, GL_TRUE);
-
-        /* Call init context callback */
-        INVOKE_WCB( *window, InitContext, ());
+    /* get and notify window's size */
+    GetClientRect(window->Window.Handle,&windowRect);
+    fghOnReshapeNotify(window, windowRect.right-windowRect.left, windowRect.bottom-windowRect.top, GL_TRUE);
+}
 
-        /* Lastly, check if we have a display callback, error out if not
-         * This is the right place to do it, as the redisplay will be
-         * next right after we exit this function, so there is no more
-         * opportunity for the user to register a callback for this window.
-         */
-        if (!FETCH_WCB(*window, Display))
-            fgError ( "ERROR:  No display callback registered for window %d\n", window->ID );
-    }
+/* On windows we can position, resize and change z order at the same time */
+void fgPlatformPosResZordWork(SFG_Window* window, unsigned int workMask)
+{
+    UINT flags = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING | SWP_NOSIZE | SWP_NOZORDER;
+    HWND insertAfter = HWND_TOP;
+    RECT clientRect;
 
-    /* On windows we can position, resize and change z order at the same time */
-    if (workMask & (GLUT_POSITION_WORK|GLUT_SIZE_WORK|GLUT_ZORDER_WORK|GLUT_FULL_SCREEN_WORK))
+#if !defined(_WIN32_WCE) /* FIXME: what about WinCE */
+    if (workMask & GLUT_FULL_SCREEN_WORK)
     {
-        UINT flags = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING | SWP_NOSIZE | SWP_NOZORDER;
-        HWND insertAfter = HWND_TOP;
-        RECT clientRect;
+        /* This asks us to toggle fullscreen mode */
+        flags |= SWP_FRAMECHANGED;
 
-#if !defined(_WIN32_WCE) /* FIXME: what about WinCE */
-        if (workMask & GLUT_FULL_SCREEN_WORK)
+        if (window->State.IsFullscreen)
         {
-            /* This asks us to toggle fullscreen mode */
-            flags |= SWP_FRAMECHANGED;
-
-            if (window->State.IsFullscreen)
-            {
-                /* If we are fullscreen, resize the current window back to its original size */
-                /* printf("OldRect %i,%i to %i,%i\n",window->State.pWState.OldRect.left,window->State.pWState.OldRect.top,window->State.pWState.OldRect.right,window->State.pWState.OldRect.bottom); */
-
-                /* restore style of window before making it fullscreen */
-                SetWindowLong(window->Window.Handle, GWL_STYLE, window->State.pWState.OldStyle);
-                SetWindowLong(window->Window.Handle, GWL_EXSTYLE, window->State.pWState.OldStyleEx);
-
-                /* Then set up resize/reposition, unless user already queued up reshape/position work */
-                if (!(workMask & GLUT_POSITION_WORK))
-                {
-                    workMask |= GLUT_POSITION_WORK;
-                    window->State.DesiredXpos   = window->State.pWState.OldRect.left;
-                    window->State.DesiredYpos   = window->State.pWState.OldRect.top;
-                }
-                if (!(workMask & GLUT_SIZE_WORK))
-                {
-                    workMask |= GLUT_SIZE_WORK;
-                    window->State.DesiredWidth  = window->State.pWState.OldRect.right  - window->State.pWState.OldRect.left;
-                    window->State.DesiredHeight = window->State.pWState.OldRect.bottom - window->State.pWState.OldRect.top;
-                }
-                
-                /* We'll finish off the fullscreen operation below after the other GLUT_POSITION_WORK|GLUT_SIZE_WORK|GLUT_ZORDER_WORK */
-            }
-            else
-            {
-                /* we are currently not fullscreen, go to fullscreen:
-                 * remove window decoration and then maximize
-                 */
-                RECT rect;
-                HMONITOR hMonitor;
-                MONITORINFO mi;
-        
-                /* save current window rect, style, exstyle and maximized state */
-                window->State.pWState.OldMaximized = !!IsZoomed(window->Window.Handle);
-                if (window->State.pWState.OldMaximized)
-                    /* We force the window into restored mode before going
-                     * fullscreen because Windows doesn't seem to hide the
-                     * taskbar if the window is in the maximized state.
-                     */
-                    SendMessage(window->Window.Handle, WM_SYSCOMMAND, SC_RESTORE, 0);
-
-                fghGetClientArea( &window->State.pWState.OldRect, window, GL_TRUE );
-                window->State.pWState.OldStyle   = GetWindowLong(window->Window.Handle, GWL_STYLE);
-                window->State.pWState.OldStyleEx = GetWindowLong(window->Window.Handle, GWL_EXSTYLE);
-
-                /* remove decorations from style */
-                SetWindowLong(window->Window.Handle, GWL_STYLE,
-                              window->State.pWState.OldStyle & ~(WS_CAPTION | WS_THICKFRAME));
-                SetWindowLong(window->Window.Handle, GWL_EXSTYLE,
-                              window->State.pWState.OldStyleEx & ~(WS_EX_DLGMODALFRAME |
-                              WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
-
-                /* For fullscreen mode, find the monitor that is covered the most
-                 * by the window and get its rect as the resize target.
-                    */
-                GetWindowRect(window->Window.Handle, &rect);
-                hMonitor= MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST);
-                mi.cbSize = sizeof(mi);
-                GetMonitorInfo(hMonitor, &mi);
-                rect = mi.rcMonitor;
-
-                /* then setup window resize, overwriting other work queued on the window */
-                window->State.WorkMask |= GLUT_POSITION_WORK | GLUT_SIZE_WORK;
-                window->State.WorkMask &= ~GLUT_ZORDER_WORK;
-                window->State.DesiredXpos   = rect.left;
-                window->State.DesiredYpos   = rect.top;
-                window->State.DesiredWidth  = rect.right  - rect.left;
-                window->State.DesiredHeight = rect.bottom - rect.top;
-            }
-        }
-#endif /*!defined(_WIN32_WCE) */
+            /* If we are fullscreen, resize the current window back to its original size */
+            /* printf("OldRect %i,%i to %i,%i\n",window->State.pWState.OldRect.left,window->State.pWState.OldRect.top,window->State.pWState.OldRect.right,window->State.pWState.OldRect.bottom); */
 
-        /* Now deal with normal position, reshape and z order requests (some might have been set when handling GLUT_FULLSCREEN_WORK above */
-        {
-            /* get rect describing window's current position and size, 
-             * in screen coordinates and in FreeGLUT format
-             * (size (right-left, bottom-top) is client area size, top and left
-             * are outside of window including decorations).
-             */
-            fghGetClientArea( &clientRect, window, TRUE );
+            /* restore style of window before making it fullscreen */
+            SetWindowLong(window->Window.Handle, GWL_STYLE, window->State.pWState.OldStyle);
+            SetWindowLong(window->Window.Handle, GWL_EXSTYLE, window->State.pWState.OldStyleEx);
 
-            if (workMask & GLUT_POSITION_WORK)
+            /* Then set up resize/reposition, unless user already queued up reshape/position work */
+            if (!(workMask & GLUT_POSITION_WORK))
             {
-                flags &= ~SWP_NOMOVE;
-                
-                /* Move rect so that top-left is at requested position */
-                /* This also automatically makes sure that child window requested coordinates are relative
-                 * to top-left of parent's client area (needed input for SetWindowPos on child windows),
-                 * so no need to further correct rect for child windows below (childs don't have decorations either).
-                 */
-                OffsetRect(&clientRect,window->State.DesiredXpos-clientRect.left,window->State.DesiredYpos-clientRect.top);
+                workMask |= GLUT_POSITION_WORK;
+                window->State.DesiredXpos   = window->State.pWState.OldRect.left;
+                window->State.DesiredYpos   = window->State.pWState.OldRect.top;
             }
-            if (workMask & GLUT_SIZE_WORK)
+            if (!(workMask & GLUT_SIZE_WORK))
             {
-                flags &= ~SWP_NOSIZE;
-                
-                /* Note on maximizing behavior of Windows: the resize borders are off
-                 * the screen such that the client area extends all the way from the
-                 * leftmost corner to the rightmost corner to maximize screen real
-                 * estate. A caption is still shown however to allow interaction with
-                 * the window controls. This is default behavior of Windows that
-                 * FreeGLUT sticks with. To alter, one would have to check if
-                 * WS_MAXIMIZE style is set when a resize event is triggered, and
-                 * then manually correct the windowRect to put the borders back on
-                 * screen.
-                 */
-
-                /* Set new size of window, WxH specify client area */
-                clientRect.right    = clientRect.left + window->State.DesiredWidth;
-                clientRect.bottom   = clientRect.top  + window->State.DesiredHeight;
+                workMask |= GLUT_SIZE_WORK;
+                window->State.DesiredWidth  = window->State.pWState.OldRect.right  - window->State.pWState.OldRect.left;
+                window->State.DesiredHeight = window->State.pWState.OldRect.bottom - window->State.pWState.OldRect.top;
             }
-            if (workMask & GLUT_ZORDER_WORK)
-            {
-                flags &= ~SWP_NOZORDER;
 
-                /* Could change this to push it down or up one window at a time with some
-                 * more code using GetWindow with GW_HWNDPREV and GW_HWNDNEXT.
-                 * What would be consistent with X11? Win32 GLUT does what we do here...
-                 */
-                if (window->State.DesiredZOrder < 0)
-                    insertAfter = HWND_BOTTOM;
-            }
+            /* We'll finish off the fullscreen operation below after the other GLUT_POSITION_WORK|GLUT_SIZE_WORK|GLUT_ZORDER_WORK */
         }
-
-        /* Adjust for window decorations
-         * Child windows don't have decoration, so no need to correct
-         */
-        if (!window->Parent)
-            /* get the window rect from this to feed to SetWindowPos, correct for window decorations */
-            fghComputeWindowRectFromClientArea_QueryWindow(&clientRect,window,TRUE);
-    
-        /* Do the requested positioning, moving, and z order push/pop. */
-        SetWindowPos( window->Window.Handle,
-                      insertAfter,
-                      clientRect.left, clientRect.top,
-                      clientRect.right - clientRect.left,
-                      clientRect.bottom- clientRect.top,
-                      flags
-        );
-
-        /* Finish off the fullscreen operation we were doing, if any */
-        if (workMask & GLUT_FULL_SCREEN_WORK)
+        else
         {
-            if (window->State.IsFullscreen)
+            /* we are currently not fullscreen, go to fullscreen:
+                * remove window decoration and then maximize
+                */
+            RECT rect;
+            HMONITOR hMonitor;
+            MONITORINFO mi;
+            DWORD newStyle;
+
+            /* save current window rect, style, exstyle and maximized state */
+            window->State.pWState.OldMaximized = !!IsZoomed(window->Window.Handle);
+            if (window->State.pWState.OldMaximized)
+                /* We force the window into restored mode before going
+                    * fullscreen because Windows doesn't seem to hide the
+                    * taskbar if the window is in the maximized state.
+                    */
+                SendMessage(window->Window.Handle, WM_SYSCOMMAND, SC_RESTORE, 0);
+
+            fghGetClientArea( &window->State.pWState.OldRect, window, GL_TRUE );
+            window->State.pWState.OldStyle   = GetWindowLong(window->Window.Handle, GWL_STYLE);
+            window->State.pWState.OldStyleEx = GetWindowLong(window->Window.Handle, GWL_EXSTYLE);
+
+            /* remove decorations from style */
+            newStyle = window->State.pWState.OldStyle & ~(WS_CAPTION | WS_THICKFRAME);
+            if (fgState.DisplayMode & GLUT_STEREO)
             {
-                /* leaving fullscreen, restore maximized state, if any */
-                if (window->State.pWState.OldMaximized)
-                    SendMessage(window->Window.Handle, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
-
-                window->State.IsFullscreen = GL_FALSE;
+                /* stereo mode does not engage on nVidia stereo buffers. This kills child
+                   windows, but we make the guess that those are rare for stereo windows. */
+                newStyle |= WS_POPUP;
             }
-            else
-                window->State.IsFullscreen = GL_TRUE;
+            SetWindowLong(window->Window.Handle, GWL_STYLE, newStyle);
+            SetWindowLong(window->Window.Handle, GWL_EXSTYLE,
+                            window->State.pWState.OldStyleEx & ~(WS_EX_DLGMODALFRAME |
+                            WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
+
+            /* For fullscreen mode, find the monitor that is covered the most
+                * by the window and get its rect as the resize target.
+                */
+            hMonitor= MonitorFromWindow(window->Window.Handle, MONITOR_DEFAULTTONEAREST);
+            mi.cbSize = sizeof(mi);
+            GetMonitorInfo(hMonitor, &mi);
+            rect = mi.rcMonitor;
+
+            /* then setup window resize, overwriting other work queued on the window */
+            window->State.WorkMask |= GLUT_POSITION_WORK | GLUT_SIZE_WORK;
+            window->State.WorkMask &= ~GLUT_ZORDER_WORK;
+            window->State.DesiredXpos   = rect.left;
+            window->State.DesiredYpos   = rect.top;
+            window->State.DesiredWidth  = rect.right  - rect.left;
+            window->State.DesiredHeight = rect.bottom - rect.top;
         }
     }
+#endif /*!defined(_WIN32_WCE) */
 
-    if (workMask & GLUT_VISIBILITY_WORK)
+    /* Now deal with normal position, reshape and z order requests (some might have been set when handling GLUT_FULLSCREEN_WORK above */
     {
-        /* Visibility status of window gets updated in the WM_SHOWWINDOW and WM_SIZE handlers */
-        int cmdShow = 0;
-        SFG_Window *win = window;
-        switch (window->State.DesiredVisibility)
+        /* get rect describing window's current position and size,
+            * in screen coordinates and in FreeGLUT format
+            * (size (right-left, bottom-top) is client area size, top and left
+            * are outside of window including decorations).
+            */
+        fghGetClientArea( &clientRect, window, TRUE );
+
+        if (workMask & GLUT_POSITION_WORK)
         {
-        case DesireHiddenState:
-            cmdShow = SW_HIDE;
-            break;
-        case DesireIconicState:
-            cmdShow = SW_MINIMIZE;
-            /* Call on top-level window */
-            while (win->Parent)
-                win = win->Parent;
-            break;
-        case DesireNormalState:
-            if (win->IsMenu && (!fgStructure.GameModeWindow || win->ActiveMenu->ParentWindow != fgStructure.GameModeWindow))
-                cmdShow = SW_SHOWNA;    /* Just show, don't activate window if its a menu. Only exception is when the parent is a gamemode window as the menu would pop under it when we do this... */
-            else
-                cmdShow = SW_SHOW;
-            break;
+            flags &= ~SWP_NOMOVE;
+
+            /* Move rect so that top-left is at requested position */
+            /* This also automatically makes sure that child window requested coordinates are relative
+                * to top-left of parent's client area (needed input for SetWindowPos on child windows),
+                * so no need to further correct rect for child windows below (childs don't have decorations either).
+                */
+            OffsetRect(&clientRect,window->State.DesiredXpos-clientRect.left,window->State.DesiredYpos-clientRect.top);
+        }
+        if (workMask & GLUT_SIZE_WORK)
+        {
+            flags &= ~SWP_NOSIZE;
+
+            /* Note on maximizing behavior of Windows: the resize borders are off
+                * the screen such that the client area extends all the way from the
+                * leftmost corner to the rightmost corner to maximize screen real
+                * estate. A caption is still shown however to allow interaction with
+                * the window controls. This is default behavior of Windows that
+                * FreeGLUT sticks with. To alter, one would have to check if
+                * WS_MAXIMIZE style is set when a resize event is triggered, and
+                * then manually correct the windowRect to put the borders back on
+                * screen.
+                */
+
+            /* Set new size of window, WxH specify client area */
+            clientRect.right    = clientRect.left + window->State.DesiredWidth;
+            clientRect.bottom   = clientRect.top  + window->State.DesiredHeight;
+        }
+        if (workMask & GLUT_ZORDER_WORK)
+        {
+            flags &= ~SWP_NOZORDER;
+
+            /* Could change this to push it down or up one window at a time with some
+                * more code using GetWindow with GW_HWNDPREV and GW_HWNDNEXT.
+                * What would be consistent with X11? Win32 GLUT does what we do here...
+                */
+            if (window->State.DesiredZOrder < 0)
+                insertAfter = HWND_BOTTOM;
         }
-
-        ShowWindow( win->Window.Handle, cmdShow );
-    }
     }
 
-    if (workMask & GLUT_DISPLAY_WORK)
+    /* Adjust for window decorations
+        * Child windows don't have decoration, so no need to correct
+        */
+    if (!window->Parent)
+        /* get the window rect from this to feed to SetWindowPos, correct for window decorations */
+        fghComputeWindowRectFromClientArea_QueryWindow(&clientRect,window,TRUE);
+
+    /* Do the requested positioning, moving, and z order push/pop. */
+    SetWindowPos( window->Window.Handle,
+                    insertAfter,
+                    clientRect.left, clientRect.top,
+                    clientRect.right - clientRect.left,
+                    clientRect.bottom- clientRect.top,
+                    flags
+    );
+
+    /* Finish off the fullscreen operation we were doing, if any */
+    if (workMask & GLUT_FULL_SCREEN_WORK)
     {
-        if( window->State.Visible )
-            fghRedrawWindow ( window );
+        if (window->State.IsFullscreen)
+        {
+            /* leaving fullscreen, restore maximized state, if any */
+            if (window->State.pWState.OldMaximized)
+                SendMessage(window->Window.Handle, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
 
-        /* Strip out display work that might have ended up on work list now as some of the above genereates callbacks */
-        window->State.WorkMask &= ~GLUT_DISPLAY_WORK;
+            window->State.IsFullscreen = GL_FALSE;
+        }
+        else
+            window->State.IsFullscreen = GL_TRUE;
     }
-}
\ No newline at end of file
+}
+
+
+void fgPlatformVisibilityWork(SFG_Window* window)
+{
+    /* Visibility status of window gets updated in the WM_SHOWWINDOW and WM_SIZE handlers */
+    int cmdShow = 0;
+    SFG_Window *win = window;
+    switch (window->State.DesiredVisibility)
+    {
+    case DesireHiddenState:
+        cmdShow = SW_HIDE;
+        break;
+    case DesireIconicState:
+        cmdShow = SW_MINIMIZE;
+        /* Call on top-level window */
+        while (win->Parent)
+            win = win->Parent;
+        break;
+    case DesireNormalState:
+        if (win->IsMenu && !fgStructure.GameModeWindow)
+            cmdShow = SW_SHOWNA;    /* Just show, don't activate window if its a menu. Only exception is when there is a gamemode window as the menu would pop under it when we do this... */
+        else
+            cmdShow = SW_SHOW;
+        break;
+    }
+
+    ShowWindow( win->Window.Handle, cmdShow );
+}