PR #23206 opened by mxschmitt URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23206 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23206.patch
On headless Windows machines (cloud VMs, remote desktop sessions, virtual display adapters), GetCursorInfo() returns flags=2 (CURSOR_SUPPRESSED) with hCursor=NULL, causing paint_mouse_pointer() to skip cursor drawing entirely. CURSOR_SUPPRESSED (0x00000002) was introduced in Windows 8 and is documented as "the system is not drawing the cursor because the user is providing input through touch or pen instead of the mouse" [1]. In practice, Windows also reports this state on any headless or virtual display where no physical monitor is consuming the display signal. This affects all cloud VM environments (AWS EC2, Azure VMs, QEMU/KVM guests) and disconnected Remote Desktop sessions. The cursor position in ptScreenPos remains valid when suppressed. Additionally, GetCursor() returns a valid cursor handle for the current thread even in the suppressed state. The paint_mouse_pointer() function and its CURSOR_SHOWING check have been unchanged since the initial gdigrab commit in 2014 (08909fb5). At that time, headless cloud VMs were not a common recording target. The existing Wine fallback (LoadCursor IDC_ARROW) already acknowledges that CopyCursor(hCursor) can fail in some environments. Fix by: 1. Only skipping cursor draw when flags=0 (truly hidden, i.e. the cursor display counter is <= 0), not when flags=2 (suppressed). 2. Using GetCursor() as fallback when CopyCursor(hCursor) returns NULL, before falling back to IDC_ARROW. On a normal desktop with a physical display, GetCursorInfo always returns flags=1 (CURSOR_SHOWING) with a valid hCursor, so the new code path is never reached. The fix only activates in suppressed or headless environments. Tested on Windows Server 2025 headless EC2 instances (AWS) at 30 and 60 fps with correct cursor shape rendering across different UI elements (arrow, I-beam, hand pointer, resize cursors). [1] https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-cursorinfo Signed-off-by: Max Schmitt <[email protected]> >From db07a3f64d648d175bb482875ce8b0e1ff2e6750 Mon Sep 17 00:00:00 2001 From: Max Schmitt <[email protected]> Date: Fri, 22 May 2026 15:03:34 -0700 Subject: [PATCH] avdevice/gdigrab: fix cursor capture when CURSOR_SUPPRESSED On headless Windows machines (cloud VMs, remote desktop sessions, virtual display adapters), GetCursorInfo() returns flags=2 (CURSOR_SUPPRESSED) with hCursor=NULL, causing paint_mouse_pointer() to skip cursor drawing entirely. CURSOR_SUPPRESSED (0x00000002) was introduced in Windows 8 and is documented as "the system is not drawing the cursor because the user is providing input through touch or pen instead of the mouse" [1]. In practice, Windows also reports this state on any headless or virtual display where no physical monitor is consuming the display signal. This affects all cloud VM environments (AWS EC2, Azure VMs, QEMU/KVM guests) and disconnected Remote Desktop sessions. The cursor position in ptScreenPos remains valid when suppressed. Additionally, GetCursor() returns a valid cursor handle for the current thread even in the suppressed state. The paint_mouse_pointer() function and its CURSOR_SHOWING check have been unchanged since the initial gdigrab commit in 2014 (08909fb5). At that time, headless cloud VMs were not a common recording target. The existing Wine fallback (LoadCursor IDC_ARROW) already acknowledges that CopyCursor(hCursor) can fail in some environments. Fix by: 1. Only skipping cursor draw when flags=0 (truly hidden, i.e. the cursor display counter is <= 0), not when flags=2 (suppressed). 2. Using GetCursor() as fallback when CopyCursor(hCursor) returns NULL, before falling back to IDC_ARROW. On a normal desktop with a physical display, GetCursorInfo always returns flags=1 (CURSOR_SHOWING) with a valid hCursor, so the new code path is never reached. The fix only activates in suppressed or headless environments. Tested on Windows Server 2025 headless EC2 instances (AWS) at 30 and 60 fps with correct cursor shape rendering across different UI elements (arrow, I-beam, hand pointer, resize cursors). [1] https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-cursorinfo Signed-off-by: Max Schmitt <[email protected]> --- libavdevice/gdigrab.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/libavdevice/gdigrab.c b/libavdevice/gdigrab.c index fa9ba27db0..c542501186 100644 --- a/libavdevice/gdigrab.c +++ b/libavdevice/gdigrab.c @@ -492,14 +492,15 @@ static void paint_mouse_pointer(AVFormatContext *s1, struct gdigrab *gdigrab) info.hbmMask = NULL; info.hbmColor = NULL; - if (ci.flags != CURSOR_SHOWING) + if (!ci.flags) return; if (!icon) { - /* Use the standard arrow cursor as a fallback. - * You'll probably only hit this in Wine, which can't fetch - * the current system cursor. */ - icon = CopyCursor(LoadCursor(NULL, IDC_ARROW)); + /* CURSOR_SUPPRESSED: hCursor is NULL. Use GetCursor() to + * obtain the current cursor for this thread. */ + icon = CopyCursor(GetCursor()); + if (!icon) + icon = CopyCursor(LoadCursor(NULL, IDC_ARROW)); } if (!GetIconInfo(icon, &info)) { -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
