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/README.md b/README.md index 0f6e747..b3a48b3 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,46 @@ -# WebView2 Rainmeter Plugin +# WebView2 Plugin for Rainmeter -A powerful Rainmeter plugin that embeds Microsoft Edge WebView2 control to display web content or local HTML files directly in your Rainmeter skins. +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) -![Platform](https://img.shields.io/badge/platform-Windows%2010%2F11-lightgrey) ![License](https://img.shields.io/badge/license-MIT-green) +![Rainmeter](https://img.shields.io/badge/rainmeter-4.5%2B-orange) -## โœจ Features +## ๐ŸŒŸ 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 +- **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 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/) +- **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) -## ๐Ÿš€ Quick Start +## ๐Ÿš€ Installation -### Installation +### Method 1: RMSKIN Package (Recommended) -1. Download the latest release `.rmskin` package -2. Double-click to install, or -3. Manually copy `WebView2.dll` to `%APPDATA%\Rainmeter\Plugins\` +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 -### Basic Usage +### 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] @@ -41,336 +49,228 @@ Update=1000 [MeasureWebView] Measure=Plugin Plugin=WebView2 -URL=https://www.google.com -Width=1000 -Height=700 -X=0 -Y=0 +URL=file:///#@#index.html +Width=800 +Height=600 ``` -## ๐Ÿ“– 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 | +| 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 | -### Bang Commands +**Note**: Transparent background is always enabled by default. Developer tools (F12) are always available. -Execute commands using `!CommandMeasure`: +### Bang Commands -```ini -; Navigate to URL -LeftMouseUpAction=[!CommandMeasure MeasureWebView "Navigate https://example.com"] +Execute commands from your skin using `[!CommandMeasure MeasureName "Command"]`: -; Reload current page -LeftMouseUpAction=[!CommandMeasure MeasureWebView "Reload"] +| 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 - - -``` - -## ๐Ÿ”Œ 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 -### Reading Options from Current Measure +| Method | Parameters | Return Type | Description | +|--------|------------|-------------|-------------| +| `ReadString(option, defaultValue)` | `option`: string
`defaultValue`: string | `Promise` | Read string option from measure | +| `ReadInt(option, defaultValue)` | `option`: string
`defaultValue`: number | `Promise` | Read integer option from measure | +| `ReadDouble(option, defaultValue)` | `option`: string
`defaultValue`: number | `Promise` | Read double option from measure | +| `ReadFormula(option, defaultValue)` | `option`: string
`defaultValue`: number | `Promise` | Read and evaluate formula option | +| `ReadPath(option, defaultValue)` | `option`: string
`defaultValue`: string | `Promise` | Read path option and convert to absolute | -| 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 Sections -### Reading from Other Sections +| Method | Parameters | Return Type | Description | +|--------|------------|-------------|-------------| +| `ReadStringFromSection(section, option, defaultValue)` | `section`: string
`option`: string
`defaultValue`: string | `Promise` | Read string option from specified section | +| `ReadIntFromSection(section, option, defaultValue)` | `section`: string
`option`: string
`defaultValue`: number | `Promise` | Read integer option from specified section | +| `ReadDoubleFromSection(section, option, defaultValue)` | `section`: string
`option`: string
`defaultValue`: number | `Promise` | Read double option from specified section | +| `ReadFormulaFromSection(section, option, defaultValue)` | `section`: string
`option`: string
`defaultValue`: number | `Promise` | Read and evaluate formula from specified section | -| 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 -### Utility Functions +| Method | Parameters | Return Type | Description | +|--------|------------|-------------|-------------| +| `ReplaceVariables(text)` | `text`: string | `Promise` | Replace Rainmeter variables in text (e.g., `#Variable#`, `[MeasureName]`) | +| `GetVariable(variableName)` | `variableName`: string | `Promise` | Get the value of a Rainmeter variable | +| `PathToAbsolute(relativePath)` | `relativePath`: string | `Promise` | Convert relative path to absolute path | +| `Bang(command)` | `command`: string | `Promise` | Execute a Rainmeter bang command | +| `Log(message, level)` | `message`: string
`level`: 'ERROR' \| 'WARNING' \| 'NOTICE' \| 'DEBUG' | `Promise` | Log a message to Rainmeter log | -| 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'` | +#### Properties -### Information Properties +| Property | Type | Description | +|----------|------|-------------| +| `MeasureName` | `Promise` | Get the name of the current measure | +| `SkinName` | `Promise` | Get the name of the current skin | +| `SkinWindowHandle` | `Promise` | Get the window handle of the skin | +| `SettingsFile` | `Promise` | Get the path to Rainmeter.data file | -| 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 | +## ๐Ÿ“š Examples -### Usage Examples +The plugin includes several example skins demonstrating various features: -#### Reading Options +- **Clock** - Animated liquid clock widget +- **Calendar** - Interactive calendar +- **ReadMeasureOption** - Reading options from measures +- **ReadSectionOption** - Reading options from other sections +- **BangCommand** - Executing Rainmeter bangs +- **UtilityFunction** - Using utility functions +- **InformationProperty** - Accessing skin properties -```javascript -// Read string option -const url = await rm.ReadString('URL', 'https://default.com'); +## ๐Ÿ› ๏ธ Building from Source -// Read integer option -const width = await rm.ReadInt('Width', 800); +### Prerequisites -// Read double/float option -const opacity = await rm.ReadDouble('Opacity', 1.0); +- Visual Studio 2022 with C++ desktop development workload +- Windows SDK +- PowerShell 5.1 or later -// Read formula option -const calculated = await rm.ReadFormula('MyFormula', 0); +### Build Steps -// Read path option -const filePath = await rm.ReadPath('DataFile', ''); -``` +1. Clone the repository: + ```bash + git clone https://github.com/yourusername/WebView2.git + cd WebView2 + ``` -#### Reading from Other Sections +2. Open `WebView2-Plugin.sln` in Visual Studio -```javascript -// Read string from another measure -const cpuValue = await rm.ReadStringFromSection('MeasureCPU', 'String', '0%'); +3. Build using PowerShell script: + ```powershell + powershell -ExecutionPolicy Bypass -Command "& {. .\Build-CPP.ps1; Dist -major 0 -minor 0 -patch 3}" + ``` -// Read integer from another section -const memoryUsage = await rm.ReadIntFromSection('MeasureRAM', 'Value', 0); +This will: +- Build both x64 and x86 versions +- Create plugin DLL ZIP file +- Package the RMSKIN installer +- Output to the `dist` folder -// Read double from another section -const temperature = await rm.ReadDoubleFromSection('MeasureTemp', 'Value', 0.0); +### Build Output -// Read formula from another section -const result = await rm.ReadFormulaFromSection('MeasureCalc', 'Formula', 0.0); -``` +- `dist/x64/WebView2.dll` - 64-bit plugin +- `dist/x32/WebView2.dll` - 32-bit plugin +- `dist/WebView2_v0.0.3_x64_x86_dll.zip` - Plugin DLLs package +- `dist/WebView2_v0.0.3_Alpha.rmskin` - Complete skin package -#### Utility Functions +## ๐Ÿ› Troubleshooting -```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'); -``` +### WebView2 Runtime Not Found -#### Information Properties +**Error**: "WebView2 Runtime is not installed" -```javascript -// Get measure name -const measureName = await rm.MeasureName; -console.log('Measure:', measureName); +**Solution**: Install the [WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) -// Get skin name -const skinName = await rm.SkinName; -console.log('Skin:', skinName); +### Controller Creation Failed -// Get skin window handle -const handle = await rm.SkinWindowHandle; +**Error**: "Failed to create WebView2 controller (HRESULT: 0x80080005)" -// Get settings file path -const settingsFile = await rm.SettingsFile; -``` +**Solution**: +- Refresh the skin: Right-click skin โ†’ Refresh +- Restart Rainmeter +- Ensure WebView2 Runtime is properly installed -### Complete Example - -```html - - - - Rainmeter Integration - - -

Rainmeter API Demo

- -
- - - - -``` +### JavaScript API Not Available -### Important Notes +**Error**: `RainmeterAPI is not defined` -- โœ… 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 +**Solution**: +- Ensure the page has fully loaded before accessing the API +- Wait for the `DOMContentLoaded` event: + ```javascript + document.addEventListener('DOMContentLoaded', () => { + // Use RainmeterAPI here + }); + ``` +### WebView Not Visible -## ๐Ÿ”ง Building from Source +**Problem**: WebView doesn't appear on the skin -### Prerequisites +**Solution**: +- Check that `Hidden=0` in your measure (or omit it, as 0 is the default) +- Use the `Show` bang command: `[!CommandMeasure MeasureName "Show"]` +- Verify the URL is correct and the HTML file exists +- Check Rainmeter log for errors -- Visual Studio 2022 (or 2019) -- Windows 10/11 SDK -- NuGet Package Manager +**Note**: The WebView always has a transparent background by default. Just use `background: transparent;` in your HTML/CSS to see through it. -### Build via PowerShell -```powershell -powershell -ExecutionPolicy Bypass -Command "& { . .\Build-CPP.ps1; Dist -major 0 -minor 0 -patch 3 }" -``` +## ๐Ÿ“„ License -This creates: -- Plugin DLLs in `dist\` folder -- Complete `.rmskin` package for distribution +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. ## ๐Ÿค Contributing Contributions are welcome! Please feel free to submit a Pull Request. -## ๐Ÿ“„ License - -This project is licensed under the MIT License. +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/AmazingFeature`) +3. Commit your changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request ## ๐Ÿ™ 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/) - +- Built with [Microsoft Edge WebView2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) +- Uses the [Rainmeter API](https://docs.rainmeter.net/developers/plugin/) +- Inspired by the Rainmeter community --- -**Made with โค๏ธ by nstechbytes** \ No newline at end of file +**Made with โค๏ธ for the Rainmeter community** 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/@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/@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/@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/@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/@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/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"] diff --git a/Resources/Skins/WebView2/Calendar/Calendar.ini b/Resources/Skins/WebView2/Calendar/Calendar.ini new file mode 100644 index 0000000..46a2c6d --- /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 +W=320 +H=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..31af180 --- /dev/null +++ b/Resources/Skins/WebView2/Clock/Clock.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=#@#Clock\index.html +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/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/Main.ini b/Resources/Skins/WebView2/Main.ini deleted file mode 100644 index ad6b49e..0000000 Binary files a/Resources/Skins/WebView2/Main.ini and /dev/null differ 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/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini b/Resources/Skins/WebView2/ReadMeasureOption/ReadMeasureOption.ini new file mode 100644 index 0000000..5bc0e87 --- /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 +; ======================================== +; Measure +; ======================================== +[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 + +; ================================================== +; Background +; ================================================== + +[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 new file mode 100644 index 0000000..bde28e0 --- /dev/null +++ b/Resources/Skins/WebView2/ReadSectionOption/ReadSectionOption.ini @@ -0,0 +1,46 @@ +[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=25 +Y=25 + +; ================================================== +; Background +; ================================================== + +[MeterBackground] +Meter=Shape +Shape=Rectangle 0,0,450,650 | FillColor 0,0,0,2 | StrokeWidth 0 diff --git a/Resources/Skins/WebView2/UtilityFunction/UtilityFunction.ini b/Resources/Skins/WebView2/UtilityFunction/UtilityFunction.ini new file mode 100644 index 0000000..6374b9d --- /dev/null +++ b/Resources/Skins/WebView2/UtilityFunction/UtilityFunction.ini @@ -0,0 +1,31 @@ +[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 +; ======================================== +; Measure +; ======================================== +[MeasureWebView] +Measure=Plugin +Plugin=WebView2 +URL=file://#@#UtilityFunction\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/WebView2/HostObject.idl b/WebView2/HostObject.idl index d553246..afb2274 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"; @@ -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 295e217..2bfffb2 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" @@ -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,76 @@ 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; - // 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""); + // Handle optional default value + LPCWSTR defValue = L""; + if (defaultValue.vt == VT_BSTR && defaultValue.bstrVal != nullptr) + { + defValue = defaultValue.bstrVal; + } + + LPCWSTR value = RmReadStringFromSection(rm, section, option, defValue, TRUE); + *result = SysAllocString(value ? value : L""); + 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; + + double value = RmReadFormulaFromSection(rm, section, option, (double)defValue); + *result = (int)value; + 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 = RmReadFormulaFromSection(rm, section, option, 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 +274,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 +297,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 6ec0f37..1a1841d 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" @@ -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 8cb28bb..1eda946 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" @@ -91,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':')) { @@ -111,25 +119,24 @@ 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 - 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); - // 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 @@ -229,7 +236,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()) { @@ -251,7 +258,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/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..e86ce02 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" @@ -133,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 ); 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