Few days back I wanted to create a macro recorder for the desktop. I though I could do this from within the Qt framework which has a neat high-level platform-independent event system that I thought I could incorporate with widgets to make a nice user interface. Well, that’s what I though at least… Truth is, Qt doesn’t offer any means for catching system-wide events. Eventually, I managed to put together a little command line application that can record and replay/loop the mouse and keyboard input messages based on the Windows API. What you will read next is my explanation why catching system-wide events is impossible from within the Qt framework, what the Windows API offers for this purpose, and a proposal on how system-wide events could be incorporated in the Qt event system.
Background
Windows and Qt applications are event driven, which means that they wait for the system to pass events to them upon which they can act. Simply put, those events are passed as “messages” to application windows through a callback function named the “window procedure”.
For example, when a user moves a mouse, the mouse device driver places the recorded movement in the system’s message queue. The system then determines the destination window and “posts” this message to the correct “thread message queue” (i.e. the message queue of the thread which created the destination window). Our Qt application has only access to these messages from its thread message queue. These messages are called “spontaneous events” in the Qt documentation. (Note: there exist also “sent” messages which bypass the message queue, but which we do not consider here.)
Now as one can see, the system is very selective on which mouse and keyboard events our Qt application will get. For example, our window will not get any keyboard messages if it is not in focus, nor will it get any mouse events if the mouse is not inside the window borders. So how can we access those events from the Qt framework? To my knowledge Qt doesn’t offer any ready made functions for this, so we will have to find some other way: directly accessing the Windows API.
Hooks
The Windows API offers a mechanism called a “hook” which we can hook somewhere in the message-handling mechanism and which will intercept and reroute the messages to some “hook procedure” we define. We define a hook with the API SetWindowsHookEx procedure. This procedure allows us to specify the “hook type” which defines the kind of messages to be intercepted (like WH_MOUSE_LL, WH_KEYBOARD_LL, WH_SHELL), the “hook scope” which can be thread or global (i.e. system), and the hook procedure with the following predefined signature:
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam); |
So to get you started here is a very basic application which uses the above described mechanism an which can be used as a starting point for building applications which are able to monitor system events.
#include <QtGui/QApplication> #include <QDebug> #include <windows.h> LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam) { // process event... qDebug() << nCode << wParam << lParam; return CallNextHookEx(NULL, nCode, wParam, lParam); } int main(int argc, char *argv[]) { QApplication a(argc, argv); if (SetWindowsHookEx(WH_KEYBOARD_LL, HookProc, qWinAppInst(), NULL) == 0) qDebug() << "Hook failed for application instance" << qWinAppInst() << "with error:" << GetLastError(); return a.exec(); } |
For more information, the following links might be interesting:
I’ve put together a little command line utility that is able to record and replay/loop the recording based on the above explanation. Sourcecode is available here. To record: “winrec -record /f “recording.txt””. To play: “winrec -play /f “recording.txt” /repeat 0″. To stop any of these, just press Crtl+Esc.
I think integration with an Qt would probably be straightforward when used with QAbstractEventDispatcher, although I didn’t have time to test it.