Skip to content

Fix cursor not captured in headless sessions #690

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

ilCosmico
Copy link

Fix for cursor capture failure in headless environments or after session lock/unlock. This adds a minimal synthetic mouse move using mouse_event to force Windows to redraw the cursor, preventing GetCursorInfo from returning null or CURSOR_SUPPRESSED. Based on a solution tested in FlaUI usage with automated UI testing on Windows Server and Windows 11.

Fix for cursor capture failure in headless environments or after session lock/unlock.
This adds a minimal synthetic mouse move using mouse_event to force Windows to redraw the cursor, preventing GetCursorInfo from returning null or CURSOR_SUPPRESSED.
Based on a solution tested in FlaUI usage with automated UI testing on Windows Server and Windows 11.
@t4m45
Copy link

t4m45 commented Apr 15, 2025

Just a comment from another user..

You're moving the mouse without checking if it's actually needed.
At the very least, you should move the workaround inside this if-statement and re-check to see if it worked:

if (cursorInfo.flags != CursorState.CURSOR_SHOWING)
{
    return null;
}

That said, I don't think this workaround belongs inside the capture method itself. There might already be tests relying on it to return null when the cursor is hidden.

Also, keep in mind: this breaks the system idle time, and there won’t be a clear indication of what caused it.

You should probably make your own extension method for this behavior. Something like:

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

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

[DllImport("user32.dll")]
private static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);

private const int MOUSEEVENTF_MOVE = 0x0001;

public static void PokeAndCaptureCursor()
{
    var cursorInfo = new CURSORINFO();
    cursorInfo.cbSize = Marshal.SizeOf(cursorInfo);
    if (!User32.GetCursorInfo(out cursorInfo) || cursorInfo.flags != CursorState.CURSOR_SHOWING)
    {
        // Workaround for headless environments or inactive sessions:
        // Windows may suppress drawing the cursor (CURSOR_SUPPRESSED) unless there's recent mouse activity.
        // This emulates a minimal movement to force Windows to update the cursor state.
        mouse_event(MOUSEEVENTF_MOVE, 1, 1, 0, 0);
        mouse_event(MOUSEEVENTF_MOVE, -1, -1, 0, 0);

        // maybe do another check before continue
    }

    Point position = new();
    var bitmap = FlaUI.Core.Capturing.CaptureUtilities.CaptureCursor(ref position);
    // ...
}

@ilCosmico
Copy link
Author

@t4m45 Thanks for the helpful suggestion. I tried the approach and it works great!

I understand your point about not modifying CaptureCursor() directly, and I agree that keeping it clean is important.
That said, I wonder if a method like TryForceCaptureCursor() (or PokeAndCaptureCursor()) could still be integrated into FlaUI itself, as an optional utility, since others might run into the same issue in headless environments.

If you think it's reasonable, I’d be happy to revise the PR accordingly to include it as a separate helper or extension method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants