Win32 Resources: Using C++ to Programmatically Retrieve a Global Cursor's Shape and ID
|This article assumes you're familiar with C++ and Win32|
|Level of Difficulty 1 2 3 |
|Download the code for this article: Cursor.exe (43KB) |
|Browse the code for this article at Code Center: VerifyCursor |
|SUMMARY Getting global cursor information is useful when developing software that drives or gathers information about other UI-based applications, including information about a remote machine. This article describes a way to programmatically identify the current cursor's ID and bitmap at any point in time. The first technique described is based on polling for information and shows how to get the handle of the current global cursor. This handle will then allow you to get information about the cursor. You can also monitor WinEvents for changes to the global cursor. |
| s you've watched the mouse pointer take on various shapes during different PC operations, did you ever wonder if there was a way to programmatically identify the current cursor's ID and bitmap at any particular point in time? Several recent projects inspired me to investigate this possibility. In this article I will discuss a technique that allows you to programmatically get the bitmap of a cursor and identify it.
Using Cursor Information Before delving into the details, let me mention a few cases where this technique may be useful. Determining the currently displayed cursor can help you get additional information about a window application being monitored from your program. If, for instance, the application supports several window groups and each group has a unique cursor defined in its WNDCLASS, then you might want to display a specific screen context (such as a tooltip or menu) or perform specific actions or verifications if the cursor is located over a particular window belonging to a particular group.
Another use might be to verify the state of a window application based on the cursor corresponding to its window(s). This would be useful when dealing with timing problems or when developing software that drives other applications. For instance, to make sure that the target application is ready for next action, a testing program can verify that the cursor that is possessed by a window of a target application is not IDC_WAIT or IDC_APPSTARTING. You might need to verify the cursor shape while resizing a window (cursor must be the double-pointed arrow), clicking on a button (arrow), or clicking on a link (hand), for example, in order to determine the state of the application at a particular point in time.
Getting cursor information can also be useful if you want to verify the results of calling SetCursor or SetSystemCursor. You might also find it useful when you need to get information about the cursor on a remote machine your server is running on.
This last case was the starting point of my investigation. My application consisted of two parts, a client and a server, that allowed me to drive a remote machine from a local machine. In order to provide a convenient way to drive a remote machine, I needed not only its screen snapshot and cursor position, but also information about the global cursor bitmap/ID. In this article I use the term current global cursor to describe the cursor displayed on the screen at any point in time. Since I need to compare this current cursor to the cursor after a change occurs, the cursor I'm comparing the current global cursor to is called the given cursor.
Overview of the Technique There are two main steps in the technique I used. The first retrieves the HCURSOR, the handle of current global cursor. The second gets unique information about a cursor based on its handle. Using this approach, you can call LoadCursor to get the handle for a given cursor and then compare this cursor with the actual global cursor based on their handles.
One way to get HCURSOR is to call AttachThreadInput from your application to attach the input processing mechanism of the calling thread to that of the thread that created a window "possessing" the global cursor. This method is described in the Knowledge Base article Q230495. Although this method works in many cases, there are some situations where it doesn't. For instance, I couldn't get a correct HCURSOR while starting an application by clicking on an icon on the desktop. The type of the global cursor in this case was IDC_APPSTARTING (standard arrow and small hourglass) and it was located over the Desktop. This method also couldn't return the correct handle of the IDC_APPSTARTING global cursor when I started the debugger from the Visual Studio® IDE.
The second and only reliable approach that I could find to get the correct HCURSOR was to call GetCursorInfo. This function works correctly on Windows® 98 and all future versions in the 9x family and Windows 2000. On Windows NT® 4.0, you need to get the address of GetAccCursorInfo in the User32.DLL and then call it. However, GetAccCursorInfo is not officially supported and may not work in future versions.
Once the handle for the current global cursor is available, you can use it to compare this global cursor with a given cursor. You can create a table of these values and compare a handle of the actual global cursor with the entries in this table. Since a table of the system cursors' handles has different values for different operating systems, and these values may even vary each time you boot a particular machine, you have to initialize this table for all system cursors when your application starts. The shortcoming of this approach is that it cannot identify user-defined cursors.
To overcome this problem, the method I suggest compares bitmaps corresponding to the actual global cursor and given cursors. An added advantage of this method is that it enables access to the bitmap of the actual global cursor, which may be very useful if you need to retrieve the actual appearance of the cursor on a local or a remote machine.
Implementation The implementation of the steps described in the previous section appears in CursorShape.cpp, which is available with the code download from the link at the top of this article. This file contains two groups of functions, Cursor Verification and Internal, that are declared in a header file CursorShare.h (see Figure 1).
The cursor verification functions do the main work, and the Internal functions are used internally by cursor verification functions, as I will describe next.
The key cursor verification function is IsCursor. It takes the same parameters as LoadCursor, namely a handle to an instance of the module whose executable file contains the resource for the given cursor, and a pointer to a null-terminated string that contains the name of this resource. Internally, IsCursor retrieves a handle to the given cursor and a handle for the actual global cursor by calling GetCursorInfo or GetAccCursorInfo, as I described in the previous section. Once both handles are available, the GetIconInfo function is called to get the corresponding bitmaps. Finally, the internal function, CompareBitmaps, is used to compare the bitmaps.
You will note that I said GetIconInfo gets the corresponding bitmaps (not bitmap). A cursor generally has two bitmaps. They are defined in the MSDN Library as the bitmask bitmap and the cursor's color bitmap. If the cursor is black and white, the bitmask is formatted so that the upper half is the AND bitmask of the cursor and the lower half is the XOR bitmask of the cursor. If the cursor is colored, this mask only defines the AND bitmask of the cursor. The handle to the color bitmap can be optional if the cursor is black and white.
The CompareBitmaps function takes two HBITMAP values (that is, the bitmap handles of the current global cursor and the given cursor) and compares device-independent bits of the bitmaps corresponding to them. These bits are retrieved by the GetCursorBitmapBits function which, in turn, allocates memory and calls GetDIBItsCursor. The GetDIBItsCursor function initializes the BITMAPINFOHEADER structure and calls the Win32® function GetDIBits to retrieve the bits of the bitmap images and copy them into a buffer. The last internal function, GetBmpInfoSizeCursor, calculates the size of the BITMAPINFO structure.
Two other cursor verification functions, CurrentCursor and WaitForCursor, are convenient if you need to retrieve an ID/bitmap of the current global cursor and wait until one of the given cursors appears or until a particular given cursor disappears.
The CurrentCursor function simply calls IsCursor in a loop, comparing the actual cursor with all entries in the pSupportedCursors table. This table contains IDs for all cursors, including user-defined ones, that are supported by the application. When the requested cursor is found in the pSupportedCursors table, CurrentCursor returns 1.
The WaitForCursor function takes a variable number of parameters. The first parameter is an instance of the module (.exe or .dll) that contains the given cursors defined by parameters 6, 7, and so on. The second parameter tells WaitForCursor to wait until one of the given cursors shows up (bWaitForGivenCursor equals true), or until a global cursor doesn't match any of the given cursors (bWaitForGivenCursor is false). The third parameter sets the sleep time between verifications, the fourth sets the timeout, and the fifth parameter defines the number of the given cursors. Parameters 6, 7, and so on are IDs of these cursors.
The current implementation of WaitForCursor, however, has two limitations. The first is that the set of cursors that WaitForCursor checks against must all be loaded from the same module (.exe or .dll). You could make a simple change in either this function or the internal functions to resolve this problem. The second limitation is more fundamental—waiting is based on polling, rather than on the preferable method of catching some sort of event.
Is there a way to catch the moment when a global cursor changes? Yes, you can monitor WinEvents, as I will describe in the next section. This approach differs from the techniques I just discussed, but both of these methods can be used together to achieve the maximum flexibility.
Sample Code The sample code that demonstrates the functions described in the previous section has three steps (see Figure 2). In the first step, I set a cursor for a window that possesses it to a user-defined cursor and verify the ID, that is, an index in the pSupportedCursors table, of the current global cursor. A success message is displayed if the ID is found correctly by the CurrentCursor function. In the second step, the WaitForCursor function waits until one of two cursors, in this case IDC_SIZENS or IDC_SIZEWE, appears.
The third step monitors WinEvents for notification of changes to the global cursor. A detailed description of WinEvents and their use can be found in my article "Software Driving Software: Active Accessibility®-Compliant Apps Give Programmers New Tools to Manipulate Software" in the April 2000 issue of MSDN Magazine. The complete sample code can be found in VerifyCursor.cpp. Now I'll explain how the third step of this code works.
Each time a cursor becomes visible or invisible, the following events are fired: EVENT_ OBJECT_SHOW, EVENT_OBJECT_HIDE, and EVENT_ OBJECT_NAMECHANGE. In order to catch these events, a WinEvents hook is set using the SetWinEventHook API. The fourth parameter of this function requires the name of a callback function that will be called by the system each time a WinEvent is fired.
As you can see in the sample code, I set a hook with a callback function, WinEventProc. Then the message loop waits for a user message, WM_CURSOR_FOUND, that is posted by the callback function when the global cursor matches the requested one. WinMain displays the message box and returns when it receives the WM_CURSOR_FOUND message.
This function uses Microsoft® Active Accessibility (MSAA) technology in order to determine whether an event fired and caught by the callback function corresponds to the cursor change. I discussed this technology in detail in the article I mentioned earlier. Generally speaking, each UI element, including cursors, is accessible if it supports the IAccessible interface. If it does, then this interface's methods can be called to retrieve information about that UI element. In order to use these methods, a pointer to the IAccessible interface and an additional ID, called the child ID, should be available. I explained the terms "pointer to IAccessible interface" and "child ID" in the Active Accessibility article that I mentioned earlier.
In my sample code, WinEventProc calls the AccessibleObjectFromEvent function to get the IAccessible interface/child ID pair. Then, based on this pair, it calls the IAccessible interface's method get_accRole to verify that the event corresponds to the cursor change. Finally, the callback function retrieves the MSAA name of the cursor by using the get_accName function, and compares this name with the given one, that is, NWSE size. Once this cursor appears, WinEventProc posts the WM_CURSOR_FOUND message to inform WinMain that the requested cursor has been found. The list of MSAA names corresponding to different system cursors can be found at http://msdn.microsoft.com/library/en-us/msaa/hh/msaa/msaapndx_47ci.asp.
As I mentioned before, you can use WinEvents just for waiting and then call IsCursor so that user-defined cursors can also be identified. There is one caveat that applies to both polling and events: if the cursor changes too quickly, you may miss some changes, since by the time you get to ask for the information, the cursor will have changed to something else. Since this approach involves bitmap operations that are costly in terms of performance, you might want to cache the results.
Thanks to Alex Klementiev and Brendan McKeon for their invaluable help with this article.
| For related articles see:|
HOWTO: Obtain A Handle To the Current Cursor
Software Driving Software: Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software
| Dmitri Klementiev is a software engineer with Microsoft. He earned his Ph.D. in mathematics from Moscow Institute of Physics and Technology. Dmitri spent more than 12 years in mathematical and computer modeling. He can be reached at firstname.lastname@example.org.|
From the October 2001 issue of MSDN Magazine.