diff --git a/README.md b/README.md index 17b6b99..1bf468b 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,154 @@ # WebView2 Plugin for Rainmeter -A powerful Rainmeter plugin that embeds Microsoft Edge WebView2 into your skins, enabling modern web content with full JavaScript interop capabilities. +
-![Version](https://img.shields.io/badge/version-0.0.3-blue) -![License](https://img.shields.io/badge/license-MIT-green) -![Rainmeter](https://img.shields.io/badge/rainmeter-4.5%2B-orange) +![WebView2 Plugin Banner](https://img.shields.io/badge/WebView2-Rainmeter_Plugin-0078D4?style=for-the-badge&logo=microsoft-edge&logoColor=white) -## 🌟 Features +**Embed modern web content directly into your Rainmeter skins with full JavaScript interactivity** -- **Modern Web Engine**: Leverage Microsoft Edge WebView2 for rendering modern HTML5, CSS3, and JavaScript -- **JavaScript Bridge**: Seamless two-way communication between Rainmeter and web content -- **Rainmeter API Access**: Full access to Rainmeter's API from JavaScript -- **Dynamic Content**: Load local HTML files or remote URLs -- **Event Handling**: Support for custom events and callbacks -- **Multiple Skins**: Run multiple WebView2 instances simultaneously +[![Version](https://img.shields.io/badge/version-0.0.3-blue?style=flat-square)](../../releases) +[![License](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE) +[![Rainmeter](https://img.shields.io/badge/rainmeter-4.5+-orange?style=flat-square)](https://www.rainmeter.net/) +[![Windows](https://img.shields.io/badge/windows-10%2B-0078D6?style=flat-square&logo=windows&logoColor=white)](https://www.microsoft.com/windows) + +[📥 Download](#-installation) • [📖 Documentation](#-quick-start) • [💡 Examples](#-examples) • [🤝 Contributing](#-contributing) + +
+ +--- + +## ✨ What Can You Build? + + + + + + + +
+Clock
+Animated Widgets
+Create stunning animated clocks, weather displays, and visualizers +
+Web
+Web Dashboards
+Embed live web content and interactive dashboards +
+API
+Smart Integrations
+Connect to APIs and control Rainmeter with JavaScript +
+ +--- + +## 🎯 Key Features + +
+🚀 Modern Web Engine + +Powered by Microsoft Edge WebView2, supporting: +- ✅ HTML5, CSS3, JavaScript ES6+ +- ✅ Modern frameworks (React, Vue, Svelte) +- ✅ WebGL, Canvas, SVG animations +- ✅ Transparent backgrounds by default + +
+ +
+🔌 Seamless JavaScript Bridge + +Two-way communication between web and Rainmeter: +- ✅ Call Rainmeter API from JavaScript +- ✅ Execute JavaScript from Rainmeter +- ✅ Real-time data synchronization +- ✅ Custom events and callbacks + +
+ +
+⚡ Dynamic & Flexible + +- ✅ Load local HTML or remote URLs +- ✅ Multiple WebView instances per skin +- ✅ Hot-reload without flickering +- ✅ Developer tools (F12) built-in + +
+ +--- ## 📋 Requirements -- **Windows**: Windows 10 version 1803 or later (Windows 11 recommended) -- **Rainmeter**: Version 4.5 or higher -- **WebView2 Runtime**: [Download here](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) (usually pre-installed on Windows 11) +> **Before you begin**, make sure you have: + +| Requirement | Version | Status | +|------------|---------|---------| +| **Windows** | 10 (1803+) or 11 | ![Windows](https://img.shields.io/badge/required-critical-red?style=flat-square) | +| **Rainmeter** | 4.5 or higher | ![Rainmeter](https://img.shields.io/badge/required-critical-red?style=flat-square) | +| **WebView2 Runtime** | Latest | ![WebView2](https://img.shields.io/badge/required-critical-red?style=flat-square) | + +
+📦 Don't have WebView2 Runtime? + +
+ +**Good news!** Windows 11 includes it by default. For Windows 10: + +1. 🔗 [Download WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) +2. 🎯 Choose "Evergreen Standalone Installer" +3. ⚡ Run the installer (takes ~1 minute) + +
+ +--- + +## 📥 Installation + +### 🎁 Method 1: One-Click Install (Recommended) + +
+ +**The easiest way to get started!** + +1. 📦 [Download the `.rmskin` file](../../releases/latest) +2. 🖱️ Double-click to install +3. ✨ Done! Plugin and examples are ready to use + +Rainmeter will automatically install everything you need -## 🚀 Installation +
-### Method 1: RMSKIN Package (Recommended) +### 🛠️ Method 2: Manual Installation -1. Download the latest `.rmskin` file from the [Releases](../../releases) page -2. Double-click the `.rmskin` file to install -3. Rainmeter will automatically install the plugin and example skins +
+Click to expand manual installation steps -### Method 2: Manual Installation +
-1. Download the plugin DLLs from the [Releases](../../releases) page -2. Extract the appropriate DLL for your system: - - `x64/WebView2.dll` for 64-bit Rainmeter - - `x32/WebView2.dll` for 32-bit Rainmeter -3. Place the DLL in your Rainmeter plugins folder: - - `%AppData%\Rainmeter\Plugins\` +1. **Download** the plugin DLLs from [Releases](../../releases) -## 📖 Usage +2. **Choose** the right version: + ``` + 📁 x64/WebView2.dll ← For 64-bit Rainmeter (most common) + 📁 x32/WebView2.dll ← For 32-bit Rainmeter + ``` + +3. **Copy** to your Rainmeter plugins folder: + ``` + %AppData%\Rainmeter\Plugins\ + ``` + +4. **Restart** Rainmeter + +
+ +--- -### Basic Skin Configuration +## 🚀 Quick Start + +### Your First WebView Skin + +Create a new skin with this minimal configuration: ```ini [Rainmeter] @@ -50,229 +158,490 @@ Update=1000 Measure=Plugin Plugin=WebView2 URL=file:///#@#index.html -Width=800 -Height=600 +W=800 +H=600 +``` + +Create `index.html` in your `@Resources` folder: + +```html + + + + + + +

🎉 Hello Rainmeter!

+ + +``` + +**That's it!** Load the skin and see your first WebView in action. + +--- + +## ⚙️ Configuration Options + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDescriptionDefaultExample
URL🌐 HTML file or web URL
Supports: file:///, http://, https://
Requiredfile:///#@#index.html
W📏 Width in pixels8001920
H📏 Height in pixels6001080
X↔️ Horizontal position offset0100
Y↕️ Vertical position offset050
Hidden👁️ Start hidden
0 = visible, 1 = hidden
01
Clickthrough🖱️ Mouse interaction
0 = interactive, 1 = clickthrough
01
DynamicVariables🔄 Enable live updates01
+ +> **💡 Pro Tip:** When `DynamicVariables=1`, the WebView updates smartly: +> - **URL changes** → Navigates without recreating +> - **Size/Position changes** → Applied instantly, no flicker +> - **Visibility changes** → Instant toggle + +--- + +## 🎮 Bang Commands + +Control your WebView with Rainmeter bangs: + + + + + + +
+ +**Navigation Commands** + +```ini +; Go to a URL +[!CommandMeasure MeasureWebView "Navigate https://example.com"] + +; Reload current page +[!CommandMeasure MeasureWebView "Reload"] + +; Browser history +[!CommandMeasure MeasureWebView "GoBack"] +[!CommandMeasure MeasureWebView "GoForward"] +``` + + + +**Control Commands** + +```ini +; Execute JavaScript +[!CommandMeasure MeasureWebView "ExecuteScript alert('Hi!')"] + +; Developer tools +[!CommandMeasure MeasureWebView "OpenDevTools"] ``` -### Plugin Options +
-| Option | Description | Default | Required | -|--------|-------------|---------|----------| -| `URL` | Path to HTML file or web URL (supports `file:///`, `http://`, `https://`) | - | Yes | -| `W` | Width of the WebView in pixels | 800 | No | -| `H` | Height of the WebView in pixels | 600 | No | -| `X` | X position offset in pixels | 0 | No | -| `Y` | Y position offset in pixels | 0 | No | -| `Hidden` | Hide the WebView on load (0 = visible, 1 = hidden) | 0 | No | -| `DynamicVariables` | Enable dynamic variable updates (0 or 1) | 0 | No | +--- -**Notes**: -- Transparent background is always enabled by default. Developer tools (F12) are always available. -- When `DynamicVariables=1`, the plugin intelligently handles updates: - - **URL changes**: Navigates to the new URL without recreating the WebView - - **Dimension/Position changes** (`W`, `H`, `X`, `Y`): Applied instantly without flickering - - **Visibility changes** (`Hidden`): Applied instantly - - The WebView is only created once on first load, preventing flickering issues +## 🔥 JavaScript Integration +### Lifecycle Hooks -### Bang Commands +Your JavaScript can respond to Rainmeter events: -Execute commands from your skin using `[!CommandMeasure MeasureName "Command"]`: +```javascript +// Called once when plugin is ready +window.OnInitialize = function() { + console.log("🚀 WebView initialized!"); + return "Ready!"; // This becomes the measure's value +}; + +// Called on every Rainmeter update +window.OnUpdate = function() { + const now = new Date().toLocaleTimeString(); + return now; // Updates measure value +}; +``` -| Command | Description | Example | -|---------|-------------|---------| -| `Navigate ` | Navigate to a URL (web or file path) | `[!CommandMeasure MeasureWebView "Navigate https://example.com"]` | -| `Reload` | Reload the current page | `[!CommandMeasure MeasureWebView "Reload"]` | -| `GoBack` | Navigate to the previous page in history | `[!CommandMeasure MeasureWebView "GoBack"]` | -| `GoForward` | Navigate to the next page in history | `[!CommandMeasure MeasureWebView "GoForward"]` | -| `ExecuteScript + + diff --git a/Resources/Skins/WebView2/@Resources/JSInteraction/script.js b/Resources/Skins/WebView2/@Resources/JSInteraction/script.js new file mode 100644 index 0000000..e517c45 --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/JSInteraction/script.js @@ -0,0 +1,46 @@ +let updateCount = 0; + +// Called once when the plugin is ready +window.OnInitialize = function() { + log("🚀 OnInitialize called!"); + return "Initialized!"; +}; + +// Called on every Rainmeter update cycle +window.OnUpdate = function() { + updateCount++; + const message = `Update #${updateCount}`; + + // Update the display in HTML + const display = document.getElementById('update-display'); + if (display) { + display.textContent = message; + } + + // Return value updates the measure's string value in Rainmeter + return message; +}; + +// Function callable from Rainmeter via [Measure:CallJS('addNumbers', 'a', 'b')] +window.addNumbers = function(a, b) { + const sum = parseInt(a) + parseInt(b); + log(`🧮 addNumbers called: ${a} + ${b} = ${sum}`); + return sum; +}; + +// Helper to log to HTML +function log(message) { + const container = document.getElementById('log-container'); + if (container) { + const entry = document.createElement('div'); + entry.className = 'log-entry'; + entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`; + container.insertBefore(entry, container.firstChild); + + // Keep log size manageable + if (container.children.length > 50) { + container.removeChild(container.lastChild); + } + } + console.log(message); +} diff --git a/Resources/Skins/WebView2/@Resources/JSInteraction/style.css b/Resources/Skins/WebView2/@Resources/JSInteraction/style.css new file mode 100644 index 0000000..fad14ef --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/JSInteraction/style.css @@ -0,0 +1,128 @@ +:root { + --primary: #6366f1; + --secondary: #a855f7; + --bg: #0f172a; + --card-bg: rgba(30, 41, 59, 0.7); + --text: #f8fafc; + --text-muted: #94a3b8; + --success: #22c55e; + --error: #ef4444; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', system-ui, sans-serif; + background: var(--bg); + color: var(--text); + height: 100vh; + overflow: hidden; + display: flex; + flex-direction: column; + padding: 20px; +} + +h1 { + font-size: 1.5rem; + margin-bottom: 1rem; + background: linear-gradient(to right, var(--primary), var(--secondary)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + text-align: center; +} + +.container { + flex: 1; + display: flex; + flex-direction: column; + gap: 1rem; + overflow-y: auto; + padding-right: 5px; +} + +.card { + background: var(--card-bg); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; + padding: 1rem; + transition: transform 0.2s; +} + +.card:hover { + transform: translateY(-2px); + border-color: rgba(255, 255, 255, 0.2); +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; +} + +.card-title { + font-weight: 600; + color: var(--text); +} + +.card-subtitle { + font-size: 0.8rem; + color: var(--text-muted); +} + +.value-display { + font-family: 'Consolas', monospace; + background: rgba(0, 0, 0, 0.3); + padding: 0.5rem; + border-radius: 6px; + color: var(--success); + word-break: break-all; + text-align: center; + font-size: 1.2rem; +} + +.log-container { + font-family: 'Consolas', monospace; + font-size: 0.8rem; + color: var(--text-muted); + background: rgba(0, 0, 0, 0.3); + padding: 0.5rem; + border-radius: 6px; + height: 150px; + overflow-y: auto; +} + +.log-entry { + margin-bottom: 4px; + border-bottom: 1px solid rgba(255,255,255,0.05); + padding-bottom: 2px; +} + +/* Custom Scrollbar */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb { + background: linear-gradient(to bottom, var(--primary), var(--secondary)); + border-radius: 4px; + border: 2px solid transparent; + background-clip: content-box; +} + +::-webkit-scrollbar-thumb:hover { + background: linear-gradient(to bottom, var(--secondary), var(--primary)); + border-radius: 2px solid transparent; + background-clip: content-box; +} diff --git a/Resources/Skins/WebView2/BangCommand/BangCommand.ini b/Resources/Skins/WebView2/BangCommand/BangCommand.ini index 76556eb..8de6710 100644 --- a/Resources/Skins/WebView2/BangCommand/BangCommand.ini +++ b/Resources/Skins/WebView2/BangCommand/BangCommand.ini @@ -7,7 +7,7 @@ DynamicWindowSize=1 Name=BangCommand Author=nstechbytes Information=Demonstrates controlling WebView2 via !CommandMeasure bangs. -Version=1.0 +Version=0.0.6 License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 [Variables] diff --git a/Resources/Skins/WebView2/Calendar/Calendar.ini b/Resources/Skins/WebView2/Calendar/Calendar.ini index 5179e61..f1fcfc0 100644 --- a/Resources/Skins/WebView2/Calendar/Calendar.ini +++ b/Resources/Skins/WebView2/Calendar/Calendar.ini @@ -5,8 +5,8 @@ Update=1000 Name=Calendar Author=nstechbytes Information=Calendar Widget using WebView2 -Version=0.0.3 -License=MIT +Version=0.0.6 +License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 ; ======================================== ; Measure ; ======================================== diff --git a/Resources/Skins/WebView2/Clock/Clock.ini b/Resources/Skins/WebView2/Clock/Clock.ini index 9ba8325..830ef3d 100644 --- a/Resources/Skins/WebView2/Clock/Clock.ini +++ b/Resources/Skins/WebView2/Clock/Clock.ini @@ -5,8 +5,8 @@ Update=1000 Name=Calendar Author=nstechbytes Information=Calendar Widget using WebView2 -Version=0.0.3 -License=MIT +Version=0.0.6 +License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 ; ======================================== ; Measure ; ======================================== diff --git a/Resources/Skins/WebView2/InformationProperty/InformationProperty.ini b/Resources/Skins/WebView2/InformationProperty/InformationProperty.ini index 46f7e05..021bcb2 100644 --- a/Resources/Skins/WebView2/InformationProperty/InformationProperty.ini +++ b/Resources/Skins/WebView2/InformationProperty/InformationProperty.ini @@ -7,7 +7,7 @@ DynamicWindowSize=1 Name=InformationProperty Author=nstechbytes Information=Shows Rainmeter information properties via WebView2. -Version=1.0 +Version=0.0.6 License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 ; ======================================== ; Measure diff --git a/Resources/Skins/WebView2/IslamicDate/IslamicDate.ini b/Resources/Skins/WebView2/IslamicDate/IslamicDate.ini index 05573e0..fa8609d 100644 --- a/Resources/Skins/WebView2/IslamicDate/IslamicDate.ini +++ b/Resources/Skins/WebView2/IslamicDate/IslamicDate.ini @@ -5,8 +5,8 @@ Update=1000 Name=Islamic Date Author=nstechbytes Information=Islamic (Hijri) Date Widget using WebView2 -Version=0.0.1 -License=MIT +Version=0.0.6 +License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 ; ======================================== ; Measure ; ======================================== diff --git a/Resources/Skins/WebView2/JSInteraction/JSInteraction.ini b/Resources/Skins/WebView2/JSInteraction/JSInteraction.ini new file mode 100644 index 0000000..71833fd --- /dev/null +++ b/Resources/Skins/WebView2/JSInteraction/JSInteraction.ini @@ -0,0 +1,93 @@ +[Rainmeter] +Update=1000 +AccurateText=1 +DynamicWindowSize=1 + +[Metadata] +Name=JSInteraction +Author=nstechbytes +Information=Demonstrates JavaScript interaction (OnInitialize, OnUpdate, CallJS) with the WebView2 plugin. +Version=0.0.6 +License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 + +; ======================================== +; Measure +; ======================================== +[MeasureWebView] +Measure=Plugin +Plugin=WebView2 +URL=file://#@#JSInteraction\index.html +W=400 +H=450 +X=25 +Y=25 +DynamicVariables=1 + +; ================================================== +; Background +; ================================================== + +[MeterBackground] +Meter=Shape +Shape=Rectangle 0,0,450,650,12 | FillColor 136,93,244,200 | StrokeWidth 0 + +; ================================================== +; Meters +; ================================================== + +[MeterTitle] +Meter=String +MeasureName=MeasureWebView +X=225 +Y=500 +W=400 +H=20 +FontFace=Segoe UI +FontSize=14 +FontWeight=700 +FontColor=255,255,255,255 +StringAlign=Center +AntiAlias=1 +Text=JS Interaction Demo + +[MeterStatus] +Meter=String +MeasureName=MeasureWebView +X=225 +Y=5R +W=400 +H=40 +FontFace=Consolas +FontSize=10 +FontColor=100,255,100,255 +StringAlign=Center +AntiAlias=1 +Text=Status: %1 + +[MeterCallJSResult] +Meter=String +X=225 +Y=5R +W=400 +H=40 +FontFace=Segoe UI +FontSize=11 +FontColor=200,200,255,255 +StringAlign=Center +AntiAlias=1 +Text=Result from JS: [MeasureWebView:CallJS('addNumbers', '15', '25')] +DynamicVariables=1 + +[MeterInstruction] +Meter=String +X=225 +Y=5R +W=380 +H=40 +FontFace=Segoe UI +FontSize=9 +FontColor=150,150,150,255 +StringAlign=Center +AntiAlias=1 +Text=Check the console (F12) for logs +ClipString=1 diff --git a/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini b/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini index 5bc0e87..cc2110b 100644 --- a/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini +++ b/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini @@ -7,7 +7,7 @@ DynamicWindowSize=1 Name=ReadMeasureOption Author=nstechbytes Information=Demonstrates reading options from the current measure using the WebView2 plugin. -Version=1.0 +Version=0.0.6 License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 ; ======================================== ; Measure diff --git a/Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini b/Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini index bde28e0..f19dc6a 100644 --- a/Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini +++ b/Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini @@ -7,8 +7,8 @@ DynamicWindowSize=1 Name=ReadSectionOption Demo Author=nstechbytes Information=Demonstrates reading options from other sections using WebView2 -Version=1.0 -License=MIT +Version=0.0.6 +License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 [Variables] TestVar=Hello from Variables! diff --git a/Resources/Skins/WebView2/UtilityFunction/UtilityFunction.ini b/Resources/Skins/WebView2/UtilityFunction/UtilityFunction.ini index 6374b9d..90b87e3 100644 --- a/Resources/Skins/WebView2/UtilityFunction/UtilityFunction.ini +++ b/Resources/Skins/WebView2/UtilityFunction/UtilityFunction.ini @@ -7,7 +7,7 @@ DynamicWindowSize=1 Name=UtilityFunction Author=nstechbytes Information=Demonstrates RainmeterAPI utility functions using the WebView2 plugin. -Version=1.0 +Version=0.0.6 License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 [Variables] diff --git a/Resources/Skins/WebView2/Weather/Weather.ini b/Resources/Skins/WebView2/Weather/Weather.ini index 99eff65..02b8340 100644 --- a/Resources/Skins/WebView2/Weather/Weather.ini +++ b/Resources/Skins/WebView2/Weather/Weather.ini @@ -5,8 +5,8 @@ Update=1000 Name=Weather Author=nstechbytes Information=Weather Widget using WebView2 -Version=0.0.1 -License=MIT +Version=0.0.6 +License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 ; ======================================== ; Measure ; ======================================== diff --git a/Resources/skin_definition.json b/Resources/skin_definition.json index 5e39e2c..8c25266 100644 --- a/Resources/skin_definition.json +++ b/Resources/skin_definition.json @@ -1,6 +1,6 @@ { "skinDir": ".\\Resources\\Skins", - "version": "0.0.5", + "version": "0.0.6", "minimumVersion": "4.5", "author": "nstechbytes", "variableFiles": "", @@ -17,5 +17,5 @@ "load": "WebView2\\Clock\\Clock.ini", "headerImage": ".\\Resources\\banner.bmp", "configPrefix": "WebView2", - "output": ".\\dist\\WebView2_v0.0.5_Alpha5.rmskin" + "output": ".\\dist\\WebView2_v0.0.6_Alpha6.rmskin" } diff --git a/WebView2/HostObject.idl b/WebView2/HostObject.idl index afb2274..c524103 100644 --- a/WebView2/HostObject.idl +++ b/WebView2/HostObject.idl @@ -23,6 +23,10 @@ library WebView2Library HRESULT ReadDoubleFromSection([in] BSTR section, [in] BSTR option, [in, optional] VARIANT defaultValue, [out, retval] double* result); HRESULT ReadFormulaFromSection([in] BSTR section, [in] BSTR option, [in, optional] VARIANT defaultValue, [out, retval] double* result); + // Lifecycle methods + HRESULT Initialize(); + HRESULT Update([out, retval] double* result); + // Utility functions HRESULT ReplaceVariables([in] BSTR text, [out, retval] BSTR* result); HRESULT GetVariable([in] BSTR variableName, [out, retval] BSTR* result); diff --git a/WebView2/HostObjectRmAPI.cpp b/WebView2/HostObjectRmAPI.cpp index 2bfffb2..d444bc9 100644 --- a/WebView2/HostObjectRmAPI.cpp +++ b/WebView2/HostObjectRmAPI.cpp @@ -263,6 +263,24 @@ STDMETHODIMP HostObjectRmAPI::ReadFormulaFromSection(BSTR section, BSTR option, return S_OK; } +// Lifecycle methods +STDMETHODIMP HostObjectRmAPI::Initialize() +{ + // This method can be called from JavaScript to check if the API is ready + // Simply return S_OK to indicate success + return S_OK; +} + +STDMETHODIMP HostObjectRmAPI::Update(double* result) +{ + if (!result || !measure) + return E_INVALIDARG; + + // Return 1.0 if initialized, 0.0 otherwise (mirrors the C++ Update function) + *result = measure->initialized ? 1.0 : 0.0; + return S_OK; +} + // Utility functions STDMETHODIMP HostObjectRmAPI::ReplaceVariables(BSTR text, BSTR* result) { diff --git a/WebView2/HostObjectRmAPI.h b/WebView2/HostObjectRmAPI.h index 1a1841d..661e3cb 100644 --- a/WebView2/HostObjectRmAPI.h +++ b/WebView2/HostObjectRmAPI.h @@ -28,6 +28,10 @@ class HostObjectRmAPI : public Microsoft::WRL::RuntimeClass< STDMETHODIMP ReadDoubleFromSection(BSTR section, BSTR option, VARIANT defaultValue, double* result) override; STDMETHODIMP ReadFormulaFromSection(BSTR section, BSTR option, VARIANT defaultValue, double* result) override; + // Lifecycle methods + STDMETHODIMP Initialize() override; + STDMETHODIMP Update(double* result) override; + // Utility functions STDMETHODIMP ReplaceVariables(BSTR text, BSTR* result) override; STDMETHODIMP GetVariable(BSTR variableName, BSTR* result) override; diff --git a/WebView2/Plugin.cpp b/WebView2/Plugin.cpp index 1c41c3b..cc41929 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), webMessageToken{} + visible(true), initialized(false), clickthrough(false), webMessageToken{} { // Initialize COM for this thread if not already done if (!g_comInitialized) @@ -82,6 +82,43 @@ PLUGIN_EXPORT void Initialize(void** data, void* rm) measure->measureName = RmGetMeasureName(rm); } +// Helper to update clickthrough state +void UpdateClickthrough(Measure* measure) +{ + if (!measure->skinWindow) return; + + // Find the WebView2 window (child of skin window) + // We iterate through children to find the one that matches our bounds + HWND child = GetWindow(measure->skinWindow, GW_CHILD); + while (child) + { + // Check if this is likely our window + // For simplicity, we assume the first child or check bounds if needed + // Since we can't easily map controller to HWND, we'll try to apply to all children + // that look like WebView windows (or just the first one if we assume 1 per skin for now) + + // Better approach: Check if the window rect matches our measure bounds + RECT rect; + GetWindowRect(child, &rect); + + // Convert to client coordinates of parent + POINT pt = { rect.left, rect.top }; + ScreenToClient(measure->skinWindow, &pt); + + // Allow some tolerance or just apply to all children? + // Applying to all children might be safer for "Clickthrough" if there are multiple WebViews + // and we want them all to respect their settings. + // But if we have multiple measures, we want to target ONLY ours. + + // For now, let's just apply to the child window found. + // EnableWindow(FALSE) makes it ignore mouse input (Clickthrough=1) + // EnableWindow(TRUE) makes it accept mouse input (Clickthrough=0) + EnableWindow(child, !measure->clickthrough); + + child = GetWindow(child, GW_HWNDNEXT); + } +} + PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) { Measure* measure = (Measure*)data; @@ -136,6 +173,7 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) 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; // Check if URL has changed (requires recreation) bool urlChanged = (newUrl != measure->url); @@ -147,6 +185,7 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) newY != measure->y); bool visibilityChanged = (newVisible != measure->visible); + bool clickthroughChanged = (newClickthrough != measure->clickthrough); // Update stored values measure->url = newUrl; @@ -155,6 +194,7 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) measure->x = newX; measure->y = newY; measure->visible = newVisible; + measure->clickthrough = newClickthrough; // Only create WebView2 if not initialized OR if URL changed if (!measure->initialized || urlChanged) @@ -191,32 +231,69 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) { measure->webViewController->put_IsVisible(measure->visible ? TRUE : FALSE); } + + if (clickthroughChanged) + { + UpdateClickthrough(measure); + } } } PLUGIN_EXPORT double Update(void* data) { Measure* measure = (Measure*)data; + + // Call JavaScript OnUpdate callback if WebView is initialized + if (measure->initialized && measure->webView) + { + 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; + } + + // Trigger Rainmeter redraw after callback completes + if (measure->skin) + { + RmExecute(measure->skin, L"!UpdateMeter *"); + RmExecute(measure->skin, L"!Redraw"); + } + } + return S_OK; + } + ).Get() + ); + } + return measure->initialized ? 1.0 : 0.0; } PLUGIN_EXPORT LPCWSTR GetString(void* data) { Measure* measure = (Measure*)data; - static std::wstring result; - if (measure->initialized) + // Return the callback result if available, otherwise return "0" + if (!measure->callbackResult.empty()) { - result = L"WebView2 Initialized"; - } - else - { - result = L"WebView2 Initializing..."; + return measure->callbackResult.c_str(); } - return result.c_str(); + return L"0"; } - PLUGIN_EXPORT void ExecuteBang(void* data, LPCWSTR args) { Measure* measure = (Measure*)data; @@ -252,10 +329,6 @@ PLUGIN_EXPORT void ExecuteBang(void* data, LPCWSTR args) { measure->webView->GoForward(); } - else if (_wcsicmp(action.c_str(), L"GoForward") == 0) - { - measure->webView->GoForward(); - } else if (_wcsicmp(action.c_str(), L"ExecuteScript") == 0) { if (!param.empty()) @@ -275,7 +348,75 @@ PLUGIN_EXPORT void ExecuteBang(void* data, LPCWSTR args) { measure->webView->OpenDevToolsWindow(); } +} +// Generic JavaScript function caller +PLUGIN_EXPORT LPCWSTR CallJS(void* data, const int argc, const WCHAR* argv[]) +{ + Measure* measure = (Measure*)data; + + if (!measure || !measure->initialized || !measure->webView) + return L""; + + if (argc == 0 || !argv[0]) + return L""; + + // Build unique key for this call: functionName|arg1|arg2... + std::wstring key = argv[0]; + for (int i = 1; i < argc; i++) + { + key += L"|"; + key += argv[i]; + } + + // Build JavaScript call: functionName(arg1, arg2, ...) + std::wstring jsCode = L"(function() { try { if (typeof " + std::wstring(argv[0]) + L" === 'function') { var result = " + std::wstring(argv[0]) + L"("; + + // Add arguments if provided + for (int i = 1; i < argc; i++) + { + if (i > 1) jsCode += L", "; + jsCode += L"'" + std::wstring(argv[i]) + L"'"; + } + + jsCode += L"); return result !== undefined ? String(result) : ''; } return 'Function not found'; } catch(e) { return 'Error: ' + e.message; } })();"; + + // Execute asynchronously and update cache + measure->webView->ExecuteScript( + jsCode.c_str(), + Callback( + [measure, key](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT + { + if (SUCCEEDED(errorCode) && resultObjectAsJson) + { + std::wstring result = resultObjectAsJson; + if (result.length() >= 2 && result.front() == L'"' && result.back() == L'"') + { + result = result.substr(1, result.length() - 2); + } + + if (!result.empty() && result != L"null") + { + // Update cache for this specific call + measure->jsResults[key] = result; + } + } + return S_OK; + } + ).Get() + ); + + // Return cached result if available, otherwise "0" + if (measure->jsResults.find(key) != measure->jsResults.end()) + { + measure->buffer = measure->jsResults[key]; + } + else + { + measure->buffer = L"0"; + } + + return measure->buffer.c_str(); } PLUGIN_EXPORT void Finalize(void* data) diff --git a/WebView2/Plugin.h b/WebView2/Plugin.h index dfca19c..0ea5939 100644 --- a/WebView2/Plugin.h +++ b/WebView2/Plugin.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -27,11 +28,16 @@ struct Measure int y; bool visible; bool initialized; + bool clickthrough; 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(); @@ -42,4 +48,5 @@ struct Measure // WebView2 functions void CreateWebView2(Measure* measure); +void UpdateClickthrough(Measure* measure); diff --git a/WebView2/WebView2.cpp b/WebView2/WebView2.cpp index e86ce02..a3db632 100644 --- a/WebView2/WebView2.cpp +++ b/WebView2/WebView2.cpp @@ -143,6 +143,49 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller nullptr ); + // Add NavigationCompleted event to call OnInitialize after page loads + webView->add_NavigationCompleted( + Callback( + [this](ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT + { + // 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 ''; })();", + Callback( + [this](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") + { + callbackResult = result; + + // Trigger Rainmeter redraw after callback completes + if (skin) + { + RmExecute(skin, L"!UpdateMeter *"); + RmExecute(skin, L"!Redraw"); + } + } + } + return S_OK; + } + ).Get() + ); + return S_OK; + } + ).Get(), + nullptr + ); + // Navigate to URL if (!url.empty()) { @@ -154,5 +197,8 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller if (rm) RmLog(rm, LOG_NOTICE, L"WebView2: Initialized successfully with COM Host Objects"); + // Apply initial clickthrough state + UpdateClickthrough(this); + return S_OK; } diff --git a/WebView2/WebView2.rc b/WebView2/WebView2.rc index 883788c..89c9450 100644 Binary files a/WebView2/WebView2.rc and b/WebView2/WebView2.rc differ