Skip to content

Commit

Permalink
move key toggling into a thread
Browse files Browse the repository at this point in the history
Instead of using the key event hook to re-issue a caps lock key event,
move this responsibility to another thread. This can help this utility
play nicely with other applications that monitor/manipulate key events
on a system by letting the initial key event do its thing and then
gracefully inject another caps lock event afterwards.

Signed-off-by: James Knight <[email protected]>
  • Loading branch information
jdknight committed Jun 22, 2024
1 parent e5405dd commit f4485d6
Showing 1 changed file with 66 additions and 21 deletions.
87 changes: 66 additions & 21 deletions src/capsblock/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,62 +7,107 @@
#endif
#include <windows.h>

// state to suppress hook events when generating new caps-lock events
volatile bool gSkip = false;
// semaphore used to signal the toggle-key thread
HANDLE gCapsEventSem;

// shutdown flag for the thread
volatile bool gShutdown = false;

// invoked when a low-level keyboard event occurs
LRESULT CALLBACK LowLevelKeyboardProc(int code, WPARAM msg, LPARAM ctx)
{
// listen for key-up events of the caps-lock key
if (!gSkip && !code >= 0 && msg == WM_KEYUP) {
if (!code >= 0 && msg == WM_KEYUP) {
KBDLLHOOKSTRUCT* kbd = (KBDLLHOOKSTRUCT*)ctx;
if (kbd->vkCode == VK_CAPITAL) {
// check if we have caps-lock enabled in our current state
bool hasCapsLock = ((GetKeyState(VK_CAPITAL) & 0x0001) != 0);
if (hasCapsLock) {
// before we attempt to toggle off the caps-lock state,
// flag our hook as disabled, to ensure we do not attempt
// to process the state of any caps-lock keys issued from
// our hook
gSkip = true;
// signal to the toggle-key thread to check/enforce
// the key state
ReleaseSemaphore(gCapsEventSem, 1, NULL);
}
}
}

return CallNextHookEx(NULL, code, msg, ctx);
}

// thread used to wait for events to check/enforce the key state
// of the caps lock key
#pragma warning(disable: 4100)
DWORD WINAPI ToggleKeyThread(void* data)
{
while (!gShutdown) {
DWORD signal = WaitForSingleObject(gCapsEventSem, INFINITE);
switch (signal) {
case WAIT_OBJECT_0:
// check (again) if we have caps-lock enabled in our current state
bool hasCapsLock = ((GetKeyState(VK_CAPITAL) & 0x0001) != 0);
if (hasCapsLock) {
// generate two additional key events:
///
// 1) a key-up event to "complete" the toggled on state
// 2) a key-down event to trigger a new caps-lock event
//
// do not consume this active event, to complete the final
// key-up event
INPUT input[2] = {0};
// 1) a key-down event to trigger a new caps-lock event
// 2) a key-up event to "complete" the event
INPUT input[2] = { 0 };
input[0].type = input[1].type = INPUT_KEYBOARD;
input[0].ki.wVk = input[1].ki.wVk = VK_CAPITAL;
input[0].ki.dwFlags = KEYEVENTF_KEYUP;
input[0].ki.wVk = input[1].ki.wVk = VK_CAPITAL;
input[1].ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(2, input, sizeof(INPUT));

// after issuing our key changes, re-enable this hook
gSkip = false;
}
break;

case WAIT_TIMEOUT:
// spurious wakeup
break;

default:
// broken state
gShutdown = true;
break;
}
}

return CallNextHookEx(NULL, code, msg, ctx);
return 0;
}

#pragma warning(disable: 4100)
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR pCmdLine, int nCmdShow)
{
// create a signal between that can be issued by the keyboard hook to
// out toggle-key thread
gCapsEventSem = CreateSemaphore(NULL, 1, 1, NULL);
if (!gCapsEventSem) {
fprintf(stderr, "unable to create keyboard hook\n");
return 1;
}

// create a hook to listen for keyboard events
HHOOK hook = SetWindowsHookEx(
WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), 0);
if (!hook) {
fprintf(stderr, "unable to create keyboard hook\n");
return 1;
return 2;
}

// build a thread reposible for toggling the cap-lock key off
HANDLE tkt = CreateThread(NULL, 0, ToggleKeyThread, NULL, 0, NULL);
if (!tkt) {
fprintf(stderr, "unable to create toggle-key thread\n");
return 3;
}

// consume all messages
MSG msg;
while(GetMessage(&msg, NULL, 0, 0) != 0);

// cleanup
gShutdown = true;
ReleaseSemaphore(gCapsEventSem, 1, NULL);
WaitForSingleObject(tkt, INFINITE);
CloseHandle(tkt);
CloseHandle(gCapsEventSem);

return 0;
}

0 comments on commit f4485d6

Please sign in to comment.