Manipulating Windows using messages and simple CBT hooking
Introduction
Windows is essentially a message driven Operating System in the sense that, the majority of actions that take place are responses to messages sent to the main window procedure of an application. Whether you press a key, or move the mouse or drag a window the application receives messages through it's message queue and reacts accordingly. Now this results in a rather interesting corollary that can be taken advantage of by us, developers; by sending the correct messages to a window or it's child windows in the proper order, we can actually simulate human actions on an application. And this has it's uses in various scenarios. Obviously the first one that comes to mind is the ability to automate a task, like for example opening a document in word, left justifying the entire text and taking a print out.
But for me a more interesting usage of this technique is when it's applied to take advantage of the Windows user interface to quickly do tasks, which might otherwise require a lot of programming calls and access to undocumented information. This includes changing various system properties, making changes through the control panel applets or even changing display properties for the desktop. In this article I will randomly select one such scenario ( a test case scenario ) and see how to go about automating the task by using some simple windows techniques like posting messages to a window, enumerating child windows and elementary CBT hooking.
Test scenario
I am going to be using Windows XP Professional as my test platform and therefore my example scenario is only meaningful in an XP context. Users of other Operating Systems might have to make suitable changes to my example code snippets to get the same end result as in this article.
By default, the keyboard navigation short cuts for menus are not shown in the XP operating system ( something that both puzzled and annoyed a lot of users when they first encountered this in Windows 2000 ). Having long abandoned Windows 2000, I do not remember if there was a documented way of changing this setting in 2000, other than by editing the registry or using some tweaking application, but in XP this setting can be easily changed by using the Display Properties control panel applet. All you need to do is select the Appearances tab from the Display Properties dialog box, bring up the Effects sub-window and uncheck the check box that says "Hide underlined letters for keyboard navigation until I press the Alt key". Now I am wholly sure that this setting can probably be changed by modifying a trivial registry entry; but for the sake of this test case scenario and the article, let's assume that we do not know how to achieve the same programmatically.
The human approach
Let's see how we'd do this had we done this manually sitting in front of the machine. We'd probably have to follow these steps ( or something very similar ) :-
- Right click on the desktop and bring up the Display Properties control panel applet
- Chose the Appearances tab
- Bring up the Effects sub-window by clicking on the Effects button
- Check/Uncheck the corresponding check box depending on what we are trying to do
- Click OK to dismiss the Effects sub-window
- Click OK to dismiss the Display Properties and apply our changes globally
The solution in code
Now we need to decide what we need to do to achieve the same sequence of events through code.
- Bringing up the Display Properties window and choosing the Appearances tab
can be done in just one step because we know that the Display Properties control
panel applet is called desk.cpl and that it takes command line arguments
that can be used to dictate which tab comes up by default. In fact we need to
call it like this :-
control.exe desk.cpl Display,@Appearance
Control.exe is used to bring up the control panel applet passed to it as first argument, and the additional arguments are used to force it to start with the Appearance tab selected.
- Now we need to enumerate the child windows ( controls ) on the Appearance
tab till we find the Effects button and this can be achieved using
EnumChildWindows
. Once we obtain the Effects button's handle we can send a button click message to it and bring up the Effects sub-window. - To locate the required check box on the Effects sub-window we would need to
first get the handle to the sub-window that just popped up. We achieve this by
setting up a global CBT hook ( this means we'd need to put all put code into a
DLL ), and watching for all newly activated windows. We know the title text for
the Effect sub-window and thus we obtain the handle to the window the moment it
gets activated. Now we do the same as previous, i.e. we use
EnumChildWindows
to retrieve the handle to the check box, and then send a button click message to it. - We post a
WM_COMMAND
message to the Effects sub-window with a command ID ofIDOK
which is the equivalent of closing the window by clicking on the OK button. - We do the same for the main Display Properties window.
Implementation details
Bringing up the Display Properties window
BOOL BringUpDisplayAppearance() |
The code is quite simple and straightforward, we simply use
ShellExecute
to bring up the Display Properties applet window with
the default tab set to the Appearances tab. I have used SW_SHOW
here because using SW_HIDE
will have no effect on the display
properties window ( I believe the control.exe program or perhaps
desk.cpl itself will later call ShowWindow(hWnd, SW_SHOW)
somewhere in the code ). We do our window hiding in the hook procedure ( but
even this is not fully effective and there is a short flash on screen, but then
our aim is not really to hide what we are doing from our end user, but to try
and make things as lucid as possible, which we achieve by reducing the time the
window remains visible to a few milliseconds ).
Getting the handle to the Effects button
HWND GetEffectsButton(HWND hWndParent) |
Using Spy++ we extract the exact text associated with the Effects button which happens to be "&Effects..." and we use this knowledge to compare the text of each child control with this text repeatedly till we get the button control we want.
Getting the check box handle
HWND GetMenuUnderlineCheck(HWND hWndParent) |
This is very similar to how we obtained the handle to the Effects button.
The CBT hook procedure
LRESULT CALLBACK CBTProc(int nCode, |
The HCBT_ACTIVATE
code indicates that a window is about to be
activated. We compare the title text of this window with "Effects" and if they
match, we know that this is the window we were searching for. If you are
wondering why we had to install a CBT hook, instead of using
FindWindow
using the title text; this is to make sure that even if
there was already an existing window with the same title text, it won't
interfere with our search because we are only checking newly activated windows.
We set the CBT hook quite late into the code and uninstall it the moment we get
the window we want. The duration the hook is active is from the time we bring up
the Display Properties window till the Effects sub-window is just about to be
activated, and under most circumstances this shouldn't be more than a few
milliseconds.
We also use the hook procedure to hide the windows that pop up, which include both the main Display Properties window as well as the Effects sub-window. The infinitesimal flash still exists and if anyone has any ideas on further reducing this, they are welcome to make suggestions.
The main function ( exported )
DEMODLL_API BOOL ToggleMenuUnderline(void) |
We first set up our CBT hook and then call the
BringUpDisplayAppearance
function to bring up the Display
Properties window with the Appearance tab selected. Once the window comes up we
obtain the handle to the Effects button using the GetEffectsButton
function, and then post a BM_CLICK
message to the Effects button
using the handle we just obtained. Almost instantly the Effect sub-window pops
up and we retrieve it's handle through our CBT hook procedure which also
uninstalls the hook since it's no longer of any use to us. We wait for the
Effects window to come up, using the following while loop :-while(!IsWindow(g_hWndEffects)) |
This way we avoid sleeping for too long or for too less. Now we obtain the
handle to the check box using the GetMenuUnderlineCheck
function
and post a BM_CLICK
message to the check box which effectively
toggles it's state which is just what we are trying to do. Now we simply post
WM_COMMAND
messages with wParam
set to
IDOK
to the Effects sub-window as well as to the Display Properties
main window. That's all; we have now successfully toggled the state of the
"Underline keyboard navigation shortcuts for menus" system-wide property.
Conclusion
The test scenario we considered was perhaps too simplistic to reveal the actual power of this technique, but when you consider that you can now do anything from your program that a user can do manually using the Windows GUI, you'll be slowly impressed by the awesome possibilities of the technique. You can use this technique to enumerate Windows themes, change the current theme, change display settings, change system settings, automate your own applications etc. The only tool you'd need in addition to Everett is Spy++ or some such similar application. Good luck with your own message based Windows automation attempts.
History
- August 8th 2003 - First written
From : http://www.voidnish.com/Articles/ShowArticle.aspx?code=manipwindows