From 225716176ee7d8ef3e9a577154d92528837b7f6e Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Wed, 26 Nov 2025 07:42:49 +0500 Subject: [PATCH 01/12] implement initial Rainmeter plugin with WebView2 embedding and host object support --- WebView2/HostObject.idl | 2 +- WebView2/HostObjectRmAPI.cpp | 2 +- WebView2/HostObjectRmAPI.h | 2 +- WebView2/Plugin.cpp | 1 + WebView2/Plugin.h | 1 + WebView2/WebView2.cpp | 1 + 6 files changed, 6 insertions(+), 3 deletions(-) diff --git a/WebView2/HostObject.idl b/WebView2/HostObject.idl index d553246..5a8e112 100644 --- a/WebView2/HostObject.idl +++ b/WebView2/HostObject.idl @@ -1,4 +1,4 @@ -// Copyright (C) 2024 WebView2 Plugin. All rights reserved. +// Copyright (C) 2025 nstechbytes. All rights reserved. import "oaidl.idl"; import "ocidl.idl"; diff --git a/WebView2/HostObjectRmAPI.cpp b/WebView2/HostObjectRmAPI.cpp index 295e217..13ef61b 100644 --- a/WebView2/HostObjectRmAPI.cpp +++ b/WebView2/HostObjectRmAPI.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2024 WebView2 Plugin. All rights reserved. +// Copyright (C) 2025 nstechbytes. All rights reserved. #include "HostObjectRmAPI.h" #include "Plugin.h" diff --git a/WebView2/HostObjectRmAPI.h b/WebView2/HostObjectRmAPI.h index 6ec0f37..7205845 100644 --- a/WebView2/HostObjectRmAPI.h +++ b/WebView2/HostObjectRmAPI.h @@ -1,4 +1,4 @@ -// Copyright (C) 2024 WebView2 Plugin. All rights reserved. +// Copyright (C) 2025 nstechbytes. All rights reserved. #pragma once #include "HostObject_h.h" diff --git a/WebView2/Plugin.cpp b/WebView2/Plugin.cpp index 8cb28bb..a300c83 100644 --- a/WebView2/Plugin.cpp +++ b/WebView2/Plugin.cpp @@ -1,3 +1,4 @@ +// Copyright (C) 2025 nstechbytes. All rights reserved. #include "Plugin.h" #include "../API/RainmeterAPI.h" diff --git a/WebView2/Plugin.h b/WebView2/Plugin.h index 0d96fea..dfca19c 100644 --- a/WebView2/Plugin.h +++ b/WebView2/Plugin.h @@ -1,3 +1,4 @@ +// Copyright (C) 2025 nstechbytes. All rights reserved. #pragma once #include diff --git a/WebView2/WebView2.cpp b/WebView2/WebView2.cpp index 54b2a8b..42fb8c2 100644 --- a/WebView2/WebView2.cpp +++ b/WebView2/WebView2.cpp @@ -1,3 +1,4 @@ +// Copyright (C) 2025 nstechbytes. All rights reserved. #include "Plugin.h" #include "HostObjectRmAPI.h" #include "../API/RainmeterAPI.h" From cd949619702ad8e11e41c89a345c28f51535acd2 Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Wed, 26 Nov 2025 08:11:19 +0500 Subject: [PATCH 02/12] add initial WebView2 plugin documentation site with HTML, CSS, and JavaScript. --- docs/README.md | 3 + docs/docs.js | 211 +++++++++++++++++++++++++++ docs/index.html | 375 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/styles.css | 370 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 959 insertions(+) create mode 100644 docs/README.md create mode 100644 docs/docs.js create mode 100644 docs/index.html create mode 100644 docs/styles.css diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..14aa8e3 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,3 @@ +# WebView2 Plugin Documentation + +To view this documentation within your Rainmeter skin, point your WebView2 measure's `URL` option to the local file path of `index.html`. For example, if your skin structure places this folder in `@Resources`, use `URL=#@#docs\index.html`. Since the documentation uses relative paths and no external dependencies, it will load instantly and function fully offline. Ensure `IsScriptEnabled` is set to `1` in your measure settings to allow the search functionality to work. diff --git a/docs/docs.js b/docs/docs.js new file mode 100644 index 0000000..0f9694a --- /dev/null +++ b/docs/docs.js @@ -0,0 +1,211 @@ +(function() { + 'use strict'; + + // DOM Elements + const searchInput = document.getElementById('search-input'); + const tocList = document.getElementById('toc-list'); + const noResults = document.getElementById('no-results'); + const sections = document.querySelectorAll('section'); + const menuToggle = document.getElementById('menu-toggle'); + const sidebar = document.getElementById('sidebar'); + + // State + let searchDebounceTimer; + const DEBOUNCE_DELAY = 300; + let originalContent = new Map(); // Store original HTML to restore after highlighting + + // Initialize + function init() { + // Store original content for restoring after search clears + sections.forEach(section => { + originalContent.set(section.id, section.innerHTML); + }); + + // Event Listeners + searchInput.addEventListener('input', handleSearchInput); + menuToggle.addEventListener('click', toggleSidebar); + + // Close sidebar when clicking outside on mobile + document.addEventListener('click', (e) => { + if (window.innerWidth <= 768 && + sidebar.classList.contains('open') && + !sidebar.contains(e.target) && + !menuToggle.contains(e.target)) { + toggleSidebar(); + } + }); + + // Handle TOC link clicks for smooth scrolling and mobile menu closing + tocList.querySelectorAll('a').forEach(link => { + link.addEventListener('click', (e) => { + if (window.innerWidth <= 768) { + toggleSidebar(); + } + }); + }); + } + + // Search Logic + function handleSearchInput(e) { + clearTimeout(searchDebounceTimer); + const query = e.target.value.trim().toLowerCase(); + + searchDebounceTimer = setTimeout(() => { + performSearch(query); + }, DEBOUNCE_DELAY); + } + + function performSearch(query) { + let hasResults = false; + + // Reset if empty + if (!query) { + resetSearch(); + return; + } + + sections.forEach(section => { + const sectionId = section.id; + const originalHTML = originalContent.get(sectionId); + const textContent = section.textContent.toLowerCase(); + const tocLink = tocList.querySelector(`a[href="#${sectionId}"]`); + const tocItem = tocLink.parentElement; + + if (textContent.includes(query)) { + // Match found + hasResults = true; + section.classList.remove('hidden'); + if (tocItem) tocItem.classList.remove('hidden'); + + // Highlight matches + section.innerHTML = highlightMatches(originalHTML, query); + } else { + // No match in this section + section.classList.add('hidden'); + if (tocItem) tocItem.classList.add('hidden'); + } + }); + + // Toggle "No results" message + if (hasResults) { + noResults.classList.add('hidden'); + } else { + noResults.classList.remove('hidden'); + } + } + + function highlightMatches(html, query) { + // Simple regex-based highlighting that avoids HTML tags + // Note: This is a basic implementation. For production with complex HTML, + // a tree walker or DOM manipulation approach is safer. + const regex = new RegExp(`(${escapeRegExp(query)})`, 'gi'); + + // We need to be careful not to replace text inside HTML tags. + // This simple version assumes text content is mostly safe to replace + // if we avoid tag brackets. + + // A safer way for this specific requirement without libraries: + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = html; + + const walker = document.createTreeWalker( + tempDiv, + NodeFilter.SHOW_TEXT, + null, + false + ); + + let node; + const nodesToReplace = []; + + while (node = walker.nextNode()) { + if (node.nodeValue.toLowerCase().includes(query)) { + nodesToReplace.push(node); + } + } + + nodesToReplace.forEach(node => { + const span = document.createElement('span'); + span.innerHTML = node.nodeValue.replace(regex, '$1'); + node.parentNode.replaceChild(span, node); + }); + + return tempDiv.innerHTML; + } + + function resetSearch() { + sections.forEach(section => { + section.classList.remove('hidden'); + section.innerHTML = originalContent.get(section.id); + }); + + tocList.querySelectorAll('li').forEach(li => li.classList.remove('hidden')); + noResults.classList.add('hidden'); + } + + function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + + // Sidebar Logic + function toggleSidebar() { + const isOpen = sidebar.classList.toggle('open'); + menuToggle.setAttribute('aria-expanded', isOpen); + } + + // Run + init(); + initCopyButtons(); + + function initCopyButtons() { + // Target ONLY pre blocks (standard code blocks) + // No buttons in tables + const codeBlocks = document.querySelectorAll('pre'); + + codeBlocks.forEach(block => { + // Create wrapper + const wrapper = document.createElement('div'); + wrapper.className = 'code-wrapper'; + + // For table cells, we need to be careful not to break layout + if (block.tagName === 'CODE') { + wrapper.style.display = 'inline-block'; + wrapper.style.width = '100%'; + } + + // Insert wrapper before block + block.parentNode.insertBefore(wrapper, block); + + // Move block into wrapper + wrapper.appendChild(block); + + // Create copy button + const button = document.createElement('button'); + button.className = 'copy-btn'; + button.textContent = 'Copy'; + button.ariaLabel = 'Copy code to clipboard'; + + // Add click handler + button.addEventListener('click', (e) => { + e.stopPropagation(); // Prevent triggering other clicks + const code = block.textContent; + + navigator.clipboard.writeText(code.trim()).then(() => { + button.textContent = 'Copied!'; + button.classList.add('copied'); + + setTimeout(() => { + button.textContent = 'Copy'; + button.classList.remove('copied'); + }, 2000); + }).catch(err => { + console.error('Failed to copy:', err); + button.textContent = 'Error'; + }); + }); + + wrapper.appendChild(button); + }); + } + +})(); diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..76b4f6d --- /dev/null +++ b/docs/index.html @@ -0,0 +1,375 @@ + + + + + + WebView2 Plugin Documentation + + + + +
+ + WebView2 Plugin Docs +
+ +
+ + +
+
+

Introduction

+

A powerful Rainmeter plugin that embeds Microsoft Edge WebView2 control to display web content or local HTML files directly in your Rainmeter skins. It provides a seamless bridge between Rainmeter measures and web content, enabling rich, interactive user interfaces.

+
+ +
+

Features

+
    +
  • 🌐 Display Web Pages - Load any website directly in your Rainmeter skin
  • +
  • 📄 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 - 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
  • +
+
+ +
+

Installation

+

To install the WebView2 plugin:

+
    +
  1. Download the latest release .rmskin package.
  2. +
  3. Double-click to install.
  4. +
  5. Alternatively, manually copy WebView2.dll to %APPDATA%\Rainmeter\Plugins\.
  6. +
+

Requirement: Windows 10/11 and WebView2 Runtime (usually pre-installed).

+
+ +
+

Quick Start

+

Here is a basic example of how to use the plugin in your skin:

+
[Rainmeter]
+Update=1000
+
+[MeasureWebView]
+Measure=Plugin
+Plugin=WebView2
+URL=https://www.google.com
+Width=1000
+Height=700
+X=0
+Y=0
+
+ +
+

Plugin Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
URLString(empty)URL or file path to load. Supports web URLs and local file paths.
WidthInteger800Width of the WebView window in pixels.
HeightInteger600Height of the WebView window in pixels.
XInteger0X position relative to skin window.
YInteger0Y position relative to skin window.
VisibleInteger1Show (1) or hide (0) the WebView window.
+
+ +
+

Bang Commands

+

Control the WebView2 instance dynamically using !CommandMeasure.

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandArgumentsDescriptionExample
NavigateurlNavigates to the specified URL.[!CommandMeasure MeasureWebView "Navigate https://google.com"]
Reload-Reloads the current page.[!CommandMeasure MeasureWebView "Reload"]
GoBack-Navigates back in history.[!CommandMeasure MeasureWebView "GoBack"]
GoForward-Navigates forward in history.[!CommandMeasure MeasureWebView "GoForward"]
Show-Makes the WebView visible.[!CommandMeasure MeasureWebView "Show"]
Hide-Hides the WebView.[!CommandMeasure MeasureWebView "Hide"]
ExecuteScriptscriptExecutes JavaScript code.[!CommandMeasure MeasureWebView "ExecuteScript alert('Hi')"]
OpenDevTools-Opens Edge DevTools.[!CommandMeasure MeasureWebView "OpenDevTools"]
SetWidthpixelsSets the width.[!CommandMeasure MeasureWebView "SetWidth 500"]
SetHeightpixelsSets the height.[!CommandMeasure MeasureWebView "SetHeight 400"]
SetXpixelsSets X position.[!CommandMeasure MeasureWebView "SetX 100"]
SetYpixelsSets Y position.[!CommandMeasure MeasureWebView "SetY 50"]
+
+
+ +
+

JavaScript API Bridge

+

The plugin automatically injects a global rm object into all loaded web pages, providing seamless access to Rainmeter API functions from JavaScript.

+ +

Reading Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodDescription
rm.ReadString(option, default)Read a string option from the current measure. Returns Promise<string>.
rm.ReadInt(option, default)Read an integer option from the current measure. Returns Promise<number>.
rm.ReadDouble(option, default)Read a double/float option from the current measure. Returns Promise<number>.
rm.ReadFormula(option, default)Read and evaluate a formula option. Returns Promise<number>.
rm.ReadPath(option, default)Read a file path option from the current measure. Returns Promise<string>.
+ +

Utility Functions

+ + + + + + + + + + + + + + + + + + + + + + + + + +
MethodDescription
rm.ReplaceVariables(text)Replace Rainmeter variables in text (e.g., #CURRENTCONFIG#). Returns Promise<string>.
rm.PathToAbsolute(path)Convert relative path to absolute path. Returns Promise<string>.
rm.Execute(command)Execute a Rainmeter bang command. Void.
rm.Log(message, level)Log a message to Rainmeter log. Levels: 'Notice', 'Warning', 'Error', 'Debug'. Void.
+ +

Information Properties

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyReturnsDescription
rm.MeasureNamePromise<string>Get the name of the current measure.
rm.SkinNamePromise<string>Get the name of the current skin.
rm.SkinWindowHandlePromise<string>Get the window handle of the skin.
rm.SettingsFilePromise<string>Get the path to Rainmeter settings file.
+ +

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 also return Promises.
  • +
+
+ +
+

Examples

+

Local HTML Dashboard

+

Create @Resources\dashboard.html:

+
<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        body {
+            font-family: 'Segoe UI', sans-serif;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            padding: 40px;
+        }
+    </style>
+</head>
+<body>
+    <h1>My Dashboard</h1>
+    <p>Time: <span id="clock"></span></p>
+    <script>
+        setInterval(() => {
+            document.getElementById('clock').textContent = 
+                new Date().toLocaleTimeString();
+        }, 1000);
+    </script>
+</body>
+</html>
+
+
+
+ + + + diff --git a/docs/styles.css b/docs/styles.css new file mode 100644 index 0000000..1d6472a --- /dev/null +++ b/docs/styles.css @@ -0,0 +1,370 @@ +:root { + /* Modern Dark Theme Palette */ + --primary-color: #60a5fa; /* Soft Blue */ + --primary-hover: #3b82f6; + --text-color: #e2e8f0; /* Slate 200 */ + --text-muted: #94a3b8; /* Slate 400 */ + --bg-color: #0f172a; /* Slate 900 */ + --sidebar-bg: #1e293b; /* Slate 800 */ + --sidebar-width: 280px; + --border-color: #334155; /* Slate 700 */ + --input-bg: #0f172a; + --highlight-color: #1e40af; /* Blue 800 */ + --focus-ring: 2px solid var(--primary-color); + --font-family: 'Inter', 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, sans-serif; + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: var(--font-family); + color: var(--text-color); + background-color: var(--bg-color); + line-height: 1.7; + -webkit-font-smoothing: antialiased; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* Layout */ +.container { + display: flex; + min-height: 100vh; +} + +/* Sidebar */ +.sidebar { + width: var(--sidebar-width); + background-color: var(--sidebar-bg); + border-right: 1px solid var(--border-color); + position: fixed; + top: 0; + bottom: 0; + left: 0; + overflow-y: auto; + padding: 24px; + z-index: 100; + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 4px 0 24px rgba(0,0,0,0.2); +} + +/* Custom Scrollbar for Sidebar */ +.sidebar::-webkit-scrollbar { + width: 6px; +} + +.sidebar::-webkit-scrollbar-track { + background: transparent; +} + +.sidebar::-webkit-scrollbar-thumb { + background-color: var(--border-color); + border-radius: 3px; +} + +.sidebar-header h1 { + margin-top: 0; + font-size: 1.5rem; + font-weight: 700; + color: var(--primary-color); + letter-spacing: -0.025em; + margin-bottom: 24px; +} + +/* Search */ +.search-container { + margin-bottom: 24px; + position: relative; +} + +#search-input { + width: 100%; + padding: 12px 16px; + background-color: var(--input-bg); + border: 1px solid var(--border-color); + border-radius: 8px; + font-size: 0.95rem; + color: var(--text-color); + transition: all 0.2s ease; +} + +#search-input:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.2); +} + +#search-input::placeholder { + color: var(--text-muted); +} + +/* TOC */ +.toc ul { + list-style: none; + padding: 0; + margin: 0; +} + +.toc li { + margin-bottom: 4px; +} + +.toc a { + text-decoration: none; + color: var(--text-muted); + display: block; + padding: 8px 12px; + border-radius: 6px; + transition: all 0.2s ease; + font-size: 0.95rem; +} + +.toc a:hover { + background-color: rgba(255, 255, 255, 0.05); + color: var(--text-color); + transform: translateX(4px); +} + +.toc a:focus { + outline: none; + box-shadow: 0 0 0 2px var(--primary-color); +} + +.toc a.active { + background-color: rgba(96, 165, 250, 0.1); + color: var(--primary-color); + font-weight: 600; +} + +#no-results { + color: var(--text-muted); + font-style: italic; + text-align: center; + margin-top: 20px; +} + +.hidden { + display: none; +} + +/* Main Content */ +.content { + margin-left: var(--sidebar-width); + padding: 60px 80px; + max-width: 1600px; + width: 100%; +} + +section { + margin-bottom: 60px; + scroll-margin-top: 40px; + animation: fadeIn 0.5s ease-out; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +h2 { + font-size: 2rem; + border-bottom: 1px solid var(--border-color); + padding-bottom: 12px; + margin-bottom: 24px; + color: var(--text-color); + font-weight: 600; + letter-spacing: -0.025em; +} + +p { + margin-bottom: 20px; + font-size: 1.05rem; + color: var(--text-muted); + line-height: 1.8; +} + +code { + background-color: rgba(0,0,0,0.3); + padding: 2px 6px; + border-radius: 4px; + font-family: 'Fira Code', monospace; + font-size: 0.9em; + color: var(--primary-color); +} + +mark { + background-color: var(--highlight-color); + color: #fff; + padding: 0 4px; + border-radius: 2px; + font-weight: 500; +} + +/* Mobile Header */ +.mobile-header { + display: none; + background-color: var(--sidebar-bg); + border-bottom: 1px solid var(--border-color); + padding: 16px 24px; + align-items: center; + position: sticky; + top: 0; + z-index: 200; + box-shadow: var(--shadow-sm); +} + +#menu-toggle { + background: none; + border: none; + cursor: pointer; + padding: 8px; + margin-right: 16px; + color: var(--text-color); + border-radius: 4px; +} + +#menu-toggle:hover { + background-color: rgba(255,255,255,0.1); +} + +.mobile-title { + font-weight: 700; + font-size: 1.2rem; + color: var(--text-color); +} + +/* Responsive */ +@media (max-width: 768px) { + .mobile-header { + display: flex; + } + + .sidebar { + transform: translateX(-100%); + box-shadow: none; + } + + .sidebar.open { + transform: translateX(0); + box-shadow: 10px 0 50px rgba(0,0,0,0.5); + } + + .content { + margin-left: 0; + padding: 32px 24px; + } + + h2 { + font-size: 1.75rem; + } +} + +/* Tables */ +table { + width: 100%; + border-collapse: collapse; + margin-bottom: 24px; + font-size: 0.95rem; +} + +th, td { + padding: 12px 16px; + text-align: left; + border-bottom: 1px solid var(--border-color); +} + +th { + font-weight: 600; + color: var(--primary-color); + background-color: rgba(255, 255, 255, 0.02); +} + +tr:hover td { + background-color: rgba(255, 255, 255, 0.02); +} + +/* Code Blocks */ +pre { + background-color: #1e293b; /* Darker slate */ + padding: 20px; + border-radius: 8px; + overflow-x: auto; + margin-bottom: 24px; + border: 1px solid var(--border-color); +} + +pre code { + background-color: transparent; + padding: 0; + color: #e2e8f0; + font-family: 'Fira Code', 'Consolas', monospace; + font-size: 0.9rem; +} + +/* Lists */ +ul, ol { + margin-bottom: 24px; + padding-left: 24px; + color: var(--text-muted); +} + +li { + margin-bottom: 8px; +} + +strong { + color: var(--text-color); + font-weight: 600; +} + +/* Copy Button */ +.code-wrapper { + position: relative; +} + +.copy-btn { + position: absolute; + top: 4px; + right: 4px; + background-color: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + color: var(--text-muted); + border-radius: 4px; + padding: 2px 6px; + font-size: 0.7rem; + cursor: pointer; + transition: all 0.2s; + opacity: 0; + z-index: 10; +} + +.code-wrapper:hover .copy-btn { + opacity: 1; +} + +.copy-btn:hover { + background-color: var(--primary-color); + color: white; + border-color: var(--primary-color); +} + +.copy-btn.copied { + background-color: #10b981; /* Green */ + border-color: #10b981; + color: white; +} From b74979467e4ce7f86981928b7c05352fc9f9ac08 Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Wed, 26 Nov 2025 09:14:32 +0500 Subject: [PATCH 03/12] add new WebView2-based Clock and Calendar widgets. --- .../WebView2/@Resources/Calendar/index.html | 42 ++++ .../WebView2/@Resources/Calendar/script.js | 76 +++++++ .../WebView2/@Resources/Calendar/style.css | 185 ++++++++++++++++++ .../WebView2/@Resources/Clock/index.html | 49 +++++ .../Skins/WebView2/@Resources/Clock/script.js | 32 +++ .../Skins/WebView2/@Resources/Clock/style.css | 172 ++++++++++++++++ .../Skins/WebView2/Calendar/Calendar.ini | 27 +++ Resources/Skins/WebView2/Clock/Clock.ini | 23 +++ 8 files changed, 606 insertions(+) create mode 100644 Resources/Skins/WebView2/@Resources/Calendar/index.html create mode 100644 Resources/Skins/WebView2/@Resources/Calendar/script.js create mode 100644 Resources/Skins/WebView2/@Resources/Calendar/style.css create mode 100644 Resources/Skins/WebView2/@Resources/Clock/index.html create mode 100644 Resources/Skins/WebView2/@Resources/Clock/script.js create mode 100644 Resources/Skins/WebView2/@Resources/Clock/style.css create mode 100644 Resources/Skins/WebView2/Calendar/Calendar.ini create mode 100644 Resources/Skins/WebView2/Clock/Clock.ini diff --git a/Resources/Skins/WebView2/@Resources/Calendar/index.html b/Resources/Skins/WebView2/@Resources/Calendar/index.html new file mode 100644 index 0000000..3e259a1 --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/Calendar/index.html @@ -0,0 +1,42 @@ + + + + + + Glass Calendar Widget + + + + + + + + +
+ +
+
+ +
+
+ +
+ Month + Year +
+ +
+ +
+
+ SuMoTuWeThFrSa +
+
+ +
+
+
+
+ + + diff --git a/Resources/Skins/WebView2/@Resources/Calendar/script.js b/Resources/Skins/WebView2/@Resources/Calendar/script.js new file mode 100644 index 0000000..69e0d64 --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/Calendar/script.js @@ -0,0 +1,76 @@ +const date = new Date(); + +const renderCalendar = () => { + date.setDate(1); + + const monthDays = document.querySelector(".days"); + + const lastDay = new Date( + date.getFullYear(), + date.getMonth() + 1, + 0 + ).getDate(); + + const prevLastDay = new Date( + date.getFullYear(), + date.getMonth(), + 0 + ).getDate(); + + const firstDayIndex = date.getDay(); + + const lastDayIndex = new Date( + date.getFullYear(), + date.getMonth() + 1, + 0 + ).getDay(); + + const nextDays = 7 - lastDayIndex - 1; + + const months = [ + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + ]; + + document.querySelector("#current-month").innerHTML = months[date.getMonth()]; + document.querySelector("#current-year").innerHTML = date.getFullYear(); + + let days = ""; + + // Previous month's days + for (let x = firstDayIndex; x > 0; x--) { + days += `
${prevLastDay - x + 1}
`; + } + + // Current month's days + for (let i = 1; i <= lastDay; i++) { + if ( + i === new Date().getDate() && + date.getMonth() === new Date().getMonth() && + date.getFullYear() === new Date().getFullYear() + ) { + days += `
${i}
`; + } else { + days += `
${i}
`; + } + } + + // Next month's days + for (let j = 1; j <= nextDays; j++) { + days += `
${j}
`; + } + + monthDays.innerHTML = days; +}; + +document.querySelector("#prev-month").addEventListener("click", () => { + date.setMonth(date.getMonth() - 1); + renderCalendar(); +}); + +document.querySelector("#next-month").addEventListener("click", () => { + date.setMonth(date.getMonth() + 1); + renderCalendar(); +}); + +renderCalendar(); diff --git a/Resources/Skins/WebView2/@Resources/Calendar/style.css b/Resources/Skins/WebView2/@Resources/Calendar/style.css new file mode 100644 index 0000000..8a12122 --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/Calendar/style.css @@ -0,0 +1,185 @@ +:root { + --bg-color: #0f0f13; + --text-primary: #ffffff; + --text-secondary: #a0a0a0; + --accent-color: #4facfe; + --accent-gradient: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); + --glass-bg: rgba(255, 255, 255, 0.05); + --glass-border: rgba(255, 255, 255, 0.1); + --day-hover: rgba(255, 255, 255, 0.1); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + background-color: transparent; + font-family: 'Outfit', sans-serif; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + user-select: none; /* Prevent text selection */ +} + +.widget-container { + position: relative; + width: 280px; + height: 320px; /* Reduced from 380px */ + display: flex; + justify-content: center; + align-items: center; +} + +/* Abstract Background Shapes */ +.bg-shape { + position: absolute; + border-radius: 50%; + filter: blur(30px); /* Reduced blur */ + opacity: 0.6; + z-index: 0; +} + +.shape-1 { + width: 160px; /* Reduced from 200px */ + height: 160px; + background: #764ba2; + top: -15px; + left: -15px; + animation: float 10s infinite ease-in-out; +} + +.shape-2 { + width: 140px; /* Reduced from 180px */ + height: 140px; + background: #4facfe; + bottom: -15px; + right: -15px; + animation: float 12s infinite ease-in-out reverse; +} + +@keyframes float { + 0%, 100% { transform: translate(0, 0); } + 50% { transform: translate(15px, 15px); } /* Reduced movement */ +} + +/* Glass Card */ +.calendar-card { + position: relative; + z-index: 1; + width: 90%; + height: 90%; + background: var(--glass-bg); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--glass-border); + border-radius: 18px; /* Reduced from 24px */ + padding: 15px; /* Reduced from 20px */ + box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37); + display: flex; + flex-direction: column; +} + +/* Header */ +.calendar-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; /* Reduced from 20px */ + color: var(--text-primary); +} + +.month-year { + font-size: 0.95rem; /* Reduced from 1.2rem */ + font-weight: 600; + text-transform: capitalize; + display: flex; + gap: 4px; /* Reduced from 5px */ +} + +#current-year { + color: var(--text-secondary); + font-weight: 400; +} + +.nav-btn { + background: transparent; + border: none; + color: var(--text-secondary); + font-size: 1rem; /* Reduced from 1.2rem */ + cursor: pointer; + padding: 4px; /* Reduced from 5px */ + border-radius: 50%; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.nav-btn:hover { + background: var(--glass-border); + color: var(--text-primary); +} + +/* Body */ +.calendar-body { + flex: 1; + display: flex; + flex-direction: column; +} + +.weekdays { + display: grid; + grid-template-columns: repeat(7, 1fr); + text-align: center; + margin-bottom: 6px; /* Reduced from 10px */ +} + +.weekdays span { + font-size: 0.7rem; /* Reduced from 0.85rem */ + font-weight: 500; + color: var(--text-secondary); + text-transform: uppercase; +} + +.days { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 3px; /* Reduced from 5px */ + text-align: center; +} + +.days div { + font-size: 0.8rem; /* Reduced from 0.95rem */ + color: var(--text-primary); + width: 100%; + aspect-ratio: 1; /* Make them square */ + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + cursor: pointer; + transition: all 0.2s ease; +} + +/* States */ +.days div:hover:not(.empty) { + background-color: var(--day-hover); +} + +.days div.today { + background: var(--accent-gradient); + color: #fff; + font-weight: 600; + box-shadow: 0 4px 15px rgba(79, 172, 254, 0.4); +} + +.days div.prev-date, +.days div.next-date { + color: rgba(255, 255, 255, 0.2); + pointer-events: none; +} diff --git a/Resources/Skins/WebView2/@Resources/Clock/index.html b/Resources/Skins/WebView2/@Resources/Clock/index.html new file mode 100644 index 0000000..2d946f5 --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/Clock/index.html @@ -0,0 +1,49 @@ + + + + + + Liquid Clock Widget + + + + + + +
+
+
+
+
+
+ +
+
+
+ 12 + : + 00 +
+ 00 + AM +
+
+
Monday, January 1
+
+
+
+ + + + + + + + + + + + + + + diff --git a/Resources/Skins/WebView2/@Resources/Clock/script.js b/Resources/Skins/WebView2/@Resources/Clock/script.js new file mode 100644 index 0000000..6b20682 --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/Clock/script.js @@ -0,0 +1,32 @@ +function updateClock() { + const now = new Date(); + + let hours = now.getHours(); + const minutes = now.getMinutes(); + const seconds = now.getSeconds(); + const ampm = hours >= 12 ? 'PM' : 'AM'; + + // Convert to 12-hour format + hours = hours % 12; + hours = hours ? hours : 12; // the hour '0' should be '12' + + // Pad with leading zeros + const hoursStr = hours.toString().padStart(2, '0'); + const minutesStr = minutes.toString().padStart(2, '0'); + const secondsStr = seconds.toString().padStart(2, '0'); + + // Update DOM + document.getElementById('hours').textContent = hoursStr; + document.getElementById('minutes').textContent = minutesStr; + document.getElementById('seconds').textContent = secondsStr; + document.getElementById('ampm').textContent = ampm; + + // Update Date + const options = { weekday: 'long', month: 'long', day: 'numeric' }; + const dateStr = now.toLocaleDateString('en-US', options); + document.getElementById('date').textContent = dateStr; +} + +// Update immediately and then every second +updateClock(); +setInterval(updateClock, 1000); diff --git a/Resources/Skins/WebView2/@Resources/Clock/style.css b/Resources/Skins/WebView2/@Resources/Clock/style.css new file mode 100644 index 0000000..ad874c9 --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/Clock/style.css @@ -0,0 +1,172 @@ +:root { + --bg-color: #0f0f13; + --text-primary: #ffffff; + --text-secondary: #a0a0a0; + --blob-color-1: #4facfe; + --blob-color-2: #00f2fe; + --blob-color-3: #a18cd1; + --glass-bg: rgba(255, 255, 255, 0.05); + --glass-border: rgba(255, 255, 255, 0.1); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + background-color: transparent; /* Transparent for widget usage */ + font-family: 'Outfit', sans-serif; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; +} + +.widget-container { + position: relative; + width: 350px; + height: 200px; + display: flex; + justify-content: center; + align-items: center; +} + +/* Liquid Background */ +.liquid-background { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + filter: url('#goo'); /* The gooey magic */ + z-index: 0; +} + +.blob { + position: absolute; + border-radius: 50%; + opacity: 0.8; + animation: move 8s infinite alternate; +} + +.blob-1 { + width: 120px; + height: 120px; + background: var(--blob-color-1); + top: 20px; + left: 40px; + animation-delay: 0s; +} + +.blob-2 { + width: 150px; + height: 150px; + background: var(--blob-color-2); + bottom: 20px; + right: 40px; + animation-delay: -2s; +} + +.blob-3 { + width: 100px; + height: 100px; + background: var(--blob-color-3); + bottom: 40px; + left: 80px; + animation-delay: -4s; +} + +@keyframes move { + 0% { + transform: translate(0, 0) scale(1); + } + 33% { + transform: translate(30px, -50px) scale(1.1); + } + 66% { + transform: translate(-20px, 20px) scale(0.9); + } + 100% { + transform: translate(0, 0) scale(1); + } +} + +/* Glass Panel */ +.glass-panel { + position: relative; + z-index: 1; + width: 90%; + height: 80%; + background: var(--glass-bg); + backdrop-filter: blur(15px); + -webkit-backdrop-filter: blur(15px); + border: 1px solid var(--glass-border); + border-radius: 20px; + display: flex; + justify-content: center; + align-items: center; + box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37); +} + +.clock-content { + text-align: center; + color: var(--text-primary); +} + +.time-wrapper { + display: flex; + align-items: baseline; + justify-content: center; + line-height: 1; + margin-bottom: 5px; +} + +#hours, #minutes { + font-size: 4rem; + font-weight: 700; + text-shadow: 0 2px 10px rgba(0,0,0,0.2); +} + +.colon { + font-size: 3rem; + font-weight: 300; + margin: 0 5px; + animation: blink 1s infinite; +} + +.seconds-wrapper { + display: flex; + flex-direction: column; + align-items: flex-start; + margin-left: 10px; + font-size: 1rem; + font-weight: 500; +} + +#seconds { + color: var(--text-secondary); + font-size: 1.2rem; +} + +#ampm { + font-size: 0.8rem; + color: var(--text-secondary); + text-transform: uppercase; + margin-top: 2px; +} + +.date { + font-size: 1rem; + font-weight: 300; + color: var(--text-secondary); + letter-spacing: 1px; + text-transform: uppercase; +} + +@keyframes blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} diff --git a/Resources/Skins/WebView2/Calendar/Calendar.ini b/Resources/Skins/WebView2/Calendar/Calendar.ini new file mode 100644 index 0000000..d519cb0 --- /dev/null +++ b/Resources/Skins/WebView2/Calendar/Calendar.ini @@ -0,0 +1,27 @@ +[Rainmeter] +Update=1000 + +[Metadata] +Name=Calendar +Author=nstechbytes +Information=Calendar Widget using WebView2 +Version=0.0.3 +License=MIT +; ======================================== +; Measure +; ======================================== +[WebView2] +Measure=Plugin +Plugin=WebView2 +URL=#@#Calendar\index.html +Width=300 +Height=400 +X=25 +Y=25 +; ======================================== +; Background +; ======================================== +[Background_Shape] +Meter=Shape +Shape=Rectangle 0,0,350,450 | StrokeWidth 0 | FillColor 0,0,0,1 + diff --git a/Resources/Skins/WebView2/Clock/Clock.ini b/Resources/Skins/WebView2/Clock/Clock.ini new file mode 100644 index 0000000..97b90ca --- /dev/null +++ b/Resources/Skins/WebView2/Clock/Clock.ini @@ -0,0 +1,23 @@ +[Rainmeter] +Update=1000 + +[Metadata] +Name=Calendar +Author=nstechbytes +Information=Calendar Widget using WebView2 +Version=0.0.3 +License=MIT + +[WebView2] +Measure=Plugin +Plugin=WebView2 +URL=#@#Clock\index.html +Width=350 +Height=200 +X=25 +Y=25 + +[Background_Shape] +Meter=Shape +Shape=Rectangle 0,0,400,250 | StrokeWidth 0 | FillColor 0,0,0,1 + From 78f1801575675a5db76f96df75362d9194cccb6f Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Wed, 26 Nov 2025 12:36:18 +0500 Subject: [PATCH 04/12] Create initial WebView2 Visual Studio project file. --- WebView2/WebView2.vcxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WebView2/WebView2.vcxproj b/WebView2/WebView2.vcxproj index 9cbaa8b..40d917f 100644 --- a/WebView2/WebView2.vcxproj +++ b/WebView2/WebView2.vcxproj @@ -201,14 +201,14 @@ - - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file From 6559e4d64ae66c122e523db7e91d6fd099fef4af Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Wed, 26 Nov 2025 13:08:36 +0500 Subject: [PATCH 05/12] Add WebView2 Rainmeter plugin with Calendar and Clock example skins, and remove README. --- README.md | 376 ------------------ .../Skins/WebView2/@Resources/api-demo.html | 342 ---------------- .../Skins/WebView2/@Resources/event-test.html | 177 --------- .../Skins/WebView2/@Resources/example.html | 137 ------- .../WebView2/@Resources/mouse-drag-test.html | 214 ---------- Resources/Skins/WebView2/@Resources/script.js | 220 ---------- Resources/Skins/WebView2/APIDemo.ini | 116 ------ .../Skins/WebView2/Calendar/Calendar.ini | 4 +- Resources/Skins/WebView2/Clock/Clock.ini | 12 +- Resources/Skins/WebView2/EventTest.ini | 28 -- Resources/Skins/WebView2/Main.ini | Bin 21264 -> 0 bytes Resources/Skins/WebView2/MouseDragTest.ini | 29 -- WebView2/Plugin.cpp | 8 +- docs/README.md | 3 - docs/docs.js | 211 ---------- docs/index.html | 375 ----------------- docs/styles.css | 370 ----------------- 17 files changed, 14 insertions(+), 2608 deletions(-) delete mode 100644 README.md delete mode 100644 Resources/Skins/WebView2/@Resources/api-demo.html delete mode 100644 Resources/Skins/WebView2/@Resources/event-test.html delete mode 100644 Resources/Skins/WebView2/@Resources/example.html delete mode 100644 Resources/Skins/WebView2/@Resources/mouse-drag-test.html delete mode 100644 Resources/Skins/WebView2/@Resources/script.js delete mode 100644 Resources/Skins/WebView2/APIDemo.ini delete mode 100644 Resources/Skins/WebView2/EventTest.ini delete mode 100644 Resources/Skins/WebView2/Main.ini delete mode 100644 Resources/Skins/WebView2/MouseDragTest.ini delete mode 100644 docs/README.md delete mode 100644 docs/docs.js delete mode 100644 docs/index.html delete mode 100644 docs/styles.css diff --git a/README.md b/README.md deleted file mode 100644 index 0f6e747..0000000 --- a/README.md +++ /dev/null @@ -1,376 +0,0 @@ -# WebView2 Rainmeter Plugin - -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-0.0.3-blue) -![Platform](https://img.shields.io/badge/platform-Windows%2010%2F11-lightgrey) -![License](https://img.shields.io/badge/license-MIT-green) - -## ✨ Features - -- 🌐 **Display Web Pages** - Load any website directly in your Rainmeter skin -- 📄 **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** - 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 - -- Windows 10/11 -- Rainmeter 4.5 or later -- WebView2 Runtime (usually pre-installed on Windows 10/11) - - If not installed, download from: [WebView2 Runtime](https://developer.microsoft.com/microsoft-edge/webview2/) - -## 🚀 Quick Start - -### Installation - -1. Download the latest release `.rmskin` package -2. Double-click to install, or -3. Manually copy `WebView2.dll` to `%APPDATA%\Rainmeter\Plugins\` - -### Basic Usage - -```ini -[Rainmeter] -Update=1000 - -[MeasureWebView] -Measure=Plugin -Plugin=WebView2 -URL=https://www.google.com -Width=1000 -Height=700 -X=0 -Y=0 -``` - -## 📖 Documentation - -### Plugin Options - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `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 | -| `Y` | Integer | 0 | Y position relative to skin window | -| `Visible` | Integer | 1 | Show (1) or hide (0) the WebView window | - -### Bang Commands - -Execute commands using `!CommandMeasure`: - -```ini -; Navigate to URL -LeftMouseUpAction=[!CommandMeasure MeasureWebView "Navigate https://example.com"] - -; Reload current page -LeftMouseUpAction=[!CommandMeasure MeasureWebView "Reload"] - -; Navigation controls -LeftMouseUpAction=[!CommandMeasure MeasureWebView "GoBack"] -LeftMouseUpAction=[!CommandMeasure MeasureWebView "GoForward"] - -; Show/Hide WebView -LeftMouseUpAction=[!CommandMeasure MeasureWebView "Show"] -LeftMouseUpAction=[!CommandMeasure MeasureWebView "Hide"] - -; Execute JavaScript -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 - -### Example 1: Mouse Drag Test - -```ini -[MeasureWebView] -Measure=Plugin -Plugin=WebView2 -URL=#@#mouse-drag-test.html -Width=600 -Height=400 -X=0 -Y=0 -``` - -### Example 2: Web Browser Skin - -```ini -[MeasureWebView] -Measure=Plugin -Plugin=WebView2 -URL=https://www.rainmeter.net -Width=1200 -Height=800 -X=0 -Y=50 -``` - -### Example 3: Local HTML Dashboard - -Create `@Resources\dashboard.html`: -```html - - - - - - -

My Dashboard

-

Time:

- - - -``` - -## 🔌 JavaScript API Bridge - -The plugin automatically injects a global `rm` object into all loaded web pages, providing seamless access to Rainmeter API functions from JavaScript. - -### Reading Options from Current Measure - -| Method | Parameters | Returns | Description | -|--------|------------|---------|-------------| -| `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 | - -### 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.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'` | - -### Information Properties - -| 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 | - -### Usage Examples - -#### Reading Options - -```javascript -// Read string option -const url = await rm.ReadString('URL', 'https://default.com'); - -// Read integer option -const width = await rm.ReadInt('Width', 800); - -// Read double/float option -const opacity = await rm.ReadDouble('Opacity', 1.0); - -// Read formula option -const calculated = await rm.ReadFormula('MyFormula', 0); - -// Read path option -const filePath = await rm.ReadPath('DataFile', ''); -``` - -#### Reading from Other Sections - -```javascript -// Read string from another measure -const cpuValue = await rm.ReadStringFromSection('MeasureCPU', 'String', '0%'); - -// Read integer from another section -const memoryUsage = await rm.ReadIntFromSection('MeasureRAM', 'Value', 0); - -// Read double from another section -const temperature = await rm.ReadDoubleFromSection('MeasureTemp', 'Value', 0.0); - -// Read formula from another section -const result = await rm.ReadFormulaFromSection('MeasureCalc', 'Formula', 0.0); -``` - -#### Utility Functions - -```javascript -// Replace Rainmeter variables -const currentPath = await rm.ReplaceVariables('#CURRENTPATH#'); -const skinPath = await rm.ReplaceVariables('#@#'); - -// Convert relative path to absolute -const absolutePath = await rm.PathToAbsolute('#@#data.json'); - -// Execute Rainmeter bang commands -rm.Execute('[!SetVariable MyVar "Hello"]'); -rm.Execute('[!UpdateMeter *][!Redraw]'); - -// 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 - -```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; - -// Get settings file path -const settingsFile = await rm.SettingsFile; -``` - -### Complete Example - -```html - - - - Rainmeter Integration - - -

Rainmeter API Demo

- -
- - - - -``` - -### 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 - - -## 🔧 Building from Source - -### Prerequisites - -- Visual Studio 2022 (or 2019) -- Windows 10/11 SDK -- NuGet Package Manager - -### Build via PowerShell - -```powershell -powershell -ExecutionPolicy Bypass -Command "& { . .\Build-CPP.ps1; Dist -major 0 -minor 0 -patch 3 }" -``` - -This creates: -- Plugin DLLs in `dist\` folder -- Complete `.rmskin` package for distribution - -## 🤝 Contributing - -Contributions are welcome! Please feel free to submit a Pull Request. - -## 📄 License - -This project is licensed under the MIT License. - -## 🙏 Acknowledgments - -- Microsoft Edge WebView2 team for the excellent SDK -- Rainmeter community for inspiration and support - -## 📧 Contact - -- **Author**: nstechbytes -- **GitHub**: [WebView2 Plugin](https://github.com/nstechbytes/WebView2) - -## 🔗 Related Links - -- [Rainmeter Documentation](https://docs.rainmeter.net/) -- [WebView2 Documentation](https://docs.microsoft.com/microsoft-edge/webview2/) -- [Example Skins](Resources/Skins/WebView2/) - ---- - -**Made with ❤️ by nstechbytes** \ No newline at end of file diff --git a/Resources/Skins/WebView2/@Resources/api-demo.html b/Resources/Skins/WebView2/@Resources/api-demo.html deleted file mode 100644 index 394a8e5..0000000 --- a/Resources/Skins/WebView2/@Resources/api-demo.html +++ /dev/null @@ -1,342 +0,0 @@ - - - - - - Rainmeter API Bridge Demo - - - -
-

Rainmeter API Bridge Demo

-

Test the JavaScript bridge to Rainmeter API

- - -
-

Skin Information

-
-
-
Measure Name
-
Loading...
-
-
-
Skin Name
-
Loading...
-
-
-
Window Handle
-
Loading...
-
-
-
Settings File
-
Loading...
-
-
-
- - -
-

Read Options

-
- - - - -
-
Click buttons to test reading options...
-
- - -
-

Execute Commands

-
- - - - -
-
Click buttons to test commands...
-
- - -
-

Interactive Test

-

Enter a Rainmeter option name to read:

- - -
Results will appear here...
-
-
- - - - diff --git a/Resources/Skins/WebView2/@Resources/event-test.html b/Resources/Skins/WebView2/@Resources/event-test.html deleted file mode 100644 index 2e9963e..0000000 --- a/Resources/Skins/WebView2/@Resources/event-test.html +++ /dev/null @@ -1,177 +0,0 @@ - - - - - Event Handler Test - - - -

🧪 Event Handler Test

-

Test if mouse and keyboard events are working in WebView2

- -
-

Click Test

-

Click this box to test mouse events

-
- -
-

Mouse Move Test

-

Move your mouse over this box

-

Position: (0, 0)

-
- -
-

Keyboard Test

- -
- -
-

Button Tests

- - - -
- -
- Event Log:
- Waiting for events... -
- - - - diff --git a/Resources/Skins/WebView2/@Resources/example.html b/Resources/Skins/WebView2/@Resources/example.html deleted file mode 100644 index efe7510..0000000 --- a/Resources/Skins/WebView2/@Resources/example.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - WebView2 Example - - - -
-

🚀 WebView2 Plugin

-

Welcome to the WebView2 Rainmeter Plugin! This is a local HTML file being displayed in your Rainmeter skin.

- -
-
-
🌐
-

Web Content

-

Display any web page

-
-
-
📄
-

Local Files

-

Load HTML files

-
-
-
-

Modern

-

Edge WebView2

-
-
-
🎨
-

Interactive

-

Full JavaScript support

-
-
- -
- - -
- -
-
- - - diff --git a/Resources/Skins/WebView2/@Resources/mouse-drag-test.html b/Resources/Skins/WebView2/@Resources/mouse-drag-test.html deleted file mode 100644 index 795df4f..0000000 --- a/Resources/Skins/WebView2/@Resources/mouse-drag-test.html +++ /dev/null @@ -1,214 +0,0 @@ - - - - - - Mouse Drag Test - - - -
-

Mouse Drag Test

-
    -
  • Drag the box around
  • -
  • Watch coordinates update
  • -
  • Test click events
  • -
  • Verify smooth movement
  • -
-
- -
-
Drag Me!
-
X: 0, Y: 0
-
- -
Ready
- - - - diff --git a/Resources/Skins/WebView2/@Resources/script.js b/Resources/Skins/WebView2/@Resources/script.js deleted file mode 100644 index 98d6fff..0000000 --- a/Resources/Skins/WebView2/@Resources/script.js +++ /dev/null @@ -1,220 +0,0 @@ -// External JavaScript file for WebView2 demo -// This file demonstrates various JavaScript capabilities - -// Initialize when DOM is loaded -document.addEventListener('DOMContentLoaded', function() { - console.log('External script loaded successfully!'); - initializeApp(); -}); - -// Main initialization function -function initializeApp() { - // Update clock every second - updateClock(); - setInterval(updateClock, 1000); - - // Add event listeners - setupEventListeners(); - - // Initialize any charts or visualizations - initializeVisuals(); -} - -// Clock update function -function updateClock() { - const clockElement = document.getElementById('clock'); - if (clockElement) { - const now = new Date(); - const timeString = now.toLocaleTimeString('en-US', { - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: true - }); - const dateString = now.toLocaleDateString('en-US', { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric' - }); - clockElement.innerHTML = ` -
${timeString}
-
${dateString}
- `; - } -} - -// Setup event listeners -function setupEventListeners() { - // Color change button - const colorBtn = document.getElementById('changeColorBtn'); - if (colorBtn) { - colorBtn.addEventListener('click', changeBackgroundColor); - } - - // Theme toggle button - const themeBtn = document.getElementById('toggleThemeBtn'); - if (themeBtn) { - themeBtn.addEventListener('click', toggleTheme); - } - - // Animation button - const animBtn = document.getElementById('animateBtn'); - if (animBtn) { - animBtn.addEventListener('click', triggerAnimation); - } -} - -// Change background color randomly -function changeBackgroundColor() { - const colors = [ - 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)', - 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)', - 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)', - 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)', - 'linear-gradient(135deg, #30cfd0 0%, #330867 100%)' - ]; - const randomColor = colors[Math.floor(Math.random() * colors.length)]; - document.body.style.background = randomColor; - - // Show notification - showNotification('Background color changed!'); -} - -// Toggle between light and dark theme -let isDarkTheme = true; -function toggleTheme() { - isDarkTheme = !isDarkTheme; - - if (isDarkTheme) { - document.body.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'; - document.body.style.color = '#ffffff'; - } else { - document.body.style.background = 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)'; - document.body.style.color = '#333333'; - } - - showNotification(`Switched to ${isDarkTheme ? 'dark' : 'light'} theme`); -} - -// Trigger animation -function triggerAnimation() { - const container = document.querySelector('.container'); - if (container) { - container.style.transform = 'scale(0.95)'; - setTimeout(() => { - container.style.transform = 'scale(1)'; - }, 200); - } - - showNotification('Animation triggered!'); -} - -// Show notification -function showNotification(message) { - // Create notification element - const notification = document.createElement('div'); - notification.className = 'notification'; - notification.textContent = message; - notification.style.cssText = ` - position: fixed; - top: 20px; - right: 20px; - background: rgba(0, 0, 0, 0.8); - color: white; - padding: 15px 25px; - border-radius: 8px; - font-size: 14px; - z-index: 1000; - animation: slideIn 0.3s ease; - `; - - document.body.appendChild(notification); - - // Remove after 3 seconds - setTimeout(() => { - notification.style.animation = 'slideOut 0.3s ease'; - setTimeout(() => { - document.body.removeChild(notification); - }, 300); - }, 3000); -} - -// Initialize visualizations -function initializeVisuals() { - // Add any chart or visualization initialization here - console.log('Visuals initialized'); -} - -// Function callable from Rainmeter -function rainmeterCommand(command, data) { - console.log('Rainmeter command received:', command, data); - - switch(command) { - case 'setColor': - document.body.style.background = data; - break; - case 'showMessage': - showNotification(data); - break; - case 'updateData': - updateDashboardData(data); - break; - default: - console.log('Unknown command:', command); - } -} - -// Update dashboard with data from Rainmeter -function updateDashboardData(data) { - const dataElement = document.getElementById('dashboardData'); - if (dataElement) { - dataElement.textContent = JSON.stringify(data, null, 2); - } -} - -// Add CSS animations -const style = document.createElement('style'); -style.textContent = ` - @keyframes slideIn { - from { - transform: translateX(100%); - opacity: 0; - } - to { - transform: translateX(0); - opacity: 1; - } - } - - @keyframes slideOut { - from { - transform: translateX(0); - opacity: 1; - } - to { - transform: translateX(100%); - opacity: 0; - } - } - - .container { - transition: transform 0.2s ease; - } -`; -document.head.appendChild(style); - -// Export functions for use in HTML -window.rainmeterCommand = rainmeterCommand; -window.changeBackgroundColor = changeBackgroundColor; -window.toggleTheme = toggleTheme; -window.triggerAnimation = triggerAnimation; - -// Alias for example.html compatibility -window.changeColor = changeBackgroundColor; - -// Show alert function for example.html -window.showAlert = function() { - alert('Hello from WebView2! 🎉\n\nThis demonstrates JavaScript execution in the WebView2 control.'); -}; diff --git a/Resources/Skins/WebView2/APIDemo.ini b/Resources/Skins/WebView2/APIDemo.ini deleted file mode 100644 index 930c942..0000000 --- a/Resources/Skins/WebView2/APIDemo.ini +++ /dev/null @@ -1,116 +0,0 @@ -[Rainmeter] -Update=1000 -BackgroundMode=2 -SolidColor=0,0,0,1 - -[Metadata] -Name=WebView2 API Bridge Demo -Author=nstechbytes -Information=Demonstrates Rainmeter API bridge in JavaScript -Version=0.0.3 -License=MIT - -[Variables] -ColorPrimary=100,70,255 -ColorSecondary=25,25,30 -ColorBackground=15,15,20 -ColorText=240,240,245 -WebWidth=1150 -WebHeight=700 -HeaderHeight=70 -ButtonHeight=36 -ButtonRadius=8 -Padding=15 - -; ======================================== -; Background -; ======================================== -[MeterBackground] -Meter=Shape -X=0 -Y=0 -Shape=Rectangle 0,0,#WebWidth#,(#WebHeight#+#HeaderHeight#+20),#ButtonRadius# | Fill Color #ColorBackground# | StrokeWidth 0 - -; ======================================== -; Header -; ======================================== -[MeterHeader] -Meter=Shape -X=0 -Y=0 -Shape=Rectangle 0,0,#WebWidth#,#HeaderHeight#,#ButtonRadius#,#ButtonRadius# | Fill LinearGradient MyGradient | StrokeWidth 0 -MyGradient=90 | 100,70,255 ; 0.0 | 140,100,255 ; 1.0 - -[MeterTitle] -Meter=String -X=#Padding# -Y=18 -FontFace=Segoe UI -FontSize=16 -FontWeight=700 -FontColor=255,255,255 -AntiAlias=1 -Text=Rainmeter API Bridge Demo - -[MeterSubtitle] -Meter=String -X=0r -Y=R -FontFace=Segoe UI -FontSize=10 -FontColor=230,230,240 -AntiAlias=1 -Text=Test JavaScript bridge to Rainmeter API - -; ======================================== -; WebView2 Measure -; ======================================== -[MeasureWebView] -Measure=Plugin -Plugin=WebView2 -Url=#@#api-demo.html -Width=#WebWidth# -Height=#WebHeight# -X=0 -Y=#HeaderHeight# -Visible=1 - -; ======================================== -; Reload Button -; ======================================== -[MeterReloadButton] -Meter=Shape -X=(#WebWidth#-120) -Y=17 -Shape=Rectangle 0,0,105,#ButtonHeight#,#ButtonRadius# | Fill Color rgba(255,255,255,0.3) | StrokeWidth 1 | Stroke Color white -DynamicVariables=1 -MouseOverAction=[!SetOption MeterReloadButton Shape "Rectangle 0,0,105,#ButtonHeight#,#ButtonRadius# | Fill Color white | StrokeWidth 1 | Stroke Color white"][!SetOption MeterReloadText FontColor "#ColorPrimary#"][!UpdateMeter MeterReloadButton][!UpdateMeter MeterReloadText][!Redraw] -MouseLeaveAction=[!SetOption MeterReloadButton Shape "Rectangle 0,0,105,#ButtonHeight#,#ButtonRadius# | Fill Color rgba(255,255,255,0.3) | StrokeWidth 1 | Stroke Color white"][!SetOption MeterReloadText FontColor "255,255,255"][!UpdateMeter MeterReloadButton][!UpdateMeter MeterReloadText][!Redraw] -LeftMouseUpAction=[!CommandMeasure MeasureWebView "Reload"] - -[MeterReloadText] -Meter=String -X=(105/2)r -Y=(#ButtonHeight#/2)r -FontFace=Segoe UI -FontSize=10 -FontWeight=600 -FontColor=255,255,255 -StringAlign=CenterCenter -AntiAlias=1 -Text=Reload Page - -; ======================================== -; Instructions -; ======================================== -[MeterInstructions] -Meter=String -X=#Padding# -Y=(#HeaderHeight#+#WebHeight#+2) -W=(#WebWidth#-30) -FontFace=Segoe UI -FontSize=9 -FontColor=150,150,160 -AntiAlias=1 -ClipString=1 -Text=The page can access Rainmeter API via JavaScript: rm.ReadString(), rm.Execute(), rm.Log(), etc. diff --git a/Resources/Skins/WebView2/Calendar/Calendar.ini b/Resources/Skins/WebView2/Calendar/Calendar.ini index d519cb0..ff6c570 100644 --- a/Resources/Skins/WebView2/Calendar/Calendar.ini +++ b/Resources/Skins/WebView2/Calendar/Calendar.ini @@ -14,8 +14,8 @@ License=MIT Measure=Plugin Plugin=WebView2 URL=#@#Calendar\index.html -Width=300 -Height=400 +W=300 +H=400 X=25 Y=25 ; ======================================== diff --git a/Resources/Skins/WebView2/Clock/Clock.ini b/Resources/Skins/WebView2/Clock/Clock.ini index 97b90ca..31af180 100644 --- a/Resources/Skins/WebView2/Clock/Clock.ini +++ b/Resources/Skins/WebView2/Clock/Clock.ini @@ -7,16 +7,20 @@ Author=nstechbytes Information=Calendar Widget using WebView2 Version=0.0.3 License=MIT - +; ======================================== +; Measure +; ======================================== [WebView2] Measure=Plugin Plugin=WebView2 URL=#@#Clock\index.html -Width=350 -Height=200 +W=350 +H=200 X=25 Y=25 - +; ======================================== +; Background +; ======================================== [Background_Shape] Meter=Shape Shape=Rectangle 0,0,400,250 | StrokeWidth 0 | FillColor 0,0,0,1 diff --git a/Resources/Skins/WebView2/EventTest.ini b/Resources/Skins/WebView2/EventTest.ini deleted file mode 100644 index 337c6b2..0000000 --- a/Resources/Skins/WebView2/EventTest.ini +++ /dev/null @@ -1,28 +0,0 @@ -[Rainmeter] -Update=1000 -BackgroundMode=2 -SolidColor=0,0,0,1 - -[Metadata] -Name=Event Handler Test -Author=nstechbytes -Information=Test page to verify mouse and keyboard events work in WebView2 -Version=0.0.3 -License=MIT - -[Variables] -WebWidth=800 -WebHeight=600 - -; ======================================== -; WebView2 Measure -; ======================================== -[MeasureWebView] -Measure=Plugin -Plugin=WebView2 -Url=#@#event-test.html -Width=#WebWidth# -Height=#WebHeight# -X=0 -Y=0 -Visible=1 diff --git a/Resources/Skins/WebView2/Main.ini b/Resources/Skins/WebView2/Main.ini deleted file mode 100644 index ad6b49eeb942c7a72f180388f9d8cda892b5f5f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21264 zcmeHP+in{-5an}${=+r`iUe_dOWfKm1k}0OK+*=WowNx0;9Kn=mK;b*+!p=zw&!p( z=5Ti@@7h{LLIgp#-61)g8O|II$^Gx&x9*kOckf)+b=ZqI#iN4Upp zZMWi<{fl=V;_9J$?@mB_?mnXIk$Zt(N1k%km)^nuGt_(J9{FGAplzduh0FJfr(=C@ zQJQV<58K-xa_}>*kgK+PgUAzHtzl5sXD%Q@9?{e654|v{$;3{EoOMzb~4oZ-b*V(YkmLg1XM--KA`s> zAt6eZeSGctWC>|)*)2j6i|)R^`yAH~K|!wB-xqG%?fN?+pV#2D2bz8Ga|Vk3P(nYV z?I2(2U-c@~=2W3{!57r*OzsG+{ILP23 z`q1;1Oe1%)hHSd93fe6DKpiYNav`6j=9_vI zuEi$AYDB*(w`ZtBN8cOt@W@*~dr9d@%Vo4%X#E{{IQ4Q|_tb1tY&foI#Y-UEjmy$1 zq@7)G!ts9Kbstlyj34^(IRIT3ZCQQ;w;aJj5!Z1anj;ss4*IPl)WdPuhE?MXe;JL{CLbw8#0PLll@=xwCv>` z`f(P9RO3Vrv3Tf*zYzZP*fA3+u&e|mJ5wz_T=tgPi}Vr?sxy()CSf(wos*83ZN z-*zqd$&~H2++A3}vb*Ey89nHK#e>o|SPS1xT8EB68!I#S?a*&`AtRBUMzN-N(HP5i z%6JO4A{e0UcA)QlXj?3pl9#bgxy!FW(FTg3|7UMF@))BsP)W3JUA9>6vs$2|CY3Ks z72CiaP_CoqfAD?^U7q1(BVdMFtS>L(l-98H4tS;id=E~8g_7qnj(^^W;03V9%g15%Yo&(G2mDfv z?`qi8b_diXWbO)G~n2;W#Bir6)lRrsg4S2){*j~$?%O-Pk<0^%Qu zW=dBc$H|G66Y=YTuSxV%OC6R|LV$TBPt#GTi$y?#MNc+68gD-#EHU= zpeN1)Gt8|x*P`$5~)*K_T?`HH! z&5%!wcU^>CuvJL?%=pEQBTcc0S)5f0AQY$v8 z8{0YaW@}o$4))Q^Sp3b}Y`+EnoIrPNU?pbCWbSHzvrHAdWJf&9@XeUZ7(lexLH$yD z9GyvLqhpc2Zj7Yd1XayrA2Z$Ki+!VYGmn5a0*;ndlW}l#%d@JbaYn<@5=!Z2+G1j_ z;8`_moQjOae$V5htRP98WGrebJ{DzfqvSKh$bsAP`GnE&(NflpjQ5b`n8!xD3Ov5p zn9|Fv`rD@Fg~85a>*c(%sd-^}o3pB!aYn+F)lPd1Jn{bW)ceb;=Yv82^LRI@cxNrk zM!M&iyB`9%aNSW>AUW^1z92s%3?(d8*^EA)EuH0P7w?U^ddyR5lgomZ>twn;s_b1_ z_1SQ)2r?@!IqCFTcJ+*BxkOTaH00H;pYJShak_P2*W~J2cvHIgT=Tk?`w1&8+>gg~ z3+{J3#(qX-Ks)%In!^RH&tuu7Vp)(mcNHE(Kj%KbNuS%)x+3dkcAYs{uC+2=+W6PU z|J%57j{Ds4*TH|aWLf!ZiZkuC?zxHi%13XdvbcpvLwII8W<&*2?qcOov zq+UIOl}W5gEK%0p^k^tX4f%cYS7GDn)0q0{fC8IXUFC|L>NSNX^!~PAQ|(@^#_L*L z*)CS@=?d$h#L@E4`c&>C=UI{rYll4_zb^b9F}cRwr`BtQb=C~E6~|xNt0@Lj8z|q? zs{3f=o4&8e70)Vgx2?3!^CaAX%{U|baMw!junfIZ9e#Q@Y#ZroV6XmeiJ?tgnxf$+Lvsfc9?U>e5mdQPz zQQ*$1u(mu$A~K$D;$f+u$>lB5+Mk|iH;s5CEw?67NxIeP(bgS2}`Ies~qC6<{*E?n_E1A5^}%+a{TQpTty zLrHnD=CB9147>9z@f^S8&#cW3eo0xu^u)tyL@z0+rJ`SmdW?hD+l`)Tb9yv23&`yU&GWq;U;FM=MXE7VJ z0j#9Cs5l$60t}VP6v;g~vsiUjOU=eR-~%S8hp)cWoRA%B7F}&&eDIk%OMc|=EF(|T z@|2#Qu(wDmuLnzM>8j+3m>H5as4(_;GH)IDE1b)lWd3Nwdx-dCUlZ$C_6aSTl}+V( z>*Y;DRKD)ny_81l#N;OBsHb%{U?mHVpQC;Gvfm|8?L=Ox@2N-{qF)(|=8ryfT=C8DTT zi$9upViniBwy`$Z$9U~w|8qDa-RyG?EJ97SU(K@8JA0U$l(nFXD5{DEmXtzvySE F{{z)WnGpa0 diff --git a/Resources/Skins/WebView2/MouseDragTest.ini b/Resources/Skins/WebView2/MouseDragTest.ini deleted file mode 100644 index 1e07c58..0000000 --- a/Resources/Skins/WebView2/MouseDragTest.ini +++ /dev/null @@ -1,29 +0,0 @@ -[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 diff --git a/WebView2/Plugin.cpp b/WebView2/Plugin.cpp index a300c83..295b1f8 100644 --- a/WebView2/Plugin.cpp +++ b/WebView2/Plugin.cpp @@ -124,8 +124,8 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) } // Read dimensions - measure->width = RmReadInt(rm, L"Width", 800); - measure->height = RmReadInt(rm, L"Height", 600); + measure->width = RmReadInt(rm, L"W", 800); + measure->height = RmReadInt(rm, L"H", 600); measure->x = RmReadInt(rm, L"X", 0); measure->y = RmReadInt(rm, L"Y", 0); @@ -230,7 +230,7 @@ PLUGIN_EXPORT void ExecuteBang(void* data, LPCWSTR args) ); } } - else if (_wcsicmp(action.c_str(), L"SetWidth") == 0) + else if (_wcsicmp(action.c_str(), L"SetW") == 0) { if (!param.empty()) { @@ -252,7 +252,7 @@ PLUGIN_EXPORT void ExecuteBang(void* data, LPCWSTR args) } } } - else if (_wcsicmp(action.c_str(), L"SetHeight") == 0) + else if (_wcsicmp(action.c_str(), L"SetH") == 0) { if (!param.empty()) { diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 14aa8e3..0000000 --- a/docs/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# WebView2 Plugin Documentation - -To view this documentation within your Rainmeter skin, point your WebView2 measure's `URL` option to the local file path of `index.html`. For example, if your skin structure places this folder in `@Resources`, use `URL=#@#docs\index.html`. Since the documentation uses relative paths and no external dependencies, it will load instantly and function fully offline. Ensure `IsScriptEnabled` is set to `1` in your measure settings to allow the search functionality to work. diff --git a/docs/docs.js b/docs/docs.js deleted file mode 100644 index 0f9694a..0000000 --- a/docs/docs.js +++ /dev/null @@ -1,211 +0,0 @@ -(function() { - 'use strict'; - - // DOM Elements - const searchInput = document.getElementById('search-input'); - const tocList = document.getElementById('toc-list'); - const noResults = document.getElementById('no-results'); - const sections = document.querySelectorAll('section'); - const menuToggle = document.getElementById('menu-toggle'); - const sidebar = document.getElementById('sidebar'); - - // State - let searchDebounceTimer; - const DEBOUNCE_DELAY = 300; - let originalContent = new Map(); // Store original HTML to restore after highlighting - - // Initialize - function init() { - // Store original content for restoring after search clears - sections.forEach(section => { - originalContent.set(section.id, section.innerHTML); - }); - - // Event Listeners - searchInput.addEventListener('input', handleSearchInput); - menuToggle.addEventListener('click', toggleSidebar); - - // Close sidebar when clicking outside on mobile - document.addEventListener('click', (e) => { - if (window.innerWidth <= 768 && - sidebar.classList.contains('open') && - !sidebar.contains(e.target) && - !menuToggle.contains(e.target)) { - toggleSidebar(); - } - }); - - // Handle TOC link clicks for smooth scrolling and mobile menu closing - tocList.querySelectorAll('a').forEach(link => { - link.addEventListener('click', (e) => { - if (window.innerWidth <= 768) { - toggleSidebar(); - } - }); - }); - } - - // Search Logic - function handleSearchInput(e) { - clearTimeout(searchDebounceTimer); - const query = e.target.value.trim().toLowerCase(); - - searchDebounceTimer = setTimeout(() => { - performSearch(query); - }, DEBOUNCE_DELAY); - } - - function performSearch(query) { - let hasResults = false; - - // Reset if empty - if (!query) { - resetSearch(); - return; - } - - sections.forEach(section => { - const sectionId = section.id; - const originalHTML = originalContent.get(sectionId); - const textContent = section.textContent.toLowerCase(); - const tocLink = tocList.querySelector(`a[href="#${sectionId}"]`); - const tocItem = tocLink.parentElement; - - if (textContent.includes(query)) { - // Match found - hasResults = true; - section.classList.remove('hidden'); - if (tocItem) tocItem.classList.remove('hidden'); - - // Highlight matches - section.innerHTML = highlightMatches(originalHTML, query); - } else { - // No match in this section - section.classList.add('hidden'); - if (tocItem) tocItem.classList.add('hidden'); - } - }); - - // Toggle "No results" message - if (hasResults) { - noResults.classList.add('hidden'); - } else { - noResults.classList.remove('hidden'); - } - } - - function highlightMatches(html, query) { - // Simple regex-based highlighting that avoids HTML tags - // Note: This is a basic implementation. For production with complex HTML, - // a tree walker or DOM manipulation approach is safer. - const regex = new RegExp(`(${escapeRegExp(query)})`, 'gi'); - - // We need to be careful not to replace text inside HTML tags. - // This simple version assumes text content is mostly safe to replace - // if we avoid tag brackets. - - // A safer way for this specific requirement without libraries: - const tempDiv = document.createElement('div'); - tempDiv.innerHTML = html; - - const walker = document.createTreeWalker( - tempDiv, - NodeFilter.SHOW_TEXT, - null, - false - ); - - let node; - const nodesToReplace = []; - - while (node = walker.nextNode()) { - if (node.nodeValue.toLowerCase().includes(query)) { - nodesToReplace.push(node); - } - } - - nodesToReplace.forEach(node => { - const span = document.createElement('span'); - span.innerHTML = node.nodeValue.replace(regex, '$1'); - node.parentNode.replaceChild(span, node); - }); - - return tempDiv.innerHTML; - } - - function resetSearch() { - sections.forEach(section => { - section.classList.remove('hidden'); - section.innerHTML = originalContent.get(section.id); - }); - - tocList.querySelectorAll('li').forEach(li => li.classList.remove('hidden')); - noResults.classList.add('hidden'); - } - - function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - } - - // Sidebar Logic - function toggleSidebar() { - const isOpen = sidebar.classList.toggle('open'); - menuToggle.setAttribute('aria-expanded', isOpen); - } - - // Run - init(); - initCopyButtons(); - - function initCopyButtons() { - // Target ONLY pre blocks (standard code blocks) - // No buttons in tables - const codeBlocks = document.querySelectorAll('pre'); - - codeBlocks.forEach(block => { - // Create wrapper - const wrapper = document.createElement('div'); - wrapper.className = 'code-wrapper'; - - // For table cells, we need to be careful not to break layout - if (block.tagName === 'CODE') { - wrapper.style.display = 'inline-block'; - wrapper.style.width = '100%'; - } - - // Insert wrapper before block - block.parentNode.insertBefore(wrapper, block); - - // Move block into wrapper - wrapper.appendChild(block); - - // Create copy button - const button = document.createElement('button'); - button.className = 'copy-btn'; - button.textContent = 'Copy'; - button.ariaLabel = 'Copy code to clipboard'; - - // Add click handler - button.addEventListener('click', (e) => { - e.stopPropagation(); // Prevent triggering other clicks - const code = block.textContent; - - navigator.clipboard.writeText(code.trim()).then(() => { - button.textContent = 'Copied!'; - button.classList.add('copied'); - - setTimeout(() => { - button.textContent = 'Copy'; - button.classList.remove('copied'); - }, 2000); - }).catch(err => { - console.error('Failed to copy:', err); - button.textContent = 'Error'; - }); - }); - - wrapper.appendChild(button); - }); - } - -})(); diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 76b4f6d..0000000 --- a/docs/index.html +++ /dev/null @@ -1,375 +0,0 @@ - - - - - - WebView2 Plugin Documentation - - - - -
- - WebView2 Plugin Docs -
- -
- - -
-
-

Introduction

-

A powerful Rainmeter plugin that embeds Microsoft Edge WebView2 control to display web content or local HTML files directly in your Rainmeter skins. It provides a seamless bridge between Rainmeter measures and web content, enabling rich, interactive user interfaces.

-
- -
-

Features

-
    -
  • 🌐 Display Web Pages - Load any website directly in your Rainmeter skin
  • -
  • 📄 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 - 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
  • -
-
- -
-

Installation

-

To install the WebView2 plugin:

-
    -
  1. Download the latest release .rmskin package.
  2. -
  3. Double-click to install.
  4. -
  5. Alternatively, manually copy WebView2.dll to %APPDATA%\Rainmeter\Plugins\.
  6. -
-

Requirement: Windows 10/11 and WebView2 Runtime (usually pre-installed).

-
- -
-

Quick Start

-

Here is a basic example of how to use the plugin in your skin:

-
[Rainmeter]
-Update=1000
-
-[MeasureWebView]
-Measure=Plugin
-Plugin=WebView2
-URL=https://www.google.com
-Width=1000
-Height=700
-X=0
-Y=0
-
- -
-

Plugin Options

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OptionTypeDefaultDescription
URLString(empty)URL or file path to load. Supports web URLs and local file paths.
WidthInteger800Width of the WebView window in pixels.
HeightInteger600Height of the WebView window in pixels.
XInteger0X position relative to skin window.
YInteger0Y position relative to skin window.
VisibleInteger1Show (1) or hide (0) the WebView window.
-
- -
-

Bang Commands

-

Control the WebView2 instance dynamically using !CommandMeasure.

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CommandArgumentsDescriptionExample
NavigateurlNavigates to the specified URL.[!CommandMeasure MeasureWebView "Navigate https://google.com"]
Reload-Reloads the current page.[!CommandMeasure MeasureWebView "Reload"]
GoBack-Navigates back in history.[!CommandMeasure MeasureWebView "GoBack"]
GoForward-Navigates forward in history.[!CommandMeasure MeasureWebView "GoForward"]
Show-Makes the WebView visible.[!CommandMeasure MeasureWebView "Show"]
Hide-Hides the WebView.[!CommandMeasure MeasureWebView "Hide"]
ExecuteScriptscriptExecutes JavaScript code.[!CommandMeasure MeasureWebView "ExecuteScript alert('Hi')"]
OpenDevTools-Opens Edge DevTools.[!CommandMeasure MeasureWebView "OpenDevTools"]
SetWidthpixelsSets the width.[!CommandMeasure MeasureWebView "SetWidth 500"]
SetHeightpixelsSets the height.[!CommandMeasure MeasureWebView "SetHeight 400"]
SetXpixelsSets X position.[!CommandMeasure MeasureWebView "SetX 100"]
SetYpixelsSets Y position.[!CommandMeasure MeasureWebView "SetY 50"]
-
-
- -
-

JavaScript API Bridge

-

The plugin automatically injects a global rm object into all loaded web pages, providing seamless access to Rainmeter API functions from JavaScript.

- -

Reading Options

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
MethodDescription
rm.ReadString(option, default)Read a string option from the current measure. Returns Promise<string>.
rm.ReadInt(option, default)Read an integer option from the current measure. Returns Promise<number>.
rm.ReadDouble(option, default)Read a double/float option from the current measure. Returns Promise<number>.
rm.ReadFormula(option, default)Read and evaluate a formula option. Returns Promise<number>.
rm.ReadPath(option, default)Read a file path option from the current measure. Returns Promise<string>.
- -

Utility Functions

- - - - - - - - - - - - - - - - - - - - - - - - - -
MethodDescription
rm.ReplaceVariables(text)Replace Rainmeter variables in text (e.g., #CURRENTCONFIG#). Returns Promise<string>.
rm.PathToAbsolute(path)Convert relative path to absolute path. Returns Promise<string>.
rm.Execute(command)Execute a Rainmeter bang command. Void.
rm.Log(message, level)Log a message to Rainmeter log. Levels: 'Notice', 'Warning', 'Error', 'Debug'. Void.
- -

Information Properties

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PropertyReturnsDescription
rm.MeasureNamePromise<string>Get the name of the current measure.
rm.SkinNamePromise<string>Get the name of the current skin.
rm.SkinWindowHandlePromise<string>Get the window handle of the skin.
rm.SettingsFilePromise<string>Get the path to Rainmeter settings file.
- -

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 also return Promises.
  • -
-
- -
-

Examples

-

Local HTML Dashboard

-

Create @Resources\dashboard.html:

-
<!DOCTYPE html>
-<html>
-<head>
-    <style>
-        body {
-            font-family: 'Segoe UI', sans-serif;
-            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-            color: white;
-            padding: 40px;
-        }
-    </style>
-</head>
-<body>
-    <h1>My Dashboard</h1>
-    <p>Time: <span id="clock"></span></p>
-    <script>
-        setInterval(() => {
-            document.getElementById('clock').textContent = 
-                new Date().toLocaleTimeString();
-        }, 1000);
-    </script>
-</body>
-</html>
-
-
-
- - - - diff --git a/docs/styles.css b/docs/styles.css deleted file mode 100644 index 1d6472a..0000000 --- a/docs/styles.css +++ /dev/null @@ -1,370 +0,0 @@ -:root { - /* Modern Dark Theme Palette */ - --primary-color: #60a5fa; /* Soft Blue */ - --primary-hover: #3b82f6; - --text-color: #e2e8f0; /* Slate 200 */ - --text-muted: #94a3b8; /* Slate 400 */ - --bg-color: #0f172a; /* Slate 900 */ - --sidebar-bg: #1e293b; /* Slate 800 */ - --sidebar-width: 280px; - --border-color: #334155; /* Slate 700 */ - --input-bg: #0f172a; - --highlight-color: #1e40af; /* Blue 800 */ - --focus-ring: 2px solid var(--primary-color); - --font-family: 'Inter', 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, sans-serif; - --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); - --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); -} - -* { - box-sizing: border-box; -} - -body { - margin: 0; - font-family: var(--font-family); - color: var(--text-color); - background-color: var(--bg-color); - line-height: 1.7; - -webkit-font-smoothing: antialiased; -} - -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - -/* Layout */ -.container { - display: flex; - min-height: 100vh; -} - -/* Sidebar */ -.sidebar { - width: var(--sidebar-width); - background-color: var(--sidebar-bg); - border-right: 1px solid var(--border-color); - position: fixed; - top: 0; - bottom: 0; - left: 0; - overflow-y: auto; - padding: 24px; - z-index: 100; - transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 4px 0 24px rgba(0,0,0,0.2); -} - -/* Custom Scrollbar for Sidebar */ -.sidebar::-webkit-scrollbar { - width: 6px; -} - -.sidebar::-webkit-scrollbar-track { - background: transparent; -} - -.sidebar::-webkit-scrollbar-thumb { - background-color: var(--border-color); - border-radius: 3px; -} - -.sidebar-header h1 { - margin-top: 0; - font-size: 1.5rem; - font-weight: 700; - color: var(--primary-color); - letter-spacing: -0.025em; - margin-bottom: 24px; -} - -/* Search */ -.search-container { - margin-bottom: 24px; - position: relative; -} - -#search-input { - width: 100%; - padding: 12px 16px; - background-color: var(--input-bg); - border: 1px solid var(--border-color); - border-radius: 8px; - font-size: 0.95rem; - color: var(--text-color); - transition: all 0.2s ease; -} - -#search-input:focus { - outline: none; - border-color: var(--primary-color); - box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.2); -} - -#search-input::placeholder { - color: var(--text-muted); -} - -/* TOC */ -.toc ul { - list-style: none; - padding: 0; - margin: 0; -} - -.toc li { - margin-bottom: 4px; -} - -.toc a { - text-decoration: none; - color: var(--text-muted); - display: block; - padding: 8px 12px; - border-radius: 6px; - transition: all 0.2s ease; - font-size: 0.95rem; -} - -.toc a:hover { - background-color: rgba(255, 255, 255, 0.05); - color: var(--text-color); - transform: translateX(4px); -} - -.toc a:focus { - outline: none; - box-shadow: 0 0 0 2px var(--primary-color); -} - -.toc a.active { - background-color: rgba(96, 165, 250, 0.1); - color: var(--primary-color); - font-weight: 600; -} - -#no-results { - color: var(--text-muted); - font-style: italic; - text-align: center; - margin-top: 20px; -} - -.hidden { - display: none; -} - -/* Main Content */ -.content { - margin-left: var(--sidebar-width); - padding: 60px 80px; - max-width: 1600px; - width: 100%; -} - -section { - margin-bottom: 60px; - scroll-margin-top: 40px; - animation: fadeIn 0.5s ease-out; -} - -@keyframes fadeIn { - from { opacity: 0; transform: translateY(10px); } - to { opacity: 1; transform: translateY(0); } -} - -h2 { - font-size: 2rem; - border-bottom: 1px solid var(--border-color); - padding-bottom: 12px; - margin-bottom: 24px; - color: var(--text-color); - font-weight: 600; - letter-spacing: -0.025em; -} - -p { - margin-bottom: 20px; - font-size: 1.05rem; - color: var(--text-muted); - line-height: 1.8; -} - -code { - background-color: rgba(0,0,0,0.3); - padding: 2px 6px; - border-radius: 4px; - font-family: 'Fira Code', monospace; - font-size: 0.9em; - color: var(--primary-color); -} - -mark { - background-color: var(--highlight-color); - color: #fff; - padding: 0 4px; - border-radius: 2px; - font-weight: 500; -} - -/* Mobile Header */ -.mobile-header { - display: none; - background-color: var(--sidebar-bg); - border-bottom: 1px solid var(--border-color); - padding: 16px 24px; - align-items: center; - position: sticky; - top: 0; - z-index: 200; - box-shadow: var(--shadow-sm); -} - -#menu-toggle { - background: none; - border: none; - cursor: pointer; - padding: 8px; - margin-right: 16px; - color: var(--text-color); - border-radius: 4px; -} - -#menu-toggle:hover { - background-color: rgba(255,255,255,0.1); -} - -.mobile-title { - font-weight: 700; - font-size: 1.2rem; - color: var(--text-color); -} - -/* Responsive */ -@media (max-width: 768px) { - .mobile-header { - display: flex; - } - - .sidebar { - transform: translateX(-100%); - box-shadow: none; - } - - .sidebar.open { - transform: translateX(0); - box-shadow: 10px 0 50px rgba(0,0,0,0.5); - } - - .content { - margin-left: 0; - padding: 32px 24px; - } - - h2 { - font-size: 1.75rem; - } -} - -/* Tables */ -table { - width: 100%; - border-collapse: collapse; - margin-bottom: 24px; - font-size: 0.95rem; -} - -th, td { - padding: 12px 16px; - text-align: left; - border-bottom: 1px solid var(--border-color); -} - -th { - font-weight: 600; - color: var(--primary-color); - background-color: rgba(255, 255, 255, 0.02); -} - -tr:hover td { - background-color: rgba(255, 255, 255, 0.02); -} - -/* Code Blocks */ -pre { - background-color: #1e293b; /* Darker slate */ - padding: 20px; - border-radius: 8px; - overflow-x: auto; - margin-bottom: 24px; - border: 1px solid var(--border-color); -} - -pre code { - background-color: transparent; - padding: 0; - color: #e2e8f0; - font-family: 'Fira Code', 'Consolas', monospace; - font-size: 0.9rem; -} - -/* Lists */ -ul, ol { - margin-bottom: 24px; - padding-left: 24px; - color: var(--text-muted); -} - -li { - margin-bottom: 8px; -} - -strong { - color: var(--text-color); - font-weight: 600; -} - -/* Copy Button */ -.code-wrapper { - position: relative; -} - -.copy-btn { - position: absolute; - top: 4px; - right: 4px; - background-color: rgba(255, 255, 255, 0.1); - border: 1px solid rgba(255, 255, 255, 0.2); - color: var(--text-muted); - border-radius: 4px; - padding: 2px 6px; - font-size: 0.7rem; - cursor: pointer; - transition: all 0.2s; - opacity: 0; - z-index: 10; -} - -.code-wrapper:hover .copy-btn { - opacity: 1; -} - -.copy-btn:hover { - background-color: var(--primary-color); - color: white; - border-color: var(--primary-color); -} - -.copy-btn.copied { - background-color: #10b981; /* Green */ - border-color: #10b981; - color: white; -} From 2e9163fff46639bcbeb544d254dd10aefad8d7fd Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Wed, 26 Nov 2025 13:22:15 +0500 Subject: [PATCH 06/12] implement a Rainmeter plugin with an embedded WebView2 control, exposing Rainmeter API via a host object and supporting various commands. --- WebView2/HostObject.idl | 21 ++-- WebView2/HostObjectRmAPI.cpp | 225 +++++++++++++++++++++++++++++++---- WebView2/HostObjectRmAPI.h | 21 ++-- WebView2/Plugin.cpp | 26 ++-- WebView2/WebView2.cpp | 6 +- 5 files changed, 246 insertions(+), 53 deletions(-) diff --git a/WebView2/HostObject.idl b/WebView2/HostObject.idl index 5a8e112..afb2274 100644 --- a/WebView2/HostObject.idl +++ b/WebView2/HostObject.idl @@ -11,22 +11,23 @@ library WebView2Library interface IHostObjectRmAPI : IUnknown { // Basic option reading - HRESULT ReadString([in] BSTR option, [in] BSTR defaultValue, [out, retval] BSTR* result); - HRESULT ReadInt([in] BSTR option, [in] int defaultValue, [out, retval] int* result); - HRESULT ReadDouble([in] BSTR option, [in] double defaultValue, [out, retval] double* result); - HRESULT ReadFormula([in] BSTR option, [in] double defaultValue, [out, retval] double* result); - HRESULT ReadPath([in] BSTR option, [in] BSTR defaultValue, [out, retval] BSTR* result); + HRESULT ReadString([in] BSTR option, [in, optional] VARIANT defaultValue, [out, retval] BSTR* result); + HRESULT ReadInt([in] BSTR option, [in, optional] VARIANT defaultValue, [out, retval] int* result); + HRESULT ReadDouble([in] BSTR option, [in, optional] VARIANT defaultValue, [out, retval] double* result); + HRESULT ReadFormula([in] BSTR option, [in, optional] VARIANT defaultValue, [out, retval] double* result); + HRESULT ReadPath([in] BSTR option, [in, optional] VARIANT defaultValue, [out, retval] BSTR* result); // Section reading - HRESULT ReadStringFromSection([in] BSTR section, [in] BSTR option, [in] BSTR defaultValue, [out, retval] BSTR* result); - HRESULT ReadIntFromSection([in] BSTR section, [in] BSTR option, [in] int defaultValue, [out, retval] int* result); - HRESULT ReadDoubleFromSection([in] BSTR section, [in] BSTR option, [in] double defaultValue, [out, retval] double* result); - HRESULT ReadFormulaFromSection([in] BSTR section, [in] BSTR option, [in] double defaultValue, [out, retval] double* result); + HRESULT ReadStringFromSection([in] BSTR section, [in] BSTR option, [in, optional] VARIANT defaultValue, [out, retval] BSTR* result); + HRESULT ReadIntFromSection([in] BSTR section, [in] BSTR option, [in, optional] VARIANT defaultValue, [out, retval] int* result); + 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); // Utility functions HRESULT ReplaceVariables([in] BSTR text, [out, retval] BSTR* result); + HRESULT GetVariable([in] BSTR variableName, [out, retval] BSTR* result); HRESULT PathToAbsolute([in] BSTR path, [out, retval] BSTR* result); - HRESULT Execute([in] BSTR command); + HRESULT Bang([in] BSTR command); HRESULT Log([in] BSTR message, [in] BSTR level); // Properties diff --git a/WebView2/HostObjectRmAPI.cpp b/WebView2/HostObjectRmAPI.cpp index 13ef61b..6b8798b 100644 --- a/WebView2/HostObjectRmAPI.cpp +++ b/WebView2/HostObjectRmAPI.cpp @@ -9,50 +9,174 @@ HostObjectRmAPI::HostObjectRmAPI(Measure* m, wil::com_ptr tLib) } // Basic option reading -STDMETHODIMP HostObjectRmAPI::ReadString(BSTR option, BSTR defaultValue, BSTR* result) +STDMETHODIMP HostObjectRmAPI::ReadString(BSTR option, VARIANT defaultValue, BSTR* result) { if (!option || !result || !rm) return E_INVALIDARG; - LPCWSTR value = RmReadString(rm, option, defaultValue ? defaultValue : L"", TRUE); + // Handle optional default value + LPCWSTR defValue = L""; + if (defaultValue.vt == VT_BSTR && defaultValue.bstrVal != nullptr) + { + defValue = defaultValue.bstrVal; + } + else if (defaultValue.vt != VT_ERROR && defaultValue.vt != VT_EMPTY) + { + // Try to convert to string if it's another type + VARIANT converted; + VariantInit(&converted); + if (SUCCEEDED(VariantChangeType(&converted, &defaultValue, 0, VT_BSTR))) + { + defValue = converted.bstrVal; + LPCWSTR value = RmReadString(rm, option, defValue, TRUE); + *result = SysAllocString(value ? value : L""); + VariantClear(&converted); + return S_OK; + } + } + + LPCWSTR value = RmReadString(rm, option, defValue, TRUE); *result = SysAllocString(value ? value : L""); return S_OK; } -STDMETHODIMP HostObjectRmAPI::ReadInt(BSTR option, int defaultValue, int* result) +STDMETHODIMP HostObjectRmAPI::ReadInt(BSTR option, VARIANT defaultValue, int* result) { if (!option || !result || !rm) return E_INVALIDARG; - double value = RmReadFormula(rm, option, defaultValue); + // Handle optional default value + int defValue = 0; + if (defaultValue.vt == VT_I4) + { + defValue = defaultValue.lVal; + } + else if (defaultValue.vt == VT_I2) + { + defValue = defaultValue.iVal; + } + else if (defaultValue.vt != VT_ERROR && defaultValue.vt != VT_EMPTY) + { + // Try to convert to int + VARIANT converted; + VariantInit(&converted); + if (SUCCEEDED(VariantChangeType(&converted, &defaultValue, 0, VT_I4))) + { + defValue = converted.lVal; + } + VariantClear(&converted); + } + + double value = RmReadFormula(rm, option, defValue); *result = static_cast(value); return S_OK; } -STDMETHODIMP HostObjectRmAPI::ReadDouble(BSTR option, double defaultValue, double* result) +STDMETHODIMP HostObjectRmAPI::ReadDouble(BSTR option, VARIANT defaultValue, double* result) { if (!option || !result || !rm) return E_INVALIDARG; - *result = RmReadFormula(rm, option, defaultValue); + // Handle optional default value + double defValue = 0.0; + if (defaultValue.vt == VT_R8) + { + defValue = defaultValue.dblVal; + } + else if (defaultValue.vt == VT_R4) + { + defValue = defaultValue.fltVal; + } + else if (defaultValue.vt == VT_I4) + { + defValue = static_cast(defaultValue.lVal); + } + else if (defaultValue.vt != VT_ERROR && defaultValue.vt != VT_EMPTY) + { + // Try to convert to double + VARIANT converted; + VariantInit(&converted); + if (SUCCEEDED(VariantChangeType(&converted, &defaultValue, 0, VT_R8))) + { + defValue = converted.dblVal; + } + VariantClear(&converted); + } + + *result = RmReadFormula(rm, option, defValue); return S_OK; } -STDMETHODIMP HostObjectRmAPI::ReadFormula(BSTR option, double defaultValue, double* result) +STDMETHODIMP HostObjectRmAPI::ReadFormula(BSTR option, VARIANT defaultValue, double* result) { if (!option || !result || !rm) return E_INVALIDARG; - *result = RmReadFormula(rm, option, defaultValue); + // Handle optional default value + double defValue = 0.0; + if (defaultValue.vt == VT_R8) + { + defValue = defaultValue.dblVal; + } + else if (defaultValue.vt == VT_R4) + { + defValue = defaultValue.fltVal; + } + else if (defaultValue.vt == VT_I4) + { + defValue = static_cast(defaultValue.lVal); + } + else if (defaultValue.vt != VT_ERROR && defaultValue.vt != VT_EMPTY) + { + // Try to convert to double + VARIANT converted; + VariantInit(&converted); + if (SUCCEEDED(VariantChangeType(&converted, &defaultValue, 0, VT_R8))) + { + defValue = converted.dblVal; + } + VariantClear(&converted); + } + + *result = RmReadFormula(rm, option, defValue); return S_OK; } -STDMETHODIMP HostObjectRmAPI::ReadPath(BSTR option, BSTR defaultValue, BSTR* result) +STDMETHODIMP HostObjectRmAPI::ReadPath(BSTR option, VARIANT defaultValue, BSTR* result) { if (!option || !result || !rm) return E_INVALIDARG; - LPCWSTR value = RmReadString(rm, option, defaultValue ? defaultValue : L"", TRUE); + // Handle optional default value + LPCWSTR defValue = L""; + if (defaultValue.vt == VT_BSTR && defaultValue.bstrVal != nullptr) + { + defValue = defaultValue.bstrVal; + } + else if (defaultValue.vt != VT_ERROR && defaultValue.vt != VT_EMPTY) + { + // Try to convert to string + VARIANT converted; + VariantInit(&converted); + if (SUCCEEDED(VariantChangeType(&converted, &defaultValue, 0, VT_BSTR))) + { + defValue = converted.bstrVal; + LPCWSTR value = RmReadString(rm, option, defValue, TRUE); + if (value) + { + LPCWSTR absolutePath = RmPathToAbsolute(rm, value); + *result = SysAllocString(absolutePath ? absolutePath : value); + } + else + { + *result = SysAllocString(L""); + } + VariantClear(&converted); + return S_OK; + } + } + + LPCWSTR value = RmReadString(rm, option, defValue, TRUE); if (value) { LPCWSTR absolutePath = RmPathToAbsolute(rm, value); @@ -66,41 +190,89 @@ STDMETHODIMP HostObjectRmAPI::ReadPath(BSTR option, BSTR defaultValue, BSTR* res } // Section reading -STDMETHODIMP HostObjectRmAPI::ReadStringFromSection(BSTR section, BSTR option, BSTR defaultValue, BSTR* result) +STDMETHODIMP HostObjectRmAPI::ReadStringFromSection(BSTR section, BSTR option, VARIANT defaultValue, BSTR* result) { if (!section || !option || !result || !rm) return E_INVALIDARG; + // Handle optional default value + LPCWSTR defValue = L""; + if (defaultValue.vt == VT_BSTR && defaultValue.bstrVal != nullptr) + { + defValue = defaultValue.bstrVal; + } + // Note: Rainmeter API doesn't have direct section reading, so we'd need to implement this differently - // For now, return empty string - *result = SysAllocString(defaultValue ? defaultValue : L""); + // For now, return default value + *result = SysAllocString(defValue); return S_OK; } -STDMETHODIMP HostObjectRmAPI::ReadIntFromSection(BSTR section, BSTR option, int defaultValue, int* result) +STDMETHODIMP HostObjectRmAPI::ReadIntFromSection(BSTR section, BSTR option, VARIANT defaultValue, int* result) { if (!section || !option || !result || !rm) return E_INVALIDARG; - *result = defaultValue; + // Handle optional default value + int defValue = 0; + if (defaultValue.vt == VT_I4) + { + defValue = defaultValue.lVal; + } + else if (defaultValue.vt == VT_I2) + { + defValue = defaultValue.iVal; + } + + *result = defValue; return S_OK; } -STDMETHODIMP HostObjectRmAPI::ReadDoubleFromSection(BSTR section, BSTR option, double defaultValue, double* result) +STDMETHODIMP HostObjectRmAPI::ReadDoubleFromSection(BSTR section, BSTR option, VARIANT defaultValue, double* result) { if (!section || !option || !result || !rm) return E_INVALIDARG; - *result = defaultValue; + // Handle optional default value + double defValue = 0.0; + if (defaultValue.vt == VT_R8) + { + defValue = defaultValue.dblVal; + } + else if (defaultValue.vt == VT_R4) + { + defValue = defaultValue.fltVal; + } + else if (defaultValue.vt == VT_I4) + { + defValue = static_cast(defaultValue.lVal); + } + + *result = defValue; return S_OK; } -STDMETHODIMP HostObjectRmAPI::ReadFormulaFromSection(BSTR section, BSTR option, double defaultValue, double* result) +STDMETHODIMP HostObjectRmAPI::ReadFormulaFromSection(BSTR section, BSTR option, VARIANT defaultValue, double* result) { if (!section || !option || !result || !rm) return E_INVALIDARG; - *result = defaultValue; + // Handle optional default value + double defValue = 0.0; + if (defaultValue.vt == VT_R8) + { + defValue = defaultValue.dblVal; + } + else if (defaultValue.vt == VT_R4) + { + defValue = defaultValue.fltVal; + } + else if (defaultValue.vt == VT_I4) + { + defValue = static_cast(defaultValue.lVal); + } + + *result = defValue; return S_OK; } @@ -115,6 +287,19 @@ STDMETHODIMP HostObjectRmAPI::ReplaceVariables(BSTR text, BSTR* result) return S_OK; } +STDMETHODIMP HostObjectRmAPI::GetVariable(BSTR variableName, BSTR* result) +{ + if (!variableName || !result || !rm) + return E_INVALIDARG; + + // Wrap variable name with # syntax + std::wstring wrappedVar = L"#" + std::wstring(variableName) + L"#"; + + LPCWSTR value = RmReplaceVariables(rm, wrappedVar.c_str()); + *result = SysAllocString(value ? value : L""); + return S_OK; +} + STDMETHODIMP HostObjectRmAPI::PathToAbsolute(BSTR path, BSTR* result) { if (!path || !result || !rm) @@ -125,7 +310,7 @@ STDMETHODIMP HostObjectRmAPI::PathToAbsolute(BSTR path, BSTR* result) return S_OK; } -STDMETHODIMP HostObjectRmAPI::Execute(BSTR command) +STDMETHODIMP HostObjectRmAPI::Bang(BSTR command) { if (!command || !skin) return E_INVALIDARG; diff --git a/WebView2/HostObjectRmAPI.h b/WebView2/HostObjectRmAPI.h index 7205845..1a1841d 100644 --- a/WebView2/HostObjectRmAPI.h +++ b/WebView2/HostObjectRmAPI.h @@ -16,22 +16,23 @@ class HostObjectRmAPI : public Microsoft::WRL::RuntimeClass< HostObjectRmAPI(Measure* measure, wil::com_ptr typeLib); // IHostObjectRmAPI methods - Basic option reading - STDMETHODIMP ReadString(BSTR option, BSTR defaultValue, BSTR* result) override; - STDMETHODIMP ReadInt(BSTR option, int defaultValue, int* result) override; - STDMETHODIMP ReadDouble(BSTR option, double defaultValue, double* result) override; - STDMETHODIMP ReadFormula(BSTR option, double defaultValue, double* result) override; - STDMETHODIMP ReadPath(BSTR option, BSTR defaultValue, BSTR* result) override; + STDMETHODIMP ReadString(BSTR option, VARIANT defaultValue, BSTR* result) override; + STDMETHODIMP ReadInt(BSTR option, VARIANT defaultValue, int* result) override; + STDMETHODIMP ReadDouble(BSTR option, VARIANT defaultValue, double* result) override; + STDMETHODIMP ReadFormula(BSTR option, VARIANT defaultValue, double* result) override; + STDMETHODIMP ReadPath(BSTR option, VARIANT defaultValue, BSTR* result) override; // Section reading - STDMETHODIMP ReadStringFromSection(BSTR section, BSTR option, BSTR defaultValue, BSTR* result) override; - STDMETHODIMP ReadIntFromSection(BSTR section, BSTR option, int defaultValue, int* result) override; - STDMETHODIMP ReadDoubleFromSection(BSTR section, BSTR option, double defaultValue, double* result) override; - STDMETHODIMP ReadFormulaFromSection(BSTR section, BSTR option, double defaultValue, double* result) override; + STDMETHODIMP ReadStringFromSection(BSTR section, BSTR option, VARIANT defaultValue, BSTR* result) override; + STDMETHODIMP ReadIntFromSection(BSTR section, BSTR option, VARIANT defaultValue, int* result) override; + STDMETHODIMP ReadDoubleFromSection(BSTR section, BSTR option, VARIANT defaultValue, double* result) override; + STDMETHODIMP ReadFormulaFromSection(BSTR section, BSTR option, VARIANT defaultValue, double* result) override; // Utility functions STDMETHODIMP ReplaceVariables(BSTR text, BSTR* result) override; + STDMETHODIMP GetVariable(BSTR variableName, BSTR* result) override; STDMETHODIMP PathToAbsolute(BSTR path, BSTR* result) override; - STDMETHODIMP Execute(BSTR command) override; + STDMETHODIMP Bang(BSTR command) override; STDMETHODIMP Log(BSTR message, BSTR level) override; // Properties diff --git a/WebView2/Plugin.cpp b/WebView2/Plugin.cpp index 295b1f8..1eda946 100644 --- a/WebView2/Plugin.cpp +++ b/WebView2/Plugin.cpp @@ -92,9 +92,16 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) { std::wstring urlStr = urlOption; - // Convert file paths to file:/// URLs - if (urlStr.find(L"://") == std::wstring::npos) + // Check if it's a web URL (http://, https://, etc.) + if (urlStr.find(L"://") != std::wstring::npos) { + // Already has a protocol - use as-is + // This handles: http://, https://, file:///, etc. + measure->url = urlStr; + } + else + { + // No protocol found - treat as file path // Check if it's a relative path or absolute path if (urlStr[0] != L'/' && (urlStr.length() < 2 || urlStr[1] != L':')) { @@ -112,15 +119,14 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) if (urlStr[i] == L'\\') urlStr[i] = L'/'; } - // Add file:/// prefix - urlStr = L"file:///" + urlStr; + // Add file:/// prefix if not already present + if (urlStr.find(L"file:///") != 0) + { + urlStr = L"file:///" + urlStr; + } measure->url = urlStr; } - else - { - measure->url = urlStr; - } } // Read dimensions @@ -129,8 +135,8 @@ PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) measure->x = RmReadInt(rm, L"X", 0); measure->y = RmReadInt(rm, L"Y", 0); - // Read visibility - measure->visible = RmReadInt(rm, L"Visible", 1) != 0; + // Read visibility (Hidden option - inverse of Visible) + measure->visible = RmReadInt(rm, L"Hidden", 0) == 0; // Always create fresh WebView2 instance on every Reload // This matches the stable PluginWebView-main pattern and prevents race conditions diff --git a/WebView2/WebView2.cpp b/WebView2/WebView2.cpp index 42fb8c2..e86ce02 100644 --- a/WebView2/WebView2.cpp +++ b/WebView2/WebView2.cpp @@ -134,12 +134,12 @@ HRESULT Measure::CreateControllerHandler(HRESULT result, ICoreWebView2Controller VARIANT variant = {}; hostObject.query_to(&variant.pdispVal); variant.vt = VT_DISPATCH; - webView->AddHostObjectToScript(L"rm", &variant); + webView->AddHostObjectToScript(L"RainmeterAPI", &variant); variant.pdispVal->Release(); - // Add script to make rm available globally + // Add script to make RainmeterAPI available globally webView->AddScriptToExecuteOnDocumentCreated( - L"window.rm = chrome.webview.hostObjects.sync.rm", + L"window.RainmeterAPI = chrome.webview.hostObjects.sync.RainmeterAPI", nullptr ); From 0ff923a8cf298da7a50a0eef6cefc6163dd60ab3 Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Wed, 26 Nov 2025 15:22:58 +0500 Subject: [PATCH 07/12] Implement HostObjectRmAPI for WebView2 plugin to interact with Rainmeter and add a ReadSectionOption demo skin. --- .gitignore | 8 +- .../@Resources/ReadSectionOption/index.html | 60 +++++++ .../@Resources/ReadSectionOption/script.js | 57 +++++++ .../@Resources/ReadSectionOption/style.css | 148 ++++++++++++++++++ .../Skins/WebView2/Calendar/Calendar.ini | 2 +- .../ReadSectionOption/ReadSectionOption.ini | 47 ++++++ WebView2/HostObjectRmAPI.cpp | 37 ++--- 7 files changed, 332 insertions(+), 27 deletions(-) create mode 100644 Resources/Skins/WebView2/@Resources/ReadSectionOption/index.html create mode 100644 Resources/Skins/WebView2/@Resources/ReadSectionOption/script.js create mode 100644 Resources/Skins/WebView2/@Resources/ReadSectionOption/style.css create mode 100644 Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini diff --git a/.gitignore b/.gitignore index de25936..7369d90 100644 --- a/.gitignore +++ b/.gitignore @@ -427,4 +427,10 @@ FodyWeavers.xsd *.msm *.msp -dist \ No newline at end of file +dist + +# Ignore all @Backup folders +**/@Backup/ + +# Ignore all @Vault folders +**/@Vault/ diff --git a/Resources/Skins/WebView2/@Resources/ReadSectionOption/index.html b/Resources/Skins/WebView2/@Resources/ReadSectionOption/index.html new file mode 100644 index 0000000..7509117 --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/ReadSectionOption/index.html @@ -0,0 +1,60 @@ + + + + + + + ReadSectionOption Demo + + + + +

Section Reader

+ +
+ +
+
+ Variable + [Variables] TestVar +
+
Waiting...
+
+
+ + +
+
+ Measure String + [MeasureTest] String +
+
Waiting...
+
+
+ + +
+
+ Measure Int + [MeasureCalc] (Int) +
+
Waiting...
+
+
+ + +
+
+ Variable Double + [Variables] DoubleExample +
+
Waiting...
+
+
+
+ + + + + + \ No newline at end of file diff --git a/Resources/Skins/WebView2/@Resources/ReadSectionOption/script.js b/Resources/Skins/WebView2/@Resources/ReadSectionOption/script.js new file mode 100644 index 0000000..f2407f3 --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/ReadSectionOption/script.js @@ -0,0 +1,57 @@ +async function refreshValues() { + // 1. Read Variable (String) + updateField('var', async () => { + + return await RainmeterAPI.ReadStringFromSection('Variables', 'TestVar', 'N/A'); + }); + + // 2. Read Measure String + updateField('measure', async () => { + return await RainmeterAPI.ReadStringFromSection('MeasureTest', 'String', 'N/A'); + }); + + // 3. Read Measure Int (Int) + updateField('measure-int', async () => { + const val = await RainmeterAPI.ReadIntFromSection('MeasureCalc', 'Formula', 0); + return val; + }); + + // 5. Read Variable Double (Double) + updateField('var-double', async () => { + const val = await RainmeterAPI.ReadDoubleFromSection('Variables', 'DoubleExample', 0); + return val; + }); +} + +async function updateField(id, fetchFn) { + const output = document.getElementById(`${id}-output`); + const status = document.getElementById(`${id}-status`); + + try { + status.textContent = 'Reading...'; + status.className = 'status loading'; + + const value = await fetchFn(); + + output.textContent = value; + status.textContent = 'Updated'; + status.className = 'status success'; + } catch (e) { + console.error(e); + output.textContent = 'Error'; + status.textContent = e.message; + status.className = 'status error'; + } +} + +// Initial load +window.addEventListener('DOMContentLoaded', () => { + if (window.RainmeterAPI) { + refreshValues(); + } else { + document.querySelectorAll('.status').forEach(el => { + el.textContent = 'API Not Found'; + el.className = 'status error'; + }); + } +}); \ No newline at end of file diff --git a/Resources/Skins/WebView2/@Resources/ReadSectionOption/style.css b/Resources/Skins/WebView2/@Resources/ReadSectionOption/style.css new file mode 100644 index 0000000..270b429 --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/ReadSectionOption/style.css @@ -0,0 +1,148 @@ +: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; /* Add padding to prevent content from hiding behind scrollbar */ +} + +.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; +} + +.btn { + background: linear-gradient(135deg, var(--primary), var(--secondary)); + border: none; + color: white; + padding: 10px 20px; + border-radius: 8px; + cursor: pointer; + font-weight: 600; + transition: opacity 0.2s; + margin-top: 1rem; + width: 100%; +} + +.btn:hover { + opacity: 0.9; +} + +.status { + font-size: 0.8rem; + margin-top: 0.5rem; + text-align: right; +} + +.status.loading { + color: var(--text-muted); +} + +.status.success { + color: var(--success); +} + +.status.error { + color: var(--error); +} + +/* 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; /* Makes the thumb smaller than the width */ +} + +::-webkit-scrollbar-thumb:hover { + background: linear-gradient(to bottom, var(--secondary), var(--primary)); + border: 2px solid transparent; + background-clip: content-box; +} + +::-webkit-scrollbar-corner { + background: transparent; +} \ No newline at end of file diff --git a/Resources/Skins/WebView2/Calendar/Calendar.ini b/Resources/Skins/WebView2/Calendar/Calendar.ini index ff6c570..46a2c6d 100644 --- a/Resources/Skins/WebView2/Calendar/Calendar.ini +++ b/Resources/Skins/WebView2/Calendar/Calendar.ini @@ -14,7 +14,7 @@ License=MIT Measure=Plugin Plugin=WebView2 URL=#@#Calendar\index.html -W=300 +W=320 H=400 X=25 Y=25 diff --git a/Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini b/Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini new file mode 100644 index 0000000..584adc8 --- /dev/null +++ b/Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini @@ -0,0 +1,47 @@ +[Rainmeter] +Update=1000 +AccurateText=1 +DynamicWindowSize=1 + +[Metadata] +Name=ReadSectionOption Demo +Author=nstechbytes +Information=Demonstrates reading options from other sections using WebView2 +Version=1.0 +License=MIT + +[Variables] +TestVar=Hello from Variables! +DoubleExample=25.5 +; ================================================== +; Measures to Read +; ================================================== + +[MeasureTest] +Measure=String +String=This is a string measure + +[MeasureCalc] +Measure=Calc +Formula=25 +; ================================================== +; WebView2 Plugin +; ================================================== + +[MeasureWebView] +Measure=Plugin +Plugin=WebView2 +URL=#@#ReadSectionOption\index.html +W=400 +H=600 +X=0 +Y=0 +Hidden=0 + +; ================================================== +; Meters +; ================================================== + +[MeterBackground] +Meter=Shape +Shape=Rectangle 0,0,400,600 | FillColor 20,20,20,255 | StrokeWidth 0 diff --git a/WebView2/HostObjectRmAPI.cpp b/WebView2/HostObjectRmAPI.cpp index 6b8798b..2bfffb2 100644 --- a/WebView2/HostObjectRmAPI.cpp +++ b/WebView2/HostObjectRmAPI.cpp @@ -202,9 +202,9 @@ STDMETHODIMP HostObjectRmAPI::ReadStringFromSection(BSTR section, BSTR option, V defValue = defaultValue.bstrVal; } - // Note: Rainmeter API doesn't have direct section reading, so we'd need to implement this differently - // For now, return default value - *result = SysAllocString(defValue); + LPCWSTR value = RmReadStringFromSection(rm, section, option, defValue, TRUE); + *result = SysAllocString(value ? value : L""); + return S_OK; } @@ -215,16 +215,12 @@ STDMETHODIMP HostObjectRmAPI::ReadIntFromSection(BSTR section, BSTR option, VARI // Handle optional default value int defValue = 0; - if (defaultValue.vt == VT_I4) - { - defValue = defaultValue.lVal; - } - else if (defaultValue.vt == VT_I2) - { - defValue = defaultValue.iVal; - } + if (defaultValue.vt == VT_I4) defValue = defaultValue.lVal; + else if (defaultValue.vt == VT_I2) defValue = defaultValue.iVal; + + double value = RmReadFormulaFromSection(rm, section, option, (double)defValue); + *result = (int)value; - *result = defValue; return S_OK; } @@ -235,20 +231,11 @@ STDMETHODIMP HostObjectRmAPI::ReadDoubleFromSection(BSTR section, BSTR option, V // Handle optional default value double defValue = 0.0; - if (defaultValue.vt == VT_R8) - { - defValue = defaultValue.dblVal; - } - else if (defaultValue.vt == VT_R4) - { - defValue = defaultValue.fltVal; - } - else if (defaultValue.vt == VT_I4) - { - defValue = static_cast(defaultValue.lVal); - } + if (defaultValue.vt == VT_R8) defValue = defaultValue.dblVal; + else if (defaultValue.vt == VT_R4) defValue = defaultValue.fltVal; + else if (defaultValue.vt == VT_I4) defValue = static_cast(defaultValue.lVal); - *result = defValue; + *result = RmReadFormulaFromSection(rm, section, option, defValue); return S_OK; } From 3797b43d90db46bdbb17c31e3adc9748f6671d82 Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Wed, 26 Nov 2025 17:11:48 +0500 Subject: [PATCH 08/12] Add new `ReadMeasureOption` WebView2 demo with UI and logic to read various measure options, and a new `ReadSectionOption` demo INI. --- .../@Resources/ReadMeasureOption/index.html | 70 +++++++++ .../@Resources/ReadMeasureOption/script.js | 64 ++++++++ .../@Resources/ReadMeasureOption/style.css | 148 ++++++++++++++++++ .../ReadMeasureOption/ReadMeasureOption.ini | 36 +++++ .../ReadSectionOption/ReadSectionOption.ini | 7 +- 5 files changed, 321 insertions(+), 4 deletions(-) create mode 100644 Resources/Skins/WebView2/@Resources/ReadMeasureOption/index.html create mode 100644 Resources/Skins/WebView2/@Resources/ReadMeasureOption/script.js create mode 100644 Resources/Skins/WebView2/@Resources/ReadMeasureOption/style.css create mode 100644 Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini diff --git a/Resources/Skins/WebView2/@Resources/ReadMeasureOption/index.html b/Resources/Skins/WebView2/@Resources/ReadMeasureOption/index.html new file mode 100644 index 0000000..37e7852 --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/ReadMeasureOption/index.html @@ -0,0 +1,70 @@ + + + + + + + ReadMeasureOption Demo + + + + +

Measure Option Reader

+ +
+ +
+
+ String Option + TestString +
+
Waiting...
+
+
+ + +
+
+ Integer Option + TestInt +
+
Waiting...
+
+
+ + +
+
+ Double Option + TestDouble +
+
Waiting...
+
+
+ + +
+
+ Formula Option + TestFormula +
+
Waiting...
+
+
+ + +
+
+ Path Option + TestPath +
+
Waiting...
+
+
+
+ + + + + + diff --git a/Resources/Skins/WebView2/@Resources/ReadMeasureOption/script.js b/Resources/Skins/WebView2/@Resources/ReadMeasureOption/script.js new file mode 100644 index 0000000..ea6f809 --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/ReadMeasureOption/script.js @@ -0,0 +1,64 @@ +async function refreshValues() { + // 1. Read String + updateField('string', async () => { + return await RainmeterAPI.ReadString('TestString', 'Default'); + }); + + // 2. Read Int + updateField('int', async () => { + return await RainmeterAPI.ReadInt('TestInt', 0); + }); + + // 3. Read Double + updateField('double', async () => { + return await RainmeterAPI.ReadDouble('TestDouble', 0.0); + }); + + // 4. Read Formula + updateField('formula', async () => { + return await RainmeterAPI.ReadFormula('TestFormula', 0); + }); + + // 5. Read Path + updateField('path', async () => { + return await RainmeterAPI.ReadPath('TestPath', ''); + }); +} + +async function updateField(id, fetchFn) { + const output = document.getElementById(`${id}-output`); + const status = document.getElementById(`${id}-status`); + + try { + status.textContent = 'Reading...'; + status.className = 'status loading'; + + const value = await fetchFn(); + + output.textContent = value; + status.textContent = 'Updated'; + status.className = 'status success'; + } catch (e) { + console.error(e); + output.textContent = 'Error'; + status.textContent = e.message; + status.className = 'status error'; + } +} + +// Initial load +window.addEventListener('DOMContentLoaded', () => { + if (window.RainmeterAPI) { + refreshValues(); + } else { + // Fallback or wait for event if needed, though DOMContentLoaded usually suffices if API is injected early + // But for safety with some injection methods: + window.addEventListener('RainmeterAPIReady', refreshValues); + + // Also set initial status + document.querySelectorAll('.status').forEach(el => { + el.textContent = 'Waiting for API...'; + el.className = 'status loading'; + }); + } +}); diff --git a/Resources/Skins/WebView2/@Resources/ReadMeasureOption/style.css b/Resources/Skins/WebView2/@Resources/ReadMeasureOption/style.css new file mode 100644 index 0000000..991ffd8 --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/ReadMeasureOption/style.css @@ -0,0 +1,148 @@ +: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; /* Add padding to prevent content from hiding behind scrollbar */ +} + +.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; +} + +.btn { + background: linear-gradient(135deg, var(--primary), var(--secondary)); + border: none; + color: white; + padding: 10px 20px; + border-radius: 8px; + cursor: pointer; + font-weight: 600; + transition: opacity 0.2s; + margin-top: 1rem; + width: 100%; +} + +.btn:hover { + opacity: 0.9; +} + +.status { + font-size: 0.8rem; + margin-top: 0.5rem; + text-align: right; +} + +.status.loading { + color: var(--text-muted); +} + +.status.success { + color: var(--success); +} + +.status.error { + color: var(--error); +} + +/* 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; /* Makes the thumb smaller than the width */ +} + +::-webkit-scrollbar-thumb:hover { + background: linear-gradient(to bottom, var(--secondary), var(--primary)); + border: 2px solid transparent; + background-clip: content-box; +} + +::-webkit-scrollbar-corner { + background: transparent; +} diff --git a/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini b/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini new file mode 100644 index 0000000..e901eff --- /dev/null +++ b/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini @@ -0,0 +1,36 @@ +[Rainmeter] +Update=1000 +AccurateText=1 +DynamicWindowSize=1 + +[Metadata] +Name=ReadMeasureOption +Author=nstechbytes +Information=Demonstrates reading options from the current measure using the WebView2 plugin. +Version=1.0 +License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 + +[Variables] + +[MeasureWebView] +Measure=Plugin +Plugin=WebView2 +URL=file://#@#ReadMeasureOption\index.html +W=400 +H=600 +X=25 +Y=25 +; Options to be read by the JavaScript +TestString=Hello from Rainmeter! +TestInt=1337 +TestDouble=3.14159 +TestFormula=(10+5)*2 +TestPath=ReadMeasureOption.ini + +; ================================================== +; Meters +; ================================================== + +[MeterBackground] +Meter=Shape +Shape=Rectangle 0,0,450,650 | FillColor 0,0,0,2 | StrokeWidth 0 \ No newline at end of file diff --git a/Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini b/Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini index 584adc8..45a41e9 100644 --- a/Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini +++ b/Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini @@ -34,9 +34,8 @@ Plugin=WebView2 URL=#@#ReadSectionOption\index.html W=400 H=600 -X=0 -Y=0 -Hidden=0 +X=25 +Y=25 ; ================================================== ; Meters @@ -44,4 +43,4 @@ Hidden=0 [MeterBackground] Meter=Shape -Shape=Rectangle 0,0,400,600 | FillColor 20,20,20,255 | StrokeWidth 0 +Shape=Rectangle 0,0,450,650 | FillColor 0,0,0,2 | StrokeWidth 0 From 7b7ae7a8cf3e210ae6529564b5db3d1c3c67b7cd Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Wed, 26 Nov 2025 17:43:39 +0500 Subject: [PATCH 09/12] add utility function demo skin with API examples --- .../@Resources/UtilityFunction/index.html | 74 ++++++++++++ .../@Resources/UtilityFunction/script.js | 110 ++++++++++++++++++ .../@Resources/UtilityFunction/style.css | 54 +++++++++ .../ReadMeasureOption/ReadMeasureOption.ini | 2 - .../UtilityFunction/UtilityFunction.ini | 27 +++++ 5 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 Resources/Skins/WebView2/@Resources/UtilityFunction/index.html create mode 100644 Resources/Skins/WebView2/@Resources/UtilityFunction/script.js create mode 100644 Resources/Skins/WebView2/@Resources/UtilityFunction/style.css create mode 100644 Resources/Skins/WebView2/UtilityFunction/UtilityFunction.ini diff --git a/Resources/Skins/WebView2/@Resources/UtilityFunction/index.html b/Resources/Skins/WebView2/@Resources/UtilityFunction/index.html new file mode 100644 index 0000000..6283a3a --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/UtilityFunction/index.html @@ -0,0 +1,74 @@ + + + + + + Utility Functions Demo + + + +

Rainmeter Utility Functions

+
+
+
+ Replace Variables + RainmeterAPI.ReplaceVariables(text) +
+ +
Waiting...
+
+ +
+ +
+
+ Get Variable + RainmeterAPI.GetVariable(name) +
+ +
Waiting...
+
+ +
+ +
+
+ Path To Absolute + RainmeterAPI.PathToAbsolute(path) +
+ +
Waiting...
+
+ +
+ +
+
+ Bang + RainmeterAPI.Bang(command) +
+ +
+ +
+ +
+
+ Log + RainmeterAPI.Log(message, level) +
+ + +
+ +
+
+ + + + diff --git a/Resources/Skins/WebView2/@Resources/UtilityFunction/script.js b/Resources/Skins/WebView2/@Resources/UtilityFunction/script.js new file mode 100644 index 0000000..e60e8fd --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/UtilityFunction/script.js @@ -0,0 +1,110 @@ +function setStatus(id, text, cls) { + const el = document.getElementById(id); + el.textContent = text; + el.className = `status ${cls || ''}`.trim(); +} + +function getSyncApi() { + return window.RainmeterAPI || (window.chrome && window.chrome.webview && window.chrome.webview.hostObjects && window.chrome.webview.hostObjects.sync && window.chrome.webview.hostObjects.sync.RainmeterAPI); +} + +async function doReplace() { + const input = document.getElementById('replace-input').value || ''; + setStatus('replace-status', 'Replacing...', 'loading'); + try { + const api = getSyncApi(); + if (!api || typeof api.ReplaceVariables !== 'function') throw new Error('RainmeterAPI not available'); + const value = api.ReplaceVariables(input); + document.getElementById('replace-output').textContent = value; + setStatus('replace-status', 'Done', 'success'); + } catch (e) { + setStatus('replace-status', e.message || 'Error', 'error'); + } +} + +async function doGetVariable() { + const name = document.getElementById('variable-input').value || ''; + setStatus('variable-status', 'Getting...', 'loading'); + try { + const api = getSyncApi(); + if (!api || typeof api.GetVariable !== 'function') throw new Error('RainmeterAPI not available'); + const value = api.GetVariable(name); + document.getElementById('variable-output').textContent = value; + setStatus('variable-status', 'Done', 'success'); + } catch (e) { + setStatus('variable-status', e.message || 'Error', 'error'); + } +} + +async function doPathToAbsolute() { + const path = document.getElementById('path-input').value || ''; + setStatus('path-status', 'Converting...', 'loading'); + try { + const api = getSyncApi(); + if (!api || typeof api.PathToAbsolute !== 'function') throw new Error('RainmeterAPI not available'); + const value = api.PathToAbsolute(path); + document.getElementById('path-output').textContent = value; + setStatus('path-status', 'Done', 'success'); + } catch (e) { + setStatus('path-status', e.message || 'Error', 'error'); + } +} + +function doBang() { + const cmd = document.getElementById('bang-input').value || ''; + try { + const api = getSyncApi(); + if (!api || typeof api.Bang !== 'function') throw new Error('RainmeterAPI not available'); + api.Bang(cmd); + setStatus('bang-status', `Executed: ${cmd}`, 'success'); + } catch (e) { + setStatus('bang-status', e.message || 'Error', 'error'); + } +} + +function doLog() { + const msg = document.getElementById('log-input').value || ''; + const level = document.getElementById('level-select').value; + try { + const api = getSyncApi(); + if (!api || typeof api.Log !== 'function') throw new Error('RainmeterAPI not available'); + api.Log(msg, level); + setStatus('log-status', `${level}: ${msg}`, 'success'); + } catch (e) { + setStatus('log-status', e.message || 'Error', 'error'); + } +} + +function bind() { + document.getElementById('replace-btn').addEventListener('click', doReplace); + document.getElementById('variable-btn').addEventListener('click', doGetVariable); + document.getElementById('path-btn').addEventListener('click', doPathToAbsolute); + document.getElementById('bang-btn').addEventListener('click', doBang); + document.getElementById('log-btn').addEventListener('click', doLog); + document.getElementById('demo-btn').addEventListener('click', runDemo); +} + +function runDemo() { + document.getElementById('replace-input').value = 'Config: #CURRENTCONFIG# / Var: #DemoVariable#'; + document.getElementById('variable-input').value = 'DemoVariable'; + document.getElementById('path-input').value = 'UtilityFunction.ini'; + document.getElementById('log-input').value = 'Hello from WebView2'; + document.getElementById('level-select').value = 'Notice'; + doReplace(); + doGetVariable(); + doPathToAbsolute(); + doBang(); + doLog(); +} + +window.addEventListener('DOMContentLoaded', () => { + bind(); + const ready = !!getSyncApi(); + const msg = ready ? 'API Ready' : 'Waiting for API...'; + const cls = ready ? 'loading' : 'error'; + setStatus('replace-status', msg, cls); + setStatus('variable-status', msg, cls); + setStatus('path-status', msg, cls); + setStatus('bang-status', msg, cls); + setStatus('log-status', msg, cls); +}); diff --git a/Resources/Skins/WebView2/@Resources/UtilityFunction/style.css b/Resources/Skins/WebView2/@Resources/UtilityFunction/style.css new file mode 100644 index 0000000..c8c9b36 --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/UtilityFunction/style.css @@ -0,0 +1,54 @@ +: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; } +.btn { background: linear-gradient(135deg, var(--primary), var(--secondary)); border: none; color: white; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-weight: 600; transition: opacity 0.2s; margin-top: 0.75rem; width: 100%; } +.btn:hover { opacity: 0.9; } +.status { font-size: 0.8rem; margin-top: 0.5rem; text-align: right; } +.status.loading { color: var(--text-muted); } +.status.success { color: var(--success); } +.status.error { color: var(--error); } +.input { width: 100%; padding: 0.5rem; margin: 0.25rem 0; border-radius: 6px; border: 1px solid rgba(255,255,255,0.15); background: rgba(0,0,0,0.2); color: var(--text); } +.select { width: 100%; padding: 0.5rem; margin: 0.25rem 0; border-radius: 6px; border: 1px solid rgba(255,255,255,0.15); background: rgba(0,0,0,0.2); color: var(--text); } + +::-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: 2px solid transparent; background-clip: content-box; } +::-webkit-scrollbar-corner { background: transparent; } diff --git a/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini b/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini index e901eff..a542710 100644 --- a/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini +++ b/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini @@ -10,8 +10,6 @@ Information=Demonstrates reading options from the current measure using the WebV Version=1.0 License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 -[Variables] - [MeasureWebView] Measure=Plugin Plugin=WebView2 diff --git a/Resources/Skins/WebView2/UtilityFunction/UtilityFunction.ini b/Resources/Skins/WebView2/UtilityFunction/UtilityFunction.ini new file mode 100644 index 0000000..0b1f39b --- /dev/null +++ b/Resources/Skins/WebView2/UtilityFunction/UtilityFunction.ini @@ -0,0 +1,27 @@ +[Rainmeter] +Update=1000 +AccurateText=1 +DynamicWindowSize=1 + +[Metadata] +Name=UtilityFunction +Author=nstechbytes +Information=Demonstrates RainmeterAPI utility functions using the WebView2 plugin. +Version=1.0 +License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 + +[Variables] +DemoVariable=WebView2 Utility + +[MeasureWebView] +Measure=Plugin +Plugin=WebView2 +URL=file://#@#UtilityFunction\index.html +W=450 +H=650 +X=25 +Y=25 + +[MeterBackground] +Meter=Shape +Shape=Rectangle 0,0,500,700 | FillColor 0,0,0,2 | StrokeWidth 0 From 197db495e6a6b2d57abb61e137d64d2af483e93c Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Wed, 26 Nov 2025 17:58:05 +0500 Subject: [PATCH 10/12] add InformationProperty skin with HTML/CSS/JS --- .../@Resources/InformationProperty/index.html | 51 +++++++++++++++++ .../@Resources/InformationProperty/script.js | 57 +++++++++++++++++++ .../@Resources/InformationProperty/style.css | 22 +++++++ .../InformationProperty.ini | 28 +++++++++ .../ReadMeasureOption/ReadMeasureOption.ini | 6 +- .../ReadSectionOption/ReadSectionOption.ini | 2 +- .../UtilityFunction/UtilityFunction.ini | 8 ++- 7 files changed, 169 insertions(+), 5 deletions(-) create mode 100644 Resources/Skins/WebView2/@Resources/InformationProperty/index.html create mode 100644 Resources/Skins/WebView2/@Resources/InformationProperty/script.js create mode 100644 Resources/Skins/WebView2/@Resources/InformationProperty/style.css create mode 100644 Resources/Skins/WebView2/InformationProperty/InformationProperty.ini diff --git a/Resources/Skins/WebView2/@Resources/InformationProperty/index.html b/Resources/Skins/WebView2/@Resources/InformationProperty/index.html new file mode 100644 index 0000000..90ccd6c --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/InformationProperty/index.html @@ -0,0 +1,51 @@ + + + + + + Information Properties + + + +

Rainmeter Information Properties

+
+
+
+ Measure Name + RainmeterAPI.MeasureName +
+
Waiting...
+
+
+ +
+
+ Skin Name + RainmeterAPI.SkinName +
+
Waiting...
+
+
+ +
+
+ Skin Window Handle + RainmeterAPI.SkinWindowHandle +
+
Waiting...
+
+
+ +
+
+ Settings File + RainmeterAPI.SettingsFile +
+
Waiting...
+
+
+
+ + + + diff --git a/Resources/Skins/WebView2/@Resources/InformationProperty/script.js b/Resources/Skins/WebView2/@Resources/InformationProperty/script.js new file mode 100644 index 0000000..7519530 --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/InformationProperty/script.js @@ -0,0 +1,57 @@ +function getSyncApi() { + const chromeApi = (window.chrome && window.chrome.webview && window.chrome.webview.hostObjects && window.chrome.webview.hostObjects.sync && window.chrome.webview.hostObjects.sync.RainmeterAPI) || null; + return chromeApi || window.RainmeterAPI || null; +} + +function setStatus(id, text, cls) { + const el = document.getElementById(id); + el.textContent = text; + el.className = `status ${cls || ''}`.trim(); +} + +async function readProperty(prop, outputId, statusId) { + setStatus(statusId, 'Reading...', 'loading'); + try { + const api = getSyncApi(); + if (!api) throw new Error('RainmeterAPI not available'); + let value = api[prop]; + if (typeof value === 'function') { + value = value(); + } + value = await Promise.resolve(value); + document.getElementById(outputId).textContent = value; + setStatus(statusId, 'Updated', 'success'); + } catch (e) { + setStatus(statusId, e.message || 'Error', 'error'); + } +} + +function refreshAll() { + readProperty('MeasureName', 'measure-output', 'measure-status'); + readProperty('SkinName', 'skin-output', 'skin-status'); + readProperty('SkinWindowHandle', 'handle-output', 'handle-status'); + readProperty('SettingsFile', 'settings-output', 'settings-status'); +} + +async function waitForApi(timeoutMs = 3000, intervalMs = 100) { + const start = Date.now(); + while (Date.now() - start < timeoutMs) { + const api = getSyncApi(); + if (api) return api; + await new Promise(r => setTimeout(r, intervalMs)); + } + return null; +} + +window.addEventListener('DOMContentLoaded', async () => { + const api = await waitForApi(); + const ready = !!api; + const msg = ready ? 'API Ready' : 'Waiting for API...'; + const cls = ready ? 'loading' : 'error'; + setStatus('measure-status', msg, cls); + setStatus('skin-status', msg, cls); + setStatus('handle-status', msg, cls); + setStatus('settings-status', msg, cls); + document.getElementById('refresh-btn').addEventListener('click', refreshAll); + if (ready) refreshAll(); +}); diff --git a/Resources/Skins/WebView2/@Resources/InformationProperty/style.css b/Resources/Skins/WebView2/@Resources/InformationProperty/style.css new file mode 100644 index 0000000..e9874a9 --- /dev/null +++ b/Resources/Skins/WebView2/@Resources/InformationProperty/style.css @@ -0,0 +1,22 @@ +: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,.1);border-radius:12px;padding:1rem;transition:transform .2s} +.card:hover{transform:translateY(-2px);border-color:rgba(255,255,255,.2)} +.card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.5rem} +.card-title{font-weight:600;color:var(--text)} +.card-subtitle{font-size:.8rem;color:var(--text-muted)} +.value-display{font-family:'Consolas',monospace;background:rgba(0,0,0,.3);padding:.5rem;border-radius:6px;color:var(--success);word-break:break-all} +.btn{background:linear-gradient(135deg,var(--primary),var(--secondary));border:none;color:#fff;padding:10px 20px;border-radius:8px;cursor:pointer;font-weight:600;transition:opacity .2s;margin-top:1rem;width:100%} +.btn:hover{opacity:.9} +.status{font-size:.8rem;margin-top:.5rem;text-align:right} +.status.loading{color:var(--text-muted)} +.status.success{color:var(--success)} +.status.error{color:var(--error)} +::-webkit-scrollbar{width:8px;height:8px} +::-webkit-scrollbar-track{background:rgba(255,255,255,.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:2px solid transparent;background-clip:content-box} +::-webkit-scrollbar-corner{background:transparent} diff --git a/Resources/Skins/WebView2/InformationProperty/InformationProperty.ini b/Resources/Skins/WebView2/InformationProperty/InformationProperty.ini new file mode 100644 index 0000000..46f7e05 --- /dev/null +++ b/Resources/Skins/WebView2/InformationProperty/InformationProperty.ini @@ -0,0 +1,28 @@ +[Rainmeter] +Update=1000 +AccurateText=1 +DynamicWindowSize=1 + +[Metadata] +Name=InformationProperty +Author=nstechbytes +Information=Shows Rainmeter information properties via WebView2. +Version=1.0 +License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 +; ======================================== +; Measure +; ======================================== +[MeasureWebView] +Measure=Plugin +Plugin=WebView2 +URL=file://#@#InformationProperty\index.html +W=450 +H=650 +X=25 +Y=25 +; ======================================== +; Background +; ======================================== +[MeterBackground] +Meter=Shape +Shape=Rectangle 0,0,500,700 | FillColor 0,0,0,2 | StrokeWidth 0 diff --git a/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini b/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini index a542710..5bc0e87 100644 --- a/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini +++ b/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini @@ -9,7 +9,9 @@ Author=nstechbytes Information=Demonstrates reading options from the current measure using the WebView2 plugin. Version=1.0 License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 - +; ======================================== +; Measure +; ======================================== [MeasureWebView] Measure=Plugin Plugin=WebView2 @@ -26,7 +28,7 @@ TestFormula=(10+5)*2 TestPath=ReadMeasureOption.ini ; ================================================== -; Meters +; Background ; ================================================== [MeterBackground] diff --git a/Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini b/Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini index 45a41e9..bde28e0 100644 --- a/Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini +++ b/Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini @@ -38,7 +38,7 @@ X=25 Y=25 ; ================================================== -; Meters +; Background ; ================================================== [MeterBackground] diff --git a/Resources/Skins/WebView2/UtilityFunction/UtilityFunction.ini b/Resources/Skins/WebView2/UtilityFunction/UtilityFunction.ini index 0b1f39b..6374b9d 100644 --- a/Resources/Skins/WebView2/UtilityFunction/UtilityFunction.ini +++ b/Resources/Skins/WebView2/UtilityFunction/UtilityFunction.ini @@ -12,7 +12,9 @@ License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 [Variables] DemoVariable=WebView2 Utility - +; ======================================== +; Measure +; ======================================== [MeasureWebView] Measure=Plugin Plugin=WebView2 @@ -21,7 +23,9 @@ W=450 H=650 X=25 Y=25 - +; ======================================== +; Background +; ======================================== [MeterBackground] Meter=Shape Shape=Rectangle 0,0,500,700 | FillColor 0,0,0,2 | StrokeWidth 0 From f43821e8c1d8423096f9f253e806770c35a0c4d6 Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Wed, 26 Nov 2025 18:18:07 +0500 Subject: [PATCH 11/12] add BangCommand demo skin for WebView2 control --- .../WebView2/BangCommand/BangCommand.ini | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 Resources/Skins/WebView2/BangCommand/BangCommand.ini diff --git a/Resources/Skins/WebView2/BangCommand/BangCommand.ini b/Resources/Skins/WebView2/BangCommand/BangCommand.ini new file mode 100644 index 0000000..2d6f61b --- /dev/null +++ b/Resources/Skins/WebView2/BangCommand/BangCommand.ini @@ -0,0 +1,155 @@ +[Rainmeter] +Update=1000 +AccurateText=1 +DynamicWindowSize=1 + +[Metadata] +Name=BangCommand +Author=nstechbytes +Information=Demonstrates controlling WebView2 via !CommandMeasure bangs. +Version=1.0 +License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0 + +; ======================================== +; Measure +; ======================================== +[MeasureWebView] +Measure=Plugin +Plugin=WebView2 +URL=https://nstechbytes.pages.dev/ +W=600 +H=350 +X=300 +Y=25 + +; ======================================== +; Background +; ======================================== +[MeterBackground] +Meter=Shape +Shape=Rectangle 0,0,950,400 | FillColor 0,0,0,1 | StrokeWidth 0 + +[Title] +Meter=String +Text=Bang Command Demo +FontSize=16 +FontColor=255,255,255,255 +AntiAlias=1 +X=25 +Y=5 + +[StyleButtonText] +FontColor=255,255,255,255 +FontSize=12 +AntiAlias=1 +W=250 +H=25 +StringAlign=CenterCenter +SolidColor=0,200,255,150 + +; ======================================== +; Navigate to URL +; ======================================== +[TxtNavigate] +Meter=String +MeterStyle=StyleButtonText +Text=Navigate to https://example.com +X=150 +Y=58 +LeftMouseUpAction=[!CommandMeasure MeasureWebView "Navigate https://example.com"] + +[TxtReload] +Meter=String +MeterStyle=StyleButtonText +Text=Reload +X=150 +Y=88 +LeftMouseUpAction=[!CommandMeasure MeasureWebView "Reload"] + +[TxtGoBack] +Meter=String +MeterStyle=StyleButtonText +Text=Go Back +W=122 +H=24 +X=86 +Y=118 +LeftMouseUpAction=[!CommandMeasure MeasureWebView "GoBack"] + +[TxtGoForward] +Meter=String +MeterStyle=StyleButtonText +Text=Go Forward +W=125 +H=24 +X=212 +Y=118 +LeftMouseUpAction=[!CommandMeasure MeasureWebView "GoForward"] + +[TxtShow] +Meter=String +MeterStyle=StyleButtonText +Text=Show +W=122 +H=24 +X=86 +Y=148 +LeftMouseUpAction=[!CommandMeasure MeasureWebView "Show"] + +[TxtHide] +Meter=String +MeterStyle=StyleButtonText +Text=Hide +W=125 +H=24 +X=212 +Y=148 +LeftMouseUpAction=[!CommandMeasure MeasureWebView "Hide"] + +[TxtExecuteScript] +Meter=String +MeterStyle=StyleButtonText +Text=Execute Script +X=150 +Y=178 +LeftMouseUpAction=[!CommandMeasure MeasureWebView "ExecuteScript alert('Hello!')"] + +[TxtOpenDevTools] +Meter=String +MeterStyle=StyleButtonText +Text=Open DevTools +X=150 +Y=208 +LeftMouseUpAction=[!CommandMeasure MeasureWebView "OpenDevTools"] + +[TxtSetWidth] +Meter=String +MeterStyle=StyleButtonText +Text=Set Width: 500 +X=150 +Y=238 +LeftMouseUpAction=[!CommandMeasure MeasureWebView "SetW 500"] + +[TxtSetHeight] +Meter=String +MeterStyle=StyleButtonText +Text=Set Height: 400 +X=150 +Y=268 +LeftMouseUpAction=[!CommandMeasure MeasureWebView "SetH 400"] + +[TxtSetX] +Meter=String +MeterStyle=StyleButtonText +Text=Set X: 100 +X=150 +Y=298 +LeftMouseUpAction=[!CommandMeasure MeasureWebView "SetX 100"] + +[TxtSetY] +Meter=String +MeterStyle=StyleButtonText +Text=Set Y: 50 +X=150 +Y=328 +LeftMouseUpAction=[!CommandMeasure MeasureWebView "SetY 50"] From 850807cc3230a45f00e495d9aa3993ef7816250f Mon Sep 17 00:00:00 2001 From: nstechbytes Date: Wed, 26 Nov 2025 18:42:09 +0500 Subject: [PATCH 12/12] Add comprehensive README.md detailing the WebView2 Rainmeter plugin's features, usage, and API. --- README.md | 276 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..b3a48b3 --- /dev/null +++ b/README.md @@ -0,0 +1,276 @@ +# 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) + +## 🌟 Features + +- **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 + +## 📋 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) + +## 🚀 Installation + +### Method 1: RMSKIN Package (Recommended) + +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 + +### 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\` + +## 📖 Usage + +### Basic Skin Configuration + +```ini +[Rainmeter] +Update=1000 + +[MeasureWebView] +Measure=Plugin +Plugin=WebView2 +URL=file:///#@#index.html +Width=800 +Height=600 +``` + +### 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 | + +**Note**: Transparent background is always enabled by default. Developer tools (F12) are always available. + +### Bang Commands + +Execute commands from your skin using `[!CommandMeasure MeasureName "Command"]`: + +| 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"]` | +| `Show` | Make the WebView visible | `[!CommandMeasure MeasureWebView "Show"]` | +| `Hide` | Hide the WebView | `[!CommandMeasure MeasureWebView "Hide"]` | +| `ExecuteScript