Saturday, May 26, 2012

Detect left and right Shift, Ctrl and Alt key presses, disable system commands (Win32)

For a game prototype I'm working on I needed to detect whether the left or right Ctrl, Shift and Alt keys were pressed or held down. I expected implementation to be straightforward but in fact, handling of these "special" keys in Windows is not particularly intuitive. Not only are these keys handled by a different set of messages (WM_SYSKEYUP and WM_SYSKEYDOWN as opposed to WM_KEYUP and WM_KEYDOWN for "regular" keys) but one also has to explicitly disable system commands (such as Alt + F4) by overriding the SC_KEYMENU message and setting the  WS_POPUP window style.

I've posted my message loop below. I hope it's useful to some.

 unordered_map<char, int> Window::initializeKeytable()  
 {  
      unordered_map<char, int> virtualKeys;  
   
      virtualKeys['0'] = Input::KEY_0;  
      virtualKeys['1'] = Input::KEY_1;  
      etc.  
   
      virtualKeys[VK_SPACE] = Input::KEY_SPACE;  
      virtualKeys[VK_RETURN] = Input::KEY_RETURN;  
      etc.  
   
      return virtualKeys;  
 }  
   
 LRESULT CALLBACK Win32Window::processMessages( UINT uMsg, WPARAM wParam, LPARAM lParam )  
 {  
      static unordered_map<char, int> virtualKeys = initializeKeytable();  
   
      switch( uMsg )  
      {  
           case WM_KILLFOCUS:  
   
                resetKeyStates();  
   
                break;  
   
           case WM_CHAR:  
   
                if( (int)wParam >= 32 )  
                {  
                     m_keystrokes += (int)wParam;  
                }  
   
                break;  
   
           case WM_KEYUP:  
           case WM_SYSKEYUP:  
   
                if( wParam == VK_MENU ) // alt keys  
                {  
                     if( lParam & (1 << 24) )  
                     {  
                          m_keyStates[Input::KEY_RALT] = false;  
                     }  
                     else  
                     {  
                          m_keyStates[Input::KEY_LALT] = false;  
                     }  
                }  
                else if( wParam == VK_CONTROL ) // ctrl keys  
                {  
                     if( lParam & (1 << 24) )  
                     {  
                          m_keyStates[Input::KEY_RCTRL] = false;  
                     }  
                     else  
                     {  
                          m_keyStates[Input::KEY_LCTRL] = false;  
                     }  
                }  
                else if( wParam == VK_SHIFT ) // shift keys  
                {  
                     auto rShiftState = GetKeyState( VK_RSHIFT );  
                     auto lShiftState = GetKeyState( VK_LSHIFT );  
   
                     if( (((unsigned short)rShiftState) >> 15) != 1 )  
                     {  
                          m_keyStates[Input::KEY_RSHIFT] = false;  
                     }  
   
                     if( (((unsigned short)lShiftState) >> 15) != 1 )  
                     {  
                          m_keyStates[Input::KEY_LSHIFT] = false;  
                     }  
                }  
                else // non-system keys  
                {  
                     m_keyStates[virtualKeys[(int)wParam]] = false;  
                }  
   
                break;  
   
           case WM_KEYDOWN:  
           case WM_SYSKEYDOWN:  
                  
                if( wParam == VK_MENU ) // alt keys  
                {  
                     if( lParam & (1 << 24) )  
                     {  
                          m_keyStates[Input::KEY_RALT] = true;  
                     }  
                     else  
                     {  
                          m_keyStates[Input::KEY_LALT] = true;  
                     }  
                }  
                else if( wParam == VK_CONTROL ) // ctrl keys  
                {  
                     if( lParam & (1 << 24) )  
                     {  
                          m_keyStates[Input::KEY_RCTRL] = true;  
                     }  
                     else  
                     {  
                          m_keyStates[Input::KEY_LCTRL] = true;  
                     }  
                }  
                else if( wParam == VK_SHIFT ) // shift keys  
                {  
                     auto rShiftState = GetKeyState( VK_RSHIFT );  
                     auto lShiftState = GetKeyState( VK_LSHIFT );  
   
                     if( (((unsigned short)rShiftState) >> 15) == 1 )  
                     {  
                          m_keyStates[Input::KEY_RSHIFT] = true;  
                     }  
   
                     if( (((unsigned short)lShiftState) >> 15) == 1 )  
                     {  
                          m_keyStates[Input::KEY_LSHIFT] = true;  
                     }  
                }  
                else // non-system keys  
                {  
                     m_keyStates[virtualKeys[(int)wParam]] = true;  
                }  
   
                break;  
   
           case WM_LBUTTONUP:  
   
                m_keyStates[Input::MOUSE_LEFT] = false;  
   
                break;  
   
           case WM_RBUTTONUP:  
   
                m_keyStates[Input::MOUSE_RIGHT] = false;  
   
                break;  
   
           case WM_MBUTTONUP:  
   
                m_keyStates[Input::MOUSE_MIDDLE] = false;  
   
                break;  
   
           case WM_LBUTTONDOWN:  
   
                m_keyStates[Input::MOUSE_LEFT] = true;  
   
                break;  
   
           case WM_RBUTTONDOWN:  
   
                m_keyStates[Input::MOUSE_RIGHT] = true;  
   
                break;  
   
           case WM_MBUTTONDOWN:  
   
                m_keyStates[Input::MOUSE_MIDDLE] = true;  
   
                break;  
   
           case WM_MOUSEWHEEL:  
   
                if( (short)HIWORD(wParam) > 0 )  
                {  
                     m_keyStates[Input::MOUSE_WHEEL_UP] = true;  
                }  
                else if( (short)HIWORD(wParam) < 0 )  
                {  
                     m_keyStates[Input::MOUSE_WHEEL_DOWN] = true;  
                }  
   
                break;  
   
           case WM_PAINT:  
   
                PAINTSTRUCT ps;  
                BeginPaint( m_handle, &ps );  
                EndPaint( m_handle, &ps );  
   
                break;  
   
           case WM_CLOSE:  
   
                PostQuitMessage( 0 );  
                m_isActive = false;  
   
                break;  
   
           case WM_SYSCOMMAND:  
   
                switch( wParam & 0xFFF0 )  
                {  
                     case SC_KEYMENU:  
                     case SC_SCREENSAVE:  
   
                     return 0;  
                }  
   
                break;  
   
           default:  
   
                return DefWindowProc( m_handle, uMsg, wParam, lParam );  
      }  
   
      m_keyStates[Input::KEY_ALT]  = m_keyStates[Input::KEY_LALT]  || m_keyStates[Input::KEY_RALT];  
      m_keyStates[Input::KEY_CTRL] = m_keyStates[Input::KEY_LCTRL] || m_keyStates[Input::KEY_RCTRL];  
      m_keyStates[Input::KEY_SHIFT] = m_keyStates[Input::KEY_LSHIFT] || m_keyStates[Input::KEY_RSHIFT];  
   
      return 0;  
 }  

No comments:

Post a Comment