#ifdef WITH_WEBVIEW #ifdef KINC_WINDOWS #include "webview.h" #include #include #include #include #include #include #include #include "WebView2.h" #include "WebView2EnvironmentCreator.h" #pragma comment(lib, "WebView2Loader.lib") struct WinWebViewState { ICoreWebView2Controller* controller; ICoreWebView2* webview; HWND parentHwnd; HWND detachedHwnd; bool isDetached; bool destroyed; bool pending; EventRegistrationToken messageToken; EventRegistrationToken navCompletedToken; EventRegistrationToken errorToken; int webviewId; int x, y, width, height; bool transparent; 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; 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; WebViewManager::instance().dispatchLoad(webviewId); return S_OK; } static HRESULT STDMETHODCALLTYPE onProcessFailed( ICoreWebView2* sender, ICoreWebView2ProcessFailedEventArgs* args, DWORD* webviewIdPtr) { int webviewId = (int)(intptr_t)webviewIdPtr; WebViewManager::instance().dispatchError(webviewId, "WebView2 process failed"); return S_OK; } static void createController(WinWebViewState* state) { 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(); } 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->messageToken = {0}; state->navCompletedToken = {0}; state->errorToken = {0}; 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 { 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); 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; } return; } if (state->webview) { if (state->messageToken.value) state->webview->remove_WebMessageReceived(state->messageToken); if (state->navCompletedToken.value) state->webview->remove_NavigationCompleted(state->navCompletedToken); if (state->errorToken.value) state->webview->remove_ProcessFailed(state->errorToken); state->webview->Release(); } if (state->controller) { state->controller->Close(); state->controller->Release(); } if (state->detachedHwnd) { DestroyWindow(state->detachedHwnd); } 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; } state->webview->NavigateToString(utf8ToWide(html).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); resultStr = std::string(wstr.begin(), wstr.end()); } 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); } } 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 { bounds.left = wv->x; bounds.top = wv->y; bounds.right = wv->x + width; bounds.bottom = wv->y + height; } state->controller->put_Bounds(bounds); } 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; bounds.left = x; bounds.top = y; bounds.right = x + wv->width; bounds.bottom = y + wv->height; state->controller->put_Bounds(bounds); } } 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 pumped by Kincs loop. } #endif #endif