I'm trying to implement the following scenario:
Requirement
Write a C++ program to capture all the keyboard inputs on Windows OS. The program should start capturing keystrokes and after about 3 seconds (the specific amount time is not very relevant, it could be 4/5/etc.), the program should stop capturing keystrokes and continue its execution.
Before I proceed with the actual implementation details, I want to clarify that I preferred tο write the requirements in a form of exercise, rather than providing a long description. I'm not trying to gather solutions for homework. (I'm actually very supportive to such questions when its done properly, but this is not the case here).
My solution
After working on different implementations the past few days, the following is the most complete one yet:
#include <iostream>
#include <chrono>
#include <windows.h>
#include <thread>
// Event, used to signal our thread to stop executing.
HANDLE ghStopEvent;
HHOOK keyboardHook;
DWORD StaticThreadStart(void *)
{
// Install low-level keyboard hook
keyboardHook = SetWindowsHookEx(
// monitor for keyboard input events about to be posted in a thread input queue.
WH_KEYBOARD_LL,
// Callback function.
[](int nCode, WPARAM wparam, LPARAM lparam) -> LRESULT {
KBDLLHOOKSTRUCT *kbs = (KBDLLHOOKSTRUCT *)lparam;
if (wparam == WM_KEYDOWN || wparam == WM_SYSKEYDOWN)
{
// -- PRINT 2 --
// print a message every time a key is pressed.
std::cout << "key was pressed " << std::endl;
}
else if (wparam == WM_DESTROY)
{
// return from message queue???
PostQuitMessage(0);
}
// Passes the keystrokes
// hook information to the next hook procedure in the current hook chain.
// That way we do not consume the input and prevent other threads from accessing it.
return CallNextHookEx(keyboardHook, nCode, wparam, lparam);
},
// install as global hook
GetModuleHandle(NULL), 0);
MSG msg;
// While thread was not signaled to temirnate...
while (WaitForSingleObject(ghStopEvent, 1) == WAIT_TIMEOUT)
{
// Retrieve the current messaged from message queue.
GetMessage(&msg, NULL, 0, 0);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Before exit the thread, remove the installed hook.
UnhookWindowsHookEx(keyboardHook);
// -- PRINT 3 --
std::cout << "thread is about to exit" << std::endl;
return 0;
}
int main(void)
{
// Create a signal event, used to terminate the thread responsible
// for captuting keyboard inputs.
ghStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
DWORD ThreadID;
HANDLE hThreadArray[1];
// -- PRINT 1 --
std::cout << "start capturing keystrokes" << std::endl;
// Create a thread to capture keystrokes.
hThreadArray[0] = CreateThread(
NULL, // default security attributes
0, // use default stack size
StaticThreadStart, // thread function name
NULL, // argument to thread function
0, // use default creation flags
&ThreadID); // returns the thread identifier
// Stop main thread for 3 seconds.
std::this_thread::sleep_for(std::chrono::milliseconds(3000));
// -- PRINT 4 --
std::cout << "signal thread to terminate gracefully" << std::endl;
// Stop gathering keystrokes after 3 seconds.
SetEvent(ghStopEvent);
// -- PRINT 5 --
std::cout << "from this point onwards, we should not capture any keystrokes" << std::endl;
// Waits until one or all of the specified objects are
// in the signaled state or the time-out interval elapses.
WaitForMultipleObjects(1, hThreadArray, TRUE, INFINITE);
// Closes the open objects handle.
CloseHandle(hThreadArray[0]);
CloseHandle(ghStopEvent);
// ---
// DO OTHER CALCULATIONS
// ---
// -- PRINT 6 --
std::cout << "exit main thread" << std::endl;
return 0;
}
Implementation details
The main requirement is the capturing of keystrokes for a certain amount of time. After that time, we should NOT exit the main program. What I thought would be suitable in this case, is to create a separate thread that will be responsible for the capturing procedure and using a event to signal the thread. I've used windows threads, rather than c++0x threads, to be more close to the target platform.
The main function starts by creating the event, followed by the creation of the thread responsible for capturing keystrokes. To fulfill the requirement of time, the laziest implementation I could think of was to stop the main thread for a certain amount of time and then signaling the secondary one to exit. After that we clean up the handlers and continue with any desired calculations.
In the secondary thread, we start by creating a low-level global keyboard hook. The callback is a lambda function, which is responsible for capturing the actual keystrokes. We also want to call CallNextHookEx
so that we can promote the message to the next hook on the chain and do not disrupt any other program from running correctly. After the initialization of the hook, we consume any global message using the GetMessage
function provided by the Windows API. This repeats until our signal is emitted to stop the thread. Before exiting the thread, we unhook the callback.
We also output certain debug messages throughout the execution of the program.
Expected behavior
Running the above code, should output similar messages like the ones bellow:
start capturing keystrokes
key was pressed
key was pressed
key was pressed
key was pressed
signal thread to terminate gracefully
thread is about to exit
from this point onwards, we should not capture any keystrokes
exit main thread
Your output might differ depending on the number of keystrokes that were captured.
Actual behavior
This is the output I'm getting:
start capturing keystrokes
key was pressed
key was pressed
key was pressed
key was pressed
signal thread to terminate gracefully
from this point onwards, we should not capture any keystrokes
key was pressed
key was pressed
key was pressed
A first glance into the output reveals that:
- The unhook function was not called
- The program keeps capturing keystrokes, which might indicate that something is wrong with the way I process the message queue
There is something wrong regarding the way I'm reading the messages from the message queue, but after hours of different approaches, I could not find any solution for the specific implementation. It might also be something wrong with the way I'm handling the terminate signal.
Notes
- The closer I could get on finding an answer, here in SO, was this question. However the solution did not helped me as much as I wanted.
- The provided implementation is a minimum reproducible example and can be compiled without the need to import any external libraries.
- A proposed solution will be to implement the capturing-keystrokes functionality as a separate child process, where will be able to start and stop whenever we like. However, I'm more interested in finding a solution using threads. I'm not sure if this is even possible (it might be).
- The above code does not contain any error handling. This was on purpose to prevent possible over bloated of the code.
For any questions you might have, feel free to comment! Thank you in advance for your time to read this question and possibly post an answer (it will be amazing!).