From 9388022b72d3a1fbeb4f2168efc5d28f66a65b3f Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Mon, 8 Dec 2025 00:41:59 -0600 Subject: [PATCH 01/17] Add new member variables for action handling Added isCreationInProgress boolean and onFinishAction and onPageLoadAction --- WebView2/Plugin.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WebView2/Plugin.h b/WebView2/Plugin.h index 0ea5939..a7f650a 100644 --- a/WebView2/Plugin.h +++ b/WebView2/Plugin.h @@ -29,7 +29,10 @@ struct Measure bool visible; bool initialized; bool clickthrough; - + bool isCreationInProgress = false; + std::wstring onFinishAction; + std::wstring onPageLoadAction; + wil::com_ptr webViewController; wil::com_ptr webView; EventRegistrationToken webMessageToken; From aa35a930ee175806bb983d6aeb8aa7ec9d058f90 Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Mon, 8 Dec 2025 00:44:54 -0600 Subject: [PATCH 02/17] Add OnFinishAction and OnPageLoadAction handling Added OnFinishAction and OnPageLoadAction handling and fixed a race condition when creating WebView by using the new isCreationInProgress boolean. --- WebView2/Plugin.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/WebView2/Plugin.cpp b/WebView2/Plugin.cpp index 239d3aa..66b0ad9 100644 --- a/WebView2/Plugin.cpp +++ b/WebView2/Plugin.cpp @@ -185,9 +185,26 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) bool newVisible = RmReadInt(rm, L"Hidden", 0) == 0; bool newClickthrough = RmReadInt(rm, L"Clickthrough", 0) != 0; + // Read OnFinishAction + std::wstring newOnFinishAction; + LPCWSTR onFinishOption = RmReadString(rm, L"OnFinishAction", L"", FALSE); + if (onFinishOption && wcslen(onFinishOption) > 0) + { + newOnFinishAction = onFinishOption; + } + + // Read OnPageLoadAction + std::wstring newOnPageLoadAction; + LPCWSTR onPageLoadOption = RmReadString(rm, L"OnPageLoadAction", L"", FALSE); + if (onPageLoadOption && wcslen(onPageLoadOption) > 0) + { + newOnPageLoadAction = onPageLoadOption; + } + // Check if URL has changed (requires recreation) bool urlChanged = (newUrl != measure->url); + // Check if dimensions or position changed (can be updated dynamically) bool dimensionsChanged = (newWidth != measure->width || newHeight != measure->height || @@ -205,7 +222,9 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) measure->y = newY; measure->visible = newVisible; measure->clickthrough = newClickthrough; - + measure->onFinishAction = newOnFinishAction; + measure->onPageLoadAction = newOnPageLoadAction; + // Only create WebView2 if not initialized OR if URL changed if (!measure->initialized || urlChanged) { @@ -220,7 +239,13 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) else { // First initialization - create WebView2 + if (measure->isCreationInProgress) + { + // Avoid re-entrancy if creation is already in progress + return; + } CreateWebView2(measure); + } } else From 67d78120fea1edb1e8d31fb090da2d412b9d4fcc Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Mon, 8 Dec 2025 00:48:13 -0600 Subject: [PATCH 03/17] Prevent multiple creation of WebView2 environment Added checks to prevent multiple creation attempts of WebView2 environment and controller. Removed unnecessary global meter and redraw bangs. Changed Initialized notice to debug. Added onPageLoadAction and onFinishAction. --- WebView2/WebView2.cpp | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/WebView2/WebView2.cpp b/WebView2/WebView2.cpp index a3db632..854b2e9 100644 --- a/WebView2/WebView2.cpp +++ b/WebView2/WebView2.cpp @@ -6,12 +6,19 @@ // Create WebView2 environment and controller void CreateWebView2(Measure* measure) { + if (measure && measure->isCreationInProgress) + { + return; + } + if (!measure || !measure->skinWindow) { if (measure && measure->rm) RmLog(measure->rm, LOG_ERROR, L"WebView2: Invalid measure or skin window"); return; } + + measure->isCreationInProgress = true; // Create user data folder in TEMP directory to avoid permission issues wchar_t tempPath[MAX_PATH]; @@ -28,6 +35,7 @@ void CreateWebView2(Measure* measure) measure, &Measure::CreateEnvironmentHandler ).Get() + ); if (FAILED(hr)) @@ -46,6 +54,7 @@ HRESULT Measure::CreateEnvironmentHandler(HRESULT result, ICoreWebView2Environme { if (FAILED(result)) { + isCreationInProgress = false; if (rm) { wchar_t errorMsg[256]; @@ -70,6 +79,7 @@ HRESULT Measure::CreateEnvironmentHandler(HRESULT result, ICoreWebView2Environme // Controller creation callback HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller* controller) { + if (FAILED(result)) { if (rm) @@ -78,6 +88,7 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller swprintf_s(errorMsg, L"WebView2: Failed to create controller (HRESULT: 0x%08X)", result); RmLog(rm, LOG_ERROR, errorMsg); } + isCreationInProgress = false; return result; } @@ -85,6 +96,7 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller { if (rm) RmLog(rm, LOG_ERROR, L"WebView2: Controller is null"); + isCreationInProgress = false; return S_FALSE; } @@ -166,16 +178,15 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller // Store the callback result if (!result.empty() && result != L"null") { - callbackResult = result; - - // Trigger Rainmeter redraw after callback completes - if (skin) - { - RmExecute(skin, L"!UpdateMeter *"); - RmExecute(skin, L"!Redraw"); - } + callbackResult = result; } } + + if (wcslen(onPageLoadAction.c_str()) > 0) + { + RmExecute(skin, onPageLoadAction.c_str()); + } + return S_OK; } ).Get() @@ -194,11 +205,19 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller initialized = true; + isCreationInProgress = false; + if (rm) - RmLog(rm, LOG_NOTICE, L"WebView2: Initialized successfully with COM Host Objects"); + RmLog(rm, LOG_DEBUG, L"WebView2: Initialized successfully with COM Host Objects"); + if (wcslen(onFinishAction.c_str()) > 0) + { + RmExecute(skin, onFinishAction.c_str()); + } + // Apply initial clickthrough state UpdateClickthrough(this); return S_OK; } + From c3643f743b9f5d92836073c85df6011835d07896 Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Mon, 8 Dec 2025 01:34:14 -0600 Subject: [PATCH 04/17] Refactor WebView2 creation logic for better validation Re-ordered creation check added check for initialization added missing reset when hr fails. --- WebView2/WebView2.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/WebView2/WebView2.cpp b/WebView2/WebView2.cpp index 854b2e9..5b51a7c 100644 --- a/WebView2/WebView2.cpp +++ b/WebView2/WebView2.cpp @@ -6,15 +6,20 @@ // Create WebView2 environment and controller void CreateWebView2(Measure* measure) { - if (measure && measure->isCreationInProgress) + if (!measure || !measure->skinWindow) { + if (measure && measure->rm) + RmLog(measure->rm, LOG_ERROR, L"WebView2: Invalid measure or skin window"); return; } - if (!measure || !measure->skinWindow) + if (measure->initialized) + { + return; + } + + if (measure->isCreationInProgress) { - if (measure && measure->rm) - RmLog(measure->rm, LOG_ERROR, L"WebView2: Invalid measure or skin window"); return; } @@ -40,6 +45,7 @@ void CreateWebView2(Measure* measure) if (FAILED(hr)) { + measure->isCreationInProgress = false; if (measure->rm) { wchar_t errorMsg[512]; From 8e1c7f2ac0abc8ba3f1a5f829681468084e1be5b Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:21:16 -0600 Subject: [PATCH 05/17] Remove unnecessary Rainmeter redraw triggers Removed redundant redraw calls after callback completion. This was causing CallJS to execute twice.. --- WebView2/Plugin.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/WebView2/Plugin.cpp b/WebView2/Plugin.cpp index 66b0ad9..49c02e2 100644 --- a/WebView2/Plugin.cpp +++ b/WebView2/Plugin.cpp @@ -300,13 +300,6 @@ PLUGIN_EXPORT double Update(void* data) { measure->callbackResult = result; } - - // Trigger Rainmeter redraw after callback completes - if (measure->skin) - { - RmExecute(measure->skin, L"!UpdateMeter *"); - RmExecute(measure->skin, L"!Redraw"); - } } return S_OK; } From e0111e18e319db154d709db96fc3be749c03280d Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:31:56 -0600 Subject: [PATCH 06/17] Implement synchronous script execution in WebView2 Ok, here's my attempt to solve the sync issue. Unfortunately, all my attempts to add a similar solution for window.OnInitialize and CallJS failed, both create a deadlock when implemented this way. However, this appears to solve the issue on window.OnUpdate which I think is the most important. Please build my Working2 branch and test it, you should see now both Status: Update #N on Rainmeter and on WebView are correctly synced. As well as the Measure's string value. Also add OnPageLoadAction=[!UpdateMeter *][!Redraw] on the WebView measure to correctly see the "Initialized!" message on the status string. --- WebView2/Plugin.cpp | 130 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 103 insertions(+), 27 deletions(-) diff --git a/WebView2/Plugin.cpp b/WebView2/Plugin.cpp index 49c02e2..94c456e 100644 --- a/WebView2/Plugin.cpp +++ b/WebView2/Plugin.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2025 nstechbytes. All rights reserved. #include "Plugin.h" #include "../API/RainmeterAPI.h" +#include #pragma comment(lib, "ole32.lib") #pragma comment(lib, "oleaut32.lib") @@ -274,40 +275,114 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) } } +// Synchronously execute script on the same thread as the WebView (pumps messages). +static std::wstring ExecuteScriptSync(ICoreWebView2* webview, const std::wstring& script, DWORD timeoutMs = 300) +{ + if (!webview) return L""; + + HANDLE hEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (!hEvent) return L""; + + std::wstring resultJson; + HRESULT execHr = E_FAIL; + + auto handler = Callback( + [&resultJson, &execHr, hEvent](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT + { + execHr = errorCode; + if (SUCCEEDED(errorCode) && resultObjectAsJson) + { + resultJson.assign(resultObjectAsJson); + } + SetEvent(hEvent); + return S_OK; + } + ); + + HRESULT hr = webview->ExecuteScript(script.c_str(), handler.Get()); + if (FAILED(hr)) + { + CloseHandle(hEvent); + return L""; + } + + auto start = std::chrono::steady_clock::now(); + for (;;) + { + auto elapsed = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start).count(); + DWORD remaining = (elapsed >= timeoutMs) ? 0 : static_cast(timeoutMs - elapsed); + + DWORD wait = ::MsgWaitForMultipleObjects(1, &hEvent, FALSE, remaining, QS_ALLINPUT); + + if (wait == WAIT_OBJECT_0) + { + // event signaled + break; + } + else if (wait == WAIT_OBJECT_0 + 1) + { + // pump messages so callback runs + MSG msg; + while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + // continue loop (respect remaining timeout) + } + else if (wait == WAIT_TIMEOUT) + { + // timeout; break and treat as failure/empty + break; + } + else + { + // unexpected error; break + break; + } + } + + CloseHandle(hEvent); + + if (FAILED(execHr)) + { + return L""; + } + + return resultJson; +} + PLUGIN_EXPORT double Update(void* data) { Measure* measure = (Measure*)data; - // Call JavaScript OnUpdate callback if WebView is initialized - if (measure->initialized && measure->webView) + // If not initialized return 0.0 + if (!measure->initialized || !measure->webView) + return 0.0; + + std::wstring script = L"(function() { try { if (typeof window.OnUpdate === 'function') { var result = window.OnUpdate(); return result !== undefined ? String(result) : ''; } return ''; } catch(e) { return ''; } })();"; + + // Synchronously execute the script on the plugin main thread, pumping messages. + std::wstring json = ExecuteScriptSync(measure->webView.get(), script, 500); + + if (!json.empty()) { - measure->webView->ExecuteScript( - L"(function() { if (typeof window.OnUpdate === 'function') { var result = window.OnUpdate(); return result !== undefined ? String(result) : ''; } return ''; })();", - Callback( - [measure](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT - { - if (SUCCEEDED(errorCode) && resultObjectAsJson) - { - // Remove quotes from JSON string result - std::wstring result = resultObjectAsJson; - if (result.length() >= 2 && result.front() == L'"' && result.back() == L'"') - { - result = result.substr(1, result.length() - 2); - } - - // Store the callback result - if (!result.empty() && result != L"null") - { - measure->callbackResult = result; - } - } - return S_OK; - } - ).Get() - ); + std::wstring result = json; + if (result.length() >= 2 && result.front() == L'"' && result.back() == L'"') + { + result = result.substr(1, result.length() - 2); + } + + // Store the latest result + if (!result.empty() && result != L"null") + { + measure->callbackResult = result; + } } - return measure->initialized ? 1.0 : 0.0; + // Return 1.0 when initialized + return 1.0; } PLUGIN_EXPORT LPCWSTR GetString(void* data) @@ -322,6 +397,7 @@ PLUGIN_EXPORT LPCWSTR GetString(void* data) return L"0"; } + PLUGIN_EXPORT void ExecuteBang(void* data, LPCWSTR args) { Measure* measure = (Measure*)data; From 38992007632d59f1cc4815b44438c5d8e124fac7 Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Tue, 9 Dec 2025 11:38:44 -0600 Subject: [PATCH 07/17] Restore to previous working version. Removed synchronous script execution function and adjusted script execution logic in Update function. --- WebView2/Plugin.cpp | 130 ++++++++++---------------------------------- 1 file changed, 28 insertions(+), 102 deletions(-) diff --git a/WebView2/Plugin.cpp b/WebView2/Plugin.cpp index 94c456e..9e790c5 100644 --- a/WebView2/Plugin.cpp +++ b/WebView2/Plugin.cpp @@ -1,7 +1,6 @@ // Copyright (C) 2025 nstechbytes. All rights reserved. #include "Plugin.h" #include "../API/RainmeterAPI.h" -#include #pragma comment(lib, "ole32.lib") #pragma comment(lib, "oleaut32.lib") @@ -275,114 +274,41 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) } } -// Synchronously execute script on the same thread as the WebView (pumps messages). -static std::wstring ExecuteScriptSync(ICoreWebView2* webview, const std::wstring& script, DWORD timeoutMs = 300) -{ - if (!webview) return L""; - - HANDLE hEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr); - if (!hEvent) return L""; - - std::wstring resultJson; - HRESULT execHr = E_FAIL; - - auto handler = Callback( - [&resultJson, &execHr, hEvent](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT - { - execHr = errorCode; - if (SUCCEEDED(errorCode) && resultObjectAsJson) - { - resultJson.assign(resultObjectAsJson); - } - SetEvent(hEvent); - return S_OK; - } - ); - - HRESULT hr = webview->ExecuteScript(script.c_str(), handler.Get()); - if (FAILED(hr)) - { - CloseHandle(hEvent); - return L""; - } - - auto start = std::chrono::steady_clock::now(); - for (;;) - { - auto elapsed = std::chrono::duration_cast( - std::chrono::steady_clock::now() - start).count(); - DWORD remaining = (elapsed >= timeoutMs) ? 0 : static_cast(timeoutMs - elapsed); - - DWORD wait = ::MsgWaitForMultipleObjects(1, &hEvent, FALSE, remaining, QS_ALLINPUT); - - if (wait == WAIT_OBJECT_0) - { - // event signaled - break; - } - else if (wait == WAIT_OBJECT_0 + 1) - { - // pump messages so callback runs - MSG msg; - while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - // continue loop (respect remaining timeout) - } - else if (wait == WAIT_TIMEOUT) - { - // timeout; break and treat as failure/empty - break; - } - else - { - // unexpected error; break - break; - } - } - - CloseHandle(hEvent); - - if (FAILED(execHr)) - { - return L""; - } - - return resultJson; -} - PLUGIN_EXPORT double Update(void* data) { Measure* measure = (Measure*)data; - // If not initialized return 0.0 - if (!measure->initialized || !measure->webView) - return 0.0; - - std::wstring script = L"(function() { try { if (typeof window.OnUpdate === 'function') { var result = window.OnUpdate(); return result !== undefined ? String(result) : ''; } return ''; } catch(e) { return ''; } })();"; - - // Synchronously execute the script on the plugin main thread, pumping messages. - std::wstring json = ExecuteScriptSync(measure->webView.get(), script, 500); - - if (!json.empty()) + // Call JavaScript OnUpdate callback if WebView is initialized + if (measure->initialized && measure->webView) { - std::wstring result = json; - if (result.length() >= 2 && result.front() == L'"' && result.back() == L'"') - { - result = result.substr(1, result.length() - 2); - } - - // Store the latest result - if (!result.empty() && result != L"null") - { - measure->callbackResult = result; - } + measure->webView->ExecuteScript( + L"(function() { if (typeof window.OnUpdate === 'function') { var result = window.OnUpdate(); return result !== undefined ? String(result) : ''; } return ''; })();", + Callback( + [measure](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT + { + if (SUCCEEDED(errorCode) && resultObjectAsJson) + { + // Remove quotes from JSON string result + std::wstring result = resultObjectAsJson; + if (result.length() >= 2 && result.front() == L'"' && result.back() == L'"') + { + result = result.substr(1, result.length() - 2); + } + + // Store the callback result + if (!result.empty() && result != L"null") + { + measure->callbackResult = result; + } + + } + return S_OK; + } + ).Get() + ); } - // Return 1.0 when initialized - return 1.0; + return measure->initialized ? 1.0 : 0.0; } PLUGIN_EXPORT LPCWSTR GetString(void* data) From fcfc0772abdf6eeb1d64a0909c03cfe2ecc508f5 Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Tue, 9 Dec 2025 23:28:34 -0600 Subject: [PATCH 08/17] Add new properties and UpdateRaincontext function Renamed `OnFinishAction` to `OnWebViewLoadAction` Added actions: `OnWebViewFailAction` `OnPageFirstLoadAction` `OnPageReloadAction` Added `raincontext` and `isFirstLoad` booleans. Added `UpdateRaincontext()` function. --- WebView2/Plugin.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/WebView2/Plugin.h b/WebView2/Plugin.h index a7f650a..5a0ce63 100644 --- a/WebView2/Plugin.h +++ b/WebView2/Plugin.h @@ -30,8 +30,13 @@ struct Measure bool initialized; bool clickthrough; bool isCreationInProgress = false; - std::wstring onFinishAction; + bool raincontext; + std::wstring OnWebViewLoadAction; + std::wstring OnWebViewFailAction; + std::wstring onPageFirstLoadAction; std::wstring onPageLoadAction; + std::wstring onPageReloadAction; + bool isFirstLoad = true; wil::com_ptr webViewController; wil::com_ptr webView; @@ -52,4 +57,5 @@ struct Measure // WebView2 functions void CreateWebView2(Measure* measure); void UpdateClickthrough(Measure* measure); +void UpdateRaincontext(Measure* measure); From b48bf009af036a87f0a015a2c525dedb262891b2 Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Tue, 9 Dec 2025 23:40:27 -0600 Subject: [PATCH 09/17] Add raincontext management to Measure class Added raincontext handling to the Measure class, including updates to the Reload function for new actions. Implemented UpdateRaincontext function to manage the raincontext state within the WebView. --- WebView2/Plugin.cpp | 106 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 22 deletions(-) diff --git a/WebView2/Plugin.cpp b/WebView2/Plugin.cpp index 9e790c5..8f4cab5 100644 --- a/WebView2/Plugin.cpp +++ b/WebView2/Plugin.cpp @@ -52,7 +52,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) Measure::Measure() : rm(nullptr), skin(nullptr), skinWindow(nullptr), measureName(nullptr), width(800), height(600), x(0), y(0), - visible(true), initialized(false), clickthrough(false), webMessageToken{} + visible(true), initialized(false), clickthrough(false), raincontext(true), webMessageToken{} { // Initialize COM for this thread if not already done if (!g_comInitialized) @@ -129,6 +129,35 @@ void UpdateClickthrough(Measure* measure) } } +void UpdateRaincontext(Measure* measure) +{ + if (!measure->webView) return; + + if (measure->raincontext) + { + measure->webView->ExecuteScript( + L"rm_setRaincontext(true);", + Callback( + [measure](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT + { + return S_OK; + } + ).Get() + ); + } + else { + measure->webView->ExecuteScript( + L"rm_setRaincontext(false);", + Callback( + [measure](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT + { + return S_OK; + } + ).Get() + ); + } +} + PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) { Measure* measure = (Measure*)data; @@ -182,24 +211,49 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) int newHeight = RmReadInt(rm, L"H", 600); int newX = RmReadInt(rm, L"X", 0); int newY = RmReadInt(rm, L"Y", 0); - bool newVisible = RmReadInt(rm, L"Hidden", 0) == 0; - bool newClickthrough = RmReadInt(rm, L"Clickthrough", 0) != 0; + bool newVisible = RmReadInt(rm, L"Hidden", 0) <= 0; + bool newClickthrough = RmReadInt(rm, L"Clickthrough", 0) >= 1; + bool newRaincontext = RmReadInt(rm, L"Raincontext", 1) >= 1; - // Read OnFinishAction - std::wstring newOnFinishAction; - LPCWSTR onFinishOption = RmReadString(rm, L"OnFinishAction", L"", FALSE); - if (onFinishOption && wcslen(onFinishOption) > 0) - { - newOnFinishAction = onFinishOption; - } + // Read OnWebViewLoadAction + std::wstring newOnWebViewLoadAction; + LPCWSTR onWebViewLoadOption = RmReadString(rm, L"OnWebViewLoadAction", L"", FALSE); + if (onWebViewLoadOption && wcslen(onWebViewLoadOption) > 0) + { + newOnWebViewLoadAction = onWebViewLoadOption; + } + + // Read OnWebViewFailAction + std::wstring newOnWebViewFailAction; + LPCWSTR onWebViewFailOption = RmReadString(rm, L"OnWebViewFailAction", L"", FALSE); + if (onWebViewFailOption && wcslen(onWebViewFailOption) > 0) + { + newOnWebViewFailAction = onWebViewFailOption; + } - // Read OnPageLoadAction - std::wstring newOnPageLoadAction; - LPCWSTR onPageLoadOption = RmReadString(rm, L"OnPageLoadAction", L"", FALSE); - if (onPageLoadOption && wcslen(onPageLoadOption) > 0) - { - newOnPageLoadAction = onPageLoadOption; - } + // Read OnPageFirstLoadAction + std::wstring newOnPageFirstLoadAction; + LPCWSTR onPageFirstLoadOption = RmReadString(rm, L"OnPageFirstLoadAction", L"", FALSE); + if (onPageFirstLoadOption && wcslen(onPageFirstLoadOption) > 0) + { + newOnPageFirstLoadAction = onPageFirstLoadOption; + } + + // Read OnPageLoadAction + std::wstring newOnPageLoadAction; + LPCWSTR onPageLoadOption = RmReadString(rm, L"OnPageLoadAction", L"", FALSE); + if (onPageLoadOption && wcslen(onPageLoadOption) > 0) + { + newOnPageLoadAction = onPageLoadOption; + } + + // Read OnPageReloadAction + std::wstring newOnPageReloadAction; + LPCWSTR onPageReloadOption = RmReadString(rm, L"OnPageReloadAction", L"", FALSE); + if (onPageReloadOption && wcslen(onPageReloadOption) > 0) + { + newOnPageReloadAction = onPageReloadOption; + } // Check if URL has changed (requires recreation) bool urlChanged = (newUrl != measure->url); @@ -213,7 +267,8 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) bool visibilityChanged = (newVisible != measure->visible); bool clickthroughChanged = (newClickthrough != measure->clickthrough); - + bool raincontextChanged = (newRaincontext != measure->raincontext); + // Update stored values measure->url = newUrl; measure->width = newWidth; @@ -222,8 +277,12 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) measure->y = newY; measure->visible = newVisible; measure->clickthrough = newClickthrough; - measure->onFinishAction = newOnFinishAction; - measure->onPageLoadAction = newOnPageLoadAction; + measure->raincontext = newRaincontext; + measure->OnWebViewLoadAction = newOnWebViewLoadAction; + measure->OnWebViewFailAction = newOnWebViewFailAction; + measure->onPageFirstLoadAction = newOnPageFirstLoadAction; + measure->onPageLoadAction = newOnPageLoadAction; + measure->onPageReloadAction = newOnPageReloadAction; // Only create WebView2 if not initialized OR if URL changed if (!measure->initialized || urlChanged) @@ -245,7 +304,6 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) return; } CreateWebView2(measure); - } } else @@ -271,6 +329,11 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) { UpdateClickthrough(measure); } + + if (raincontextChanged) + { + UpdateRaincontext(measure); + } } } @@ -300,7 +363,6 @@ PLUGIN_EXPORT double Update(void* data) { measure->callbackResult = result; } - } return S_OK; } From edb5084af8e1c182c7088b143c181537bff447b3 Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Wed, 10 Dec 2025 00:04:51 -0600 Subject: [PATCH 10/17] Added handling of new actions and raincontext. --- WebView2/WebView2.cpp | 71 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/WebView2/WebView2.cpp b/WebView2/WebView2.cpp index 5b51a7c..f3c3533 100644 --- a/WebView2/WebView2.cpp +++ b/WebView2/WebView2.cpp @@ -45,13 +45,17 @@ void CreateWebView2(Measure* measure) if (FAILED(hr)) { - measure->isCreationInProgress = false; if (measure->rm) { wchar_t errorMsg[512]; swprintf_s(errorMsg, L"WebView2: Failed to start creation process (HRESULT: 0x%08X). Make sure WebView2 Runtime is installed.", hr); RmLog(measure->rm, LOG_ERROR, errorMsg); } + if (measure->skin && wcslen(measure->OnWebViewFailAction.c_str()) > 0) + { + RmExecute(measure->skin, measure->OnWebViewFailAction.c_str()); + } + measure->isCreationInProgress = false; } } @@ -60,13 +64,17 @@ HRESULT Measure::CreateEnvironmentHandler(HRESULT result, ICoreWebView2Environme { if (FAILED(result)) { - isCreationInProgress = false; if (rm) { wchar_t errorMsg[256]; swprintf_s(errorMsg, L"WebView2: Failed to create environment (HRESULT: 0x%08X)", result); RmLog(rm, LOG_ERROR, errorMsg); } + if (skin && wcslen(OnWebViewFailAction.c_str()) > 0) + { + RmExecute(skin, OnWebViewFailAction.c_str()); + } + isCreationInProgress = false; return result; } @@ -94,6 +102,10 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller swprintf_s(errorMsg, L"WebView2: Failed to create controller (HRESULT: 0x%08X)", result); RmLog(rm, LOG_ERROR, errorMsg); } + if (skin && wcslen(OnWebViewFailAction.c_str()) > 0) + { + RmExecute(skin, OnWebViewFailAction.c_str()); + } isCreationInProgress = false; return result; } @@ -102,6 +114,10 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller { if (rm) RmLog(rm, LOG_ERROR, L"WebView2: Controller is null"); + if (skin && wcslen(OnWebViewFailAction.c_str()) > 0) + { + RmExecute(skin, OnWebViewFailAction.c_str()); + } isCreationInProgress = false; return S_FALSE; } @@ -166,6 +182,20 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller Callback( [this](ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT { + // Inject script to capture page load events for drag/move and context menu + webView->ExecuteScript( + L"let rm_raincontext=false,rm_RaincontextOn=false,rm_RaincontextClientX=0,rm_RaincontextClientY=0;function rm_setRaincontext(v){rm_raincontext=!!v;if(!rm_raincontext)rm_RaincontextOn=false;}document.body.onpointerdown=e=>{if(!rm_raincontext)return;if(e.button===0&&e.ctrlKey){e.preventDefault();e.stopImmediatePropagation();rm_RaincontextOn=true;rm_RaincontextClientX=e.clientX;rm_RaincontextClientY=e.clientY;try{document.body.setPointerCapture(e.pointerId);}catch{}}};document.body.onpointermove=e=>{if(!rm_raincontext||!rm_RaincontextOn)return;e.preventDefault();RainmeterAPI.Bang('[!Move '+(e.screenX-RainmeterAPI.ReadFormula('X',0)-rm_RaincontextClientX)+' '+(e.screenY-RainmeterAPI.ReadFormula('Y',0)-rm_RaincontextClientY)+']');};document.body.onpointerup=e=>{if(!rm_raincontext)return;if(e.button===0){e.preventDefault();rm_RaincontextOn=false;try{document.body.releasePointerCapture(e.pointerId);}catch{}}};document.body.oncontextmenu=e=>{if(!rm_raincontext)return;if(e.button===2&&e.ctrlKey){e.preventDefault();RainmeterAPI.Bang('[!SkinMenu]');}};", + Callback( + [this](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT + { + return S_OK; + } + ).Get() + ); + + // Apply initial raincontext state + UpdateRaincontext(this); + // Call JavaScript OnInitialize callback if it exists and capture return value webView->ExecuteScript( L"(function() { if (typeof window.OnInitialize === 'function') { var result = window.OnInitialize(); return result !== undefined ? String(result) : ''; } return ''; })();", @@ -188,10 +218,28 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller } } - if (wcslen(onPageLoadAction.c_str()) > 0) - { - RmExecute(skin, onPageLoadAction.c_str()); - } + if (isFirstLoad) + { + if (wcslen(onPageFirstLoadAction.c_str()) > 0) + { + if (skin) + RmExecute(skin, onPageFirstLoadAction.c_str()); + } + isFirstLoad = false; + } + else { + if (wcslen(onPageReloadAction.c_str()) > 0) + { + if (skin) + RmExecute(skin, onPageReloadAction.c_str()); + } + } + + if (wcslen(onPageLoadAction.c_str()) > 0) + { + if (skin) + RmExecute(skin, onPageLoadAction.c_str()); + } return S_OK; } @@ -214,16 +262,15 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller isCreationInProgress = false; if (rm) - RmLog(rm, LOG_DEBUG, L"WebView2: Initialized successfully with COM Host Objects"); + RmLog(rm, LOG_NOTICE, L"WebView2: Initialized successfully with COM Host Objects"); - if (wcslen(onFinishAction.c_str()) > 0) - { - RmExecute(skin, onFinishAction.c_str()); - } + if (wcslen(OnWebViewLoadAction.c_str()) > 0) + { + RmExecute(skin, OnWebViewLoadAction.c_str()); + } // Apply initial clickthrough state UpdateClickthrough(this); return S_OK; } - From 8e397a91a2795113d08ba63789a1adcbe17e06db Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:40:48 -0600 Subject: [PATCH 11/17] Refactor for dual control handling Added new actions. --- WebView2/Plugin.h | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/WebView2/Plugin.h b/WebView2/Plugin.h index 5a0ce63..b554e40 100644 --- a/WebView2/Plugin.h +++ b/WebView2/Plugin.h @@ -30,13 +30,17 @@ struct Measure bool initialized; bool clickthrough; bool isCreationInProgress = false; - bool raincontext; - std::wstring OnWebViewLoadAction; - std::wstring OnWebViewFailAction; + bool isFirstLoad = true; + bool allowDualControl; + bool isAllowDualControlInjected = false; + + std::wstring onWebViewLoadAction; + std::wstring onWebViewFailAction; std::wstring onPageFirstLoadAction; - std::wstring onPageLoadAction; + std::wstring onPageLoadStartAction; + std::wstring onPageLoadingAction; + std::wstring onPageLoadFinishAction; std::wstring onPageReloadAction; - bool isFirstLoad = true; wil::com_ptr webViewController; wil::com_ptr webView; @@ -57,5 +61,7 @@ struct Measure // WebView2 functions void CreateWebView2(Measure* measure); void UpdateClickthrough(Measure* measure); -void UpdateRaincontext(Measure* measure); +void InjectAllowDualControl(Measure* measure); +void UpdateAllowDualControl(Measure* measure); + From 90c430d2a5898028826fab839390835eaa54ad19 Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:51:40 -0600 Subject: [PATCH 12/17] Implement AllowDualControl feature Raincontext is now called AllowDualControl. The script is no longer injected when the option is disabled. Added new actions. --- WebView2/Plugin.cpp | 151 ++++++++++++++++++++++++++++---------------- 1 file changed, 97 insertions(+), 54 deletions(-) diff --git a/WebView2/Plugin.cpp b/WebView2/Plugin.cpp index 8f4cab5..bca0b66 100644 --- a/WebView2/Plugin.cpp +++ b/WebView2/Plugin.cpp @@ -52,7 +52,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) Measure::Measure() : rm(nullptr), skin(nullptr), skinWindow(nullptr), measureName(nullptr), width(800), height(600), x(0), y(0), - visible(true), initialized(false), clickthrough(false), raincontext(true), webMessageToken{} + visible(true), initialized(false), clickthrough(false), allowDualControl(true), webMessageToken{} { // Initialize COM for this thread if not already done if (!g_comInitialized) @@ -129,33 +129,52 @@ void UpdateClickthrough(Measure* measure) } } -void UpdateRaincontext(Measure* measure) +// Inject AllowDualControl script into the WebView +void InjectAllowDualControl(Measure* measure) { - if (!measure->webView) return; + if (!measure->webView) return; + // Inject script to capture page load events for drag/move and context menu + measure->webView->ExecuteScript( + L"let rm_AllowDualControl=false,rm_AllowDualControlOn=false,rm_AllowDualControlClientX=0,rm_AllowDualControlClientY=0;function rm_SetAllowDualControl(v){rm_AllowDualControl=!!v;if(!rm_AllowDualControl)rm_AllowDualControlOn=false;}document.body.onpointerdown=e=>{if(!rm_AllowDualControl)return;if(e.button===0&&e.ctrlKey){e.preventDefault();e.stopImmediatePropagation();rm_AllowDualControlOn=true;rm_AllowDualControlClientX=e.clientX;rm_AllowDualControlClientY=e.clientY;try{document.body.setPointerCapture(e.pointerId);}catch{}}};document.body.onpointermove=e=>{if(!rm_AllowDualControl||!rm_AllowDualControlOn)return;e.preventDefault();RainmeterAPI.Bang('[!Move '+(e.screenX-RainmeterAPI.ReadFormula('X',0)-rm_AllowDualControlClientX)+' '+(e.screenY-RainmeterAPI.ReadFormula('Y',0)-rm_AllowDualControlClientY)+']');};document.body.onpointerup=e=>{if(!rm_AllowDualControl)return;if(e.button===0){e.preventDefault();rm_AllowDualControlOn=false;try{document.body.releasePointerCapture(e.pointerId);}catch{}}};document.body.oncontextmenu=e=>{if(!rm_AllowDualControl)return;if(e.button===2&&e.ctrlKey){e.preventDefault();RainmeterAPI.Bang('[!SkinMenu]');}};", + Callback( + [measure](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT + { + return S_OK; + } + ).Get() + ); + measure->isAllowDualControlInjected = true; + UpdateAllowDualControl(measure); +} - if (measure->raincontext) - { - measure->webView->ExecuteScript( - L"rm_setRaincontext(true);", - Callback( - [measure](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT - { - return S_OK; - } - ).Get() - ); - } - else { - measure->webView->ExecuteScript( - L"rm_setRaincontext(false);", - Callback( - [measure](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT - { - return S_OK; - } - ).Get() - ); - } +// Update AllowDualControl state in the WebView +void UpdateAllowDualControl(Measure* measure) +{ + if (!measure->webView) return; + + if (measure->allowDualControl) + { + measure->webView->ExecuteScript( + L"rm_SetAllowDualControl(true);", + Callback( + [measure](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT + { + return S_OK; + } + ).Get() + ); + } + else { + measure->webView->ExecuteScript( + L"rm_SetAllowDualControl(false);", + Callback( + [measure](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT + { + return S_OK; + } + ).Get() + ); + } } PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) @@ -213,22 +232,48 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) int newY = RmReadInt(rm, L"Y", 0); bool newVisible = RmReadInt(rm, L"Hidden", 0) <= 0; bool newClickthrough = RmReadInt(rm, L"Clickthrough", 0) >= 1; - bool newRaincontext = RmReadInt(rm, L"Raincontext", 1) >= 1; - + + // Read AllowDualControl for Yincognito's script injection + bool newAllowDualControl = RmReadInt(rm, L"AllowDualControl", 1) >= 1; + // Read OnWebViewLoadAction std::wstring newOnWebViewLoadAction; LPCWSTR onWebViewLoadOption = RmReadString(rm, L"OnWebViewLoadAction", L"", FALSE); if (onWebViewLoadOption && wcslen(onWebViewLoadOption) > 0) { - newOnWebViewLoadAction = onWebViewLoadOption; + newOnWebViewLoadAction = onWebViewLoadOption; } - + // Read OnWebViewFailAction std::wstring newOnWebViewFailAction; LPCWSTR onWebViewFailOption = RmReadString(rm, L"OnWebViewFailAction", L"", FALSE); if (onWebViewFailOption && wcslen(onWebViewFailOption) > 0) { - newOnWebViewFailAction = onWebViewFailOption; + newOnWebViewFailAction = onWebViewFailOption; + } + + // Read OnPageLoadStartAction + std::wstring newOnPageLoadStartAction; + LPCWSTR onPageLoadStartOption = RmReadString(rm, L"OnPageLoadStartAction", L"", FALSE); + if (onPageLoadStartOption && wcslen(onPageLoadStartOption) > 0) + { + newOnPageLoadStartAction = onPageLoadStartOption; + } + + // Read OnPageLoadingAction + std::wstring newOnPageLoadingAction; + LPCWSTR onPageLoadingOption = RmReadString(rm, L"OnPageLoadingAction", L"", FALSE); + if (onPageLoadingOption && wcslen(onPageLoadingOption) > 0) + { + newOnPageLoadingAction = onPageLoadingOption; + } + + // Read OnPageLoadFinishAction + std::wstring newOnPageLoadFinishAction; + LPCWSTR onPageLoadFinishOption = RmReadString(rm, L"OnPageLoadFinishAction", L"", FALSE); + if (onPageLoadFinishOption && wcslen(onPageLoadFinishOption) > 0) + { + newOnPageLoadFinishAction = onPageLoadFinishOption; } // Read OnPageFirstLoadAction @@ -236,15 +281,7 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) LPCWSTR onPageFirstLoadOption = RmReadString(rm, L"OnPageFirstLoadAction", L"", FALSE); if (onPageFirstLoadOption && wcslen(onPageFirstLoadOption) > 0) { - newOnPageFirstLoadAction = onPageFirstLoadOption; - } - - // Read OnPageLoadAction - std::wstring newOnPageLoadAction; - LPCWSTR onPageLoadOption = RmReadString(rm, L"OnPageLoadAction", L"", FALSE); - if (onPageLoadOption && wcslen(onPageLoadOption) > 0) - { - newOnPageLoadAction = onPageLoadOption; + newOnPageFirstLoadAction = onPageFirstLoadOption; } // Read OnPageReloadAction @@ -252,12 +289,11 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) LPCWSTR onPageReloadOption = RmReadString(rm, L"OnPageReloadAction", L"", FALSE); if (onPageReloadOption && wcslen(onPageReloadOption) > 0) { - newOnPageReloadAction = onPageReloadOption; + newOnPageReloadAction = onPageReloadOption; } - + // Check if URL has changed (requires recreation) bool urlChanged = (newUrl != measure->url); - // Check if dimensions or position changed (can be updated dynamically) bool dimensionsChanged = (newWidth != measure->width || @@ -267,7 +303,7 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) bool visibilityChanged = (newVisible != measure->visible); bool clickthroughChanged = (newClickthrough != measure->clickthrough); - bool raincontextChanged = (newRaincontext != measure->raincontext); + bool allowDualControlChanged = (newAllowDualControl != measure->allowDualControl); // Update stored values measure->url = newUrl; @@ -277,12 +313,14 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) measure->y = newY; measure->visible = newVisible; measure->clickthrough = newClickthrough; - measure->raincontext = newRaincontext; - measure->OnWebViewLoadAction = newOnWebViewLoadAction; - measure->OnWebViewFailAction = newOnWebViewFailAction; - measure->onPageFirstLoadAction = newOnPageFirstLoadAction; - measure->onPageLoadAction = newOnPageLoadAction; - measure->onPageReloadAction = newOnPageReloadAction; + measure->allowDualControl = newAllowDualControl; + measure->onWebViewLoadAction = newOnWebViewLoadAction; + measure->onWebViewFailAction = newOnWebViewFailAction; + measure->onPageLoadStartAction = newOnPageLoadStartAction; + measure->onPageLoadingAction = newOnPageLoadingAction; + measure->onPageLoadFinishAction = newOnPageLoadFinishAction; + measure->onPageFirstLoadAction = newOnPageFirstLoadAction; + measure->onPageReloadAction = newOnPageReloadAction; // Only create WebView2 if not initialized OR if URL changed if (!measure->initialized || urlChanged) @@ -330,10 +368,15 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) UpdateClickthrough(measure); } - if (raincontextChanged) - { - UpdateRaincontext(measure); - } + if (allowDualControlChanged) + { + if (!measure->isAllowDualControlInjected) + { + InjectAllowDualControl(measure); + } + else + UpdateAllowDualControl(measure); + } } } From 38144bc65342de184b9d514beb107b0ee291b8a0 Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:52:43 -0600 Subject: [PATCH 13/17] Add currentUrl member to Plugin class --- WebView2/Plugin.h | 1 + 1 file changed, 1 insertion(+) diff --git a/WebView2/Plugin.h b/WebView2/Plugin.h index b554e40..a1581ab 100644 --- a/WebView2/Plugin.h +++ b/WebView2/Plugin.h @@ -22,6 +22,7 @@ struct Measure LPCWSTR measureName; std::wstring url; + std::wstring currentUrl; int width; int height; int x; From 899ddf84e93eee228ac2603d99a1d050bcd4259a Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:02:36 -0600 Subject: [PATCH 14/17] Update WebView2.cpp Added new event listeners for triggering new actions. --- WebView2/WebView2.cpp | 179 +++++++++++++++++++++++++++--------------- 1 file changed, 117 insertions(+), 62 deletions(-) diff --git a/WebView2/WebView2.cpp b/WebView2/WebView2.cpp index f3c3533..a11b77d 100644 --- a/WebView2/WebView2.cpp +++ b/WebView2/WebView2.cpp @@ -51,10 +51,10 @@ void CreateWebView2(Measure* measure) swprintf_s(errorMsg, L"WebView2: Failed to start creation process (HRESULT: 0x%08X). Make sure WebView2 Runtime is installed.", hr); RmLog(measure->rm, LOG_ERROR, errorMsg); } - if (measure->skin && wcslen(measure->OnWebViewFailAction.c_str()) > 0) - { - RmExecute(measure->skin, measure->OnWebViewFailAction.c_str()); - } + if (measure->skin && wcslen(measure->onWebViewFailAction.c_str()) > 0) + { + RmExecute(measure->skin, measure->onWebViewFailAction.c_str()); + } measure->isCreationInProgress = false; } } @@ -70,10 +70,10 @@ HRESULT Measure::CreateEnvironmentHandler(HRESULT result, ICoreWebView2Environme swprintf_s(errorMsg, L"WebView2: Failed to create environment (HRESULT: 0x%08X)", result); RmLog(rm, LOG_ERROR, errorMsg); } - if (skin && wcslen(OnWebViewFailAction.c_str()) > 0) - { - RmExecute(skin, OnWebViewFailAction.c_str()); - } + if (skin && wcslen(onWebViewFailAction.c_str()) > 0) + { + RmExecute(skin, onWebViewFailAction.c_str()); + } isCreationInProgress = false; return result; } @@ -102,10 +102,10 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller swprintf_s(errorMsg, L"WebView2: Failed to create controller (HRESULT: 0x%08X)", result); RmLog(rm, LOG_ERROR, errorMsg); } - if (skin && wcslen(OnWebViewFailAction.c_str()) > 0) - { - RmExecute(skin, OnWebViewFailAction.c_str()); - } + if (skin && wcslen(onWebViewFailAction.c_str()) > 0) + { + RmExecute(skin, onWebViewFailAction.c_str()); + } isCreationInProgress = false; return result; } @@ -114,10 +114,10 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller { if (rm) RmLog(rm, LOG_ERROR, L"WebView2: Controller is null"); - if (skin && wcslen(OnWebViewFailAction.c_str()) > 0) - { - RmExecute(skin, OnWebViewFailAction.c_str()); - } + if (skin && wcslen(onWebViewFailAction.c_str()) > 0) + { + RmExecute(skin, onWebViewFailAction.c_str()); + } isCreationInProgress = false; return S_FALSE; } @@ -176,26 +176,81 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller L"window.RainmeterAPI = chrome.webview.hostObjects.sync.RainmeterAPI", nullptr ); - - // Add NavigationCompleted event to call OnInitialize after page loads + + // Add SourceChanged event to detect changes in URL + webView->add_SourceChanged( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2SourceChangedEventArgs* args) -> HRESULT + { + wil::unique_cotaskmem_string updatedUri; + + if (SUCCEEDED(sender->get_Source(&updatedUri)) && updatedUri.get() != nullptr) + { + std::wstring newUrl = updatedUri.get(); + + if (currentUrl != newUrl) + { + // URL changed + isFirstLoad = true; + currentUrl = newUrl; + } + else + { + // URL did not change + isFirstLoad = false; + } + } + return S_OK; + } + ).Get(), + nullptr + ); + + // Add NavigationStarting event to call action when navigation starts + webView->add_NavigationStarting( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT + { + if (wcslen(onPageLoadStartAction.c_str()) > 0) + { + if (skin) + RmExecute(skin, onPageLoadStartAction.c_str()); + } + return S_OK; + } + ).Get(), + nullptr + ); + + // Add ContentLoading event to call action when page starts loading + webView->add_ContentLoading( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2ContentLoadingEventArgs* args) -> HRESULT + { + if (wcslen(onPageLoadingAction.c_str()) > 0) + { + if (skin) + RmExecute(skin, onPageLoadingAction.c_str()); + } + return S_OK; + } + ).Get(), + nullptr + ); + + // Add NavigationCompleted event to call OnInitialize after page loads and handle load actions webView->add_NavigationCompleted( Callback( [this](ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT { - // Inject script to capture page load events for drag/move and context menu - webView->ExecuteScript( - L"let rm_raincontext=false,rm_RaincontextOn=false,rm_RaincontextClientX=0,rm_RaincontextClientY=0;function rm_setRaincontext(v){rm_raincontext=!!v;if(!rm_raincontext)rm_RaincontextOn=false;}document.body.onpointerdown=e=>{if(!rm_raincontext)return;if(e.button===0&&e.ctrlKey){e.preventDefault();e.stopImmediatePropagation();rm_RaincontextOn=true;rm_RaincontextClientX=e.clientX;rm_RaincontextClientY=e.clientY;try{document.body.setPointerCapture(e.pointerId);}catch{}}};document.body.onpointermove=e=>{if(!rm_raincontext||!rm_RaincontextOn)return;e.preventDefault();RainmeterAPI.Bang('[!Move '+(e.screenX-RainmeterAPI.ReadFormula('X',0)-rm_RaincontextClientX)+' '+(e.screenY-RainmeterAPI.ReadFormula('Y',0)-rm_RaincontextClientY)+']');};document.body.onpointerup=e=>{if(!rm_raincontext)return;if(e.button===0){e.preventDefault();rm_RaincontextOn=false;try{document.body.releasePointerCapture(e.pointerId);}catch{}}};document.body.oncontextmenu=e=>{if(!rm_raincontext)return;if(e.button===2&&e.ctrlKey){e.preventDefault();RainmeterAPI.Bang('[!SkinMenu]');}};", - Callback( - [this](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT - { - return S_OK; - } - ).Get() - ); + isAllowDualControlInjected = false; - // Apply initial raincontext state - UpdateRaincontext(this); - + // Inject script to capture page load events for drag/move and context menu + if (allowDualControl) + { + InjectAllowDualControl(this); + } + // Call JavaScript OnInitialize callback if it exists and capture return value webView->ExecuteScript( L"(function() { if (typeof window.OnInitialize === 'function') { var result = window.OnInitialize(); return result !== undefined ? String(result) : ''; } return ''; })();", @@ -210,47 +265,47 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller { result = result.substr(1, result.length() - 2); } - + // Store the callback result if (!result.empty() && result != L"null") { - callbackResult = result; + callbackResult = result; } } - - if (isFirstLoad) - { - if (wcslen(onPageFirstLoadAction.c_str()) > 0) - { - if (skin) - RmExecute(skin, onPageFirstLoadAction.c_str()); - } - isFirstLoad = false; - } - else { - if (wcslen(onPageReloadAction.c_str()) > 0) - { - if (skin) - RmExecute(skin, onPageReloadAction.c_str()); - } - } - - if (wcslen(onPageLoadAction.c_str()) > 0) - { - if (skin) - RmExecute(skin, onPageLoadAction.c_str()); - } - return S_OK; } ).Get() ); + + if (isFirstLoad) // First load + { + if (wcslen(onPageFirstLoadAction.c_str()) > 0) + { + if (skin) + RmExecute(skin, onPageFirstLoadAction.c_str()); + } + isFirstLoad = false; + } + else // Page reload + { + if (wcslen(onPageReloadAction.c_str()) > 0) + { + if (skin) + RmExecute(skin, onPageReloadAction.c_str()); + } + } + // Common action after any page load + if (wcslen(onPageLoadFinishAction.c_str()) > 0) + { + if (skin) + RmExecute(skin, onPageLoadFinishAction.c_str()); + } return S_OK; } ).Get(), nullptr ); - + // Navigate to URL if (!url.empty()) { @@ -264,10 +319,10 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller if (rm) RmLog(rm, LOG_NOTICE, L"WebView2: Initialized successfully with COM Host Objects"); - if (wcslen(OnWebViewLoadAction.c_str()) > 0) - { - RmExecute(skin, OnWebViewLoadAction.c_str()); - } + if (wcslen(onWebViewLoadAction.c_str()) > 0) + { + RmExecute(skin, onWebViewLoadAction.c_str()); + } // Apply initial clickthrough state UpdateClickthrough(this); From 24ce69e02352c81f885041f183e7b87b9d614ed6 Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Thu, 11 Dec 2025 14:10:47 -0600 Subject: [PATCH 15/17] added zoomFactor double --- WebView2/Plugin.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/WebView2/Plugin.h b/WebView2/Plugin.h index a1581ab..c51e5d6 100644 --- a/WebView2/Plugin.h +++ b/WebView2/Plugin.h @@ -20,20 +20,21 @@ struct Measure void* skin; HWND skinWindow; LPCWSTR measureName; - + std::wstring url; std::wstring currentUrl; int width; int height; int x; int y; + double zoomFactor; bool visible; bool initialized; bool clickthrough; bool isCreationInProgress = false; bool isFirstLoad = true; bool allowDualControl; - bool isAllowDualControlInjected = false; + bool isAllowDualControlInjected = false; std::wstring onWebViewLoadAction; std::wstring onWebViewFailAction; @@ -46,14 +47,14 @@ struct Measure wil::com_ptr webViewController; wil::com_ptr webView; EventRegistrationToken webMessageToken; - + std::wstring buffer; // Buffer for section variable return values std::wstring callbackResult; // Stores return value from OnInitialize/OnUpdate callbacks std::map jsResults; // Cache for CallJS results - + Measure(); ~Measure(); - + // Member callback functions for WebView2 creation HRESULT CreateEnvironmentHandler(HRESULT result, ICoreWebView2Environment* env); HRESULT CreateControllerHandler(HRESULT result, ICoreWebView2Controller* controller); From 3de406e8b5b2e9869de9734d508f20483c879aa0 Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Thu, 11 Dec 2025 14:18:19 -0600 Subject: [PATCH 16/17] added zoomFactor --- WebView2/Plugin.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/WebView2/Plugin.cpp b/WebView2/Plugin.cpp index bca0b66..dbab4cd 100644 --- a/WebView2/Plugin.cpp +++ b/WebView2/Plugin.cpp @@ -51,7 +51,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) // Measure constructor Measure::Measure() : rm(nullptr), skin(nullptr), skinWindow(nullptr), measureName(nullptr), - width(800), height(600), x(0), y(0), + width(800), height(600), x(0), y(0), zoomFactor(1.0), visible(true), initialized(false), clickthrough(false), allowDualControl(true), webMessageToken{} { // Initialize COM for this thread if not already done @@ -230,6 +230,7 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) int newHeight = RmReadInt(rm, L"H", 600); int newX = RmReadInt(rm, L"X", 0); int newY = RmReadInt(rm, L"Y", 0); + double newZoomFactor = RmReadFormula(rm, L"ZoomFactor", 1.0); bool newVisible = RmReadInt(rm, L"Hidden", 0) <= 0; bool newClickthrough = RmReadInt(rm, L"Clickthrough", 0) >= 1; @@ -304,13 +305,15 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) bool visibilityChanged = (newVisible != measure->visible); bool clickthroughChanged = (newClickthrough != measure->clickthrough); bool allowDualControlChanged = (newAllowDualControl != measure->allowDualControl); - + bool zoomFactorChanged = (newZoomFactor != measure->zoomFactor); + // Update stored values measure->url = newUrl; measure->width = newWidth; measure->height = newHeight; measure->x = newX; measure->y = newY; + measure->zoomFactor = newZoomFactor; measure->visible = newVisible; measure->clickthrough = newClickthrough; measure->allowDualControl = newAllowDualControl; @@ -362,6 +365,11 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) { measure->webViewController->put_IsVisible(measure->visible ? TRUE : FALSE); } + + if (zoomFactorChanged && measure->webViewController) + { + measure->webViewController->put_ZoomFactor(measure->zoomFactor); + } if (clickthroughChanged) { From 2834a3a188d2ca25554d205ce2dedb6260b1a5f0 Mon Sep 17 00:00:00 2001 From: RicardoTM05 <168048518+RicardoTM05@users.noreply.github.com> Date: Thu, 11 Dec 2025 14:20:40 -0600 Subject: [PATCH 17/17] Added ZoomFactor --- WebView2/WebView2.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WebView2/WebView2.cpp b/WebView2/WebView2.cpp index a11b77d..6eb16b6 100644 --- a/WebView2/WebView2.cpp +++ b/WebView2/WebView2.cpp @@ -142,7 +142,10 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller // Set initial visibility webViewController->put_IsVisible(visible ? TRUE : FALSE); - + + // Set initial zoom factor + webViewController->put_ZoomFactor(zoomFactor); + // Transparent background auto controller2 = webViewController.query(); if (controller2)