C# - Capturing the Mouse Cursor Image

C# - Capturing the Mouse cursor image

While I can't explain exactly why this happens, I think I can show how to get around it.

The ICONINFO struct contains two members, hbmMask and hbmColor, that contain the mask and color bitmaps, respectively, for the cursor (see the MSDN page for ICONINFO for the official documentation).

When you call GetIconInfo() for the default cursor, the ICONINFO struct contains both valid mask and color bitmaps, as shown below (Note: the red border has been added to clearly show the image boundaries):

Default Cursor Mask Bitmap default cursor mask bitmap image

Default Cursor Color Bitmap default cursor color bitmap image

When Windows draws the default cursor, the mask bitmap is first applied with an AND raster operation, then the color bitmap is applied with an XOR raster operation. This results in an opaque cursor and a transparent background.

When you call GetIconInfo() for the I-Beam cursor, though, the ICONINFO struct only contains a valid mask bitmap, and no color bitmap, as shown below (Note: again, the red border has been added to clearly show the image boundaries):

I-Beam Cursor Mask Bitmap ibeam cursor mask bitmap image

According to the ICONINFO documentation, the I-Beam cursor is then a monochrome cursor. The top half of the mask bitmap is the AND mask, and the bottom half of the mask bitmap is the XOR bitmap. When Windows draws the I-Beam cursor, the top half of this bitmap is first drawn over the desktop with an AND raster operation. The bottom half of the bitmap is then drawn over top with an XOR raster operation. Onscreen, The cursor will appear as the inverse of the content behind it.

One of the comments for the original article that you linked mentions this. On the desktop, since the raster operations are applied over the desktop content, the cursor will appear correct. However, when the image is drawn over no background, as in your posted code, the raster operations that Windows performs result in a faded image.

That being said, this updated CaptureCursor() method will handle both color and monochrome cursors, supplying a plain black cursor image when the cursor is monochrome.

static Bitmap CaptureCursor(ref int x, ref int y)
{
Win32Stuff.CURSORINFO cursorInfo = new Win32Stuff.CURSORINFO();
cursorInfo.cbSize = Marshal.SizeOf(cursorInfo);
if (!Win32Stuff.GetCursorInfo(out cursorInfo))
return null;

if (cursorInfo.flags != Win32Stuff.CURSOR_SHOWING)
return null;

IntPtr hicon = Win32Stuff.CopyIcon(cursorInfo.hCursor);
if (hicon == IntPtr.Zero)
return null;

Win32Stuff.ICONINFO iconInfo;
if (!Win32Stuff.GetIconInfo(hicon, out iconInfo))
return null;

x = cursorInfo.ptScreenPos.x - ((int)iconInfo.xHotspot);
y = cursorInfo.ptScreenPos.y - ((int)iconInfo.yHotspot);

using (Bitmap maskBitmap = Bitmap.FromHbitmap(iconInfo.hbmMask))
{
// Is this a monochrome cursor?
if (maskBitmap.Height == maskBitmap.Width * 2)
{
Bitmap resultBitmap = new Bitmap(maskBitmap.Width, maskBitmap.Width);

Graphics desktopGraphics = Graphics.FromHwnd(Win32Stuff.GetDesktopWindow());
IntPtr desktopHdc = desktopGraphics.GetHdc();

IntPtr maskHdc = Win32Stuff.CreateCompatibleDC(desktopHdc);
IntPtr oldPtr = Win32Stuff.SelectObject(maskHdc, maskBitmap.GetHbitmap());

using (Graphics resultGraphics = Graphics.FromImage(resultBitmap))
{
IntPtr resultHdc = resultGraphics.GetHdc();

// These two operation will result in a black cursor over a white background.
// Later in the code, a call to MakeTransparent() will get rid of the white background.
Win32Stuff.BitBlt(resultHdc, 0, 0, 32, 32, maskHdc, 0, 32, Win32Stuff.TernaryRasterOperations.SRCCOPY);
Win32Stuff.BitBlt(resultHdc, 0, 0, 32, 32, maskHdc, 0, 0, Win32Stuff.TernaryRasterOperations.SRCINVERT);

resultGraphics.ReleaseHdc(resultHdc);
}

IntPtr newPtr = Win32Stuff.SelectObject(maskHdc, oldPtr);
Win32Stuff.DeleteObject(newPtr);
Win32Stuff.DeleteDC(maskHdc);
desktopGraphics.ReleaseHdc(desktopHdc);

// Remove the white background from the BitBlt calls,
// resulting in a black cursor over a transparent background.
resultBitmap.MakeTransparent(Color.White);
return resultBitmap;
}
}

Icon icon = Icon.FromHandle(hicon);
return icon.ToBitmap();
}

There are some issues with the code that may or may not be a problem.

  1. The check for a monochrome cursor simply tests whether the height is twice the width. While this seems logical, the ICONINFO documentation does not mandate that only a monochrome cursor is defined by this.
  2. There is probably a better way to render the cursor that the BitBlt() - BitBlt() - MakeTransparent() combination of method calls I used.

Capturing Mouse Cursor and Draw on Image Buffer c#

it is possible implement your task, but it needs copy some code from C++ on C#. In my project Desktop Screen Capture on Windows via Windows Desktop Duplication API with Drawing of Cursor's Image you can find the suitable solution. It is based on using a special
Direct3D11 texture - it is created with MiscFlag - D3D11_RESOURCE_MISC_GDI_COMPATIBLE. It allows GDI rendering on the surface via IDXGISurface1::GetDC and using of Windows function DrawIconEx for drawing cursor's image. The drawing of cursor is executed by Windows function and is done in context of video memory.

Regards.

Get mouse cursor image position

You can use the GetIconInfo() Win32 function (via pinvoke) - this fills out an ICONINFO structure that has fields which provide the x and y hotspot for the cursor.

Note that when this function returns successfully it has also created two GDI bitmap objects, which you are responsible for freeing via DeleteObject().

Include mouse cursor in screen capture using SharpDX

You could utilize the WinAPI's GetCursorInfo() function to get a handle to the current cursor. By then calling Icon.FromHandle() and passing the cursor handle as a parameter you'll get the cursor as a drawable icon.

As different cursors may have different hotspots you'll have to take that into account too. For this you can use the WinAPI's GetIconInfo() function to get the X and Y-coordinates for the hotspot, then you'll just have to substract the cursor's position with the hotspot's position.

The method for drawing the cursor:

Public Shared Sub DrawCursor(ByVal Graphics As Graphics)
Dim CursorInfo As New NativeMethods.CURSORINFO With {.cbSize = Marshal.SizeOf(GetType(NativeMethods.CURSORINFO))}

'Get the cursor info.
If NativeMethods.GetCursorInfo(CursorInfo) = True Then
Using CursorIcon As System.Drawing.Icon = System.Drawing.Icon.FromHandle(CursorInfo.hCursor) 'Get the cursor icon.
Dim IconInfo As New NativeMethods.ICONINFO

Try
Dim ModifierPoint As New Point(0, 0) 'Declare the modifier point (the cursor's hotspot).

'Get the info for the cursor icon.
If NativeMethods.GetIconInfo(CursorInfo.hCursor, IconInfo) = True Then
If IconInfo.fIcon = False Then 'If the cursor is an icon the hotspot will always be (0, 0).
ModifierPoint = New Point(IconInfo.xHotspot, IconInfo.yHotspot) 'Set the hotspot modifier.
End If

End If

'Normalize the coordinates according to the hotspot.
Dim FinalPoint As New Point(CursorInfo.ptScreenPos.x - ModifierPoint.X, CursorInfo.ptScreenPos.y - ModifierPoint.Y)

'Draw the cursor.
Graphics.DrawIcon(CursorIcon, New Rectangle(FinalPoint, CursorIcon.Size))

Finally
'Some cleaning up...
If IconInfo.hbmMask <> IntPtr.Zero Then
NativeMethods.DeleteObject(IconInfo.hbmMask)
IconInfo.hbmMask = IntPtr.Zero
End If

If IconInfo.hbmColor <> IntPtr.Zero Then
NativeMethods.DeleteObject(IconInfo.hbmColor)
IconInfo.hbmColor = IntPtr.Zero
End If

End Try
End Using
End If
End Sub

NativeMethods.vb:

Imports System.Runtime.InteropServices

Public NotInheritable Class NativeMethods
<StructLayout(LayoutKind.Sequential)> _
Public Structure POINT
Public x As Integer
Public y As Integer
End Structure

<StructLayout(LayoutKind.Sequential)> _
Public Structure CURSORINFO
Public cbSize As Integer
Public flags As Integer
Public hCursor As IntPtr
Public ptScreenPos As POINT
End Structure

<DllImport("user32.dll")> _
Public Shared Function GetCursorInfo(ByRef pci As CURSORINFO) As Boolean
End Function

<StructLayout(LayoutKind.Sequential)> _
Public Structure ICONINFO
Public fIcon As Boolean
Public xHotspot As Integer
Public yHotspot As Integer
Public hbmMask As IntPtr
Public hbmColor As IntPtr
End Structure

<DllImport("user32.dll")> _
Public Shared Function GetIconInfo(ByVal hIcon As IntPtr, ByRef piconinfo As ICONINFO) As Boolean
End Function

<DllImport("gdi32.dll")> _
Public Shared Function DeleteObject(ByVal hObject As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
End Class

If you take the resulting screenshot you'd draw the cursor onto it like this:

Using g As Graphics = Graphics.FromImage(yourImage)
DrawCursor(g)
End Using

How to capture the screen and mouse pointer using Windows APIs?

[StructLayout(LayoutKind.Sequential)]
struct CURSORINFO
{
public Int32 cbSize;
public Int32 flags;
public IntPtr hCursor;
public POINTAPI ptScreenPos;
}

[StructLayout(LayoutKind.Sequential)]
struct POINTAPI
{
public int x;
public int y;
}

[DllImport("user32.dll")]
static extern bool GetCursorInfo(out CURSORINFO pci);

[DllImport("user32.dll")]
static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon);

const Int32 CURSOR_SHOWING = 0x00000001;

public static Bitmap CaptureScreen(bool CaptureMouse)
{
Bitmap result = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format24bppRgb);

try
{
using (Graphics g = Graphics.FromImage(result))
{
g.CopyFromScreen(0, 0, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);

if (CaptureMouse)
{
CURSORINFO pci;
pci.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(CURSORINFO));

if (GetCursorInfo(out pci))
{
if (pci.flags == CURSOR_SHOWING)
{
DrawIcon(g.GetHdc(), pci.ptScreenPos.x, pci.ptScreenPos.y, pci.hCursor);
g.ReleaseHdc();
}
}
}
}
}
catch
{
result = null;
}

return result;
}

.NET - Capturing cursor bitmap at specific coordinates

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.

C# get current cursor icon

I've solved my issues, after some research I got to something interesting that would probably work all the time.

Instead of trying to save a cursor's icon and loading it I thought of a different idea.

I've noticed that whenever I change a cursor icon (lets say using a code, to some random icon) then whenever I go to the windows cursor settings the icon is not changed there, but there's no apply button to apply the settings I had before, because windows thinks I'm using those settings.

So I changed the settings to some random other settings without applying and returned to the settings I had before and applied, doing so resetted my cursor to its original cursor, before any of the changes.

Therefore what windows was just doing is "refreshing" the mouse, so I went that way, maybe if I can force a refresh everything would be perfect, and so I found a way to do it, using this function :

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern Int32 SystemParametersInfo(UInt32 uiAction,UInt32 uiParam, String pvParam, UInt32 fWinIni);

As I've watched msdn I've found something interesting in its parameters, the parameter "SPI_SETCURSORS (0x57)", and I quote :

"Reloads the system cursors. Set the uiParam parameter to zero and the pvParam parameter to NULL."

So I've tried it, and it worked, example of the process :

[DllImport("User32.dll", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
private static extern IntPtr LoadCursorFromFile(String str);

uint SPI_SETCURSORS = 0x57;
var NewArrow = LoadCursorFromFile("C:\\Users\\mtnju\\Downloads\\invisible.cur"); // loads some new cursor icon using the LoadCursorFromFile function
SetSystemCursor(NewArrow, OCR_NORMAL); // sets the new cursor icon using the SetSystemCursor function
SystemParametersInfo(SPI_SETCURSORS, 0, null, 0);// reloads all of the system cursors

I thought it'd take me 5 minutes to do something like this... I hope it'll help you guys, and I really appreciate the comments trying to help me.



Related Topics



Leave a reply



Submit