From a30a407de644c6fdbeb8d1f2b1df5b515bf50a53 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Wed, 22 Jul 2026 20:58:04 -0700 Subject: [PATCH] Windows WebView2 --- .../Windows/Sources/kinc/backend/system.c.h | 5 +- README.md | 16 +- Sources/websocket.cpp | 2 +- Sources/webview_win.cpp | 719 ++++++++++++++++-- kfile.js | 6 +- 5 files changed, 667 insertions(+), 81 deletions(-) diff --git a/Kore/Backends/System/Windows/Sources/kinc/backend/system.c.h b/Kore/Backends/System/Windows/Sources/kinc/backend/system.c.h index 305ad0c..b7488a9 100644 --- a/Kore/Backends/System/Windows/Sources/kinc/backend/system.c.h +++ b/Kore/Backends/System/Windows/Sources/kinc/backend/system.c.h @@ -19,6 +19,7 @@ #define DIRECTINPUT_VERSION 0x0800 #include #include +#include #include #include @@ -1236,7 +1237,9 @@ static bool co_initialized = false; void kinc_windows_co_initialize(void) { if (!co_initialized) { - kinc_microsoft_affirm(CoInitializeEx(0, COINIT_MULTITHREADED)); + // TODO find way to make multi thread work with webview + kinc_microsoft_affirm(OleInitialize(0)); + //kinc_microsoft_affirm(CoInitializeEx(0, COINIT_MULTITHREADED)); co_initialized = true; } } diff --git a/README.md b/README.md index 90f1450..78e8198 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ cd LNXRNT **Windows** ```bash # Unpack `v8\libraries\win32\release\v8_monolith.7z` using 7-Zip - Extract Here (exceeds 100MB) -Kinc\make -g direct3d11 +Kore\make -g direct3d11 # Open generated Visual Studio project at `build\RunT.sln` # or use command line for solution file like C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe" "build\Leenkx-RunT.sln" /p:Configuration=Release /p:Platform=x64 /m # Build for x64 & release @@ -38,4 +38,16 @@ Kore/make -g opengl git clone https://github.com/jeroen/apple-libressl-sdk.git cd apple-libressl-sdk git checkout fce8ffe30c9939fa1d71076745d9b3c811b8a90e -``` \ No newline at end of file +``` + +**Webview for windodows** +```bash +# Windows needs the WebView2 SDK while mac and linux are OS implementations +curl -L https://www.nuget.org/api/v2/package/Microsoft.Web.WebView2 -o webview2.zip +powershell -Command "Expand-Archive -Path webview2.zip -DestinationPath webview2-tmp -Force" +xcopy /E /I /Y webview2-tmp\build\native\include webview2\include\ +mkdir webview2\lib\x64 +copy webview2-tmp\build\native\x64\WebView2Loader.dll.lib webview2\lib\x64\WebView2Loader.lib +rmdir /S /Q webview2-tmp +del webview2.zip +``` diff --git a/Sources/websocket.cpp b/Sources/websocket.cpp index 62c33b3..fcaf918 100644 --- a/Sources/websocket.cpp +++ b/Sources/websocket.cpp @@ -1292,8 +1292,8 @@ namespace WebSocketWrapper { if (remainingBytes == 0 && currentFin) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Large message complete (%zu bytes)", fragmentBuffer.size()); - std::vector payload(fragmentBuffer.begin(), fragmentBuffer.end()); #endif + std::vector payload(fragmentBuffer.begin(), fragmentBuffer.end()); processFrame(currentOpcode, currentFin, payload); fragmentBuffer.clear(); wantsHead = true; diff --git a/Sources/webview_win.cpp b/Sources/webview_win.cpp index ac4138c..ef98700 100644 --- a/Sources/webview_win.cpp +++ b/Sources/webview_win.cpp @@ -1,19 +1,40 @@ #ifdef WITH_WEBVIEW -#ifdef KINC_WINDOWS +#ifdef _WIN32 #include "webview.h" #include #include #include +#include #include #include +#include +#include #include #include +#include #include "WebView2.h" -#include "WebView2EnvironmentCreator.h" -#pragma comment(lib, "WebView2Loader.lib") +#pragma comment(lib, "dcomp.lib") + +using namespace Microsoft::WRL; + +extern "C" struct HWND__ *kinc_windows_window_handle(int window_index); + +struct WinWebViewState; + +struct DCompShared { + IDCompositionDevice* device; + IDCompositionTarget* target; + IDCompositionVisual* rootVisual; + HWND dcompHwnd; + HWND parentHwnd; + int refCount; + std::vector webviews; +}; +static std::map g_dcompShared; +static std::map g_dcompByHwnd; struct WinWebViewState { ICoreWebView2Controller* controller; @@ -23,12 +44,22 @@ struct WinWebViewState { bool isDetached; bool destroyed; bool pending; + bool pendingShow; + DCompShared* dcompShared; + IDCompositionVisual* dcompVisual; + ICoreWebView2CompositionController* compController; + bool useComposition; EventRegistrationToken messageToken; + EventRegistrationToken navStartingToken; EventRegistrationToken navCompletedToken; EventRegistrationToken errorToken; + EventRegistrationToken sourceChangedToken; + EventRegistrationToken contentLoadingToken; int webviewId; int x, y, width, height; bool transparent; + EventRegistrationToken cursorChangedToken; + HCURSOR currentCursor; std::wstring pendingHTML; std::wstring pendingURL; }; @@ -72,6 +103,12 @@ static void ensureWebView2Environment() { if (g_webview2Env || g_envCreating) return; g_envCreating = true; + LPWSTR versionInfo = nullptr; + HRESULT verHr = GetAvailableCoreWebView2BrowserVersionString(nullptr, &versionInfo); + if (SUCCEEDED(verHr) && versionInfo) { + CoTaskMemFree(versionInfo); + } + HRESULT hr = CreateCoreWebView2EnvironmentWithOptions( nullptr, nullptr, nullptr, Callback( @@ -104,85 +141,536 @@ static HRESULT STDMETHODCALLTYPE onMessageReceived( static HRESULT STDMETHODCALLTYPE onNavCompleted( ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args, DWORD* webviewIdPtr) { int webviewId = (int)(intptr_t)webviewIdPtr; + BOOL success = FALSE; + if (args) args->get_IsSuccess(&success); WebViewManager::instance().dispatchLoad(webviewId); return S_OK; } +static HRESULT STDMETHODCALLTYPE onSourceChanged( + ICoreWebView2* sender, ICoreWebView2SourceChangedEventArgs* args, DWORD* webviewIdPtr) { + int webviewId = (int)(intptr_t)webviewIdPtr; + LPWSTR uri = nullptr; + sender->get_Source(&uri); + if (uri) CoTaskMemFree(uri); + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE onContentLoading( + ICoreWebView2* sender, ICoreWebView2ContentLoadingEventArgs* args, DWORD* webviewIdPtr) { + int webviewId = (int)(intptr_t)webviewIdPtr; + return S_OK; +} + static HRESULT STDMETHODCALLTYPE onProcessFailed( ICoreWebView2* sender, ICoreWebView2ProcessFailedEventArgs* args, DWORD* webviewIdPtr) { int webviewId = (int)(intptr_t)webviewIdPtr; + COREWEBVIEW2_PROCESS_FAILED_KIND kind = COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED; + if (args) args->get_ProcessFailedKind(&kind); WebViewManager::instance().dispatchError(webviewId, "WebView2 process failed"); return S_OK; } +static void onControllerReady(WinWebViewState* state, HRESULT result, ICoreWebView2Controller* controller) { + if (state->destroyed) { + if (controller) controller->Release(); + delete state; + return; + } + if (FAILED(result) || !controller) { + return; + } + + state->controller = controller; + state->controller->AddRef(); + controller->get_CoreWebView2(&state->webview); + state->webview->AddRef(); + + RECT bounds; + if (state->isDetached) { + GetClientRect(state->detachedHwnd, &bounds); + } else if (state->useComposition) { + bounds.left = 0; + bounds.top = 0; + bounds.right = state->width; + bounds.bottom = state->height; + } else { + bounds.left = state->x; + bounds.top = state->y; + bounds.right = state->x + state->width; + bounds.bottom = state->y + state->height; + } + controller->put_Bounds(bounds); + + if (state->transparent) { + COREWEBVIEW2_COLOR bgColor = {0, 0, 0, 0}; + ICoreWebView2Controller2* ctrl2; + if (SUCCEEDED(controller->QueryInterface(IID_PPV_ARGS(&ctrl2)))) { + ctrl2->put_DefaultBackgroundColor(bgColor); + ctrl2->Release(); + } + } + + HRESULT hr; + hr = state->webview->add_WebMessageReceived( + Callback( + [state](ICoreWebView2* s, ICoreWebView2WebMessageReceivedEventArgs* a) -> HRESULT { + return onMessageReceived(s, a, (DWORD*)(intptr_t)state->webviewId); + }).Get(), + &state->messageToken); + + hr = state->webview->add_NavigationStarting( + Callback( + [state](ICoreWebView2* s, ICoreWebView2NavigationStartingEventArgs* a) -> HRESULT { + LPWSTR uri = nullptr; + a->get_Uri(&uri); + if (uri) CoTaskMemFree(uri); + return S_OK; + }).Get(), + &state->navStartingToken); + + hr = state->webview->add_NavigationCompleted( + Callback( + [state](ICoreWebView2* s, ICoreWebView2NavigationCompletedEventArgs* a) -> HRESULT { + return onNavCompleted(s, a, (DWORD*)(intptr_t)state->webviewId); + }).Get(), + &state->navCompletedToken); + + hr = state->webview->add_SourceChanged( + Callback( + [state](ICoreWebView2* s, ICoreWebView2SourceChangedEventArgs* a) -> HRESULT { + return onSourceChanged(s, a, (DWORD*)(intptr_t)state->webviewId); + }).Get(), + &state->sourceChangedToken); + + hr = state->webview->add_ContentLoading( + Callback( + [state](ICoreWebView2* s, ICoreWebView2ContentLoadingEventArgs* a) -> HRESULT { + return onContentLoading(s, a, (DWORD*)(intptr_t)state->webviewId); + }).Get(), + &state->contentLoadingToken); + + hr = state->webview->add_ProcessFailed( + Callback( + [state](ICoreWebView2* s, ICoreWebView2ProcessFailedEventArgs* a) -> HRESULT { + return onProcessFailed(s, a, (DWORD*)(intptr_t)state->webviewId); + }).Get(), + &state->errorToken); + + + if (state->pendingShow) { + HRESULT visHr = state->controller->put_IsVisible(TRUE); + state->pendingShow = false; + } + + if (!state->pendingHTML.empty()) { + HRESULT navHr = state->webview->NavigateToString(state->pendingHTML.c_str()); + state->pendingHTML.clear(); + } + { + LPWSTR uri = nullptr; + state->webview->get_Source(&uri); + if (uri) CoTaskMemFree(uri); + } + if (!state->pendingURL.empty()) { + HRESULT navHr = state->webview->Navigate(state->pendingURL.c_str()); + state->pendingURL.clear(); + } +} + +static const wchar_t* g_dcompClassName = L"LNXRNT_DCompChild"; +static HWND g_dcompParentHwnd = nullptr; + +static WinWebViewState* findWebviewAtPoint(DCompShared* shared, int screenX, int screenY) { + if (!shared) return nullptr; + RECT popupRect; + GetWindowRect(shared->dcompHwnd, &popupRect); + int localX = screenX - popupRect.left; + int localY = screenY - popupRect.top; + for (auto* wv : shared->webviews) { + if (wv->destroyed || !wv->compController) continue; + if (localX >= wv->x && localX < wv->x + wv->width && + localY >= wv->y && localY < wv->y + wv->height) { + return wv; + } + } + return nullptr; +} + +static bool isWebviewInteractive(WinWebViewState* wv) { + if (!wv || !wv->compController) return false; + HCURSOR cursor = nullptr; + HRESULT hr = wv->compController->get_Cursor(&cursor); + if (FAILED(hr) || !cursor) return false; + HCURSOR defaultArrow = LoadCursor(nullptr, IDC_ARROW); + if (cursor == defaultArrow) return false; + return true; +} + +static void forwardMouseToWebview(WinWebViewState* wv, UINT msg, WPARAM wParam, LPARAM lParam) { + if (!wv || !wv->compController) return; + int x = GET_X_LPARAM(lParam) - wv->x; + int y = GET_Y_LPARAM(lParam) - wv->y; + POINT pt = {x, y}; + + COREWEBVIEW2_MOUSE_EVENT_KIND kind; + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS vkeys = (COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS)0; + UINT32 mouseData = 0; + + switch (msg) { + case WM_MOUSEMOVE: + kind = COREWEBVIEW2_MOUSE_EVENT_KIND_MOVE; + break; + case WM_LBUTTONDOWN: + kind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_DOWN; + vkeys = (COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS)( + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_LEFT_BUTTON | + (GET_KEYSTATE_WPARAM(wParam) & MK_SHIFT ? COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_SHIFT : 0) | + (GET_KEYSTATE_WPARAM(wParam) & MK_CONTROL ? COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_CONTROL : 0)); + break; + case WM_LBUTTONUP: + kind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_UP; + vkeys = (COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS)( + (GET_KEYSTATE_WPARAM(wParam) & MK_SHIFT ? COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_SHIFT : 0) | + (GET_KEYSTATE_WPARAM(wParam) & MK_CONTROL ? COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_CONTROL : 0)); + break; + case WM_RBUTTONDOWN: + kind = COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_DOWN; + vkeys = COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_RIGHT_BUTTON; + break; + case WM_RBUTTONUP: + kind = COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_UP; + break; + case WM_MBUTTONDOWN: + kind = COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_DOWN; + vkeys = COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_MIDDLE_BUTTON; + break; + case WM_MBUTTONUP: + kind = COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_UP; + break; + default: + return; + } + + wv->compController->SendMouseInput(kind, vkeys, mouseData, pt); +} + +static void forwardMouseToParent(DCompShared* shared, HWND popupHwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + if (!shared || !shared->parentHwnd) return; + POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; + MapWindowPoints(popupHwnd, shared->parentHwnd, &pt, 1); + LPARAM newLParam = MAKELPARAM(pt.x, pt.y); + SendMessageW(shared->parentHwnd, msg, wParam, newLParam); +} + +static WinWebViewState* g_mouseCaptureWv = nullptr; +static bool g_mouseCaptureIsWebview = false; + +static LRESULT CALLBACK dcompWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + if (msg == WM_TIMER && wParam == 1) { + HWND parent = g_dcompParentHwnd; + if (parent && IsWindow(parent)) { + RECT clientRect; + GetClientRect(parent, &clientRect); + POINT pt = {0, 0}; + ClientToScreen(parent, &pt); + SetWindowPos(hwnd, HWND_TOP, pt.x, pt.y, + clientRect.right - clientRect.left, + clientRect.bottom - clientRect.top, + SWP_NOACTIVATE | SWP_SHOWWINDOW); + } + return 0; + } + + auto it = g_dcompByHwnd.find(hwnd); + DCompShared* shared = (it != g_dcompByHwnd.end()) ? it->second : nullptr; + + if (msg == WM_NCHITTEST) { + return HTCLIENT; + } + + if (msg == WM_SETCURSOR) { + POINT screenPt; + GetCursorPos(&screenPt); + WinWebViewState* wv = findWebviewAtPoint(shared, screenPt.x, screenPt.y); + if (wv && isWebviewInteractive(wv) && wv->currentCursor) { + SetCursor(wv->currentCursor); + return TRUE; + } + return DefWindowProcW(hwnd, msg, wParam, lParam); + } + + if (msg == WM_MOUSEMOVE) { + POINT clientPt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; + POINT screenPt = clientPt; + ClientToScreen(hwnd, &screenPt); + WinWebViewState* wv = findWebviewAtPoint(shared, screenPt.x, screenPt.y); + bool interactive = isWebviewInteractive(wv); + + if (g_mouseCaptureIsWebview && g_mouseCaptureWv) { + forwardMouseToWebview(g_mouseCaptureWv, msg, wParam, lParam); + return 0; + } + + if (wv) { + forwardMouseToWebview(wv, msg, wParam, lParam); + } + if (!interactive) { + forwardMouseToParent(shared, hwnd, msg, wParam, lParam); + } + return 0; + } + + if (msg == WM_LBUTTONDOWN || msg == WM_RBUTTONDOWN || msg == WM_MBUTTONDOWN) { + POINT clientPt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; + POINT screenPt = clientPt; + ClientToScreen(hwnd, &screenPt); + WinWebViewState* wv = findWebviewAtPoint(shared, screenPt.x, screenPt.y); + bool interactive = isWebviewInteractive(wv); + + if (interactive && wv) { + g_mouseCaptureWv = wv; + g_mouseCaptureIsWebview = true; + forwardMouseToWebview(wv, msg, wParam, lParam); + } else { + g_mouseCaptureWv = nullptr; + g_mouseCaptureIsWebview = false; + forwardMouseToParent(shared, hwnd, msg, wParam, lParam); + } + return 0; + } + + if (msg == WM_LBUTTONUP || msg == WM_RBUTTONUP || msg == WM_MBUTTONUP) { + if (g_mouseCaptureIsWebview && g_mouseCaptureWv) { + forwardMouseToWebview(g_mouseCaptureWv, msg, wParam, lParam); + g_mouseCaptureWv = nullptr; + g_mouseCaptureIsWebview = false; + } else { + forwardMouseToParent(shared, hwnd, msg, wParam, lParam); + } + return 0; + } + + if (msg == WM_MOUSEWHEEL) { + POINT screenPt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; + WinWebViewState* wv = findWebviewAtPoint(shared, screenPt.x, screenPt.y); + if (wv && isWebviewInteractive(wv)) { + POINT clientPt = screenPt; + ScreenToClient(hwnd, &clientPt); + int x = clientPt.x - wv->x; + int y = clientPt.y - wv->y; + POINT pt = {x, y}; + wv->compController->SendMouseInput( + COREWEBVIEW2_MOUSE_EVENT_KIND_WHEEL, + (COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS)0, + GET_WHEEL_DELTA_WPARAM(wParam), pt); + } else { + forwardMouseToParent(shared, hwnd, msg, wParam, lParam); + } + return 0; + } + + return DefWindowProcW(hwnd, msg, wParam, lParam); +} + +static DCompShared* getOrCreateDCompShared(HWND hwnd) { + auto it = g_dcompShared.find(hwnd); + if (it != g_dcompShared.end()) { + it->second->refCount++; + return it->second; + } + + auto* shared = new DCompShared(); + shared->device = nullptr; + shared->target = nullptr; + shared->rootVisual = nullptr; + shared->dcompHwnd = nullptr; + shared->parentHwnd = hwnd; + shared->refCount = 1; + + static bool registered = false; + if (!registered) { + WNDCLASSW wc = {}; + wc.lpfnWndProc = dcompWndProc; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpszClassName = g_dcompClassName; + RegisterClassW(&wc); + registered = true; + } + + g_dcompParentHwnd = hwnd; + + RECT clientRect; + GetClientRect(hwnd, &clientRect); + + POINT pt = {0, 0}; + ClientToScreen(hwnd, &pt); + int screenX = pt.x; + int screenY = pt.y; + int width = clientRect.right - clientRect.left; + int height = clientRect.bottom - clientRect.top; + + shared->dcompHwnd = CreateWindowExW( + WS_EX_NOREDIRECTIONBITMAP | WS_EX_NOACTIVATE, + g_dcompClassName, L"", + WS_POPUP, + screenX, screenY, + width, height, + nullptr, nullptr, GetModuleHandle(nullptr), nullptr); + + if (!shared->dcompHwnd) { + delete shared; + return nullptr; + } + + g_dcompByHwnd[shared->dcompHwnd] = shared; + SetWindowPos(shared->dcompHwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW); + SetTimer(shared->dcompHwnd, 1, 16, nullptr); + + HRESULT hr = DCompositionCreateDevice(nullptr, IID_PPV_ARGS(&shared->device)); + if (FAILED(hr) || !shared->device) { + DestroyWindow(shared->dcompHwnd); + delete shared; + return nullptr; + } + + hr = shared->device->CreateTargetForHwnd(shared->dcompHwnd, TRUE, &shared->target); + if (FAILED(hr) || !shared->target) { + shared->device->Release(); + DestroyWindow(shared->dcompHwnd); + delete shared; + return nullptr; + } + + hr = shared->device->CreateVisual(&shared->rootVisual); + if (FAILED(hr) || !shared->rootVisual) { + shared->target->Release(); + shared->device->Release(); + DestroyWindow(shared->dcompHwnd); + delete shared; + return nullptr; + } + + shared->target->SetRoot(shared->rootVisual); + shared->device->Commit(); + + g_dcompShared[hwnd] = shared; + return shared; +} + +static void releaseDCompShared(HWND hwnd) { + auto it = g_dcompShared.find(hwnd); + if (it == g_dcompShared.end()) return; + DCompShared* shared = it->second; + shared->refCount--; + if (shared->refCount <= 0) { + if (shared->rootVisual) shared->rootVisual->Release(); + if (shared->target) { + shared->target->SetRoot(nullptr); + shared->target->Release(); + } + if (shared->device) shared->device->Release(); + if (shared->dcompHwnd) { + g_dcompByHwnd.erase(shared->dcompHwnd); + KillTimer(shared->dcompHwnd, 1); + DestroyWindow(shared->dcompHwnd); + } + delete shared; + g_dcompShared.erase(it); + } +} + +static void createCompositionController(WinWebViewState* state) { + + state->dcompShared = getOrCreateDCompShared(state->parentHwnd); + if (!state->dcompShared) { + return; + } + + HRESULT hr = state->dcompShared->device->CreateVisual(&state->dcompVisual); + if (FAILED(hr) || !state->dcompVisual) { + releaseDCompShared(state->parentHwnd); + state->dcompShared = nullptr; + return; + } + + state->dcompVisual->SetOffsetX((float)state->x); + state->dcompVisual->SetOffsetY((float)state->y); + + ICoreWebView2Environment3* env3; + hr = g_webview2Env->QueryInterface(IID_PPV_ARGS(&env3)); + if (FAILED(hr) || !env3) { + state->dcompVisual->Release(); + state->dcompVisual = nullptr; + releaseDCompShared(state->parentHwnd); + state->dcompShared = nullptr; + return; + } + + env3->CreateCoreWebView2CompositionController( + state->dcompShared->dcompHwnd, + Callback( + [state](HRESULT result, ICoreWebView2CompositionController* compController) -> HRESULT { + if (state->destroyed) { + if (compController) compController->Release(); + delete state; + return S_OK; + } + if (FAILED(result) || !compController) { + return result; + } + + state->compController = compController; + state->compController->AddRef(); + + HRESULT targetHr = compController->put_RootVisualTarget(state->dcompVisual); + + state->dcompShared->rootVisual->AddVisual(state->dcompVisual, FALSE, nullptr); + state->dcompShared->device->Commit(); + + ICoreWebView2Controller* controller = nullptr; + HRESULT qiHr = compController->QueryInterface(IID_PPV_ARGS(&controller)); + if (FAILED(qiHr) || !controller) { + compController->Release(); + return qiHr; + } + + onControllerReady(state, result, controller); + + state->dcompShared->webviews.push_back(state); + + state->compController->add_CursorChanged( + Callback( + [state](ICoreWebView2CompositionController* sender, IUnknown* args) -> HRESULT { + HCURSOR cursor = nullptr; + HRESULT hr = sender->get_Cursor(&cursor); + if (SUCCEEDED(hr) && cursor) { + state->currentCursor = cursor; + } + return S_OK; + }).Get(), + &state->cursorChangedToken); + + controller->Release(); + compController->Release(); + return S_OK; + }).Get()); + + env3->Release(); +} + static void createController(WinWebViewState* state) { + + if (state->useComposition) { + createCompositionController(state); + return; + } + g_webview2Env->CreateCoreWebView2Controller( state->parentHwnd, Callback( [state](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT { - if (state->destroyed) { - if (controller) controller->Release(); - delete state; - return S_OK; - } - if (FAILED(result) || !controller) { - kinc_log(KINC_LOG_LEVEL_ERROR, "CreateController for webview failed: 0x%08X", result); - return result; - } - - state->controller = controller; - controller->get_CoreWebView2(&state->webview); - - RECT bounds; - if (state->isDetached) { - GetClientRect(state->detachedHwnd, &bounds); - } else { - bounds.left = state->x; - bounds.top = state->y; - bounds.right = state->x + state->width; - bounds.bottom = state->y + state->height; - } - controller->put_Bounds(bounds); - - if (state->transparent) { - COREWEBVIEW2_COLOR bgColor = {0, 0, 0, 0}; - ICoreWebView2Controller2* ctrl2; - if (SUCCEEDED(controller->QueryInterface(IID_PPV_ARGS(&ctrl2)))) { - ctrl2->put_DefaultBackgroundColor(bgColor); - ctrl2->Release(); - } - } - - state->webview->add_WebMessageReceived( - Callback( - [state](ICoreWebView2* s, ICoreWebView2WebMessageReceivedEventArgs* a) -> HRESULT { - return onMessageReceived(s, a, (DWORD*)(intptr_t)state->webviewId); - }).Get(), - &state->messageToken); - - state->webview->add_NavigationCompleted( - Callback( - [state](ICoreWebView2* s, ICoreWebView2NavigationCompletedEventArgs* a) -> HRESULT { - return onNavCompleted(s, a, (DWORD*)(intptr_t)state->webviewId); - }).Get(), - &state->navCompletedToken); - - state->webview->add_ProcessFailed( - Callback( - [state](ICoreWebView2* s, ICoreWebView2ProcessFailedEventArgs* a) -> HRESULT { - return onProcessFailed(s, a, (DWORD*)(intptr_t)state->webviewId); - }).Get(), - &state->errorToken); - - if (!state->pendingHTML.empty()) { - state->webview->NavigateToString(state->pendingHTML.c_str()); - state->pendingHTML.clear(); - } - if (!state->pendingURL.empty()) { - state->webview->Navigate(state->pendingURL.c_str()); - state->pendingURL.clear(); - } - + onControllerReady(state, result, controller); return S_OK; }).Get()); } @@ -197,9 +685,19 @@ int platformCreate(WebViewInstance* wv) { state->isDetached = (wv->parentWindow < 0); state->destroyed = false; state->pending = false; + state->pendingShow = false; + state->dcompShared = nullptr; + state->dcompVisual = nullptr; + state->compController = nullptr; + state->useComposition = (wv->transparent && !state->isDetached); state->messageToken = {0}; + state->navStartingToken = {0}; state->navCompletedToken = {0}; state->errorToken = {0}; + state->sourceChangedToken = {0}; + state->contentLoadingToken = {0}; + state->cursorChangedToken = {0}; + state->currentCursor = nullptr; state->webviewId = wv->id; state->x = wv->x; state->y = wv->y; @@ -228,7 +726,6 @@ int platformCreate(WebViewInstance* wv) { nullptr, nullptr, GetModuleHandle(nullptr), nullptr); state->parentHwnd = state->detachedHwnd; } else { - extern struct HWND__ *kinc_windows_window_handle(int window_index); state->parentHwnd = kinc_windows_window_handle(wv->parentWindow); if (!state->parentHwnd) { kinc_log(KINC_LOG_LEVEL_ERROR, "[WebView] Failed to get Kinc HWND for window %d", wv->parentWindow); @@ -275,15 +772,54 @@ void platformDestroy(WebViewInstance* wv) { DestroyWindow(state->detachedHwnd); state->detachedHwnd = nullptr; } + if (state->dcompShared) { + auto& wvs = state->dcompShared->webviews; + wvs.erase(std::remove(wvs.begin(), wvs.end(), state), wvs.end()); + } + if (state->compController) { + if (state->cursorChangedToken.value) state->compController->remove_CursorChanged(state->cursorChangedToken); + state->compController->put_RootVisualTarget(nullptr); + state->compController->Release(); + state->compController = nullptr; + } + if (state->dcompShared && state->dcompVisual) { + state->dcompShared->rootVisual->RemoveVisual(state->dcompVisual); + state->dcompShared->device->Commit(); + state->dcompVisual->Release(); + state->dcompVisual = nullptr; + } + if (state->dcompShared) { + releaseDCompShared(state->parentHwnd); + state->dcompShared = nullptr; + } return; } if (state->webview) { if (state->messageToken.value) state->webview->remove_WebMessageReceived(state->messageToken); + if (state->navStartingToken.value) state->webview->remove_NavigationStarting(state->navStartingToken); if (state->navCompletedToken.value) state->webview->remove_NavigationCompleted(state->navCompletedToken); if (state->errorToken.value) state->webview->remove_ProcessFailed(state->errorToken); + if (state->sourceChangedToken.value) state->webview->remove_SourceChanged(state->sourceChangedToken); + if (state->contentLoadingToken.value) state->webview->remove_ContentLoading(state->contentLoadingToken); state->webview->Release(); } + if (state->dcompShared) { + auto& wvs = state->dcompShared->webviews; + wvs.erase(std::remove(wvs.begin(), wvs.end(), state), wvs.end()); + } + if (state->compController) { + if (state->cursorChangedToken.value) state->compController->remove_CursorChanged(state->cursorChangedToken); + state->compController->put_RootVisualTarget(nullptr); + state->compController->Release(); + state->compController = nullptr; + } + if (state->dcompShared && state->dcompVisual) { + state->dcompShared->rootVisual->RemoveVisual(state->dcompVisual); + state->dcompShared->device->Commit(); + state->dcompVisual->Release(); + state->dcompVisual = nullptr; + } if (state->controller) { state->controller->Close(); state->controller->Release(); @@ -291,6 +827,10 @@ void platformDestroy(WebViewInstance* wv) { if (state->detachedHwnd) { DestroyWindow(state->detachedHwnd); } + if (state->dcompShared) { + releaseDCompShared(state->parentHwnd); + state->dcompShared = nullptr; + } delete state; wv->platformHandle = nullptr; } @@ -303,7 +843,8 @@ void platformLoadHTML(WebViewInstance* wv, const std::string& html) { state->pendingURL.clear(); return; } - state->webview->NavigateToString(utf8ToWide(html).c_str()); + std::wstring wHtml = utf8ToWide(html); + HRESULT navHr = state->webview->NavigateToString(wHtml.c_str()); } void platformLoadURL(WebViewInstance* wv, const std::string& url) { @@ -335,7 +876,11 @@ void platformEvalJSAsync(WebViewInstance* wv, const std::string& js) { resultStr = "null"; } else { std::wstring wstr(resultJson); - resultStr = std::string(wstr.begin(), wstr.end()); + int len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr); + if (len > 0) { + resultStr.resize(len - 1); + WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &resultStr[0], len, nullptr, nullptr); + } } WebViewManager::instance().dispatchEvalResult(webviewId, resultStr); return S_OK; @@ -350,6 +895,8 @@ void platformShow(WebViewInstance* wv) { } if (state->controller) { state->controller->put_IsVisible(TRUE); + } else { + state->pendingShow = true; } } @@ -378,6 +925,11 @@ void platformResize(WebViewInstance* wv, int width, int height) { RECT bounds; if (state->isDetached) { GetClientRect(state->detachedHwnd, &bounds); + } else if (state->useComposition) { + bounds.left = 0; + bounds.top = 0; + bounds.right = width; + bounds.bottom = height; } else { bounds.left = wv->x; bounds.top = wv->y; @@ -385,6 +937,12 @@ void platformResize(WebViewInstance* wv, int width, int height) { bounds.bottom = wv->y + height; } state->controller->put_Bounds(bounds); + + if (state->dcompShared && state->dcompVisual) { + state->dcompVisual->SetOffsetX((float)wv->x); + state->dcompVisual->SetOffsetY((float)wv->y); + state->dcompShared->device->Commit(); + } } void platformMove(WebViewInstance* wv, int x, int y) { @@ -395,12 +953,25 @@ void platformMove(WebViewInstance* wv, int x, int y) { SetWindowPos(state->detachedHwnd, nullptr, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); } else if (state->controller) { RECT bounds; - bounds.left = x; - bounds.top = y; - bounds.right = x + wv->width; - bounds.bottom = y + wv->height; + if (state->useComposition) { + bounds.left = 0; + bounds.top = 0; + bounds.right = wv->width; + bounds.bottom = wv->height; + } else { + bounds.left = x; + bounds.top = y; + bounds.right = x + wv->width; + bounds.bottom = y + wv->height; + } state->controller->put_Bounds(bounds); } + + if (state->dcompShared && state->dcompVisual) { + state->dcompVisual->SetOffsetX((float)x); + state->dcompVisual->SetOffsetY((float)y); + state->dcompShared->device->Commit(); + } } void platformSetTransparent(WebViewInstance* wv, bool transparent) { @@ -588,7 +1159,7 @@ void platformSetContextMenuEnabled(WebViewInstance* wv, bool enabled) { } void platformTick() { - // COM event loop is pumped by Kincs loop. + // COM event loop is by Kores loop via PeekMessageW/DispatchMessageW. } #endif diff --git a/kfile.js b/kfile.js index ac58bbd..97e3080 100644 --- a/kfile.js +++ b/kfile.js @@ -10,10 +10,10 @@ let flags = { with_compute: true, with_networking: true, with_viewport: true, - with_webview: false, + with_webview: true, with_uws: false, with_ssl: true, - debug_network: true + debug_network: false }; const system = platform === Platform.Windows ? "win32" : @@ -142,7 +142,7 @@ if (platform === Platform.Windows) { project.addLib(libdir + 'v8_monolith'); if (flags.with_webview) { project.addIncludeDir('webview2/include'); - project.addLib('WebView2Loader'); + project.addLib(root + '/webview2/lib/x64/WebView2Loader'); } if (!flags.release) { project.addDefine('_HAS_ITERATOR_DEBUGGING=0'); -- 2.40.1