Click here to Skip to main content
15,885,546 members
Articles / Desktop Programming / MFC

Toggling the Num Lock, Caps Lock, and Scroll Lock keys

Rate me:
Please Sign up or sign in to vote.
4.84/5 (23 votes)
24 May 2002CPOL3 min read 252.4K   7K   65   41
How to toggle the Num Lock, Caps Lock, and Scroll Lock keys programmatically
There are two more functions that can toggle keyboard state but each of them has some specific limitations like SendInput does. In this article, you will find all possible variants with their descriptions and limitations and a simple solution for toggling these keys avoiding compatibility problems.

Image 1

Introduction

I have a status bar that toggles the Num Lock, Caps Lock and Scroll Lock keys in one application, but some time ago, I discovered that the API function SendInput I used for it requires Windows 98 or later and Windows NT 4.0 with Service Pack 3.0 or later. I have browsed MSDN and found that there are two more functions that can toggle keyboard state but each of them has some specific limitations like SendInput does. To make it easier for others, I have posted all possible variants with their descriptions and limitations and a simple solution for toggling these keys avoiding compatibility problems.

Variant A - using keybd_event

Description

The keybd_event function synthesizes a keystroke. The system can use such a synthesized keystroke to generate a WM_KEYUP or WM_KEYDOWN message. The keyboard driver's interrupt handler calls the keybd_event function.

Version Computability

  • Windows NT/2000/XP: Requires Windows NT 3.1 or later
  • Windows 95/98/ME: Requires Windows 95 or later
  • Windows CE: Requires version 1.0 or later

Important Notes

  1. An application can simulate a press of the PRINTSCRN key in order to obtain a screen snapshot and save it to the clipboard. To do this, call keybd_event with the first parameter set to VK_SNAPSHOT.
  2. Windows 95/98/Me: The keybd_event function can toggle only the CAPS LOCK and SCROLL LOCK keys. It cannot toggle the NUM LOCK key. We can do this with SendInput but it requires Windows NT 4.0 SP3 and later or Windows 98 and later.
  3. Windows NT/2000/XP: The keybd_event function can toggle the NUM LOCK, CAPS LOCK, and SCROLL LOCK keys.

Sample Usage

When setting key state, be sure to generate both key press and key release (see below). Notes: Windows NT/2000: This function has been superseded. Use SendInput instead.

C++
//
    // Toggle Caps Lock key:
    ::keybd_event( VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY, 0 );
    ::keybd_event( VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0 );
//

Variant B - using SendInput

Description

The SendInput function synthesizes keystrokes, mouse motions, and button clicks.

Version Compatibility

  • Windows NT/2000/XP: Requires Windows NT 4.0 SP3 or later
  • Windows 95/98/Me: Requires Windows 98
  • Windows CE: Requires version 2.0 or later

Sample Usage

When setting key state, be sure to generate both key press and key release (see below). And do not forget to include WinAble.h, not WinUser.h as MSDN says.

C++
//
    #include <WinAble.h> // Required for the ::SendInput function
//...
    // Toggle Caps Lock key:
    INPUT input[2];
    ::ZeroMemory(input, sizeof(input));        
    input[0].type = input[1].type = INPUT_KEYBOARD;
    input[0].ki.wVk  = input[1].ki.wVk = VK_CAPITAL;        
    input[1].ki.dwFlags = KEYEVENTF_KEYUP;  // THIS IS IMPORTANT
    ::SendInput(2, input, sizeof(INPUT));
//

Variant C - using SetKeyboardState

Description

The SetKeyboardState function copies a 256-byte array of keyboard key states into the calling thread's keyboard-input state table.

Version Compatibility

  • Windows NT/2000/XP: Requires Windows NT 3.1 or later
  • Windows 95/98/Me: Requires Windows 95 or later
  • Windows CE: Unsupported

Important Notes

  1. Because the SetKeyboardState function alters the input state of the calling thread and not the global input state of the system, an application cannot use SetKeyboardState to set the NUM LOCK, CAPS LOCK, or SCROLL LOCK indicator lights on the keyboard.
  2. SetKeyboardState function sets keyboard state for calling thread only, not the global input state of the system.

Sample Usage

C++
//
    // Toggle Caps Lock key:
    BYTE byKeybState[256];
    ::GetKeyboardState(byKeybState);         
    byKeybState[nVirtKey] = !(BOOL)::GetKeyState(VK_CAPITAL);
    ::SetKeyboardState(byKeybState);
//

Support for Setting Num Lock, Caps Lock and Scroll Lock Keys State

Function\OS
Win CE 1.0
Win CE 2.0
Win 95
Win 98/Me
Win NT
Win 2000/XP
keybd_event
Yes
Yes
All but Num Lock
All but Num Lock
Yes
Yes
SendInput
 
Yes
 
Yes
SP3*
Yes
SetKeyboardState
 
 
Yes**
Yes**
Yes**
Yes**

* - Requires Service Pack 3 or later.
** - Does not set indicator lights on the keyboard and toggles input state of the calling thread only.

Simple Solution

Sample Image

I have provided a simple StatusBar class that toggles the keys when the appropriate pane is double-clicked. See below:

C++
//
#include <WinAble.h> // Required for the ::SendInput function
//...

void CStatusBarEx::OnNMDblclk(NMHDR* pNMHDR, LRESULT *pResult)
{
    // Get the double-clicked pane index:
    
    NMMOUSE* pNMMOUSE = (NMMOUSE*)pNMHDR;
    INT nInd = GetItemID((int)pNMMOUSE->dwItemSpec);

    if((nInd >= ID_INDICATOR_CAPS) && 
       (nInd <= ID_INDICATOR_OVR))
    {        
        UINT nVirtKey = 0;
        switch(nInd)
        {
        case ID_INDICATOR_CAPS: nVirtKey = VK_CAPITAL; break;
        case ID_INDICATOR_NUM:  nVirtKey = VK_NUMLOCK; break;
        case ID_INDICATOR_SCRL: nVirtKey = VK_SCROLL;  break;
        case ID_INDICATOR_OVR:  nVirtKey = VK_INSERT;  break;
        }
        
        ASSERT(nVirtKey >= 1 && nVirtKey<= 254);
    
        DWORD dwVersion = ::GetVersion();

        // Windows NT/2000/XP: The keybd_event function can toggle the 
        // NUM LOCK, CAPS LOCK, and SCROLL LOCK keys although
        // this function has been superseded by ::SendInput.

        if( dwVersion < 0x80000000) // Win NT
        {            
            ::keybd_event( nVirtKey, 0x45, KEYEVENTF_EXTENDEDKEY, 0 );
            ::keybd_event( nVirtKey, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0 );
        }
        else
        {
            // Windows 95/98/Me: The keybd_event function can toggle only the 
            // CAPS LOCK and SCROLL LOCK keys. It cannot toggle the NUM LOCK key.
            // We can do this with SendInput but it requires 
            // Windows NT 4.0 SP3 and later or Windows 98 and later.

            DWORD dwMin = (DWORD)(HIBYTE(LOWORD(dwVersion)));
            if(dwMin >= 10) // Windows 98 and later
            {
                INPUT input[2];
                ::ZeroMemory(input, sizeof(input));        
                input[0].type = input[1].type = INPUT_KEYBOARD;
                input[0].ki.wVk  = input[1].ki.wVk = nVirtKey;        
                input[1].ki.dwFlags = KEYEVENTF_KEYUP;  // THIS IS IMPORTANT
                ::SendInput(2, input, sizeof(INPUT));
            }
            else // Windows  95
            {
                // Because the SetKeyboardState function alters the input state 
                // of the calling thread and not the global input state of the system, 
                // an application cannot use SetKeyboardState to set the 
                // NUM LOCK, CAPS LOCK, or SCROLL LOCK (or the Japanese KANA) indicator
                // lights on the keyboard.

                BYTE byKeybState[256];
                ::GetKeyboardState(byKeybState);         
                byKeybState[nVirtKey] = !(BOOL)::GetKeyState(nVirtKey);
                ::SetKeyboardState(byKeybState);
            }
        }
        ::Beep(500, 100);
    }
    else
    {
        CWnd* pWnd = GetParent();
        if(pWnd != NULL)
        {             
            //TODO: SendNotifyMessage; 
        }
    }

    *pResult = 0;
}
//

Please send me all bugs, fixes or better solutions.

History

  • 25th May, 2002: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) SafeNet Inc
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionSendInput not displaying in Win98 Dos Box Pin
SirGuyon2-Dec-08 22:20
SirGuyon2-Dec-08 22:20 
I have searched and searched... and experimented and tried all sorts of things... it works perfectly on XP but not on 98SE. The call to SendInput seems to work... e.g. GenerateKeyStrokes("Cat") generates....

sent 2 events
sent 2 events
sent 2 events


I am no windows guru... it is all some sort of black magic... but I am experienced enough to spend a few days reading and debugging... and I can usually manage to write code that is reasonably efficient, works correctly, and doesn't leak memory.

Any help would be appreciated. This has me totally stumped.

Please note: neither version of code works on 98SE... neither keybd_event OR SendInput generate any output on my Win 98 SE machines, although both versions work perfectly under XP.

I suspect that there is something simple that I don't understand or am doing wrong.... could someone please tell me what that simple thing is....

/*
 *
 *  GenerateKeyStrokes
 *
 *  Put some text on the screen that can be edited
 */
void GenerateKeyStrokes ( const char* str )
{
#undef  _KEYBD_EVENT
#define _SENDINPUT

#ifdef _KEYBD_EVENT
	// use keybd_event because 
	//   a) couldn't get ':' with SendInput
	//   b) keybd_event seems more likely to work on win98
  for (LPCTSTR p=str; *p; p++)  // loops through chars
  {
    short vk=VkKeyScan(*p); // keycode of char

    if((vk>>8)&1)
	{	
		keybd_event(VK_LSHIFT,0,0,0);
	} // hold shift if necessary

    keybd_event((unsigned char)vk,0,0,0); // key in
    keybd_event((unsigned char)vk,0,KEYEVENTF_KEYUP,0); // key out

    if((vk>>8)&1)
	{
		keybd_event(VK_LSHIFT,0,KEYEVENTF_KEYUP,0);
	} // release shift if necessary
  }
#endif

#ifdef _SENDINPUT
  for (LPCTSTR p=str; *p; p++)  // loops through chars
  {
    short vk=VkKeyScan(*p); // keycode of char

    INPUT input[2];
	if((vk>>8)&1)
	{	
       ZeroMemory(input, sizeof(input[1]));        
       input[0].type = INPUT_KEYBOARD;
       input[0].ki.wVk = VK_LSHIFT;        
       SendInput(1, input, sizeof(INPUT[1]));
	} // hold shift if necessary

    ZeroMemory(input, sizeof(input));        
    input[0].type = input[1].type = INPUT_KEYBOARD;
    input[0].ki.wVk  = input[1].ki.wVk = vk;        
    input[1].ki.dwFlags = KEYEVENTF_KEYUP;  

    unsigned res = SendInput(2, input, sizeof(INPUT));

#ifdef _DEBUG
	std::cout << "sent " << res << " events" << std::endl;
#endif

	if((vk>>8)&1)
	{	
       ZeroMemory(input, sizeof(input[1]));        
       input[0].type = INPUT_KEYBOARD;
       input[0].ki.dwFlags = KEYEVENTF_KEYUP; 
       input[0].ki.wVk  = VK_LSHIFT;        
       SendInput(1, input, sizeof(INPUT[1]));
	} // release shift if necessary
  }
}


Thank-you very much.
John
AnswerRe: SendInput not displaying in Win98 Dos Box Pin
Armen Hakobyan4-Dec-08 6:03
professionalArmen Hakobyan4-Dec-08 6:03 
GeneralRe: SendInput not displaying in Win98 Dos Box Pin
SirGuyon4-Dec-08 12:24
SirGuyon4-Dec-08 12:24 
AnswerRe: SendInput not displaying in Win98 Dos Box Pin
SirGuyon4-Dec-08 19:09
SirGuyon4-Dec-08 19:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.