1

I need to check the toggled state of caps lock and block it. I have tried using a low-level keyboard hook SetWindowsHookEx with WH_KEYBOARD_LL and checking for WPARAM==WM_KEYDOWN || WPARAM==WM_SYSKEYDOWN messages, and LPARAM.vkCode==VK_CAPITAL || LPARAM.scanCode==0x3A, but this results in me intercepting/blocking caps lock when it's held down/pressed, not when it's actually toggled. It's important that I intercept the toggled event exclusively because I don't wish to rely on a single press of caps lock toggling its state, and I don't want to disrupt other events in case of caps lock being used as a modifier. I'm currently using GetKeyState(VK_CAPITAL)&1 to check for caps lock state in my window callback, and forcing it back off with SendInput , but I would rather intercept/block it if any possible. I have tried Raw Input as well, and it generates a pair of RI_KEY_BREAK And RI_KEY_MAKE messages when caps lock gets toggled, but (unless I'm mistaken), there is no way to block keys based on WM_INPUT messages, and trying to synchronize a hook and Raw Input seems to be difficult because the hook always gets them first. Using GetKeyState or GetAsyncKeyState from a hook also seems not to work, as they seem to get the event after the hook.

NS studios
  • 149
  • 14
  • You state on the one hand that you do not want to disrupt certain software behaviors, but on the other hand, you want to _suppress_ a Caps Lock keystroke toggle from being registered, by intercepting it first. (as opposed to registering a toggle, then undoing it by toggling again.) I do not think you can have both. If a solution is presented that disables the Caps Lock key upon program start-up (and hopefully re-enables it upon exit), then that solution will undoubtedly disrupt those certain software behaviors you identify. I think this is an X-Y problem. Please clarify your actual need. – ryyker Jan 09 '23 at 15:14
  • My apologies for not being clear enough. All I wish to do is intercept and block caps lock toggling, however, I haven't been able to find a way to distinguish toggled vs up/down, and it's up/down that I want to avoid blocking. The software I was referring to makes it so the up/down messages are treated as a modifier, but pressing the key twice passes the key through and thus toggles the caps lock state. – NS studios Jan 09 '23 at 21:11
  • Are you comfortable with programmatically reading/editing registry keys? – ryyker Jan 10 '23 at 15:15
  • Again, you are asking for two incompatible features. i.e. _"I don't want to disrupt other events in case of caps lock being used as a modifier"_ , while at the same time _"...intercept and block the caps lock toggle event globally..."_. I do not believe these two _features_ can co-exist as stated. They are logical equivalents to these [graphical impossibilities](https://www.google.com/search?q=m.c.+escher+stairs&client=firefox-b-1-d&sxsrf=AJOqlzU5MlZ327tkuVgJ3Sq_7_1tE-Dv7A:1673889608384&source=lnms&tbm=isch&sa=X&ved=2ahUKEwi21oDczMz8AhURAjQIHV9TB2IQ_AUoAXoECAEQAw&biw=1273&bih=842&dpr=1) – ryyker Jan 16 '23 at 17:26
  • Given that it is never a good idea to use a toggle key as a modifier to any other key stroke, would you be satisfied with a programmatic method that would perform a one time event rendering the Caps Lock key disabled? There are at least two methods I can think of, and will be happy to illustrate if you are. – ryyker Jan 16 '23 at 17:30
  • I'd appreciate any kind of idea/solution for sure. – NS studios Jan 16 '23 at 22:08
  • I tested out an idea this morning, but I am walking out the door now. I will see if I can put it out to you sometime tomorrow morning. – ryyker Jan 16 '23 at 22:20
  • No worries. I'd appreciate it very much whenever you get some free time. – NS studios Jan 17 '23 at 15:00
  • I remember writing a program doing _exactly_ this, using low level keyboard hooks. It was long time ago though and probably never tested beyond Win XP. but it worked like a charm at least in XP and earlier. I could try to dig up the source for it... – Lundin Jan 17 '23 at 15:25
  • I put what I think will meet _only the primary need_ into a separate answer below. I am satisfied that it does provide the capability to disable a key effectively, but with a couple of small caveats. (described in the answer. ) – ryyker Jan 17 '23 at 15:25
  • Spontaneously, I think some little internal state machine in the LLKeyboardProc should do the trick. Return 1 when you received a WM message that you want to block, based on the state machine. – Lundin Jan 17 '23 at 15:36
  • @Lundin Yeah, that's what I'm doing right now with WM_KEYDOWN and WM_KEYUP, WM_SYSKEYDOWN/WM_SYSKEYUP, but those are only for held/released, and I haven't been able to find a message for toggled. – NS studios Jan 17 '23 at 19:41
  • Hence the state machine. A button has these states: not pressed state, going from not pressed to pressed state, pressed state, going from pressed state to not pressed state. When writing low level drivers for keyboards and the like you only have high/low states, nothing else. – Lundin Jan 18 '23 at 07:15

3 Answers3

1

Use GetAsyncKeyState to detect when/if the caps key is hit, and its current state (up or down). Then call keybd_event (or SendInput) to programmatically set the caps key back to the state that you want it to be.

The following code snippet (along with other setup code) is included in this link, and will toggle CAPS lock on or off when executed:

RUN keybd_event ({&VK_CAPITAL}, 0, {&KEYEVENTF_KEYUP},       0, OUTPUT intResult).
RUN keybd_event ({&VK_CAPITAL}, 0, {&KEYEVENTF_EXTENDEDKEY}, 0, OUTPUT intResult).
RUN keybd_event ({&VK_SHIFT}, 0, {&KEYEVENTF_KEYUP},       0, OUTPUT intResult).
RUN keybd_event ({&VK_SHIFT}, 0, {&KEYEVENTF_EXTENDEDKEY}, 0, OUTPUT intResult).    

The recommended way to deploy this implementation (GetAsyncKeyState / keybd_event combination) within your application is to encapsulate it into a worker thread set in a forever loop with sleep() set to allow sampling of the state approximately every 100ms.

(Note, I believe GetAsyncKeyState() over GetKeyState() is an improvement for what you want to do here as GetKeyState() gets the key status returned from the thread's message queue. The status does not reflect the interrupt-level state associated with the hardware. GetAsyncKeyState() specifies whether the key was pressed since the last call to GetAsyncKeyState(), and whether the key is currently up or down.) With a reasonable and appropriate sample cycle using GetAsyncKeyState().

The concept above is comprised of functions that run in user-mode, therefore almost certainly limited to implementations of reaction algorithms (detect toggle, then execute another toggle.) as opposed to a true prevention algorithm. (ie, one that either re-maps a key to a no-op at run-time, or trap the request at a low level.)
Most true prevention algorithms would likely make use of Kernel mode driver calls, which are accessible and implementable via the WinAPI and for which concepts are introduce (among other places) by burrowing down through the content here RAWKEYBOARD into areas such as Keyboard and Mouse HID drivers.

ryyker
  • 22,849
  • 3
  • 43
  • 87
  • keybd_event has been superseded by SendInput, and as far as I can tell, GetAsyncKeyState only reports if the physical key is up or down, not if it's toggled. What I'm doing right now is very similar to your idea, though, but I'd very much prefer to intercept and block the toggled message instead of counteracting it. – NS studios Jan 03 '23 at 16:49
  • _"but I'd very much prefer to intercept and block the toggled message instead of counteracting it"_ One way would be to intercept it at the HID level, where the in-signal comes in from the keyboard, but has not yet reached back to the OS to instruct CAPS to be on (or off). Or, create a set of low level libraries. Maybe ***[this](https://www.codeproject.com/Articles/716591/Combining-Raw-Input-and-keyboard-Hook-to-selective)*** is worth a read. – ryyker Jan 03 '23 at 17:39
  • 1
    I have used [this product](https://www.eltima.com/products/vspdxp/) to hi-jack the UART of a HW serial port so that input data coming from a sensor could be routed to a bypass and modified with a calibration constant before completing its trip to the sensor display. But it is commercially off-the-shelf, and not free :( (And I am not sure if this can be used with HID SW.) – ryyker Jan 03 '23 at 17:48
  • I have tried implementing RawInput, but it seems like hook overrides RawInput. RawInput actually gives me a set of RI_KEY_BREAK And RI_KEY_MAKE messages every time capslock is toggled, but getting that synced with hook is difficult. Can I block keys based on RawInput? – NS studios Jan 07 '23 at 18:34
  • @NSstudios - FWIW Regarding your assumption_"...as I can tell, GetAsyncKeyState _only_ reports if the physical key is up or down"_, (empasis mine) It also indicates whether the key was pressed after a previous call to GetAsyncKeyState. This is useful to track state of CAPS, ie, are they ON or OFF. – ryyker Jan 09 '23 at 14:53
  • Thank you. Although GetAsyncKeyState(VK_CAPITAL)&1 only reports 1 once when the state changes, while GetKeyState(VK_CAPITAL)&1 reports the current state no matter what. – NS studios Jan 09 '23 at 23:38
1

A key-mapping approach

The method described below meets the primary need, i.e. to disable the the Caps Lock key from toggling the keyboard into CAPS mode. However, it does not maintain the ability of key to be used as a modifier once it has been re-mapped. (One of the criteria you list.)

The uncap project worked (almost out-of-the-box) for me to disable the Caps Lock key. Before trying it, I recommend going through the README.md to get the details. In short, it uses a key map approach that allows keys to be mapped to different locations. I found it essentially does what it claims in terms of disabling Caps Lock, and it is capable of doing much more. This could be good or bad. Having the source code available allows you to create a pared down version that simply disables the Caps Key, or do other modifications as needed.

While exploring it, I found a couple of issues that I describe below under problems.

Note that the default behavior is to map Caps Lock key to VK_ESCAPE upon startup. I commented out the following line in the parseArguments(...) function to disable that feature so I could experiment with other mappings...

/*my.keymap[VK_CAPITAL] = VK_ESCAPE;*/

I used uncap.c as the only source file and the following on a Windows 10 machine:

gcc.exe -Wall -g -std=c89 -I"C:\Program Files (x86)\CodeBlocks\MinGW\mingw32\lib" -c C:\play_cb\uncap\uncap.c -o obj\Debug\uncap.o 

Problems

  • It builds with a few warnings related to wrong number of arguments, or format specifiers in sprintf, but once addressing those issues, the code worked as described in this section of documentation.

  • Although the feature list claims "Disable key mappings easily by stopping Uncap.". did not work. Once the PC was re-booted, normal key mappings are restored.

  • If the keyboard is set to CAPS ON when uncap is executed, it remains in CAPS mode and the Caps Lock key is not able to undo it :)

I found this link useful when experimenting with mappings: Virtual key codes

ryyker
  • 22,849
  • 3
  • 43
  • 87
-2

You could set a low-level hook with SetWindowsHookEx. Refer to the thread: Best way to intercept pressing of Caps Lock

Jeaninez - MSFT
  • 3,210
  • 1
  • 5
  • 20
  • That's what the OP is already doing. The question also explains why this doesn't address the specific requirements. – IInspectable Jan 04 '23 at 09:07
  • That's what I'm doing already. The problem is that I can't seem to be able to check for the toggled state like I can with GetKeyState(VK_CAPITAL)&1 The return value specifies the status of the specified virtual key, as follows: • If the high-order bit is 1, the key is down; otherwise, it is up. • If the low-order bit is 1, the key is toggled. A key, such as the CAPS LOCK key, is toggled if it is turned on. The key is off and untoggled if the low-order bit is 0. A toggle key's indicator light (if any) on the keyboard will be on when the key is toggled, and off when the key is untoggled. – NS studios Jan 07 '23 at 20:27