1

DISCLAIMER

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

BACKGROUND

  • I am writing a scriptable automation client that scraps data from 3 legacy Win32 systems.
  • Two of these systems may indicate the presence of finished tasks via a change in cursor bitmap when the cursor is hovered over some specific areas. No other hints (color change, status message) are offered.
  • My own code is derived from the original post mentioned on the disclaimer.

REQUIREMENTS

  • While I an able to capture the cursor bitmaps by programatically moving the cursor to a specific coordinate and capturing it via CURSORINFO, the idea was to allow an interactive user to continue using the computer. As it is, the forced positioning disrupts the process.

QUESTION

  • Is there a way to capture the cursor bitmap by parametrized position (e.g., request the CURSORINFO as if the focus was in window W at coordinates X, Y)?
Community
  • 1
  • 1
OnoSendai
  • 3,960
  • 2
  • 22
  • 46
  • You can use this : http://stackoverflow.com/questions/6750056/how-to-capture-the-screen-and-mouse-pointer-using-windows-apis – Zz Oussama Aug 11 '13 at 00:55
  • Thank you for the (extremely) quick reply, @Zz. Nonetheless, I'm afraid the issue stands - the GetCursorInfo function only reports the current cursor position and its associated info, bitmap included. My specific requirement involves obtaining the cursor info at a given coordinate. If i misunderstood (or misread) the linked entry, I would appreciate if you could point it out to me. – OnoSendai Aug 11 '13 at 01:01
  • I am sure you will find your answer here :) http://stackoverflow.com/questions/1316681/getting-mouse-position-in-c-sharp – Zz Oussama Aug 11 '13 at 01:12
  • 1
    You can't do this without causing visual artifacts. You can fake the WM_NCHITTEST and WM_SETCURSOR messages with some hope that the program will use the mouse position passed in WM_NCHITTEST. The cursor will however change and you have to restore it. That is very visible and very ugly. This is a feature you don't want to have to implement. – Hans Passant Aug 11 '13 at 10:27
  • I'll check on WM_NCHITTEST, @Hans, thanks for pointing it out. About the feature implementation, for a process POV I can't agree more - I'd rather import data directly to a buffer on the new unified system and create an interface where the user could consolidate everything. But a custom storage for the legacy apps and sheer curiosity made me wonder: 'Is that arbitrary bitmap capture even possible?' =) – OnoSendai Aug 11 '13 at 16:18
  • Addendum: @Hans, it worked the way you mentioned - artifacts included. Since it fulfills the question, would you mind to post a reply so I can accept it? Thanks. – OnoSendai Aug 12 '13 at 23:33
  • 1
    I'll skip, I like happy answers. You could just post your own answer and accept it. – Hans Passant Aug 12 '13 at 23:52

1 Answers1

1

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.
Community
  • 1
  • 1
OnoSendai
  • 3,960
  • 2
  • 22
  • 46