/* GStreamer * Copyright (C) 2024 Seungha Yang * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WINAPI_PARTITION_APP #undef WINAPI_PARTITION_APP #endif #define WINAPI_PARTITION_APP 1 #include "gstwebview2object.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include GST_DEBUG_CATEGORY_EXTERN (gst_webview2_src_debug); #define GST_CAT_DEFAULT gst_webview2_src_debug /* *INDENT-OFF* */ using namespace Microsoft::WRL; using namespace ABI::Windows::System; using namespace ABI::Windows::UI::Composition; using namespace ABI::Windows::Foundation; using namespace ABI::Windows::Graphics; using namespace ABI::Windows::Graphics::Capture; using namespace ABI::Windows::Graphics::DirectX; using namespace ABI::Windows::Graphics::DirectX::Direct3D11; using namespace Windows::Graphics::DirectX::Direct3D11; typedef ABI::Windows::Foundation::__FITypedEventHandler_2_Windows__CGraphics__CCapture__CDirect3D11CaptureFramePool_IInspectable_t IFrameArrivedHandler; #define DEFAULT_WIDTH 1920 #define DEFAULT_HEIGHT 1080 static void gst_webview2_object_initialized (GstWebView2Object * obj); static void gst_webview2_object_frame_arrived (GstWebView2Object * obj, ID3D11Texture2D * texture); enum { PROP_0, PROP_DEVICE, }; template < typename InterfaceType, PCNZWCH runtime_class_id > static HRESULT GstGetActivationFactory (InterfaceType ** factory) { HSTRING class_id_hstring; HRESULT hr = WindowsCreateString (runtime_class_id, wcslen (runtime_class_id), &class_id_hstring); if (FAILED (hr)) return hr; hr = RoGetActivationFactory (class_id_hstring, IID_PPV_ARGS (factory)); if (FAILED (hr)) { WindowsDeleteString (class_id_hstring); return hr; } return WindowsDeleteString (class_id_hstring); } #define CLOSE_COM(obj) G_STMT_START { \ if (obj) { \ ComPtr closable; \ obj.As (&closable); \ if (closable) \ closable->Close (); \ obj = nullptr; \ } \ } G_STMT_END #define CHECK_HR_AND_RETURN(hr,func) G_STMT_START { \ if (FAILED (hr)) { \ GST_ERROR_OBJECT (obj_, G_STRINGIFY (func) " failed, hr 0x%x", (guint) hr); \ SetEvent (event_handle_); \ return hr; \ } \ } G_STMT_END class GstWebView2Item : public RuntimeClass, FtmBase, IFrameArrivedHandler, ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler, ICoreWebView2CreateCoreWebView2CompositionControllerCompletedHandler, ICoreWebView2NavigationCompletedEventHandler, ICoreWebView2ExecuteScriptCompletedHandler> { public: static LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { return DefWindowProcA (hwnd, msg, wparam, lparam); } STDMETHODIMP RuntimeClassInitialize (GstWebView2Object * obj, GstD3D11Device * device, HANDLE event_handle, HWND hwnd) { obj_ = obj; device_ = device; event_handle_ = event_handle; hwnd_ = hwnd; HRESULT hr; ComPtr insp; HSTRING class_id_hstring; WindowsCreateString (RuntimeClass_Windows_UI_Composition_Compositor, wcslen (RuntimeClass_Windows_UI_Composition_Compositor), &class_id_hstring); hr = RoActivateInstance (class_id_hstring, &insp); WindowsDeleteString (class_id_hstring); CHECK_HR_AND_RETURN (hr, RoActivateInstance); hr = insp.As (&comp_); CHECK_HR_AND_RETURN (hr, QueryInterface); hr = comp_->CreateContainerVisual (&root_container_visual_); CHECK_HR_AND_RETURN (hr, CreateContainerVisual); hr = root_container_visual_.As (&root_visual_); CHECK_HR_AND_RETURN (hr, QueryInterface); hr = root_visual_->put_Size ({ DEFAULT_WIDTH, DEFAULT_HEIGHT }); CHECK_HR_AND_RETURN (hr, put_Size); hr = root_visual_->put_IsVisible (TRUE); CHECK_HR_AND_RETURN (hr, put_IsVisible); ComPtr collection; hr = root_container_visual_->get_Children (&collection); CHECK_HR_AND_RETURN (hr, get_Children); hr = comp_->CreateContainerVisual (&webview_container_visual_); CHECK_HR_AND_RETURN (hr, CreateContainerVisual); hr = webview_container_visual_.As (&webview_visual_); CHECK_HR_AND_RETURN (hr, QueryInterface); hr = webview_visual_.As (&webview_visual2_); CHECK_HR_AND_RETURN (hr, QueryInterface); hr = collection->InsertAtTop (webview_visual_.Get ()); CHECK_HR_AND_RETURN (hr, InsertAtTop); hr = webview_visual2_->put_RelativeSizeAdjustment ({ 1, 1 }); CHECK_HR_AND_RETURN (hr, put_RelativeSizeAdjustment); hr = webview_visual_->put_IsVisible (TRUE); CHECK_HR_AND_RETURN (hr, put_IsVisible); return CreateCoreWebView2Environment (this); } IFACEMETHOD (Invoke) (HRESULT hr, ICoreWebView2Environment * env) { CHECK_HR_AND_RETURN (hr, OnEnvironmentCompleted); hr = env->QueryInterface (IID_PPV_ARGS (&env_)); CHECK_HR_AND_RETURN (hr, QueryInterface); hr = env_->CreateCoreWebView2CompositionController (hwnd_, this); CHECK_HR_AND_RETURN (hr, CreateCoreWebView2CompositionController); return S_OK; } IFACEMETHOD (Invoke) (HRESULT hr, ICoreWebView2CompositionController * comp_ctrl) { CHECK_HR_AND_RETURN (hr, OnControllerCompleted); comp_ctrl_ = comp_ctrl; ComPtr ctrl; hr = comp_ctrl_.As (&ctrl); CHECK_HR_AND_RETURN (hr, QueryInterface); hr = ctrl.As (&ctrl_); CHECK_HR_AND_RETURN (hr, QueryInterface); hr = ctrl_->put_BoundsMode (COREWEBVIEW2_BOUNDS_MODE_USE_RAW_PIXELS); CHECK_HR_AND_RETURN (hr, put_BoundsMode); RECT rect = {}; rect.left = 0; rect.top = 0; rect.right = DEFAULT_WIDTH; rect.bottom = DEFAULT_HEIGHT; hr = ctrl_->put_Bounds (rect); CHECK_HR_AND_RETURN (hr, put_Bounds); hr = ctrl_->put_ShouldDetectMonitorScaleChanges (FALSE); CHECK_HR_AND_RETURN (hr, put_ShouldDetectMonitorScaleChanges); hr = ctrl_->put_RasterizationScale (1); CHECK_HR_AND_RETURN (hr, put_RasterizationScale); hr = ctrl_->put_IsVisible (TRUE); CHECK_HR_AND_RETURN (hr, put_IsVisible); hr = comp_ctrl_->put_RootVisualTarget (webview_container_visual_.Get ()); CHECK_HR_AND_RETURN (hr, put_RootVisualTarget); hr = ctrl_->get_CoreWebView2 (&webview_); CHECK_HR_AND_RETURN (hr, get_CoreWebView2); hr = webview_->add_NavigationCompleted (this, &navigation_compl_token_); CHECK_HR_AND_RETURN (hr, get_CoreWebView2); ComPtr webview8; hr = webview_.As (&webview8); if (SUCCEEDED (hr)) webview8->put_IsMuted (TRUE); hr = startCapture (); CHECK_HR_AND_RETURN (hr, startCapture); gst_webview2_object_initialized (obj_); return S_OK; } IFACEMETHOD(Invoke) (HRESULT hr, LPCWSTR result_json) { GST_DEBUG_OBJECT (obj_, "Executing script result 0x%x", (guint) hr); return S_OK; } IFACEMETHOD(Invoke) (ICoreWebView2 * sender, ICoreWebView2NavigationCompletedEventArgs * arg) { GST_DEBUG_OBJECT (obj_, "Navigation completed"); if (!script_.empty ()) { GST_DEBUG_OBJECT (obj_, "Executing script"); sender->ExecuteScript (script_.c_str (), this); } return S_OK; } HRESULT startCapture () { ComPtr item_statics; auto hr = GstGetActivationFactory (&item_statics); CHECK_HR_AND_RETURN (hr, GstGetActivationFactory); hr = item_statics->CreateFromVisual (root_visual_.Get (), &item_); CHECK_HR_AND_RETURN (hr, CreateFromVisual); auto device = gst_d3d11_device_get_device_handle (device_); ComPtr multi_thread; hr = device->QueryInterface (IID_PPV_ARGS (&multi_thread)); CHECK_HR_AND_RETURN (hr, QueryInterface); multi_thread->SetMultithreadProtected (TRUE); ComPtr dxgi_device; hr = device->QueryInterface (IID_PPV_ARGS (&dxgi_device)); CHECK_HR_AND_RETURN (hr, QueryInterface); ComPtr < IInspectable > insp; hr = CreateDirect3D11DeviceFromDXGIDevice (dxgi_device.Get (), &insp); CHECK_HR_AND_RETURN (hr, CreateDirect3D11DeviceFromDXGIDevice); hr = insp.As (&d3d_device_); CHECK_HR_AND_RETURN (hr, QueryInterface); ComPtr < IDirect3D11CaptureFramePoolStatics > pool_statics; hr = GstGetActivationFactory < IDirect3D11CaptureFramePoolStatics, RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool > (&pool_statics); CHECK_HR_AND_RETURN (hr, GstGetActivationFactory); frame_size_.Width = DEFAULT_WIDTH; frame_size_.Height = DEFAULT_HEIGHT; hr = pool_statics->Create (d3d_device_.Get (), DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized, 2, frame_size_, &frame_pool_); CHECK_HR_AND_RETURN (hr, Create); hr = frame_pool_->add_FrameArrived (this, &arrived_token_); CHECK_HR_AND_RETURN (hr, add_FrameArrived); hr = frame_pool_->CreateCaptureSession (item_.Get (), &session_); CHECK_HR_AND_RETURN (hr, CreateCaptureSession); hr = session_->StartCapture (); CHECK_HR_AND_RETURN (hr, StartCapture); return S_OK; } IFACEMETHOD(Invoke) (IDirect3D11CaptureFramePool * pool, IInspectable * arg) { HRESULT hr; GST_LOG_OBJECT (obj_, "Frame arrived"); ComPtr < IDirect3D11CaptureFrame > new_frame; pool->TryGetNextFrame (&new_frame); if (!new_frame) { GST_WARNING_OBJECT (obj_, "No frame"); return S_OK; } ComPtr < IDirect3DSurface > surface; hr = new_frame->get_Surface (&surface); CHECK_HR_AND_RETURN (hr, get_Surface); ComPtr < IDirect3DDxgiInterfaceAccess > access; hr = surface.As (&access); CHECK_HR_AND_RETURN (hr, QueryInterface); ComPtr < ID3D11Texture2D > texture; hr = access->GetInterface (IID_PPV_ARGS (&texture)); CHECK_HR_AND_RETURN (hr, QueryInterface); gst_webview2_object_frame_arrived (obj_, texture.Get ()); return S_OK; } void Close () { if (frame_pool_) frame_pool_->remove_FrameArrived (arrived_token_); CLOSE_COM (session_); CLOSE_COM (frame_pool_); CLOSE_COM (item_); if (webview_) { webview_->Stop (); webview_ = nullptr; } if (ctrl_) { ctrl_->Close (); ctrl_ = nullptr; } comp_ctrl_ = nullptr; ctrl_ = nullptr; env_ = nullptr; webview_visual2_ = nullptr; webview_visual_ = nullptr; webview_container_visual_ = nullptr; root_visual_ = nullptr; root_container_visual_ = nullptr; comp_ = nullptr; } HRESULT Navigate (LPCWSTR location, LPCWSTR script) { if (!webview_ || !location) return E_FAIL; if (script) script_ = script; else script_.clear (); return webview_->Navigate (location); } HRESULT UpdateSize (FLOAT width, FLOAT height) { GST_DEBUG_OBJECT (obj_, "Updating size to %dx%d", (UINT) width, (UINT) height); auto hr = root_visual_->put_Size ({width, height}); CHECK_HR_AND_RETURN (hr, put_Size); RECT rect = {}; rect.left = 0; rect.top = 0; rect.right = width; rect.bottom = height; hr = ctrl_->put_Bounds (rect); CHECK_HR_AND_RETURN (hr, put_Bounds); frame_size_.Width = width; frame_size_.Height = height; hr = frame_pool_->Recreate (d3d_device_.Get (), DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized, 2, frame_size_); CHECK_HR_AND_RETURN (hr, Recreate); return S_OK; } void HandleEvent (GstEvent * event) { auto type = gst_navigation_event_get_type (event); gdouble x, y; gint button; switch (type) { /* FIXME: Implement key event */ case GST_NAVIGATION_EVENT_MOUSE_BUTTON_PRESS: case GST_NAVIGATION_EVENT_MOUSE_BUTTON_RELEASE: case GST_NAVIGATION_EVENT_MOUSE_DOUBLE_CLICK: if (gst_navigation_event_parse_mouse_button_event (event, &button, &x, &y)) { GST_TRACE_OBJECT (obj_, "Mouse press, button %d, %lfx%lf", button, x, y); COREWEBVIEW2_MOUSE_EVENT_KIND kind; COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS vkeys = COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_NONE; GstNavigationModifierType state = GST_NAVIGATION_MODIFIER_NONE; POINT point; point.x = (LONG) x; point.y = (LONG) y; switch (button) { case 1: if (type == GST_NAVIGATION_EVENT_MOUSE_BUTTON_PRESS) kind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_DOWN; else if (type == GST_NAVIGATION_EVENT_MOUSE_BUTTON_RELEASE) kind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_UP; else kind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_DOUBLE_CLICK; break; case 2: if (type == GST_NAVIGATION_EVENT_MOUSE_BUTTON_PRESS) kind = COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_DOWN; else if (type == GST_NAVIGATION_EVENT_MOUSE_BUTTON_RELEASE) kind = COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_UP; else kind = COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_DOUBLE_CLICK; break; case 3: if (type == GST_NAVIGATION_EVENT_MOUSE_BUTTON_PRESS) kind = COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_DOWN; else if (type == GST_NAVIGATION_EVENT_MOUSE_BUTTON_RELEASE) kind = COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_UP; else kind = COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_DOUBLE_CLICK; break; default: return; } if (gst_navigation_event_parse_modifier_state (event, &state)) { if ((state & GST_NAVIGATION_MODIFIER_SHIFT_MASK) != 0) vkeys |= COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_SHIFT; if ((state & GST_NAVIGATION_MODIFIER_CONTROL_MASK) != 0) vkeys |= COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_CONTROL; if ((state & GST_NAVIGATION_MODIFIER_BUTTON1_MASK) != 0) vkeys |= COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_LEFT_BUTTON; if ((state & GST_NAVIGATION_MODIFIER_BUTTON2_MASK) != 0) vkeys |= COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_RIGHT_BUTTON; } comp_ctrl_->SendMouseInput (kind, vkeys, 0, point); } break; case GST_NAVIGATION_EVENT_MOUSE_MOVE: if (gst_navigation_event_parse_mouse_move_event (event, &x, &y)) { GST_TRACE_OBJECT (obj_, "Mouse move, %lfx%lf", x, y); POINT point; point.x = (LONG) x; point.y = (LONG) y; COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS vkeys = COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_NONE; GstNavigationModifierType state = GST_NAVIGATION_MODIFIER_NONE; if (gst_navigation_event_parse_modifier_state (event, &state)) { if ((state & GST_NAVIGATION_MODIFIER_SHIFT_MASK) != 0) vkeys |= COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_SHIFT; if ((state & GST_NAVIGATION_MODIFIER_CONTROL_MASK) != 0) vkeys |= COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_CONTROL; if ((state & GST_NAVIGATION_MODIFIER_BUTTON1_MASK) != 0) vkeys |= COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_LEFT_BUTTON; if ((state & GST_NAVIGATION_MODIFIER_BUTTON2_MASK) != 0) vkeys |= COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_RIGHT_BUTTON; } comp_ctrl_->SendMouseInput (COREWEBVIEW2_MOUSE_EVENT_KIND_MOVE, vkeys, 0, point); } break; default: break; } } private: HANDLE event_handle_; HWND hwnd_ = nullptr; GstWebView2Object *obj_ = nullptr; GstD3D11Device *device_ = nullptr; ComPtr comp_; ComPtr root_container_visual_; ComPtr root_visual_; ComPtr webview_container_visual_; ComPtr webview_visual_; ComPtr webview_visual2_; ComPtr env_; ComPtr ctrl_; ComPtr comp_ctrl_; ComPtr webview_; EventRegistrationToken navigation_compl_token_ = { }; std::wstring script_; ComPtr item_; SizeInt32 frame_size_ = { }; ComPtr d3d_device_; ComPtr frame_pool_; ComPtr session_; EventRegistrationToken arrived_token_ = { }; }; class NaviEventHandler : public RuntimeClass, FtmBase, IDispatcherQueueHandler> { public: STDMETHODIMP RuntimeClassInitialize (GstWebView2Item * item, GstEvent * event) { item_ = item; event_ = gst_event_ref (event); return S_OK; } IFACEMETHOD(Invoke) (void) { item_->HandleEvent (event_); item_ = nullptr; return S_OK; } ~NaviEventHandler () { gst_clear_event (&event_); } private: ComPtr item_; GstEvent *event_; }; class UpdateSizeHandler : public RuntimeClass, FtmBase, IDispatcherQueueHandler> { public: STDMETHODIMP RuntimeClassInitialize (GstWebView2Item * item, guint width, guint height) { item_ = item; width_ = width; height_ = height; return S_OK; } IFACEMETHOD(Invoke) (void) { item_->UpdateSize (width_, height_); item_ = nullptr; return S_OK; } private: ComPtr item_; FLOAT width_; FLOAT height_; }; class UpdateLocationHandler : public RuntimeClass, FtmBase, IDispatcherQueueHandler> { public: STDMETHODIMP RuntimeClassInitialize (GstWebView2Item * item, const std::string & location, const std::string & script) { item_ = item; location_wide_ = g_utf8_to_utf16 (location.c_str (), -1, nullptr, nullptr, nullptr); if (!script.empty ()) { script_wide_ = g_utf8_to_utf16 (script.c_str (), -1, nullptr, nullptr, nullptr); } return S_OK; } IFACEMETHOD(Invoke) (void) { item_->Navigate ((LPCWSTR) location_wide_, (LPCWSTR) script_wide_); item_ = nullptr; return S_OK; } ~UpdateLocationHandler () { g_free (location_wide_); g_free (script_wide_); } private: ComPtr item_; gunichar2 *location_wide_ = nullptr; gunichar2 *script_wide_ = nullptr; }; class AsyncWaiter : public RuntimeClass, FtmBase, IAsyncActionCompletedHandler> { public: STDMETHODIMP RuntimeClassInitialize (HANDLE event_handle) { event_handle_ = event_handle; return S_OK; } STDMETHOD (Invoke) (IAsyncAction * action, AsyncStatus status) { SetEvent (event_handle_); return S_OK; } private: HANDLE event_handle_; }; enum WebView2State { WEBVIEW2_STATE_INIT, WEBVIEW2_STATE_RUNNING, WEBVIEW2_STATE_EXIT, }; struct GstWebView2ObjectPrivate { GstWebView2ObjectPrivate () { shutdown_begin_handle = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS); shutdown_end_handle = CreateEventEx (nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS); } ~GstWebView2ObjectPrivate () { SetEvent (shutdown_begin_handle); g_clear_pointer (&main_thread, g_thread_join); CloseHandle (shutdown_begin_handle); CloseHandle (shutdown_end_handle); gst_clear_object (&device); } GstD3D11Device *device = nullptr; std::mutex lock; std::condition_variable cond; ComPtr item; ComPtr texture; ComPtr queue; GThread *main_thread = nullptr; std::string location; HANDLE shutdown_begin_handle; HANDLE shutdown_end_handle; WebView2State state = WEBVIEW2_STATE_INIT; gboolean flushing = FALSE; }; /* *INDENT-ON* */ struct _GstWebView2Object { GstObject parent; GstWebView2ObjectPrivate *priv; }; static void gst_webview2_object_constructed (GObject * object); static void gst_webview2_object_finalize (GObject * object); static void gst_webview2_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static gpointer gst_webview2_thread_func (GstWebView2Object * self); #define gst_webview2_object_parent_class parent_class G_DEFINE_TYPE (GstWebView2Object, gst_webview2_object, GST_TYPE_OBJECT); static void gst_webview2_object_class_init (GstWebView2ObjectClass * klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = gst_webview2_object_constructed; object_class->finalize = gst_webview2_object_finalize; object_class->set_property = gst_webview2_set_property; g_object_class_install_property (object_class, PROP_DEVICE, g_param_spec_object ("device", "D3D11 Device", "GstD3D11Device object for operating", GST_TYPE_D3D11_DEVICE, (GParamFlags) (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS))); } static void gst_webview2_object_init (GstWebView2Object * self) { self->priv = new GstWebView2ObjectPrivate (); } static void gst_webview2_object_constructed (GObject * object) { auto self = GST_WEBVIEW2_OBJECT (object); auto priv = self->priv; priv->main_thread = g_thread_new ("d3d11-webview2", (GThreadFunc) gst_webview2_thread_func, self); std::unique_lock < std::mutex > lk (priv->lock); while (!priv->state != WEBVIEW2_STATE_INIT) priv->cond.wait (lk); lk.unlock (); G_OBJECT_CLASS (parent_class)->constructed (object); } static void gst_webview2_object_finalize (GObject * object) { auto self = GST_WEBVIEW2_OBJECT (object); GST_DEBUG_OBJECT (self, "Clearing engine"); delete self->priv; GST_DEBUG_OBJECT (self, "Cleared"); G_OBJECT_CLASS (parent_class)->finalize (object); } static void gst_webview2_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { auto self = GST_WEBVIEW2_OBJECT (object); auto priv = self->priv; std::lock_guard < std::mutex > lk (priv->lock); switch (prop_id) { case PROP_DEVICE: priv->device = (GstD3D11Device *) g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_webview2_event_loop (GstWebView2Object * self) { auto priv = self->priv; ComPtr < AsyncWaiter > async_waiter; ComPtr < IAsyncAction > shutdown_action; HRESULT hr; ComPtr < IDispatcherQueueController > queue_ctrl; DispatcherQueueOptions queue_opt; HWND hwnd = nullptr; HANDLE waitables[] = { priv->shutdown_begin_handle, priv->shutdown_end_handle }; GST_D3D11_CALL_ONCE_BEGIN { WNDCLASSEXA wc = { }; wc.cbSize = sizeof (WNDCLASSEXA); wc.lpfnWndProc = &GstWebView2Item::WndProc; wc.hInstance = GetModuleHandle (nullptr); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpszClassName = "GstWebView2Item"; RegisterClassExA (&wc); } GST_D3D11_CALL_ONCE_END; hwnd = CreateWindowExA (0, "GstWebView2Item", "GstWebView2Item", 0, CW_DEFAULT, CW_DEFAULT, 0, 0, HWND_MESSAGE, nullptr, GetModuleHandle (nullptr), nullptr); if (!hwnd) { GST_ERROR_OBJECT (self, "Couldn't create message hwnd"); goto out; } hr = MakeAndInitialize < AsyncWaiter > (&async_waiter, priv->shutdown_end_handle); if (FAILED (hr)) { GST_ERROR_OBJECT (self, "Couldn't create shutdown waiter"); goto out; } queue_opt.dwSize = sizeof (DispatcherQueueOptions); queue_opt.threadType = DQTYPE_THREAD_CURRENT; queue_opt.apartmentType = DQTAT_COM_NONE; hr = CreateDispatcherQueueController (queue_opt, &queue_ctrl); if (FAILED (hr)) { GST_ERROR_OBJECT (self, "Couldn't create queue controller"); goto out; } hr = queue_ctrl->get_DispatcherQueue (&priv->queue); if (FAILED (hr)) { GST_ERROR_OBJECT (self, "Couldn't get dispatcher queue"); goto out; } hr = MakeAndInitialize < GstWebView2Item > (&priv->item, self, priv->device, priv->shutdown_begin_handle, hwnd); if (FAILED (hr)) { GST_ERROR_OBJECT (self, "Couldn't initialize item"); goto out; } while (true) { MSG msg; while (PeekMessage (&msg, nullptr, 0, 0, PM_REMOVE)) { TranslateMessage (&msg); DispatchMessage (&msg); } auto wait_ret = MsgWaitForMultipleObjects (G_N_ELEMENTS (waitables), waitables, FALSE, INFINITE, QS_ALLINPUT); if (wait_ret == WAIT_OBJECT_0) { GST_DEBUG_OBJECT (self, "Begin shutdown"); { std::lock_guard < std::mutex > lk (priv->lock); priv->texture = nullptr; priv->queue = nullptr; priv->item->Close (); } hr = queue_ctrl->ShutdownQueueAsync (&shutdown_action); if (FAILED (hr)) { GST_ERROR_OBJECT (self, "Shutdown failed"); break; } hr = shutdown_action->put_Completed (async_waiter.Get ()); if (FAILED (hr)) { GST_ERROR_OBJECT (self, "Couldn't install shutdown callback"); break; } } else if (wait_ret == WAIT_OBJECT_0 + 1) { GST_DEBUG_OBJECT (self, "Shutdown completed"); break; } else if (wait_ret == WAIT_IO_COMPLETION) { /* Do nothing */ } else if (wait_ret != WAIT_OBJECT_0 + G_N_ELEMENTS (waitables)) { GST_ERROR_OBJECT (self, "Unexpected wait return %u", (guint) wait_ret); break; } } out: std::lock_guard < std::mutex > lk (priv->lock); priv->state = WEBVIEW2_STATE_EXIT; priv->cond.notify_all (); priv->item = nullptr; priv->queue = nullptr; if (hwnd) CloseWindow (hwnd); } static gpointer gst_webview2_thread_func (GstWebView2Object * self) { auto priv = self->priv; GST_DEBUG_OBJECT (self, "Entering thread"); SetThreadDpiAwarenessContext (DPI_AWARENESS_CONTEXT_SYSTEM_AWARE); RoInitialize (RO_INIT_SINGLETHREADED); gst_webview2_event_loop (self); RoUninitialize (); SetEvent (priv->shutdown_end_handle); return nullptr; } static void gst_webview2_object_initialized (GstWebView2Object * obj) { auto priv = obj->priv; GST_DEBUG_OBJECT (obj, "Initialized"); std::lock_guard < std::mutex > lk (priv->lock); priv->state = WEBVIEW2_STATE_RUNNING; priv->cond.notify_all (); } static void gst_webview2_object_frame_arrived (GstWebView2Object * obj, ID3D11Texture2D * texture) { auto priv = obj->priv; std::lock_guard < std::mutex > lk (priv->lock); priv->texture = nullptr; priv->texture = texture; priv->cond.notify_all (); } GstWebView2Object * gst_webview2_object_new (GstD3D11Device * device) { GstWebView2Object *self; g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), nullptr); self = (GstWebView2Object *) g_object_new (GST_TYPE_WEBVIEW2_OBJECT, "device", device, nullptr); gst_object_ref_sink (self); if (self->priv->state != WEBVIEW2_STATE_RUNNING) { gst_object_unref (self); return nullptr; } return self; } gboolean gst_webview2_object_set_location (GstWebView2Object * object, const std::string & location, const std::string & script) { auto priv = object->priv; std::lock_guard < std::mutex > lk (priv->lock); if (!priv->queue || !priv->item) return FALSE; ComPtr < UpdateLocationHandler > handler; auto hr = MakeAndInitialize < UpdateLocationHandler > (&handler, priv->item.Get (), location, script); if (FAILED (hr)) return FALSE; boolean ret; priv->queue->TryEnqueue (handler.Get (), &ret); return ret; } gboolean gst_webview_object_update_size (GstWebView2Object * object, guint width, guint height) { auto priv = object->priv; std::lock_guard < std::mutex > lk (priv->lock); if (!priv->queue || !priv->item) return FALSE; ComPtr < UpdateSizeHandler > handler; auto hr = MakeAndInitialize < UpdateSizeHandler > (&handler, priv->item.Get (), width, height); if (FAILED (hr)) return FALSE; boolean ret; priv->queue->TryEnqueue (handler.Get (), &ret); return ret; } void gst_webview2_object_send_event (GstWebView2Object * object, GstEvent * event) { auto priv = object->priv; std::lock_guard < std::mutex > lk (priv->lock); if (!priv->queue || !priv->item) return; ComPtr < NaviEventHandler > handler; auto hr = MakeAndInitialize < NaviEventHandler > (&handler, priv->item.Get (), event); if (FAILED (hr)) return; boolean ret; priv->queue->TryEnqueue (handler.Get (), &ret); } GstFlowReturn gst_webview2_object_do_capture (GstWebView2Object * object, ID3D11Texture2D * texture, ID3D11DeviceContext4 * context4, ID3D11Fence * fence, guint64 * fence_val, gboolean need_signal) { auto priv = object->priv; std::unique_lock < std::mutex > lk (priv->lock); while (!priv->flushing && priv->state == WEBVIEW2_STATE_RUNNING && !priv->texture) { priv->cond.wait (lk); } if (priv->flushing) { GST_DEBUG_OBJECT (object, "We are flushing"); return GST_FLOW_FLUSHING; } if (priv->state != WEBVIEW2_STATE_RUNNING) { GST_DEBUG_OBJECT (object, "Not a running state"); return GST_FLOW_EOS; } D3D11_TEXTURE2D_DESC src_desc; D3D11_TEXTURE2D_DESC dst_desc; priv->texture->GetDesc (&src_desc); texture->GetDesc (&dst_desc); auto context = gst_d3d11_device_get_device_context_handle (priv->device); GstD3D11DeviceLockGuard dlk (priv->device); D3D11_BOX box = { }; box.right = MIN (src_desc.Width, dst_desc.Width); box.bottom = MIN (src_desc.Height, dst_desc.Height); box.front = 0; box.back = 1; context->CopySubresourceRegion (texture, 0, 0, 0, 0, priv->texture.Get (), 0, &box); if (need_signal) { auto fence_value = *fence_val + 1; auto hr = context4->Signal (fence, fence_value); if (!gst_d3d11_result (hr, priv->device)) { GST_ERROR_OBJECT (object, "Signal failed"); return GST_FLOW_ERROR; } *fence_val = fence_value; } return GST_FLOW_OK; } void gst_webview2_object_set_flushing (GstWebView2Object * object, gboolean flushing) { auto priv = object->priv; std::lock_guard < std::mutex > lk (priv->lock); priv->flushing = flushing; priv->cond.notify_all (); }