#ifdef WITH_WEBVIEW #ifdef _WIN32 #include "webview.h" #include #include #include #include #include #include #include #include #include #include #include #include "WebView2.h" #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; ICoreWebView2* webview; HWND parentHwnd; HWND detachedHwnd; 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; }; static ICoreWebView2Environment* g_webview2Env = nullptr; static bool g_envCreating = false; static std::vector g_pendingCreates; static std::wstring utf8ToWide(const std::string& str) { if (str.empty()) return std::wstring(); int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.length(), nullptr, 0); std::wstring wstr(len, 0); MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.length(), &wstr[0], len); return wstr; } static void createController(WinWebViewState* state); static HRESULT STDMETHODCALLTYPE onEnvCreated(HRESULT result, ICoreWebView2Environment* env) { if (FAILED(result) || !env) { kinc_log(KINC_LOG_LEVEL_ERROR, "[WebView] Failed to create WebView2 environment: 0x%08X", result); g_envCreating = false; return result; } g_webview2Env = env; g_webview2Env->AddRef(); g_envCreating = false; auto pending = std::move(g_pendingCreates); for (auto* wv : pending) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || state->destroyed) continue; state->pending = false; createController(state); } return S_OK; } 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( onEnvCreated).Get()); if (FAILED(hr)) { kinc_log(KINC_LOG_LEVEL_ERROR, "[WebView] CreateCoreWebView2EnvironmentWithOptions failed: 0x%08X", hr); g_envCreating = false; } } static HRESULT STDMETHODCALLTYPE onMessageReceived( ICoreWebView2* sender, ICoreWebView2WebMessageReceivedEventArgs* args, DWORD* webviewIdPtr) { LPWSTR message = nullptr; args->TryGetWebMessageAsString(&message); if (message) { int len = WideCharToMultiByte(CP_UTF8, 0, message, -1, nullptr, 0, nullptr, nullptr); std::string utf8Message(len - 1, 0); WideCharToMultiByte(CP_UTF8, 0, message, -1, &utf8Message[0], len, nullptr, nullptr); int webviewId = (int)(intptr_t)webviewIdPtr; WebViewManager::instance().dispatchMessage(webviewId, utf8Message); CoTaskMemFree(message); } return S_OK; } 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 { onControllerReady(state, result, controller); return S_OK; }).Get()); } int platformCreate(WebViewInstance* wv) { ensureWebView2Environment(); auto* state = new WinWebViewState(); state->controller = nullptr; state->webview = nullptr; state->detachedHwnd = nullptr; 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; state->width = wv->width; state->height = wv->height; state->transparent = wv->transparent; wv->platformHandle = state; if (state->isDetached) { static const wchar_t* className = L"LNXRNT_WebViewWindow"; static bool registered = false; if (!registered) { WNDCLASSW wc = {}; wc.lpfnWndProc = DefWindowProcW; wc.hInstance = GetModuleHandle(nullptr); wc.lpszClassName = className; RegisterClassW(&wc); registered = true; } state->detachedHwnd = CreateWindowExW( WS_EX_APPWINDOW, className, utf8ToWide(wv->title).c_str(), WS_OVERLAPPEDWINDOW, wv->x, wv->y, wv->width, wv->height, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); state->parentHwnd = state->detachedHwnd; } else { 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); delete state; wv->platformHandle = nullptr; return -1; } } if (!g_webview2Env) { state->pending = true; g_pendingCreates.push_back(wv); kinc_log(KINC_LOG_LEVEL_INFO, "[WebView] Environment not ready, queuing creation id=%d", wv->id); return wv->id; } createController(state); return wv->id; } void platformDestroy(WebViewInstance* wv) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state) return; if (state->pending) { g_pendingCreates.erase( std::remove(g_pendingCreates.begin(), g_pendingCreates.end(), wv), g_pendingCreates.end()); state->pending = false; if (state->detachedHwnd) { DestroyWindow(state->detachedHwnd); state->detachedHwnd = nullptr; } delete state; wv->platformHandle = nullptr; return; } if (!state->controller) { state->destroyed = true; wv->platformHandle = nullptr; if (state->detachedHwnd) { 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(); } if (state->detachedHwnd) { DestroyWindow(state->detachedHwnd); } if (state->dcompShared) { releaseDCompShared(state->parentHwnd); state->dcompShared = nullptr; } delete state; wv->platformHandle = nullptr; } void platformLoadHTML(WebViewInstance* wv, const std::string& html) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state) return; if (!state->webview) { state->pendingHTML = utf8ToWide(html); state->pendingURL.clear(); return; } std::wstring wHtml = utf8ToWide(html); HRESULT navHr = state->webview->NavigateToString(wHtml.c_str()); } void platformLoadURL(WebViewInstance* wv, const std::string& url) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state) return; if (!state->webview) { state->pendingURL = utf8ToWide(url); state->pendingHTML.clear(); return; } state->webview->Navigate(utf8ToWide(url).c_str()); } void platformEvalJS(WebViewInstance* wv, const std::string& js) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->webview) return; state->webview->ExecuteScript(utf8ToWide(js).c_str(), nullptr); } void platformEvalJSAsync(WebViewInstance* wv, const std::string& js) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->webview) return; int webviewId = wv->id; state->webview->ExecuteScript(utf8ToWide(js).c_str(), Callback( [webviewId](HRESULT errorCode, LPCWSTR resultJson) -> HRESULT { std::string resultStr; if (FAILED(errorCode) || resultJson == nullptr) { resultStr = "null"; } else { std::wstring wstr(resultJson); 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; }).Get()); } void platformShow(WebViewInstance* wv) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state) return; if (state->isDetached && state->detachedHwnd) { ShowWindow(state->detachedHwnd, SW_SHOW); } if (state->controller) { state->controller->put_IsVisible(TRUE); } else { state->pendingShow = true; } } void platformHide(WebViewInstance* wv) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state) return; if (state->isDetached && state->detachedHwnd) { ShowWindow(state->detachedHwnd, SW_HIDE); } if (state->controller) { state->controller->put_IsVisible(FALSE); } } void platformResize(WebViewInstance* wv, int width, int height) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->controller) return; if (state->isDetached && state->detachedHwnd) { RECT rect = {0, 0, width, height}; AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE); SetWindowPos(state->detachedHwnd, nullptr, 0, 0, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOZORDER); } 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; bounds.right = wv->x + width; 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) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state) return; if (state->isDetached && state->detachedHwnd) { SetWindowPos(state->detachedHwnd, nullptr, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); } else if (state->controller) { RECT bounds; 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) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->controller) return; ICoreWebView2Controller2* ctrl2; if (SUCCEEDED(state->controller->QueryInterface(IID_PPV_ARGS(&ctrl2)))) { COREWEBVIEW2_COLOR bgColor; if (transparent) { bgColor = {0, 0, 0, 0}; } else { bgColor = {255, 255, 255, 255}; } ctrl2->put_DefaultBackgroundColor(bgColor); ctrl2->Release(); } } void platformSetClickThrough(WebViewInstance* wv, bool enabled) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->controller) return; if (enabled) { HWND childHwnd = FindWindowEx(state->parentHwnd, nullptr, L"WebView2", nullptr); if (childHwnd) { LONG_PTR exStyle = GetWindowLongPtr(childHwnd, GWL_EXSTYLE); SetWindowLongPtr(childHwnd, GWL_EXSTYLE, exStyle | WS_EX_TRANSPARENT); } } else { HWND childHwnd = FindWindowEx(state->parentHwnd, nullptr, L"WebView2", nullptr); if (childHwnd) { LONG_PTR exStyle = GetWindowLongPtr(childHwnd, GWL_EXSTYLE); SetWindowLongPtr(childHwnd, GWL_EXSTYLE, exStyle & ~WS_EX_TRANSPARENT); } } } void platformSetTitle(WebViewInstance* wv, const std::string& title) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->isDetached || !state->detachedHwnd) return; SetWindowTextW(state->detachedHwnd, utf8ToWide(title).c_str()); } void platformSend(WebViewInstance* wv, const std::string& message) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->webview) return; state->webview->PostWebMessageAsString(utf8ToWide(message).c_str()); } void platformGoBack(WebViewInstance* wv) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->webview) return; BOOL canGo = FALSE; state->webview->get_CanGoBack(&canGo); if (canGo) state->webview->GoBack(); } void platformGoForward(WebViewInstance* wv) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->webview) return; BOOL canGo = FALSE; state->webview->get_CanGoForward(&canGo); if (canGo) state->webview->GoForward(); } void platformReload(WebViewInstance* wv) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->webview) return; state->webview->Reload(); } bool platformCanGoBack(WebViewInstance* wv) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->webview) return false; BOOL canGo = FALSE; state->webview->get_CanGoBack(&canGo); return canGo != FALSE; } bool platformCanGoForward(WebViewInstance* wv) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->webview) return false; BOOL canGo = FALSE; state->webview->get_CanGoForward(&canGo); return canGo != FALSE; } std::string platformGetURL(WebViewInstance* wv) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->webview) return ""; LPWSTR uri = nullptr; state->webview->get_Source(&uri); std::string result; if (uri) { int len = WideCharToMultiByte(CP_UTF8, 0, uri, -1, nullptr, 0, nullptr, nullptr); result.resize(len - 1); WideCharToMultiByte(CP_UTF8, 0, uri, -1, &result[0], len, nullptr, nullptr); CoTaskMemFree(uri); } return result; } std::string platformGetTitle(WebViewInstance* wv) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->webview) return ""; LPWSTR title = nullptr; state->webview->get_DocumentTitle(&title); std::string result; if (title) { int len = WideCharToMultiByte(CP_UTF8, 0, title, -1, nullptr, 0, nullptr, nullptr); result.resize(len - 1); WideCharToMultiByte(CP_UTF8, 0, title, -1, &result[0], len, nullptr, nullptr); CoTaskMemFree(title); } return result; } void platformMinimize(WebViewInstance* wv) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->isDetached || !state->detachedHwnd) return; ShowWindow(state->detachedHwnd, SW_MINIMIZE); } void platformMaximize(WebViewInstance* wv) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->isDetached || !state->detachedHwnd) return; ShowWindow(state->detachedHwnd, SW_MAXIMIZE); } void platformRestore(WebViewInstance* wv) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->isDetached || !state->detachedHwnd) return; ShowWindow(state->detachedHwnd, SW_RESTORE); } void platformSetFullscreen(WebViewInstance* wv, bool fullscreen) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->isDetached || !state->detachedHwnd) return; LONG_PTR style = GetWindowLongPtr(state->detachedHwnd, GWL_STYLE); if (fullscreen) { SetWindowLongPtr(state->detachedHwnd, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW); HMONITOR hmon = MonitorFromWindow(state->detachedHwnd, MONITOR_DEFAULTTONEAREST); MONITORINFO mi = { sizeof(mi) }; GetMonitorInfo(hmon, &mi); SetWindowPos(state->detachedHwnd, HWND_TOP, mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right - mi.rcMonitor.left, mi.rcMonitor.bottom - mi.rcMonitor.top, SWP_NOOWNERZORDER | SWP_FRAMECHANGED); } else { SetWindowLongPtr(state->detachedHwnd, GWL_STYLE, style | WS_OVERLAPPEDWINDOW); SetWindowPos(state->detachedHwnd, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); } } bool platformIsFullscreen(WebViewInstance* wv) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->isDetached || !state->detachedHwnd) return false; LONG_PTR style = GetWindowLongPtr(state->detachedHwnd, GWL_STYLE); return (style & WS_OVERLAPPEDWINDOW) == 0; } void platformEnableDevTools(WebViewInstance* wv, bool enabled) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->webview) return; ICoreWebView2Settings* settings; if (SUCCEEDED(state->webview->get_Settings(&settings))) { ICoreWebView2Settings3* settings3; if (SUCCEEDED(settings->QueryInterface(IID_PPV_ARGS(&settings3)))) { settings3->put_AreDevToolsEnabled(enabled ? TRUE : FALSE); settings3->Release(); } settings->Release(); } } void platformSetContextMenuEnabled(WebViewInstance* wv, bool enabled) { auto* state = (WinWebViewState*)wv->platformHandle; if (!state || !state->webview) return; ICoreWebView2Settings* settings; if (SUCCEEDED(state->webview->get_Settings(&settings))) { settings->put_AreDefaultContextMenusEnabled(enabled ? TRUE : FALSE); settings->Release(); } } void platformTick() { // COM event loop is by Kores loop via PeekMessageW/DispatchMessageW. } #endif #endif