mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-27 12:11:13 +00:00
f7bdf91ad7
Allow javascript injection for various custom use cases. For example, scrollbars and scrolling can be disabled via gst-launch-1.0 webview2src location=https://gstreamer.freedesktop.org \ javascript="document.querySelector('body').style.overflow='hidden'" ! ... Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6487>
1081 lines
29 KiB
C++
1081 lines
29 KiB
C++
/* GStreamer
|
|
* Copyright (C) 2024 Seungha Yang <seungha@centricular.com>
|
|
*
|
|
* 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 <gst/d3d11/gstd3d11-private.h>
|
|
#include <webview2.h>
|
|
#include <dcomp.h>
|
|
#include <wrl.h>
|
|
#include <mutex>
|
|
#include <condition_variable>
|
|
#include <string>
|
|
#include <string.h>
|
|
#include <dwmapi.h>
|
|
#include <locale>
|
|
#include <codecvt>
|
|
#include <winstring.h>
|
|
#include <roapi.h>
|
|
#include <dispatcherqueue.h>
|
|
#include <windows.system.h>
|
|
#include <windows.ui.composition.h>
|
|
#include <windows.graphics.capture.h>
|
|
#include <windows.graphics.directx.direct3d11.h>
|
|
#include <windows.graphics.directx.direct3d11.interop.h>
|
|
|
|
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<IClosable> 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<RuntimeClassFlags<ClassicCom>,
|
|
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<IInspectable> 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<IVisualCollection> 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<ICoreWebView2Controller> 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<ICoreWebView2_8> 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<IGraphicsCaptureItemStatics> item_statics;
|
|
|
|
auto hr = GstGetActivationFactory <IGraphicsCaptureItemStatics,
|
|
RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem> (&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<ID3D10Multithread> multi_thread;
|
|
hr = device->QueryInterface (IID_PPV_ARGS (&multi_thread));
|
|
CHECK_HR_AND_RETURN (hr, QueryInterface);
|
|
|
|
multi_thread->SetMultithreadProtected (TRUE);
|
|
ComPtr<IDXGIDevice> 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:
|
|
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;
|
|
POINT point;
|
|
|
|
point.x = (LONG) x;
|
|
point.y = (LONG) y;
|
|
|
|
switch (button) {
|
|
case 1:
|
|
kind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_DOWN;
|
|
break;
|
|
case 2:
|
|
kind = COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_DOWN;
|
|
break;
|
|
case 3:
|
|
kind = COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_DOWN;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
/* FIXME: need to know the virtual key state */
|
|
comp_ctrl_->SendMouseInput (kind,
|
|
COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_NONE, 0, point);
|
|
}
|
|
break;
|
|
case GST_NAVIGATION_EVENT_MOUSE_BUTTON_RELEASE:
|
|
if (gst_navigation_event_parse_mouse_button_event (event,
|
|
&button, &x, &y)) {
|
|
GST_TRACE_OBJECT (obj_, "Mouse release, button %d, %lfx%lf",
|
|
button, x, y);
|
|
COREWEBVIEW2_MOUSE_EVENT_KIND kind;
|
|
POINT point;
|
|
|
|
point.x = (LONG) x;
|
|
point.y = (LONG) y;
|
|
|
|
switch (button) {
|
|
case 1:
|
|
kind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_UP;
|
|
break;
|
|
case 2:
|
|
kind = COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_UP;
|
|
break;
|
|
case 3:
|
|
kind = COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_UP;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
/* FIXME: need to know the virtual key state */
|
|
comp_ctrl_->SendMouseInput (kind,
|
|
COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_NONE, 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;
|
|
|
|
/* FIXME: need to know the virtual key state */
|
|
comp_ctrl_->SendMouseInput (COREWEBVIEW2_MOUSE_EVENT_KIND_MOVE,
|
|
COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_NONE, 0, point);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
private:
|
|
HANDLE event_handle_;
|
|
HWND hwnd_ = nullptr;
|
|
GstWebView2Object *obj_ = nullptr;
|
|
GstD3D11Device *device_ = nullptr;
|
|
ComPtr<ICompositor> comp_;
|
|
ComPtr<IContainerVisual> root_container_visual_;
|
|
ComPtr<IVisual> root_visual_;
|
|
ComPtr<IContainerVisual> webview_container_visual_;
|
|
ComPtr<IVisual> webview_visual_;
|
|
ComPtr<IVisual2> webview_visual2_;
|
|
|
|
ComPtr<ICoreWebView2Environment3> env_;
|
|
ComPtr<ICoreWebView2Controller3> ctrl_;
|
|
ComPtr<ICoreWebView2CompositionController > comp_ctrl_;
|
|
ComPtr<ICoreWebView2 > webview_;
|
|
EventRegistrationToken navigation_compl_token_ = { };
|
|
std::wstring script_;
|
|
|
|
ComPtr<IGraphicsCaptureItem> item_;
|
|
SizeInt32 frame_size_ = { };
|
|
ComPtr<IDirect3DDevice> d3d_device_;
|
|
ComPtr<IDirect3D11CaptureFramePool> frame_pool_;
|
|
ComPtr<IGraphicsCaptureSession> session_;
|
|
EventRegistrationToken arrived_token_ = { };
|
|
};
|
|
|
|
class NaviEventHandler : public RuntimeClass<RuntimeClassFlags<ClassicCom>,
|
|
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<GstWebView2Item> item_;
|
|
GstEvent *event_;
|
|
};
|
|
|
|
class UpdateSizeHandler : public RuntimeClass<RuntimeClassFlags<ClassicCom>,
|
|
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<GstWebView2Item> item_;
|
|
FLOAT width_;
|
|
FLOAT height_;
|
|
};
|
|
|
|
class UpdateLocationHandler : public RuntimeClass<RuntimeClassFlags<ClassicCom>,
|
|
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<GstWebView2Item> item_;
|
|
gunichar2 *location_wide_ = nullptr;
|
|
gunichar2 *script_wide_ = nullptr;
|
|
};
|
|
|
|
class AsyncWaiter : public RuntimeClass<RuntimeClassFlags<ClassicCom>,
|
|
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<GstWebView2Item> item;
|
|
ComPtr<ID3D11Texture2D> texture;
|
|
ComPtr<IDispatcherQueue> 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)
|
|
{
|
|
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);
|
|
|
|
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 ();
|
|
}
|