Browse Source

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.
jpark37 5 years ago
parent
commit
cb4954c279
3 changed files with 128 additions and 22 deletions
  1. 99 10
      libobs-winrt/winrt-capture.cpp
  2. 6 3
      libobs-winrt/winrt-capture.h
  3. 23 9
      plugins/win-capture/window-capture.c

+ 99 - 10
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<typename T>
@@ -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<IDXGIDevice> 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)
 {

+ 6 - 3
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);

+ 23 - 9
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;
 	}