From cb4954c27970357d2a871e856a49b37a1fe34b30 Mon Sep 17 00:00:00 2001 From: jpark37 Date: Sat, 7 Mar 2020 09:27:58 -0800 Subject: [PATCH] libobs-winrt: win-capture: HDC cursor capture for WGC Starting with Windows 10 2004, we can disable WGC cursor capture, and provide a user toggle. We swap out WGC support for our own though because ours does not break hardware cursor support. --- libobs-winrt/winrt-capture.cpp | 109 ++++++++++++++++++++++++--- libobs-winrt/winrt-capture.h | 9 ++- plugins/win-capture/window-capture.c | 32 +++++--- 3 files changed, 128 insertions(+), 22 deletions(-) diff --git a/libobs-winrt/winrt-capture.cpp b/libobs-winrt/winrt-capture.cpp index 7fb6a68ce..46c3df04e 100644 --- a/libobs-winrt/winrt-capture.cpp +++ b/libobs-winrt/winrt-capture.cpp @@ -12,12 +12,24 @@ struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")) void **object) = 0; }; -extern "C" EXPORT bool winrt_capture_supported() +extern "C" EXPORT BOOL winrt_capture_supported() { - /* no contract for IGraphicsCaptureItemInterop, verify 10.0.18362.0 */ + return winrt::Windows::Foundation::Metadata::ApiInformation::IsTypePresent( + L"Windows.Graphics.Capture.GraphicsCaptureSession") && + winrt::Windows::Graphics::Capture::GraphicsCaptureSession:: + IsSupported(); +} + +extern "C" EXPORT BOOL winrt_capture_cursor_toggle_supported() +{ +#ifdef NTDDI_WIN10_VB return winrt::Windows::Foundation::Metadata::ApiInformation:: - IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", - 8); + IsPropertyPresent( + L"Windows.Graphics.Capture.GraphicsCaptureSession", + L"IsCursorCaptureEnabled"); +#else + return false; +#endif } template @@ -78,10 +90,12 @@ static bool get_client_box(HWND window, uint32_t width, uint32_t height, } struct winrt_capture { - bool capture_cursor; HWND window; bool client_area; + bool capture_cursor; + bool cursor_visible; + gs_texture_t *texture; bool texture_written; winrt::Windows::Graphics::Capture::GraphicsCaptureItem item{nullptr}; @@ -104,6 +118,58 @@ struct winrt_capture { bool thread_changed; struct winrt_capture *next; + void draw_cursor() + { + CURSORINFO ci{}; + ci.cbSize = sizeof(CURSORINFO); + if (!GetCursorInfo(&ci)) + return; + + if (!ci.flags & CURSOR_SHOWING) + return; + + HICON icon = CopyIcon(ci.hCursor); + if (!icon) + return; + + ICONINFO ii; + if (GetIconInfo(icon, &ii)) { + POINT win_pos{}; + if (window) { + if (client_area) { + ClientToScreen(window, &win_pos); + } else { + RECT window_rect; + if (DwmGetWindowAttribute( + window, + DWMWA_EXTENDED_FRAME_BOUNDS, + &window_rect, + sizeof(window_rect)) == + S_OK) { + win_pos.x = window_rect.left; + win_pos.y = window_rect.top; + } + } + } + + POINT pos; + pos.x = ci.ptScreenPos.x - (int)ii.xHotspot - win_pos.x; + pos.y = ci.ptScreenPos.y - (int)ii.yHotspot - win_pos.y; + + HDC hdc = (HDC)gs_texture_get_dc(texture); + + DrawIconEx(hdc, pos.x, pos.y, icon, 0, 0, 0, NULL, + DI_NORMAL); + + gs_texture_release_dc(texture); + + DeleteObject(ii.hbmColor); + DeleteObject(ii.hbmMask); + } + + DestroyIcon(icon); + } + void on_frame_arrived(winrt::Windows::Graphics::Capture:: Direct3D11CaptureFramePool const &sender, winrt::Windows::Foundation::IInspectable const &) @@ -146,9 +212,8 @@ struct winrt_capture { } if (!texture) { - texture = gs_texture_create(texture_width, - texture_height, GS_BGRA, 1, - nullptr, 0); + texture = gs_texture_create_gdi(texture_width, + texture_height); } if (client_box_available) { @@ -163,6 +228,10 @@ struct winrt_capture { frame_surface.get()); } + if (capture_cursor && cursor_visible) { + draw_cursor(); + } + texture_written = true; if (frame_content_size.Width != last_size.Width || @@ -222,6 +291,12 @@ static void winrt_capture_device_loss_rebuild(void *device_void, void *data) const winrt::Windows::Graphics::Capture::GraphicsCaptureSession session = frame_pool.CreateCaptureSession(capture->item); + /* disable cursor capture if possible since ours performs better */ +#ifdef NTDDI_WIN10_VB + if (winrt_capture_cursor_toggle_supported()) + session.IsCursorCaptureEnabled(false); +#endif + capture->device = device; d3d_device->GetImmediateContext(&capture->context); capture->frame_pool = frame_pool; @@ -236,7 +311,7 @@ static void winrt_capture_device_loss_rebuild(void *device_void, void *data) thread_local bool initialized_tls; extern "C" EXPORT struct winrt_capture * -winrt_capture_init(bool cursor, HWND window, bool client_area) +winrt_capture_init(BOOL cursor, HWND window, BOOL client_area) { ID3D11Device *const d3d_device = (ID3D11Device *)gs_get_device_obj(); ComPtr dxgi_device; @@ -287,13 +362,21 @@ winrt_capture_init(bool cursor, HWND window, bool client_area) const winrt::Windows::Graphics::Capture::GraphicsCaptureSession session = frame_pool.CreateCaptureSession(item); + /* disable cursor capture if possible since ours performs better */ + const BOOL cursor_toggle_supported = + winrt_capture_cursor_toggle_supported(); +#ifdef NTDDI_WIN10_VB + if (cursor_toggle_supported) + session.IsCursorCaptureEnabled(false); +#endif + if (capture_list == nullptr) initialized_tls = true; struct winrt_capture *capture = new winrt_capture{}; - capture->capture_cursor = cursor; capture->window = window; capture->client_area = client_area; + capture->capture_cursor = cursor && cursor_toggle_supported; capture->item = item; capture->device = device; d3d_device->GetImmediateContext(&capture->context); @@ -366,6 +449,12 @@ static void draw_texture(struct winrt_capture *capture, gs_effect_t *effect) gs_technique_end(tech); } +extern "C" EXPORT void winrt_capture_show_cursor(struct winrt_capture *capture, + BOOL visible) +{ + capture->cursor_visible = visible; +} + extern "C" EXPORT void winrt_capture_render(struct winrt_capture *capture, gs_effect_t *effect) { diff --git a/libobs-winrt/winrt-capture.h b/libobs-winrt/winrt-capture.h index ae5cefb1f..33c237dee 100644 --- a/libobs-winrt/winrt-capture.h +++ b/libobs-winrt/winrt-capture.h @@ -9,11 +9,14 @@ extern "C" { #endif -EXPORT bool winrt_capture_supported(); -EXPORT struct winrt_capture *winrt_capture_init(bool cursor, HWND window, - bool client_area); +EXPORT BOOL winrt_capture_supported(); +EXPORT BOOL winrt_capture_cursor_toggle_supported(); +EXPORT struct winrt_capture *winrt_capture_init(BOOL cursor, HWND window, + BOOL client_area); EXPORT void winrt_capture_free(struct winrt_capture *capture); +EXPORT void winrt_capture_show_cursor(struct winrt_capture *capture, + BOOL visible); EXPORT void winrt_capture_render(struct winrt_capture *capture, gs_effect_t *effect); EXPORT uint32_t winrt_capture_width(const struct winrt_capture *capture); diff --git a/plugins/win-capture/window-capture.c b/plugins/win-capture/window-capture.c index 219ae505e..e23601efb 100644 --- a/plugins/win-capture/window-capture.c +++ b/plugins/win-capture/window-capture.c @@ -26,10 +26,13 @@ #define WC_CHECK_TIMER 1.0f struct winrt_exports { - bool *(*winrt_capture_supported)(); - struct winrt_capture *(*winrt_capture_init)(bool cursor, HWND window, - bool client_area); + BOOL *(*winrt_capture_supported)(); + BOOL *(*winrt_capture_cursor_toggle_supported)(); + struct winrt_capture *(*winrt_capture_init)(BOOL cursor, HWND window, + BOOL client_area); void (*winrt_capture_free)(struct winrt_capture *capture); + void (*winrt_capture_show_cursor)(struct winrt_capture *capture, + BOOL visible); void (*winrt_capture_render)(struct winrt_capture *capture, gs_effect_t *effect); uint32_t (*winrt_capture_width)(const struct winrt_capture *capture); @@ -171,8 +174,10 @@ static bool load_winrt_imports(struct winrt_exports *exports, void *module, bool success = true; WINRT_IMPORT(winrt_capture_supported); + WINRT_IMPORT(winrt_capture_cursor_toggle_supported); WINRT_IMPORT(winrt_capture_init); WINRT_IMPORT(winrt_capture_free); + WINRT_IMPORT(winrt_capture_show_cursor); WINRT_IMPORT(winrt_capture_render); WINRT_IMPORT(winrt_capture_width); WINRT_IMPORT(winrt_capture_height); @@ -260,13 +265,18 @@ static void wc_defaults(obs_data_t *defaults) } static void update_settings_visibility(obs_properties_t *props, - enum window_capture_method method) + struct window_capture *wc) { + const enum window_capture_method method = wc->method; const bool bitblt_options = method == METHOD_BITBLT; const bool wgc_options = method == METHOD_WGC; + const bool wgc_cursor_toggle = + wgc_options && + wc->exports.winrt_capture_cursor_toggle_supported(); + obs_property_t *p = obs_properties_get(props, "cursor"); - obs_property_set_visible(p, bitblt_options); + obs_property_set_visible(p, bitblt_options || wgc_cursor_toggle); p = obs_properties_get(props, "compatibility"); obs_property_set_visible(p, bitblt_options); @@ -281,7 +291,7 @@ static bool wc_capture_method_changed(obs_properties_t *props, struct window_capture *wc = obs_properties_get_param(props); update_settings(wc, settings); - update_settings_visibility(props, wc->method); + update_settings_visibility(props, wc); return true; } @@ -297,7 +307,7 @@ static bool wc_window_changed(obs_properties_t *props, obs_property_t *p, struct window_capture *wc = obs_properties_get_param(props); update_settings(wc, settings); - update_settings_visibility(props, wc->method); + update_settings_visibility(props, wc); check_window_property_setting(props, p, settings, "window", 0); return true; @@ -417,8 +427,12 @@ static void wc_tick(void *data, float seconds) if (!GetWindowThreadProcessId(wc->window, &target_pid)) target_pid = 0; - wc->capture.cursor_hidden = foreground_pid && target_pid && - foreground_pid != target_pid; + const bool cursor_hidden = foreground_pid && target_pid && + foreground_pid != target_pid; + wc->capture.cursor_hidden = cursor_hidden; + if (wc->capture_winrt) + wc->exports.winrt_capture_show_cursor(wc->capture_winrt, + !cursor_hidden); wc->cursor_check_time = 0.0f; }