From b35896b71958d355e9f5ec1d759c804c15fa8b95 Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Tue, 25 Nov 2025 17:09:24 +0500 Subject: [PATCH 1/9] impement WebView2 plugin for Rainmeter with COM host object integration for API access. --- Build-CPP.ps1 | 2 +- .../Skins/WebView2/@Resources/api-demo.html | 38 +- WebView2/HostObject.idl | 46 ++ WebView2/HostObjectRmAPI.cpp | 240 +++++++++++ WebView2/HostObjectRmAPI.h | 57 +++ WebView2/Plugin.cpp | 40 ++ WebView2/Plugin.h | 5 +- WebView2/WebView2.cpp | 398 +----------------- WebView2/WebView2.rc | Bin 1994 -> 2300 bytes WebView2/WebView2.vcxproj | 7 + WebView2/resource.h | 15 + 11 files changed, 449 insertions(+), 399 deletions(-) create mode 100644 WebView2/HostObject.idl create mode 100644 WebView2/HostObjectRmAPI.cpp create mode 100644 WebView2/HostObjectRmAPI.h create mode 100644 WebView2/resource.h diff --git a/Build-CPP.ps1 b/Build-CPP.ps1 index 1e001a1..19cfe87 100644 --- a/Build-CPP.ps1 +++ b/Build-CPP.ps1 @@ -1,4 +1,4 @@ -#usage -----> powershell -ExecutionPolicy Bypass -Command "& {. .\Build-CPP.ps1; Dist -major 1 -minor 2 -patch 0}" +#usage -----> powershell -ExecutionPolicy Bypass -Command "& {. .\Build-CPP.ps1; Dist -major 0 -minor 0 -patch 3}" $msbuild = "C:\Program Files\Microsoft Visual Studio\2022\Community\Msbuild\Current\Bin\MSBuild.exe" function Add-RMSkinFooter { diff --git a/Resources/Skins/WebView2/@Resources/api-demo.html b/Resources/Skins/WebView2/@Resources/api-demo.html index 125c116..394a8e5 100644 --- a/Resources/Skins/WebView2/@Resources/api-demo.html +++ b/Resources/Skins/WebView2/@Resources/api-demo.html @@ -205,12 +205,12 @@

Interactive Test

+ + diff --git a/Resources/Skins/WebView2/APIDemo.ini b/Resources/Skins/WebView2/APIDemo.ini index 8faab8a..930c942 100644 --- a/Resources/Skins/WebView2/APIDemo.ini +++ b/Resources/Skins/WebView2/APIDemo.ini @@ -5,7 +5,7 @@ SolidColor=0,0,0,1 [Metadata] Name=WebView2 API Bridge Demo -Author=NSTechBytes +Author=nstechbytes Information=Demonstrates Rainmeter API bridge in JavaScript Version=0.0.3 License=MIT diff --git a/Resources/Skins/WebView2/EventTest.ini b/Resources/Skins/WebView2/EventTest.ini index f7d3350..337c6b2 100644 --- a/Resources/Skins/WebView2/EventTest.ini +++ b/Resources/Skins/WebView2/EventTest.ini @@ -5,7 +5,7 @@ SolidColor=0,0,0,1 [Metadata] Name=Event Handler Test -Author=NSTechBytes +Author=nstechbytes Information=Test page to verify mouse and keyboard events work in WebView2 Version=0.0.3 License=MIT diff --git a/Resources/Skins/WebView2/MouseDragTest.ini b/Resources/Skins/WebView2/MouseDragTest.ini new file mode 100644 index 0000000..1e07c58 --- /dev/null +++ b/Resources/Skins/WebView2/MouseDragTest.ini @@ -0,0 +1,29 @@ +[Rainmeter] +Update=1000 +DynamicWindowSize=1 +AccurateText=1 + +[Metadata] +Name=Mouse Drag Test +Information=Test skin to verify mouse drag events work in WebView2 +Version=0.0.3 +License=MIT +Author=nstechbytes + +[Variables] +Width=600 +Height=400 + +[MeasureWebView] +Measure=Plugin +Plugin=WebView2 +X=0 +Y=0 +Width=#Width# +Height=#Height# +URL=#@#mouse-drag-test.html + +[MeterBackground] +Meter=Shape +Shape=Rectangle 0,0,#Width#,#Height# | Fill Color 20,20,30,255 | StrokeWidth 0 +DynamicVariables=1 From 8f6072337fbc016d863fb0a36e00d213a6c914d3 Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Tue, 25 Nov 2025 19:18:13 +0500 Subject: [PATCH 4/9] Add core WebView2 plugin implementation with Rainmeter API bridge and update README with project metadata and usage. --- README.md | 241 +++++++++++++++++++++--------------------------------- 1 file changed, 94 insertions(+), 147 deletions(-) diff --git a/README.md b/README.md index 791debb..f59aae1 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ A powerful Rainmeter plugin that embeds Microsoft Edge WebView2 control to display web content or local HTML files directly in your Rainmeter skins. -![Version](https://img.shields.io/badge/version-1.0.0-blue) +![Version](https://img.shields.io/badge/version-0.0.3-blue) ![Platform](https://img.shields.io/badge/platform-Windows%2010%2F11-lightgrey) -![License](https://img.shields.io/badge/license-GPL--2.0-green) +![License](https://img.shields.io/badge/license-MIT-green) ## ✨ Features @@ -12,9 +12,10 @@ A powerful Rainmeter plugin that embeds Microsoft Edge WebView2 control to displ - 📄 **Local HTML Files** - Display custom HTML/CSS/JavaScript content - 🪟 **Seamless Integration** - WebView window automatically parents to skin window - 🎮 **Full Control** - Navigate, reload, go back/forward via bang commands -- 💻 **JavaScript Support** - Execute JavaScript code in the WebView +- 💻 **JavaScript Support** - Full JavaScript execution with event handling - 🎨 **Customizable** - Configure size, position, and visibility - ⚡ **Modern** - Uses Microsoft Edge WebView2 (Chromium-based) +- 🔌 **Rainmeter API Bridge** - Access Rainmeter functions from JavaScript ## 📋 Requirements @@ -27,8 +28,8 @@ A powerful Rainmeter plugin that embeds Microsoft Edge WebView2 control to displ ### Installation -1. Download the latest release from [Releases](https://github.com/nstechbytes/WebView2/releases) -2. Install the `.rmskin` package, or +1. Download the latest release `.rmskin` package +2. Double-click to install, or 3. Manually copy `WebView2.dll` to `%APPDATA%\Rainmeter\Plugins\` ### Basic Usage @@ -40,12 +41,11 @@ Update=1000 [MeasureWebView] Measure=Plugin Plugin=WebView2 -Url=https://www.google.com +URL=https://www.google.com Width=1000 Height=700 X=0 Y=0 -Visible=1 ``` ## 📖 Documentation @@ -54,7 +54,7 @@ Visible=1 | Option | Type | Default | Description | |--------|------|---------|-------------| -| `Url` | String | (empty) | URL or file path to load. Supports web URLs and local file paths | +| `URL` | String | (empty) | URL or file path to load. Supports web URLs and local file paths | | `Width` | Integer | 800 | Width of the WebView window in pixels | | `Height` | Integer | 600 | Height of the WebView window in pixels | | `X` | Integer | 0 | X position relative to skin window | @@ -65,93 +65,55 @@ Visible=1 Execute commands using `!CommandMeasure`: -#### Navigate to URL ```ini +; Navigate to URL LeftMouseUpAction=[!CommandMeasure MeasureWebView "Navigate https://example.com"] -``` -#### Reload Current Page -```ini +; Reload current page LeftMouseUpAction=[!CommandMeasure MeasureWebView "Reload"] -``` -#### Navigation Controls -```ini +; Navigation controls LeftMouseUpAction=[!CommandMeasure MeasureWebView "GoBack"] LeftMouseUpAction=[!CommandMeasure MeasureWebView "GoForward"] -``` -#### Show/Hide WebView -```ini +; Show/Hide WebView LeftMouseUpAction=[!CommandMeasure MeasureWebView "Show"] LeftMouseUpAction=[!CommandMeasure MeasureWebView "Hide"] -``` -#### Execute JavaScript -```ini -LeftMouseUpAction=[!CommandMeasure MeasureWebView "ExecuteScript alert('Hello from Rainmeter!')"] +; Execute JavaScript +LeftMouseUpAction=[!CommandMeasure MeasureWebView "ExecuteScript alert('Hello!')"] ``` ## 💡 Examples -### Example 1: Web Browser Skin +### Example 1: Mouse Drag Test ```ini -[Rainmeter] -Update=1000 -BackgroundMode=2 -SolidColor=30,30,30,255 - -[Variables] -WebWidth=1200 -WebHeight=800 - [MeasureWebView] Measure=Plugin Plugin=WebView2 -Url=https://www.rainmeter.net -Width=#WebWidth# -Height=#WebHeight# +URL=#@#mouse-drag-test.html +Width=600 +Height=400 X=0 -Y=50 -Visible=1 - -[MeterNavigate] -Meter=String -X=20 -Y=15 -FontSize=12 -FontColor=100,150,255 -Text="Go to Google" -LeftMouseUpAction=[!CommandMeasure MeasureWebView "Navigate https://www.google.com"] - -[MeterReload] -Meter=String -X=150 -Y=15 -FontSize=12 -FontColor=200,200,200 -Text="Reload" -LeftMouseUpAction=[!CommandMeasure MeasureWebView "Reload"] +Y=0 ``` -### Example 2: Local HTML Dashboard +### Example 2: Web Browser Skin ```ini -[Rainmeter] -Update=1000 - [MeasureWebView] Measure=Plugin Plugin=WebView2 -Url=#@#dashboard.html -Width=800 -Height=600 +URL=https://www.rainmeter.net +Width=1200 +Height=800 X=0 -Y=0 -Visible=1 +Y=50 ``` +### Example 3: Local HTML Dashboard + Create `@Resources\dashboard.html`: ```html @@ -164,52 +126,50 @@ Create `@Resources\dashboard.html`: color: white; padding: 40px; } - h1 { font-size: 3em; }

My Dashboard

-

Current time:

+

Time:

``` -## JavaScript API Bridge - -The WebView2 plugin automatically injects a global `rm` object into all loaded web pages, providing seamless access to Rainmeter API functions from JavaScript. +## 🔌 JavaScript API Bridge -## API Reference Tables +The plugin automatically injects a global `rm` object into all loaded web pages, providing seamless access to Rainmeter API functions from JavaScript. -### Reading Options +### Reading Options from Current Measure | Method | Parameters | Returns | Description | |--------|------------|---------|-------------| -| `rm.ReadString(option, default)` | `option` (string), `default` (string) | Promise | Read a string option from the skin | -| `rm.ReadInt(option, default)` | `option` (string), `default` (number) | Promise | Read an integer option from the skin | -| `rm.ReadDouble(option, default)` | `option` (string), `default` (number) | Promise | Read a double/float option from the skin | -| `rm.ReadFormula(option, default)` | `option` (string), `default` (number) | Promise | Read and evaluate a formula option | -| `rm.ReadPath(option, default)` | `option` (string), `default` (string) | Promise | Read a file path option from the skin | +| `rm.ReadString(option, default)` | `option` (string), `default` (string) | Promise\ | Read a string option from the current measure | +| `rm.ReadInt(option, default)` | `option` (string), `default` (number) | Promise\ | Read an integer option from the current measure | +| `rm.ReadDouble(option, default)` | `option` (string), `default` (number) | Promise\ | Read a double/float option from the current measure | +| `rm.ReadFormula(option, default)` | `option` (string), `default` (number) | Promise\ | Read and evaluate a formula option | +| `rm.ReadPath(option, default)` | `option` (string), `default` (string) | Promise\ | Read a file path option from the current measure | ### Reading from Other Sections | Method | Parameters | Returns | Description | |--------|------------|---------|-------------| -| `rm.ReadStringFromSection(section, option, default)` | `section` (string), `option` (string), `default` (string) | Promise | Read a string from another section/measure | -| `rm.ReadIntFromSection(section, option, default)` | `section` (string), `option` (string), `default` (number) | Promise | Read an integer from another section/measure | -| `rm.ReadDoubleFromSection(section, option, default)` | `section` (string), `option` (string), `default` (number) | Promise | Read a double from another section/measure | -| `rm.ReadFormulaFromSection(section, option, default)` | `section` (string), `option` (string), `default` (number) | Promise | Read and evaluate a formula from another section | +| `rm.ReadStringFromSection(section, option, default)` | `section` (string), `option` (string), `default` (string) | Promise\ | Read a string from another section/measure | +| `rm.ReadIntFromSection(section, option, default)` | `section` (string), `option` (string), `default` (number) | Promise\ | Read an integer from another section/measure | +| `rm.ReadDoubleFromSection(section, option, default)` | `section` (string), `option` (string), `default` (number) | Promise\ | Read a double from another section/measure | +| `rm.ReadFormulaFromSection(section, option, default)` | `section` (string), `option` (string), `default` (number) | Promise\ | Read and evaluate a formula from another section | ### Utility Functions | Method | Parameters | Returns | Description | |--------|------------|---------|-------------| -| `rm.ReplaceVariables(text)` | `text` (string) | Promise | Replace Rainmeter variables in text (e.g., `#CURRENTCONFIG#`) | -| `rm.PathToAbsolute(path)` | `path` (string) | Promise | Convert relative path to absolute path | +| `rm.ReplaceVariables(text)` | `text` (string) | Promise\ | Replace Rainmeter variables in text (e.g., `#CURRENTCONFIG#`) | +| `rm.PathToAbsolute(path)` | `path` (string) | Promise\ | Convert relative path to absolute path | | `rm.Execute(command)` | `command` (string) | void | Execute a Rainmeter bang command | | `rm.Log(message, level)` | `message` (string), `level` (string) | void | Log a message to Rainmeter log. Levels: `'Notice'`, `'Warning'`, `'Error'`, `'Debug'` | @@ -217,62 +177,67 @@ The WebView2 plugin automatically injects a global `rm` object into all loaded w | Property | Returns | Description | |----------|---------|-------------| -| `rm.MeasureName` | Promise | Get the name of the current measure | -| `rm.SkinName` | Promise | Get the name of the current skin | -| `rm.SkinWindowHandle` | Promise | Get the window handle of the skin | -| `rm.SettingsFile` | Promise | Get the path to Rainmeter settings file | +| `rm.MeasureName` | Promise\ | Get the name of the current measure | +| `rm.SkinName` | Promise\ | Get the name of the current skin | +| `rm.SkinWindowHandle` | Promise\ | Get the window handle of the skin | +| `rm.SettingsFile` | Promise\ | Get the path to Rainmeter settings file | -## Usage Examples +### Usage Examples #### Reading Options ```javascript // Read string option -const url = await rm.ReadString('Url', 'default'); +const url = await rm.ReadString('URL', 'https://default.com'); // Read integer option const width = await rm.ReadInt('Width', 800); // Read double/float option -const height = await rm.ReadDouble('Height', 600.0); +const opacity = await rm.ReadDouble('Opacity', 1.0); // Read formula option -const value = await rm.ReadFormula('SomeFormula', 0); +const calculated = await rm.ReadFormula('MyFormula', 0); // Read path option -const path = await rm.ReadPath('FilePath', ''); +const filePath = await rm.ReadPath('DataFile', ''); ``` #### Reading from Other Sections ```javascript -// Read string from another section -const value = await rm.ReadStringFromSection('MeasureName', 'Option', 'default'); +// Read string from another measure +const cpuValue = await rm.ReadStringFromSection('MeasureCPU', 'String', '0%'); -// Read int from another section -const num = await rm.ReadIntFromSection('MeasureName', 'Option', 0); +// Read integer from another section +const memoryUsage = await rm.ReadIntFromSection('MeasureRAM', 'Value', 0); // Read double from another section -const dbl = await rm.ReadDoubleFromSection('MeasureName', 'Option', 0.0); +const temperature = await rm.ReadDoubleFromSection('MeasureTemp', 'Value', 0.0); // Read formula from another section -const formula = await rm.ReadFormulaFromSection('MeasureName', 'Option', 0.0); +const result = await rm.ReadFormulaFromSection('MeasureCalc', 'Formula', 0.0); ``` #### Utility Functions ```javascript // Replace Rainmeter variables -const replaced = await rm.ReplaceVariables('#CURRENTCONFIG#'); +const currentPath = await rm.ReplaceVariables('#CURRENTPATH#'); +const skinPath = await rm.ReplaceVariables('#@#'); // Convert relative path to absolute -const absolutePath = await rm.PathToAbsolute('#@#file.txt'); +const absolutePath = await rm.PathToAbsolute('#@#data.json'); -// Execute Rainmeter bang +// Execute Rainmeter bang commands rm.Execute('[!SetVariable MyVar "Hello"]'); +rm.Execute('[!UpdateMeter *][!Redraw]'); -// Log message to Rainmeter log -rm.Log('Message from JavaScript', 'Notice'); // Levels: Notice, Warning, Error, Debug +// Log messages to Rainmeter log +rm.Log('JavaScript initialized', 'Notice'); +rm.Log('Warning: Low memory', 'Warning'); +rm.Log('Error occurred', 'Error'); +rm.Log('Debug info', 'Debug'); ``` #### Information Properties @@ -280,9 +245,11 @@ rm.Log('Message from JavaScript', 'Notice'); // Levels: Notice, Warning, Error, ```javascript // Get measure name const measureName = await rm.MeasureName; +console.log('Measure:', measureName); // Get skin name const skinName = await rm.SkinName; +console.log('Skin:', skinName); // Get skin window handle const handle = await rm.SkinWindowHandle; @@ -310,10 +277,14 @@ const settingsFile = await rm.SettingsFile; // Read values from Rainmeter const width = await rm.ReadInt('Width', 800); const skinName = await rm.SkinName; + const measureName = await rm.MeasureName; // Display results - document.getElementById('output').innerHTML = - `Skin: ${skinName}
Width: ${width}px`; + document.getElementById('output').innerHTML = ` + Skin: ${skinName}
+ Measure: ${measureName}
+ Width: ${width}px + `; // Log to Rainmeter rm.Log('Updated from JavaScript', 'Notice'); @@ -322,6 +293,7 @@ const settingsFile = await rm.SettingsFile; rm.Execute('[!UpdateMeter *][!Redraw]'); } catch (error) { console.error('Error:', error); + rm.Log('Error: ' + error.message, 'Error'); } } @@ -329,13 +301,14 @@ const settingsFile = await rm.SettingsFile; ``` -### Notes +### Important Notes + +- ✅ All read methods return **Promises** and should be used with `await` or `.then()` +- ✅ Execute and Log methods are **fire-and-forget** (no return value) +- ✅ Property getters (MeasureName, SkinName, etc.) also return **Promises** +- ✅ The `rm` object is **automatically available** in all pages loaded by the plugin +- ✅ No additional setup or imports required -- All read methods return Promises and should be used with `await` or `.then()` -- Execute and Log methods are fire-and-forget (no return value) -- Property getters (MeasureName, SkinName, etc.) also return Promises -- The `rm` object is automatically available in all pages loaded by the plugin -- No additional setup or imports required ## 🔧 Building from Source @@ -345,28 +318,10 @@ const settingsFile = await rm.SettingsFile; - Windows 10/11 SDK - NuGet Package Manager -### Build Steps - -1. Clone the repository: - ```bash - git clone https://github.com/nstechbytes/WebView2.git - cd WebView2 - ``` - -2. Open `WebView2-Plugin.sln` in Visual Studio - -3. Restore NuGet packages (right-click solution → Restore NuGet Packages) - -4. Build the solution (Ctrl+Shift+B) - -5. Find the compiled DLLs in: - - `WebView2\x64\Release\WebView2.dll` (64-bit) - - `WebView2\x32\Release\WebView2.dll` (32-bit) - ### Build via PowerShell ```powershell -powershell -ExecutionPolicy Bypass -Command "& { . .\Build-CPP.ps1; Dist -major 1 -minor 0 -patch 0 }" +powershell -ExecutionPolicy Bypass -Command "& { . .\Build-CPP.ps1; Dist -major 0 -minor 0 -patch 3 }" ``` This creates: @@ -378,16 +333,15 @@ This creates: ### WebView2 doesn't appear - Ensure WebView2 Runtime is installed - Check Rainmeter log for error messages -- Verify the skin window is visible and has appropriate dimensions +- Verify skin window has appropriate dimensions -### File paths not loading -- Use `#@#` for @Resources folder: `Url=#@#mypage.html` -- Or use absolute paths: `Url=C:\MyFolder\page.html` -- The plugin automatically converts file paths to `file:///` format +### Mouse/Keyboard events not working +- **Fixed in v0.0.3**: Added JavaScript settings (`put_IsScriptEnabled`, `put_AreDefaultScriptDialogsEnabled`, `put_IsWebMessageEnabled`) +- Ensure you're using the latest version -### Access Denied Error -- The plugin uses TEMP directory for WebView2 data -- Ensure you have write permissions to `%TEMP%\RainmeterWebView2` +### File paths not loading +- Use `#@#` for @Resources folder: `URL=#@#mypage.html` +- Or use absolute paths: `URL=C:\MyFolder\page.html` ## 📝 Technical Details @@ -401,26 +355,19 @@ This creates: Contributions are welcome! Please feel free to submit a Pull Request. -1. Fork the repository -2. Create your feature branch (`git checkout -b feature/AmazingFeature`) -3. Commit your changes (`git commit -m 'Add some AmazingFeature'`) -4. Push to the branch (`git push origin feature/AmazingFeature`) -5. Open a Pull Request - ## 📄 License -This project is licensed under the GPL-2.0 License - see the [LICENSE](LICENSE) file for details. +This project is licensed under the MIT License. ## 🙏 Acknowledgments - Microsoft Edge WebView2 team for the excellent SDK - Rainmeter community for inspiration and support -- ModernSearchBar plugin for reference implementation ## 📧 Contact - **Author**: nstechbytes -- **GitHub**: [https://github.com/nstechbytes/WebView2](https://github.com/nstechbytes/WebView2) +- **GitHub**: [WebView2 Plugin](https://github.com/nstechbytes/WebView2) ## 🔗 Related Links From f0589f358f324ac0db5f2492ee84b0fa16a41b89 Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Tue, 25 Nov 2025 19:36:50 +0500 Subject: [PATCH 5/9] Implement WebView2 integration for Rainmeter with COM host objects and DevTools support. --- README.md | 5 ++++- WebView2/Plugin.cpp | 4 ++++ WebView2/WebView2.cpp | 7 ++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f59aae1..942bc42 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,12 @@ LeftMouseUpAction=[!CommandMeasure MeasureWebView "Hide"] ; Execute JavaScript LeftMouseUpAction=[!CommandMeasure MeasureWebView "ExecuteScript alert('Hello!')"] + +;Open DevTools +LeftMouseUpAction=[!CommandMeasure MeasureWebView "OpenDevTools"] ``` -## 💡 Examples +## 💡 Examples ### Example 1: Mouse Drag Test diff --git a/WebView2/Plugin.cpp b/WebView2/Plugin.cpp index 7209d05..281edf7 100644 --- a/WebView2/Plugin.cpp +++ b/WebView2/Plugin.cpp @@ -298,6 +298,10 @@ PLUGIN_EXPORT void ExecuteBang(void* data, LPCWSTR args) ); } } + else if (_wcsicmp(action.c_str(), L"OpenDevTools") == 0) + { + measure->webView->OpenDevToolsWindow(); + } } PLUGIN_EXPORT void Finalize(void* data) diff --git a/WebView2/WebView2.cpp b/WebView2/WebView2.cpp index db97cea..4cfbb7a 100644 --- a/WebView2/WebView2.cpp +++ b/WebView2/WebView2.cpp @@ -143,10 +143,15 @@ void CreateWebView2(Measure* measure) // Set initial visibility measure->webViewController->put_IsVisible(measure->visible ? TRUE : FALSE); - // Enable host objects in settings + // Enable host objects and JavaScript in settings wil::com_ptr settings; measure->webView->get_Settings(&settings); + settings->put_IsScriptEnabled(TRUE); + settings->put_AreDefaultScriptDialogsEnabled(TRUE); + settings->put_IsWebMessageEnabled(TRUE); settings->put_AreHostObjectsAllowed(TRUE); + settings->put_AreDevToolsEnabled(TRUE); + settings->put_AreDefaultContextMenusEnabled(TRUE); // Create and inject COM Host Object for Rainmeter API wil::com_ptr hostObject = From 63052c7134c8d550b3cdc8092e3b60f495d6115c Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Tue, 25 Nov 2025 20:10:44 +0500 Subject: [PATCH 6/9] Implement initial WebView2 Rainmeter plugin with core functionality, lifecycle management, and JavaScript interaction. --- WebView2/Plugin.cpp | 17 +++-- WebView2/Plugin.h | 1 + WebView2/WebView2.cpp | 145 +++++++++++++++++++++++++++--------------- 3 files changed, 107 insertions(+), 56 deletions(-) diff --git a/WebView2/Plugin.cpp b/WebView2/Plugin.cpp index 281edf7..718140d 100644 --- a/WebView2/Plugin.cpp +++ b/WebView2/Plugin.cpp @@ -51,7 +51,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) Measure::Measure() : rm(nullptr), skin(nullptr), skinWindow(nullptr), webViewWindow(nullptr), measureName(nullptr), width(800), height(600), x(0), y(0), - visible(true), initialized(false), webMessageToken{} + visible(true), initialized(false), isCleaningUp(false), webMessageToken{} { // Initialize COM for this thread if not already done if (!g_comInitialized) @@ -67,7 +67,10 @@ Measure::Measure() : rm(nullptr), skin(nullptr), skinWindow(nullptr), // Measure destructor Measure::~Measure() { - // Proper cleanup sequence to prevent crashes + // Mark that cleanup is in progress + isCleaningUp = true; + + // Proper cleanup sequence to prevent crashes and race conditions // 1. Remove event handlers first if (webView && webMessageToken.value != 0) @@ -89,8 +92,9 @@ Measure::~Measure() webView.reset(); // Explicit release } - // 4. Small delay to allow async cleanup - Sleep(50); + // 4. Longer delay to ensure WebView2 runtime fully releases resources + // This prevents 0x80080005 error on skin refresh + Sleep(250); // 5. Destroy window last if (webViewWindow && IsWindow(webViewWindow)) @@ -98,6 +102,9 @@ Measure::~Measure() DestroyWindow(webViewWindow); webViewWindow = nullptr; } + + // 6. Final delay to ensure complete cleanup + Sleep(50); } // Rainmeter Plugin Exports @@ -163,7 +170,7 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) measure->visible = RmReadInt(rm, L"Visible", 1) != 0; // Create WebView2 if not already created - if (!measure->initialized) + if (!measure->initialized && !measure->isCleaningUp) { CreateWebView2(measure); } diff --git a/WebView2/Plugin.h b/WebView2/Plugin.h index a9da225..7d499f1 100644 --- a/WebView2/Plugin.h +++ b/WebView2/Plugin.h @@ -27,6 +27,7 @@ struct Measure int y; bool visible; bool initialized; + bool isCleaningUp; wil::com_ptr webViewController; wil::com_ptr webView; diff --git a/WebView2/WebView2.cpp b/WebView2/WebView2.cpp index 4cfbb7a..b5a885d 100644 --- a/WebView2/WebView2.cpp +++ b/WebView2/WebView2.cpp @@ -115,14 +115,105 @@ void CreateWebView2(Measure* measure) return result; } - // Create WebView2 controller + // Helper function to initialize controller after successful creation + auto initializeController = [measure](ICoreWebView2Controller* controller) -> HRESULT + { + measure->webViewController = controller; + measure->webViewController->get_CoreWebView2(&measure->webView); + + // Set bounds + RECT bounds; + GetClientRect(measure->webViewWindow, &bounds); + measure->webViewController->put_Bounds(bounds); + + // Set initial visibility + measure->webViewController->put_IsVisible(measure->visible ? TRUE : FALSE); + + // Enable host objects and JavaScript in settings + wil::com_ptr settings; + measure->webView->get_Settings(&settings); + settings->put_IsScriptEnabled(TRUE); + settings->put_AreDefaultScriptDialogsEnabled(TRUE); + settings->put_IsWebMessageEnabled(TRUE); + settings->put_AreHostObjectsAllowed(TRUE); + settings->put_AreDevToolsEnabled(TRUE); + settings->put_AreDefaultContextMenusEnabled(TRUE); + + // Create and inject COM Host Object for Rainmeter API + wil::com_ptr hostObject = + Microsoft::WRL::Make(measure, g_typeLib); + + VARIANT variant = {}; + hostObject.query_to(&variant.pdispVal); + variant.vt = VT_DISPATCH; + measure->webView->AddHostObjectToScript(L"rm", &variant); + variant.pdispVal->Release(); + + // Add script to make rm available globally + measure->webView->AddScriptToExecuteOnDocumentCreated( + L"window.rm = chrome.webview.hostObjects.sync.rm", + nullptr + ); + + // Navigate to URL + if (!measure->url.empty()) + { + measure->webView->Navigate(measure->url.c_str()); + } + + measure->initialized = true; + + if (measure->rm) + RmLog(measure->rm, LOG_NOTICE, L"WebView2: Initialized successfully with COM Host Objects"); + + return S_OK; + }; + + // Create WebView2 controller with retry for 0x80080005 env->CreateCoreWebView2Controller( measure->webViewWindow, Callback( - [measure](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT + [measure, env, initializeController](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT { if (FAILED(result)) { + // Retry once for 0x80080005 (CO_E_SERVER_EXEC_FAILURE) + // This error occurs when previous instance is still cleaning up + if (result == 0x80080005) + { + if (measure->rm) + { + RmLog(measure->rm, LOG_WARNING, L"WebView2: Controller creation failed (previous instance cleaning up), retrying after delay..."); + } + + // Wait for previous instance to fully clean up + Sleep(300); + + // Single retry attempt + env->CreateCoreWebView2Controller( + measure->webViewWindow, + Callback( + [measure, initializeController](HRESULT retryResult, ICoreWebView2Controller* retryController) -> HRESULT + { + if (FAILED(retryResult)) + { + if (measure->rm) + { + wchar_t errorMsg[256]; + swprintf_s(errorMsg, L"WebView2: Failed to create controller after retry (HRESULT: 0x%08X)", retryResult); + RmLog(measure->rm, LOG_ERROR, errorMsg); + } + return retryResult; + } + + return initializeController(retryController); + } + ).Get() + ); + + return result; + } + if (measure->rm) { wchar_t errorMsg[256]; @@ -132,55 +223,7 @@ void CreateWebView2(Measure* measure) return result; } - measure->webViewController = controller; - measure->webViewController->get_CoreWebView2(&measure->webView); - - // Set bounds - RECT bounds; - GetClientRect(measure->webViewWindow, &bounds); - measure->webViewController->put_Bounds(bounds); - - // Set initial visibility - measure->webViewController->put_IsVisible(measure->visible ? TRUE : FALSE); - - // Enable host objects and JavaScript in settings - wil::com_ptr settings; - measure->webView->get_Settings(&settings); - settings->put_IsScriptEnabled(TRUE); - settings->put_AreDefaultScriptDialogsEnabled(TRUE); - settings->put_IsWebMessageEnabled(TRUE); - settings->put_AreHostObjectsAllowed(TRUE); - settings->put_AreDevToolsEnabled(TRUE); - settings->put_AreDefaultContextMenusEnabled(TRUE); - - // Create and inject COM Host Object for Rainmeter API - wil::com_ptr hostObject = - Microsoft::WRL::Make(measure, g_typeLib); - - VARIANT variant = {}; - hostObject.query_to(&variant.pdispVal); - variant.vt = VT_DISPATCH; - measure->webView->AddHostObjectToScript(L"rm", &variant); - variant.pdispVal->Release(); - - // Add script to make rm available globally - measure->webView->AddScriptToExecuteOnDocumentCreated( - L"window.rm = chrome.webview.hostObjects.sync.rm", - nullptr - ); - - // Navigate to URL - if (!measure->url.empty()) - { - measure->webView->Navigate(measure->url.c_str()); - } - - measure->initialized = true; - - if (measure->rm) - RmLog(measure->rm, LOG_NOTICE, L"WebView2: Initialized successfully with COM Host Objects"); - - return S_OK; + return initializeController(controller); } ).Get() ); From 0c56ee631fdc84daca2d8e09bf9709a99bd9dcc2 Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Tue, 25 Nov 2025 20:20:05 +0500 Subject: [PATCH 7/9] Add WebView2 Rainmeter plugin with host object API for JavaScript interaction. --- WebView2/Plugin.cpp | 83 ++++------- WebView2/Plugin.h | 9 +- WebView2/WebView2.cpp | 324 +++++++++++++++--------------------------- 3 files changed, 153 insertions(+), 263 deletions(-) diff --git a/WebView2/Plugin.cpp b/WebView2/Plugin.cpp index 718140d..b3f316b 100644 --- a/WebView2/Plugin.cpp +++ b/WebView2/Plugin.cpp @@ -49,9 +49,9 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) // Measure constructor Measure::Measure() : rm(nullptr), skin(nullptr), skinWindow(nullptr), - webViewWindow(nullptr), measureName(nullptr), + measureName(nullptr), width(800), height(600), x(0), y(0), - visible(true), initialized(false), isCleaningUp(false), webMessageToken{} + visible(true), initialized(false), webMessageToken{} { // Initialize COM for this thread if not already done if (!g_comInitialized) @@ -67,10 +67,7 @@ Measure::Measure() : rm(nullptr), skin(nullptr), skinWindow(nullptr), // Measure destructor Measure::~Measure() { - // Mark that cleanup is in progress - isCleaningUp = true; - - // Proper cleanup sequence to prevent crashes and race conditions + // Proper cleanup sequence to prevent crashes // 1. Remove event handlers first if (webView && webMessageToken.value != 0) @@ -92,19 +89,8 @@ Measure::~Measure() webView.reset(); // Explicit release } - // 4. Longer delay to ensure WebView2 runtime fully releases resources - // This prevents 0x80080005 error on skin refresh - Sleep(250); - - // 5. Destroy window last - if (webViewWindow && IsWindow(webViewWindow)) - { - DestroyWindow(webViewWindow); - webViewWindow = nullptr; - } - - // 6. Final delay to ensure complete cleanup - Sleep(50); + // 4. Brief delay to allow async cleanup + Sleep(100); } // Rainmeter Plugin Exports @@ -170,7 +156,7 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) measure->visible = RmReadInt(rm, L"Visible", 1) != 0; // Create WebView2 if not already created - if (!measure->initialized && !measure->isCleaningUp) + if (!measure->initialized) { CreateWebView2(measure); } @@ -182,24 +168,23 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) measure->webView->Navigate(measure->url.c_str()); } - // Update window position and size - if (measure->webViewWindow) + // Update WebView2 bounds + if (measure->webViewController) { - SetWindowPos( - measure->webViewWindow, - nullptr, - measure->x, measure->y, - measure->width, measure->height, - SWP_NOZORDER | SWP_NOACTIVATE - ); - - ShowWindow(measure->webViewWindow, measure->visible ? SW_SHOW : SW_HIDE); - - // Update WebView2 controller visibility - if (measure->webViewController) + RECT bounds; + GetClientRect(measure->skinWindow, &bounds); + bounds.left = measure->x; + bounds.top = measure->y; + if (measure->width > 0) + { + bounds.right = measure->x + measure->width; + } + if (measure->height > 0) { - measure->webViewController->put_IsVisible(measure->visible ? TRUE : FALSE); + bounds.bottom = measure->y + measure->height; } + measure->webViewController->put_Bounds(bounds); + measure->webViewController->put_IsVisible(measure->visible ? TRUE : FALSE); } } } @@ -264,30 +249,22 @@ PLUGIN_EXPORT void ExecuteBang(void* data, LPCWSTR args) } else if (_wcsicmp(action.c_str(), L"Show") == 0) { - if (measure->webViewWindow) + measure->visible = true; + + // Make WebView2 controller visible + if (measure->webViewController) { - ShowWindow(measure->webViewWindow, SW_SHOW); - measure->visible = true; - - // Also make WebView2 controller visible - if (measure->webViewController) - { - measure->webViewController->put_IsVisible(TRUE); - } + measure->webViewController->put_IsVisible(TRUE); } } else if (_wcsicmp(action.c_str(), L"Hide") == 0) { - if (measure->webViewWindow) + measure->visible = false; + + // Hide WebView2 controller + if (measure->webViewController) { - ShowWindow(measure->webViewWindow, SW_HIDE); - measure->visible = false; - - // Also hide WebView2 controller - if (measure->webViewController) - { - measure->webViewController->put_IsVisible(FALSE); - } + measure->webViewController->put_IsVisible(FALSE); } } else if (_wcsicmp(action.c_str(), L"ExecuteScript") == 0) diff --git a/WebView2/Plugin.h b/WebView2/Plugin.h index 7d499f1..0d96fea 100644 --- a/WebView2/Plugin.h +++ b/WebView2/Plugin.h @@ -17,7 +17,6 @@ struct Measure void* rm; void* skin; HWND skinWindow; - HWND webViewWindow; LPCWSTR measureName; std::wstring url; @@ -27,7 +26,6 @@ struct Measure int y; bool visible; bool initialized; - bool isCleaningUp; wil::com_ptr webViewController; wil::com_ptr webView; @@ -35,9 +33,12 @@ struct Measure Measure(); ~Measure(); + + // Member callback functions for WebView2 creation + HRESULT CreateEnvironmentHandler(HRESULT result, ICoreWebView2Environment* env); + HRESULT CreateControllerHandler(HRESULT result, ICoreWebView2Controller* controller); }; // WebView2 functions void CreateWebView2(Measure* measure); -void RegisterWebViewWindowClass(); -LRESULT CALLBACK WebViewWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + diff --git a/WebView2/WebView2.cpp b/WebView2/WebView2.cpp index b5a885d..54b2a8b 100644 --- a/WebView2/WebView2.cpp +++ b/WebView2/WebView2.cpp @@ -1,57 +1,6 @@ #include "Plugin.h" #include "HostObjectRmAPI.h" #include "../API/RainmeterAPI.h" -#include -#include - -// Window class registration flag -static bool g_windowClassRegistered = false; - -// Window procedure for WebView2 host window -LRESULT CALLBACK WebViewWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) - { - case WM_SIZE: - { - // Get measure from window user data - Measure* measure = reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); - if (measure && measure->webViewController) - { - RECT bounds; - GetClientRect(hwnd, &bounds); - measure->webViewController->put_Bounds(bounds); - } - return 0; - } - - case WM_DESTROY: - return 0; - - default: - return DefWindowProc(hwnd, uMsg, wParam, lParam); - } -} - -// Register window class for WebView2 host -void RegisterWebViewWindowClass() -{ - if (g_windowClassRegistered) - return; - - WNDCLASSEX wc = { 0 }; - wc.cbSize = sizeof(WNDCLASSEX); - wc.lpfnWndProc = WebViewWindowProc; - wc.hInstance = GetModuleHandle(nullptr); - wc.lpszClassName = L"RainmeterWebView2Host"; - wc.hCursor = LoadCursor(nullptr, IDC_ARROW); - - if (RegisterClassEx(&wc)) - { - g_windowClassRegistered = true; - } -} - // Create WebView2 environment and controller void CreateWebView2(Measure* measure) @@ -63,33 +12,6 @@ void CreateWebView2(Measure* measure) return; } - // Register window class - RegisterWebViewWindowClass(); - - // Create host window as child of skin window - measure->webViewWindow = CreateWindowEx( - 0, - L"RainmeterWebView2Host", - L"WebView2", - WS_CHILD | (measure->visible ? WS_VISIBLE : 0), - measure->x, measure->y, - measure->width, measure->height, - measure->skinWindow, - nullptr, - GetModuleHandle(nullptr), - nullptr - ); - - if (!measure->webViewWindow) - { - if (measure->rm) - RmLog(measure->rm, LOG_ERROR, L"WebView2: Failed to create host window"); - return; - } - - // Store measure pointer in window - SetWindowLongPtr(measure->webViewWindow, GWLP_USERDATA, reinterpret_cast(measure)); - // Create user data folder in TEMP directory to avoid permission issues wchar_t tempPath[MAX_PATH]; GetTempPathW(MAX_PATH, tempPath); @@ -102,134 +24,8 @@ void CreateWebView2(Measure* measure) HRESULT hr = CreateCoreWebView2EnvironmentWithOptions( nullptr, userDataFolder.c_str(), nullptr, Callback( - [measure](HRESULT result, ICoreWebView2Environment* env) -> HRESULT - { - if (FAILED(result)) - { - if (measure->rm) - { - wchar_t errorMsg[256]; - swprintf_s(errorMsg, L"WebView2: Failed to create environment (HRESULT: 0x%08X)", result); - RmLog(measure->rm, LOG_ERROR, errorMsg); - } - return result; - } - - // Helper function to initialize controller after successful creation - auto initializeController = [measure](ICoreWebView2Controller* controller) -> HRESULT - { - measure->webViewController = controller; - measure->webViewController->get_CoreWebView2(&measure->webView); - - // Set bounds - RECT bounds; - GetClientRect(measure->webViewWindow, &bounds); - measure->webViewController->put_Bounds(bounds); - - // Set initial visibility - measure->webViewController->put_IsVisible(measure->visible ? TRUE : FALSE); - - // Enable host objects and JavaScript in settings - wil::com_ptr settings; - measure->webView->get_Settings(&settings); - settings->put_IsScriptEnabled(TRUE); - settings->put_AreDefaultScriptDialogsEnabled(TRUE); - settings->put_IsWebMessageEnabled(TRUE); - settings->put_AreHostObjectsAllowed(TRUE); - settings->put_AreDevToolsEnabled(TRUE); - settings->put_AreDefaultContextMenusEnabled(TRUE); - - // Create and inject COM Host Object for Rainmeter API - wil::com_ptr hostObject = - Microsoft::WRL::Make(measure, g_typeLib); - - VARIANT variant = {}; - hostObject.query_to(&variant.pdispVal); - variant.vt = VT_DISPATCH; - measure->webView->AddHostObjectToScript(L"rm", &variant); - variant.pdispVal->Release(); - - // Add script to make rm available globally - measure->webView->AddScriptToExecuteOnDocumentCreated( - L"window.rm = chrome.webview.hostObjects.sync.rm", - nullptr - ); - - // Navigate to URL - if (!measure->url.empty()) - { - measure->webView->Navigate(measure->url.c_str()); - } - - measure->initialized = true; - - if (measure->rm) - RmLog(measure->rm, LOG_NOTICE, L"WebView2: Initialized successfully with COM Host Objects"); - - return S_OK; - }; - - // Create WebView2 controller with retry for 0x80080005 - env->CreateCoreWebView2Controller( - measure->webViewWindow, - Callback( - [measure, env, initializeController](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT - { - if (FAILED(result)) - { - // Retry once for 0x80080005 (CO_E_SERVER_EXEC_FAILURE) - // This error occurs when previous instance is still cleaning up - if (result == 0x80080005) - { - if (measure->rm) - { - RmLog(measure->rm, LOG_WARNING, L"WebView2: Controller creation failed (previous instance cleaning up), retrying after delay..."); - } - - // Wait for previous instance to fully clean up - Sleep(300); - - // Single retry attempt - env->CreateCoreWebView2Controller( - measure->webViewWindow, - Callback( - [measure, initializeController](HRESULT retryResult, ICoreWebView2Controller* retryController) -> HRESULT - { - if (FAILED(retryResult)) - { - if (measure->rm) - { - wchar_t errorMsg[256]; - swprintf_s(errorMsg, L"WebView2: Failed to create controller after retry (HRESULT: 0x%08X)", retryResult); - RmLog(measure->rm, LOG_ERROR, errorMsg); - } - return retryResult; - } - - return initializeController(retryController); - } - ).Get() - ); - - return result; - } - - if (measure->rm) - { - wchar_t errorMsg[256]; - swprintf_s(errorMsg, L"WebView2: Failed to create controller (HRESULT: 0x%08X)", result); - RmLog(measure->rm, LOG_ERROR, errorMsg); - } - return result; - } - - return initializeController(controller); - } - ).Get() - ); - - return S_OK; - } + measure, + &Measure::CreateEnvironmentHandler ).Get() ); @@ -243,3 +39,119 @@ void CreateWebView2(Measure* measure) } } } + +// Environment creation callback +HRESULT Measure::CreateEnvironmentHandler(HRESULT result, ICoreWebView2Environment* env) +{ + if (FAILED(result)) + { + if (rm) + { + wchar_t errorMsg[256]; + swprintf_s(errorMsg, L"WebView2: Failed to create environment (HRESULT: 0x%08X)", result); + RmLog(rm, LOG_ERROR, errorMsg); + } + return result; + } + + // Create WebView2 controller using skin window directly + env->CreateCoreWebView2Controller( + skinWindow, + Callback( + this, + &Measure::CreateControllerHandler + ).Get() + ); + + return S_OK; +} + +// Controller creation callback +HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller* controller) +{ + if (FAILED(result)) + { + if (rm) + { + wchar_t errorMsg[256]; + swprintf_s(errorMsg, L"WebView2: Failed to create controller (HRESULT: 0x%08X)", result); + RmLog(rm, LOG_ERROR, errorMsg); + } + return result; + } + + if (controller == nullptr) + { + if (rm) + RmLog(rm, LOG_ERROR, L"WebView2: Controller is null"); + return S_FALSE; + } + + webViewController = controller; + webViewController->get_CoreWebView2(&webView); + + // Set bounds within the skin window + RECT bounds; + GetClientRect(skinWindow, &bounds); + bounds.left = x; + bounds.top = y; + if (width > 0) + { + bounds.right = x + width; + } + if (height > 0) + { + bounds.bottom = y + height; + } + webViewController->put_Bounds(bounds); + + // Set initial visibility + webViewController->put_IsVisible(visible ? TRUE : FALSE); + + // Transparent background + auto controller2 = webViewController.query(); + if (controller2) + { + COREWEBVIEW2_COLOR transparentColor = { 0, 0, 0, 0 }; + controller2->put_DefaultBackgroundColor(transparentColor); + } + + // Enable host objects and JavaScript in settings + wil::com_ptr settings; + webView->get_Settings(&settings); + settings->put_IsScriptEnabled(TRUE); + settings->put_AreDefaultScriptDialogsEnabled(TRUE); + settings->put_IsWebMessageEnabled(TRUE); + settings->put_AreHostObjectsAllowed(TRUE); + settings->put_AreDevToolsEnabled(TRUE); + settings->put_AreDefaultContextMenusEnabled(TRUE); + + // Create and inject COM Host Object for Rainmeter API + wil::com_ptr hostObject = + Microsoft::WRL::Make(this, g_typeLib); + + VARIANT variant = {}; + hostObject.query_to(&variant.pdispVal); + variant.vt = VT_DISPATCH; + webView->AddHostObjectToScript(L"rm", &variant); + variant.pdispVal->Release(); + + // Add script to make rm available globally + webView->AddScriptToExecuteOnDocumentCreated( + L"window.rm = chrome.webview.hostObjects.sync.rm", + nullptr + ); + + // Navigate to URL + if (!url.empty()) + { + webView->Navigate(url.c_str()); + } + + initialized = true; + + if (rm) + RmLog(rm, LOG_NOTICE, L"WebView2: Initialized successfully with COM Host Objects"); + + return S_OK; +} From a45e45c96fe8f03974c5178c35b43b387720c57c Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Wed, 26 Nov 2025 07:18:30 +0500 Subject: [PATCH 8/9] implement core WebView2 Rainmeter plugin with COM setup, TypeLib loading, URL handling, and bang command processing for WebView2 actions. --- WebView2/Plugin.cpp | 59 +++------------------------------------------ 1 file changed, 3 insertions(+), 56 deletions(-) diff --git a/WebView2/Plugin.cpp b/WebView2/Plugin.cpp index b3f316b..c4cd8a9 100644 --- a/WebView2/Plugin.cpp +++ b/WebView2/Plugin.cpp @@ -67,30 +67,6 @@ Measure::Measure() : rm(nullptr), skin(nullptr), skinWindow(nullptr), // Measure destructor Measure::~Measure() { - // Proper cleanup sequence to prevent crashes - - // 1. Remove event handlers first - if (webView && webMessageToken.value != 0) - { - webView->remove_WebMessageReceived(webMessageToken); - webMessageToken = {}; - } - - // 2. Close and release WebView2 controller - if (webViewController) - { - webViewController->Close(); - webViewController.reset(); // Explicit release - } - - // 3. Release WebView COM pointer - if (webView) - { - webView.reset(); // Explicit release - } - - // 4. Brief delay to allow async cleanup - Sleep(100); } // Rainmeter Plugin Exports @@ -155,38 +131,9 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) // Read visibility measure->visible = RmReadInt(rm, L"Visible", 1) != 0; - // Create WebView2 if not already created - if (!measure->initialized) - { - CreateWebView2(measure); - } - else - { - // Update existing WebView - if (measure->webView && !measure->url.empty()) - { - measure->webView->Navigate(measure->url.c_str()); - } - - // Update WebView2 bounds - if (measure->webViewController) - { - RECT bounds; - GetClientRect(measure->skinWindow, &bounds); - bounds.left = measure->x; - bounds.top = measure->y; - if (measure->width > 0) - { - bounds.right = measure->x + measure->width; - } - if (measure->height > 0) - { - bounds.bottom = measure->y + measure->height; - } - measure->webViewController->put_Bounds(bounds); - measure->webViewController->put_IsVisible(measure->visible ? TRUE : FALSE); - } - } + // Always create fresh WebView2 instance on every Reload + // This matches the stable PluginWebView-main pattern and prevents race conditions + CreateWebView2(measure); } PLUGIN_EXPORT double Update(void* data) From f4ef9ee9fcb2d9b56623fd0587cf04563c2e460c Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Wed, 26 Nov 2025 07:32:45 +0500 Subject: [PATCH 9/9] add WebView2 Rainmeter plugin implementation with core functionality and package dependencies. --- README.md | 41 +++++++---------- WebView2/Plugin.cpp | 95 ++++++++++++++++++++++++++++++++++++++++ WebView2/packages.config | 4 +- 3 files changed, 114 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 942bc42..0f6e747 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,22 @@ LeftMouseUpAction=[!CommandMeasure MeasureWebView "ExecuteScript alert('Hello!') ;Open DevTools LeftMouseUpAction=[!CommandMeasure MeasureWebView "OpenDevTools"] + +; SetWidth +LeftMouseUpAction=[!CommandMeasure MeasureWebView "SetWidth 500"] +; Dynamically sets the width of the WebView2 control in pixels. + +; SetHeight +LeftMouseUpAction=[!CommandMeasure MeasureWebView "SetHeight 400"] +; Dynamically sets the height of the WebView2 control in pixels. + +; SetX +LeftMouseUpAction=[!CommandMeasure MeasureWebView "SetX 100"] +; Dynamically sets the X position of the WebView2 control relative to the skin window. + +;SetY +LeftMouseUpAction=[!CommandMeasure MeasureWebView "SetY 50"] +;Dynamically sets the Y position of the WebView2 control relative to the skin window. ``` ## 💡 Examples @@ -331,29 +347,6 @@ This creates: - Plugin DLLs in `dist\` folder - Complete `.rmskin` package for distribution -## 🐛 Troubleshooting - -### WebView2 doesn't appear -- Ensure WebView2 Runtime is installed -- Check Rainmeter log for error messages -- Verify skin window has appropriate dimensions - -### Mouse/Keyboard events not working -- **Fixed in v0.0.3**: Added JavaScript settings (`put_IsScriptEnabled`, `put_AreDefaultScriptDialogsEnabled`, `put_IsWebMessageEnabled`) -- Ensure you're using the latest version - -### File paths not loading -- Use `#@#` for @Resources folder: `URL=#@#mypage.html` -- Or use absolute paths: `URL=C:\MyFolder\page.html` - -## 📝 Technical Details - -- **WebView2 SDK**: Microsoft.Web.WebView2 (v1.0.2792.45) -- **Runtime**: Uses Windows Implementation Library (WIL) -- **Architecture**: Supports both x86 and x64 -- **Language**: C++17 -- **User Data**: Stored in `%TEMP%\RainmeterWebView2` - ## 🤝 Contributing Contributions are welcome! Please feel free to submit a Pull Request. @@ -380,4 +373,4 @@ This project is licensed under the MIT License. --- -**Made with ❤️ for the Rainmeter community** \ No newline at end of file +**Made with ❤️ by nstechbytes** \ No newline at end of file diff --git a/WebView2/Plugin.cpp b/WebView2/Plugin.cpp index c4cd8a9..8cb28bb 100644 --- a/WebView2/Plugin.cpp +++ b/WebView2/Plugin.cpp @@ -229,10 +229,105 @@ PLUGIN_EXPORT void ExecuteBang(void* data, LPCWSTR args) ); } } + else if (_wcsicmp(action.c_str(), L"SetWidth") == 0) + { + if (!param.empty()) + { + measure->width = _wtoi(param.c_str()); + + // Update WebView2 bounds + if (measure->webViewController) + { + RECT bounds; + GetClientRect(measure->skinWindow, &bounds); + bounds.left = measure->x; + bounds.top = measure->y; + bounds.right = measure->x + measure->width; + if (measure->height > 0) + { + bounds.bottom = measure->y + measure->height; + } + measure->webViewController->put_Bounds(bounds); + } + } + } + else if (_wcsicmp(action.c_str(), L"SetHeight") == 0) + { + if (!param.empty()) + { + measure->height = _wtoi(param.c_str()); + + // Update WebView2 bounds + if (measure->webViewController) + { + RECT bounds; + GetClientRect(measure->skinWindow, &bounds); + bounds.left = measure->x; + bounds.top = measure->y; + if (measure->width > 0) + { + bounds.right = measure->x + measure->width; + } + bounds.bottom = measure->y + measure->height; + measure->webViewController->put_Bounds(bounds); + } + } + } + else if (_wcsicmp(action.c_str(), L"SetX") == 0) + { + if (!param.empty()) + { + measure->x = _wtoi(param.c_str()); + + // Update WebView2 bounds + if (measure->webViewController) + { + RECT bounds; + GetClientRect(measure->skinWindow, &bounds); + bounds.left = measure->x; + bounds.top = measure->y; + if (measure->width > 0) + { + bounds.right = measure->x + measure->width; + } + if (measure->height > 0) + { + bounds.bottom = measure->y + measure->height; + } + measure->webViewController->put_Bounds(bounds); + } + } + } + else if (_wcsicmp(action.c_str(), L"SetY") == 0) + { + if (!param.empty()) + { + measure->y = _wtoi(param.c_str()); + + // Update WebView2 bounds + if (measure->webViewController) + { + RECT bounds; + GetClientRect(measure->skinWindow, &bounds); + bounds.left = measure->x; + bounds.top = measure->y; + if (measure->width > 0) + { + bounds.right = measure->x + measure->width; + } + if (measure->height > 0) + { + bounds.bottom = measure->y + measure->height; + } + measure->webViewController->put_Bounds(bounds); + } + } + } else if (_wcsicmp(action.c_str(), L"OpenDevTools") == 0) { measure->webView->OpenDevToolsWindow(); } + } PLUGIN_EXPORT void Finalize(void* data) diff --git a/WebView2/packages.config b/WebView2/packages.config index 4b13e9e..4662d01 100644 --- a/WebView2/packages.config +++ b/WebView2/packages.config @@ -1,5 +1,5 @@ - - + +