OnoSendai
OnoSendai

Reputation: 3970

.NET - Capturing cursor bitmap at specific coordinates

DISCLAIMER

This question is somewhat similar to another on StackOverflow, C# - Capturing the Mouse cursor image - but with a slightly different requirement.

BACKGROUND

REQUIREMENTS

QUESTION

Upvotes: 1

Views: 1210

Answers (1)

OnoSendai
OnoSendai

Reputation: 3970

A solution fulfilling the specifics of this question was implemented using the information provided by Hans Passant, so all credit must go to him.

The current setup is as shown:

Environment definition

It runs on a machine with two displays. Not shown in the picture is a small application that is actually responsible for the event monitoring and data scraping - it runs minimized and unattended.

Solution

  • Obtain the Window handle for the application to be tested (in this case, I cycled through all processes returned by Process.GetProcesses():

        IntPtr _probeHwnd;
        var _procs = Process.GetProcesses();
    
        foreach (var item in _procs)
        {
            if (item.MainWindowTitle == "WinApp#1")
            {
                _probeHwnd= item.MainWindowHandle;
                break;
            }
        }
    
  • With the window handle for the target application, we are now able to craft specific messages and send to it via SendMessage.

  • In order to pass coordinates to SendMessage we need to serialize both X and Y coordinates into a single long value:

    public int MakeLong(short lowPart, short highPart)
    {
        return (int)(((ushort)lowPart) | (uint)(highPart << 16)); 
    }
    
  • Knowing the specific coordinates we want to probe (_probeX,_probeY), now we can issue a WM_NCHITTEST message:

    SendMessage(_probeHwnd, WM_NCHITTEST, NULL, (LPARAM)MakeLong(_probeX, _probeY));
    
  • We need GetCursorInfo to obtain the Bitmap:

    Win32Stuff.CURSORINFO ci = new Win32Stuff.CURSORINFO();
    Win32Stuff.GetCursorInfo(ci);
    
  • Check if the return flag from GetCursorInfo indicates that the cursor is showing (pco.flags == CURSOR_SHOWING):

  • Use CopyIcon in order to obtain a valid handle for the cursor bitmap:

    IntPtr hicon = default(IntPtr);
    hicon = Win32Stuff.CopyIcon(ci.hCursor);
    
  • Use GetIconInfo to extract the information from the handler:

    Win32Stuff.ICONINFO icInfo = default(Win32Stuff.ICONINFO);
    Win32Stuff.GetIconInfo(hicon, icInfo);
    
  • Use the System.Drawing.Icon class to obtain a manageable copy using Icon.FromHandle, passing the value returned by CopyIcon;

    Icon ic = Icon.FromHandle(hicon);
    
  • Extract the bitmap via Icon.ToBitmap method.

    Bitmap bmp = ic.ToBitmap();
    

Limitations

  • This solution was tested on two different OSes: Windows XP and Windows 8. It only worked on Windows XP. On Windows 8 the cursor would flicker and return to the 'correct' format immediately, and the the captured CURSORINFO reflected that.
  • The test point areas must be visible (i.e., application must not be minimized, and test points can't be under an overlapping window. Tested window may be partially overlapped, though - and it doesn't need to have focus.)
  • When WM_NCHITTEST is issued, the current physical cursor over WebApp changes to whatever cursor bitmap is set by the probed application. CURSORINFO contains the cursor bitmap set by the probed application, but the coordinates always indicate the 'physical' location.

Upvotes: 1

Related Questions