Skip to content

Embed modern web content directly into your Rainmeter skins with full JavaScript interactivity

License

Notifications You must be signed in to change notification settings

NSTechBytes/WebView2

Repository files navigation

WebView2 Plugin for Rainmeter

WebView2 Plugin Banner

Embed modern web content directly into your Rainmeter skins with full JavaScript interactivity

Version License Rainmeter Windows

📥 Download📖 Documentation💡 Examples🤝 Contributing


🚀 Quick Navigation


✨ What Can You Build?

Clock
Animated Widgets
Create stunning animated clocks, weather displays, and visualizers
Web
Web Dashboards
Embed live web content and interactive dashboards
API
Smart Integrations
Connect to APIs and control Rainmeter with JavaScript

🎯 Key Features

🚀 Modern Web Engine

Powered by Microsoft Edge WebView2, supporting:

  • ✅ HTML5, CSS3, JavaScript ES6+
  • ✅ Modern frameworks (React, Vue, Svelte)
  • ✅ WebGL, Canvas, SVG animations
  • ✅ Transparent backgrounds by default
🔌 Seamless JavaScript Bridge

Two-way communication between web and Rainmeter:

  • ✅ Call Rainmeter API from JavaScript
  • ✅ Execute JavaScript from Rainmeter
  • ✅ Real-time data synchronization
  • ✅ Custom events and callbacks
⚡ Dynamic & Flexible
  • ✅ Load local HTML or remote URLs
  • ✅ Multiple WebView instances per skin
  • ✅ Hot-reload without flickering
  • ✅ Developer tools (F12) built-in

📋 Requirements

Before you begin, make sure you have:

Requirement Version Status
Windows 10 (1803+) or 11 Windows
Rainmeter 4.5 or higher Rainmeter
WebView2 Runtime Latest WebView2
📦 Don't have WebView2 Runtime?

Good news! Windows 11 includes it by default. For Windows 10:

  1. 🔗 Download WebView2 Runtime
  2. 🎯 Choose "Evergreen Standalone Installer"
  3. ⚡ Run the installer (takes ~1 minute)

📥 Installation

🎁 Method 1: One-Click Install (Recommended)

The easiest way to get started!

  1. 📦 Download the .rmskin file
  2. 🖱️ Double-click to install
  3. ✨ Done! Plugin and examples are ready to use

Rainmeter will automatically install everything you need

🛠️ Method 2: Manual Installation

Click to expand manual installation steps
  1. Download the plugin DLLs from Releases

  2. Choose the right version:

    📁 x64/WebView2.dll  ← For 64-bit Rainmeter (most common)
    📁 x32/WebView2.dll  ← For 32-bit Rainmeter
    
  3. Copy to your Rainmeter plugins folder:

    %AppData%\Rainmeter\Plugins\
    
  4. Restart Rainmeter


🚀 Quick Start

Your First WebView Skin

Create a new skin with this minimal configuration:

skin.ini
[Rainmeter]
Update=1000

[WebView2]
Measure=Plugin
Plugin=WebView2
URL=file:///#@#index.html
W=800
H=600
X=0
Y=0

[Background]
Meter=Image
W=800
H=600
x=0
Y=0
SolidColor=0,0,0,1

Create index.html in your @Resources folder:

index.html
<!DOCTYPE html>
<html>
	<head>
		<style>
			body {
				background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
				color: white;
				font-family: 'Segoe UI', sans-serif;
				display: flex;
				flex-direction: column;
				justify-content: center;
				align-items: center;
				height: 100vh;
				width: 100vw;
				margin: 0;
			}
			h1, p { animation: fadeIn 1s; }
			h1 { font-size: 2.8em; user-select: none;}
			p { font-size: 1.3em;}
			p + p { margin-top: 0.1em; }
			@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
		</style>
	</head>
	<body>
		<h1>🎉 Hello Rainmeter!</h1>
		<p>Hold CTRL to drag the skin</p>
		<p>CTRL + Right Click to open the Skin Menu</p>
	</body>
</html>

That's it! Load the skin and see your first WebView in action.


⚙️ Measure

👑 Measure Values

👑 Measure Values

WebView's measure returns the following values:


Number Value

The number value represents the internal state of the WebView2 instance while it's initializing. When the WebView2 instance is initialized, the number value represent's the navigation state.

  • -2 WebView failed during initialization.
  • -1 WebView2 is idle.
  • 0 WebView2 is initializing.
  • 1 WebView2 is initialized.
  • 100 Navigation has started.
  • 200 Web content is loading.
  • 300 Web is loaded.
  • 400 Navigation has finished.

Whenever the state changes, the plugin triggers OnStateChangeAction.


String Value

The string value represents the current URL. This URL will change when the user navigates through WebView either internally by clicking on links or externally by using commands.

  • CurrentURL

Whenever the URL changes, the plugin triggers OnURLChangeAction.

🛠️ Measure Options

🛠️ Measure Options

Option Description Default Examples
WebView Options
AutoStart Automatically load WebView on skin load.
0 = Disabled, 1 = Enabled
1 AutoStart=0
URL The URL WebView will navigate to when it starts.
This is the URL the Navigate Home command navigates to.
Supports: file paths and web URLs
Supported schemes: file:///, http://, https://, view-source:
Relative paths are also supported: URL=file.html
Paths are relative to #CURRENTPATH#.
When HostPath is set to a valid folder path, this option is relative to that folder and will navigate to the default virtual host: https://rootconfig/file.html
See Setting Up a Virtual Host.
Required URL=file:///#@#file.html
URL=http://example.com
URL=path\to\file.html
URL=path\to\img.gif
URL=file.html
Virtual Host Options
HostSecurity Set a preferred protocol for the virtual host:
0 = http (not secure), 1 = https (secure)
Https allows to use JavaScript APIs that are normally blocked by CORS.
See Setting Up a Virtual Host.
1 HostSecurity=0
HostOrigin Set a preferred host name for the virtual host:
0 = current-config (not shared), 1 = rootconfig (shared)
When current-config 0 is used, the local storage is isolated to the current skin's origin, eg. https://illustro-clock
When rootconfig 1 is used, the local storage is shared between all configs that are part of the same suite, eg. https://illustro
See Setting Up a Virtual Host.
1 HostOrigin=0
HostPath The path to the folder that contains the index.html that the virtual host will load.
When this option is set to a folder path, the URL option will become relative to the path set on this option.
See Setting Up a Virtual Host.
"" HostPath=path\to\folder
HostPath=#CURRENTPATH#
HostPath=#@#
Window Options
W Window width (pixels) 800 W=1920
H Window height (pixels) 600 H=1080
X Horizontal position offset 0 X=100
Y Vertical position offset 0 Y=50
ZoomFactor Site zoom factor 1.0 ZoomFactor=1.5
Hidden Window visibility
0 = Visible, 1 = Hidden
0 Hidden=1
Clickthrough Mouse interaction mode
0 = Interactive, 1 = Click Through, 2 = Press CTRL to toggle
This option is very usefull to drag the skin, while set to 2 press CTRL to drag the skin and RMB to open the Skin Menu.
2 Clickthrough=1
Browser Options
UserAgent Custom user-agent string override "" UserAgent=MyBrowser/1.0
ZoomControl Allow user-controlled zoom through:
CTRL+SCROLL, CTRL + PLUS, CTRL + MINUS and CTRL + 0
0 = Disabled, 1 = Enabled
1 ZoomControl=0
NewWindow Allow opening links in a new window
0 = Disabled, 1 = Enabled
0 NewWindow=1
Notifications Override JavaScript's Notification API permission.
0 = Deny, 1 = Allow
0 Notifications=1
AssistiveFeatures Allow Print, Find and Caret Browsing features.
0 = Disabled, 1 = Enabled
1 AssistiveFeatures=0

💡 Pro Tip: When DynamicVariables=1, the WebView updates smartly:

  • URL changes → Sets a new Home Page
  • Size/Position changes → Applied instantly, no flicker
  • Visibility changes → Instant toggle
  • All options can be updated dynamically
▶️ Measure Actions

▶️ Measure Actions

Action Description Example
WebView State Actions
OnWebViewLoadAction Triggers when WebView starts. OnWebViewLoadActio=[!log "WebView2 loaded succesfully!"]
OnWebViewFailAction Triggers when WebView fails. OnWebViewFailAction=[!log "WebView2 failed :("]
OnWebViewStopAction Triggers when WebView stops. OnWebViewStopAction=[!log "WebView2 has stopped!"]
OnStateChangeAction Triggers when WebView initialization or navigation states change. OnStateChangeAction=[!UpdateMeasure #CURRENTSECTION#][!UpdateMeter CurrentState][!Redraw]
Navigation Actions
OnUrlChangeAction Triggers when the current URL changes. OnUrlChangeAction=[!UpdateMeasure #CURRENTSECTION#][!UpdateMeter CurrentURL][!Redraw]
OnPageLoadStartAction Triggers when navigation starts. OnPageLoadStartAction=[!log "Navigation has started!"]
OnPageLoadingAction Triggers when the page starts loading. OnPageLoadingAction=[!log "Page is loading!"]
OnPageDOMLoadAction Triggers when the DOM content is loaded. OnPageDOMLoadAction=[!log "DOM content loaded!"]
OnPageFirstLoadAction Triggers the first time a page is loaded. OnPageFirstLoadAction=[!log "First time on this page!"]
OnPageReloadAction Triggers when the page is reloaded. OnPageReloadAction=[!log "Page has been reloaded!"]
OnPageLoadFinishAction Triggers when the navigation is finished. OnPageLoadFinishAction=[!log "Navigation has finished!"]
💥 Bang Commands

💥 Bang Commands

Control your WebView with Rainmeter bangs:

Command Description Example
WebView
WebView Start Starts the WebView instance. [!CommandMeasure WebView2 "WebView Start"]
WebView Stop Stops the WebView instance. [!CommandMeasure WebView2 "WebView Stop"]
WebView Restart Restarts the WebView instance. [!CommandMeasure WebView2 "WebView Restart"]
Navigate
Navigate Home Navigates to URL option. [!CommandMeasure WebView2 "Navigate Home"]
Navigate Back Navigates to the previous page. [!CommandMeasure WebView2 "Navigate Back"]
Navigate Forward Navigates to the next page. [!CommandMeasure WebView2 "Navigate Forward"]
Navigate Reload Reloads the current page. [!CommandMeasure WebView2 "Navigate Reload"]
Navigate Stop Stops any navigation. [!CommandMeasure WebView2 "Navigate Stop"]
Navigate URL Navigates to a URL. [!CommandMeasure WebView2 "Navigate http://example.com"]
Open
Open DevTools Opens DeveloperTools. [!CommandMeasure WebView2 "Open DevTools"]
Open TaskManager Opens the web task manager. [!CommandMeasure WebView2 "Open TaskManager"]
Execute
Execute Script
Execute File.js
Executes given JS script or .js script file [!CommandMeasure WebView2 "Execute alert('Hello Rainmeter!')"]
[!CommandMeasure WebView2 "Execute #@#script.js"]
🗺️ Defaults Map

🗺️ Defaults Map

[WebView2]
Measure=Plugin
Plugin=WebView2

; WebView Options
AutoStart=1
URL=""

; Virtual Host Options
HostSecurity=1
HostOrigin=1
HostPath=""

; Window Options
W=800
H=600
X=0
Y=0
ZoomFactor=1.0
Hidden=0
Clickthrough=2

;Browser Options
UserAgent=""
ZoomControl=1
NewWindow=0
Notifications=0
AssistiveFeatures=1

; WebView State Actions
OnWebViewLoadAction=[]
OnWebViewFailAction=[]
OnWebViewStopAction=[]

; Navigation State Actions
OnStateChangeAction=[]
OnUrlChangeAction=[]
OnPageLoadStartAction=[]
OnPageLoadingAction=[]
OnPageDOMLoadAction=[]
OnPageFirstLoadAction=[]
OnPageReloadAction=[]
OnPageLoadFinishAction=[]

---

; WebView Commands
[!CommandMeasure WebView2 "WebView Start"]
[!CommandMeasure WebView2 "WebView Stop"]
[!CommandMeasure WebView2 "WebView Restart"]

; Navigation Commands
[!CommandMeasure WebView2 "Navigate Home"]
[!CommandMeasure WebView2 "Navigate Back"]
[!CommandMeasure WebView2 "Navigate Forward"]
[!CommandMeasure WebView2 "Navigate Reload"]
[!CommandMeasure WebView2 "Navigate Stop"]
[!CommandMeasure WebView2 "Navigate http://www.example.com"]

; Open Commands
[!CommandMeasure WebView2 "Open DevTools"]
[!CommandMeasure WebView2 "Open TaskManager"]

; Execute Commands
[!CommandMeasure WebView2 "Execute alert('Hello Rainmeter!')"]
[!CommandMeasure WebView2 "Execute path\to\file.js"]

;Section Variables
[WebView2:CallJS('alert("Example script")')]

;User Data Folder Path
C:\Users\User\AppData\Local\Temp\RainmeterWebView2\
📂 User Data Folder

User Data Folder

📁 RainmeterWebView2\
  ├── 📁 EBWebView\
  │   └── 📁 [WebView2 Data]\
  ├── 📁 Extensions\
  │   └── Extensions.ini
  │   └── 📁 MyExtension\
  │   └── 📁 MyOtherExtension\
  └── UserSettings.ini

When the plugin loads for the first time, a user data folder (UDP) is created. This folder contains all the data related to WebView2.

Path: C:\Users\User\AppData\Local\Temp\RainmeterWebView2\

If deleted, the folder will be re-created the next time the plugin is loaded.

To delete all your WebView2 data, it is recommended to delete RainmeterWebView2\EBWebView\ instead. This way you preserve your User Settings File and Extensions.

💡 IMPORTANT:

  • Exit Rainmeter before modifying anything in this folder, failing to do so will make WebView2 instances fail to start.
  • Restart Rainmeter if this happens.

User Settings File

There are certain settings that affect all WebView2 instances, for this reason they can't be exposed through the measure's options.

Such settings can be found in a file called UserSettings.ini.

Path: C:\Users\User\AppData\Local\Temp\RainmeterWebView2\UserSettings.ini

Options
Option Description Default
Environment Options
Extensions Allows to use extensions. false
FluentOverlayScrollBars Enable Fluent Overlay scrollbars. true
TrackingPrevention Enables Microsoft Edge tracking prevention. true
BrowserArguments Additional command-line arguments passed to the WebView2 browser process. --allow-file-access-from-files
BrowserLocale Sets the browser UI locale. Use system to follow the OS language.
Locales need to be in the format of BCP 47 Language Tags. e.g:
en-US es-MX fr-FR
A list of locales can be found here
system
Controller Options
PrivateMode Enables private mode, also commonly called incognito mode. false
ScriptLocale Locale used for JavaScript APIs such as date, time, and number formatting.
Use system to follow the OS language.
Locales need to be in the format of BCP 47 Language Tags. e.g:
en-US es-MX fr-FR
A list of locales can be found here
system
Core Options
StatusBar Shows or hides the browser status bar. true
PinchZoom Enables pinch-to-zoom gestures. true
SwipeNavigation Enables swipe gestures for back and forward navigation. true
SmartScreen Enables SmartScreen protection. More info. true
Profile Options
DownloadsFolderPath Custom folder path where downloaded files are saved. Empty uses the system default.
ColorScheme Controls the browser color scheme (light, dark, or system). system
PasswordAutoSave Allows automatic saving of passwords. false
GeneralAutoFill Enables form autofill for non-password fields. true
UserSettings.ini
[Environment]
Extensions = false
FluentOverlayScrollBars = true
TrackingPrevention = true
BrowserLocale = system
BrowserArguments = --allow-file-access-from-files

[Controller]
ScriptLocale = system
PrivateMode = false

[Core]
StatusBar = true
PinchZoom = true
SwipeNavigation = true
SmartScreen = true

[Profile]
DownloadsFolderPath = 
ColorScheme = system
PasswordAutoSave = false
GeneralAutoFill = true

Extensions

Using extensions on WebView2 is now possible!

Unfortunately, this feature doesn't come without limitations.

Limitations

  • Extensions' UI may not show up at all, or they may show up on the DevTools(F5) window.

⚠️ Important:

  • Manipulating Extensions require Rainmeter to be exited.

Path: C:\Users\User\AppData\Local\Temp\RainmeterWebView2\Extensions\

Installing Extensions

Installing Extensions

To install an extension:

  1. Exit Rainmeter
  2. Go to UserSettings.ini and enable extensions -> Extensions=true
  3. Drop an unpacked extension folder inside the Extensions\ folder
  4. Start Rainmeter
  5. Done

On Rainmeter, WebView2: "Extension Name" extension installed. will be logged.

Once an extension is installed, a new ini file will be created at Extensions\Extensions.ini, where you can control your extensions.

⚠️ Important:

  • Extensions must be unpacked folders.
Toggling Extensions

Toggling Extensions

To enable\disable an extension:

  1. Exit Rainmeter
  2. Open Extensions\Extensions.ini
  3. Find [YourExtensionFolderName] section
  4. Set Enabled=false or Enabled=false
  5. Save the file.
  6. Launch Rainmeter
  7. Done

On Rainmeter, WebView2: "Extension Name" extension enabled\disabled. will be logged.

Unninstalling Extensions

Unninstalling Extensions

To uninstall an extension:

  1. Exit Rainmeter
  2. Open Extensions\Extensions.ini
  3. Find [YourExtensionFolderName] section
  4. Set Uninstall=true
  5. Save the file
  6. Launch Rainmeter
  7. Manually remove the extension's unpacked folder from Extensions\
  8. Done

On Rainmeter, WebView2: "Extension Name" extension removed. will be logged.

⚠️ Important:

  • Uninstalling an extension will automatically delete its [section] from Extensions.ini, but will not remove its folder from the Extensions\ folder.
  • If the folder is not manually removed after uninstalling the extension, it will be automatically re-installed the next time you launch Rainmeter.
Extensions.ini

Path: C:\Users\User\AppData\Local\Temp\RainmeterWebView2\Extensions\Extensions.ini

[TheExtensionFolderName]
ID = theextensionid
Name = The Extension Name
Enabled = true
Uninstall = false

⚠️ Important:

  • You can only modify Enabled and Unninstall, other options are informative only.
  • Modifying or deleting ID will cause the extension to be reinstalled.
  • Modifying or deleting Name won't do anything, the name will be restored next time the plugin loads.

🔥 JavaScript Integration

Lifecycle Hooks

Your JavaScript can respond to Rainmeter events:

// Called once when navigation starts
window.OnInitialize = function() {
   console.log("🚀 WebView initialized!");
   RainmeterAPI.Bang('[!Log "🚀 WebView initialized!"]')
};

// Called on every Rainmeter update
window.OnUpdate = function() {
    const now = new Date().toLocaleTimeString();
    updateSomething(now);
};

Call JavaScript from Rainmeter

Use section variables to call any JavaScript function:

[MeterTemperature]
Meter=String
Text=Current temp: [WebView2:CallJS('getTemperature')]°C
DynamicVariables=1
// In your HTML
window.getTemperature = function() {
    return 72;
};

⚠️ Note: JavaScript execution is asynchronous, so there's a 1-update delay between JS return and Rainmeter display. This is normal!

Inject JS to Web Sites

From inline one-liner strings:

[WebView2]
Measure=Plugin
Plugin=WebView2
URL=https://example.com/
OnPageLoadFinishAction=[!CommandMeasure WebView2 "Execute alert('script executed'); console.log('hola!');"]

From files:

// @Resources\script.js

alert('script executed from file');
console.log('hola!');
; skin.ini

[WebView2]
Measure=Plugin
Plugin=WebView2
URL=https://example.com/
OnPageLoadFinishAction=[!CommandMeasure WebView2 "Execute #@#script.js"]

Use app-region CSS Style

Dragging elements with app-region: drag; set up will move the skin window. Right clicking these elements also opens the Skin Menu.

body {
	app-region: drag;
}

button {
	app-region: no-drag;
}

When using drag on a div or any other container, you need to manually set no-drag on the interactable children of that container, otherwise they won't work properly.

This CSS style is being used on the YoutubePlayer example skin.


🌉 RainmeterAPI Bridge

Access Rainmeter's full power from JavaScript:

Read Skin Options

// Read from your measure
const refreshRate = await RainmeterAPI.ReadInt('UpdateRate', 1000);
const siteName = await RainmeterAPI.ReadString('SiteName', 'Default');

// Read from other sections
const cpuUsage = await RainmeterAPI.ReadStringFromSection('MeasureCPU', 'Value', '0');

Execute Bangs

// Set variables
await RainmeterAPI.Bang('!SetVariable MyVar "Hello World"');

// Control skins
await RainmeterAPI.Bang('!ActivateConfig "MySkin" "Variant.ini"');

// Update meters
await RainmeterAPI.Bang('!UpdateMeter MeterName');
await RainmeterAPI.Bang('!Redraw');

Get Skin Information

const skinName = await RainmeterAPI.SkinName;
const measureName = await RainmeterAPI.MeasureName;

// Replace variables
const path = await RainmeterAPI.ReplaceVariables('#@#images/logo.png');

// Get variable values
const theme = await RainmeterAPI.GetVariable('CurrentTheme');

Logging

await RainmeterAPI.Log('Debug info', 'DEBUG');
await RainmeterAPI.Log('Warning message', 'WARNING');
await RainmeterAPI.Log('Error occurred', 'ERROR');

Complete API Reference

📚 Click to see all available methods

Reading Options

  • ReadString(option, defaultValue)Promise<string>
  • ReadInt(option, defaultValue)Promise<number>
  • ReadDouble(option, defaultValue)Promise<number>
  • ReadFormula(option, defaultValue)Promise<number>
  • ReadPath(option, defaultValue)Promise<string>

Reading from Sections

  • ReadStringFromSection(section, option, defaultValue)Promise<string>
  • ReadIntFromSection(section, option, defaultValue)Promise<number>
  • ReadDoubleFromSection(section, option, defaultValue)Promise<number>
  • ReadFormulaFromSection(section, option, defaultValue)Promise<number>

Utility Functions

  • ReplaceVariables(text)Promise<string>
  • GetVariable(variableName)Promise<string>
  • PathToAbsolute(relativePath)Promise<string>
  • Bang(command)Promise<void>
  • Log(message, level)Promise<void>

Properties

  • MeasureNamePromise<string>
  • SkinNamePromise<string>
  • SkinWindowHandlePromise<string>
  • SettingsFilePromise<string>

❓ How to Make a Draggable Skin

There are currently two ways to make WebView2 skins draggable.

Using Clickthrough

Clickthrough is a powerful option that allows mouse input to pass through the WebView2 window to the skin layer behind it, effectively making the window invisible to mouse interactions.

A new value, 2, was recently added. This makes Clickthrough toggleable by holding the CTRL key.

What do you need to do? Nothing.

By default, Clickthrough=2. To drag the skin, simply hold CTRL and drag. This works because you are interacting directly with the skin, not with the WebView2 window.
You can also open the Skin Menu with CTRL + RMB. Calendar.ini uses this method.

[WebView2]
Measure=Plugin
Plugin=WebView2
X=0
Y=0
W=500
H=500
Clickthrough=2

Alternatively, you can use Clickthrough=1 if the skin is only displaying information or images. This removes the need to hold CTRL, but disables all interaction with the WebView2 window. Clock.ini uses this method.

[WebView2]
Measure=Plugin
Plugin=WebView2
X=0
Y=0
W=500
H=500
Clickthrough=1
Using app-region

WebView2 supports the app-region CSS property to define draggable and non-draggable areas on a page, allowing to drag the skin without holding CTRL. This is a more advanced way to do it, but it will behave much more skin-like than the clickthrough way. YouTubePlayer.ini uses this method.

The property has two values:

  • drag
  • no-drag

app-region: drag

  • Dragging the element moves the window
  • Right-click opens the Skin Menu
  • All child elements inherit this behavior

Use for non-interactive areas such as backgrounds or title bars.

.titlebar {
  app-region: drag;
}

app-region: no-drag

  • Disables window dragging
  • Enables normal mouse interaction
  • Overrides inherited drag

Required for buttons, inputs, sliders, and other interactive elements.

.button,
input {
  app-region: no-drag;
}

Common Pattern

.window {
  app-region: drag;
}

.controls {
  app-region: no-drag;
}

This keeps the window draggable while preserving UI interactivity.

You can also apply app-region directly in HTML using inline styles.

<div class="titlebar" style="app-region: drag;">
  <span>My Skin</span>
  <button style="app-region: no-drag;">Nice Button</button>
</div>

In this example, the title bar remains draggable while the button stays fully interactive.

Draggable skin example

Load this file using a default WebView2 measure:

index.html

<!DOCTYPE html>
<html>
	<style>
	body {
		background: black;
		color: white;
		display: flex;
		flex-direction: column;
		justify-content: center;
		align-items: center;
		height: 100vh;
		margin: 0;
		text-align: center;
		<!-- dragging magic -->
		app-region: drag; 
	}
	button {
		background: green;
		color: white;
		<!-- no-dragging magic -->
		app-region: no-drag; 
	}
	button:hover {
		background: lightgreen;
		color: black;
		cursor: pointer;
	}
	p { color: grey; }
	p:hover { color: white; }
	code { color: silver; }
	</style>
	<body>
		<h2>Draggable Skin Example</h1>
		<p>Drag the skin</p>
		<p>you can drag over everything</p>
		<button>but this button.</button>
		<p>hold <code>CTRL</code> and <code>drag</code> over the button</p>
	</body>
</html>

☄️ Setting Up a Virtual Host

Some APIs and features do not function correctly when accessed via the file:/// protocol. Previously, this limitation required installing an external http-server to enable proper behavior. WebView2 now provides a built-in Virtual Host feature, which allows a plugin to be served from a virtual http or https URL, effectively replicating the behavior of a local web server. In practice, this means you can generate and use a custom virtual URL such as https://my-skin-config-name/ or http://my-skin-config-name/.

This feature provides the following configuration options:


HostSecurity (default: 1)
Specifies which protocol the virtual host will use.
0 - Not secure (http)
1 - Secure (https)


HostOrigin (default: 1)
Defines which config is used as the origin for the virtual host.
0 - Current config
1 - Root config

This setting also determines how Local Storage is scoped:

  • Using Current config restricts Local Storage access to the active skin (config) only.
  • Using Root config allows Local Storage to be shared across all skins (configs) under the same root, which is useful for suites.

HostPath (default: "")
Specifies the path to the folder containing your file.html.
Example: HostPath = #@#

When this option is set, the plugin enables the Virtual Host feature and generates a virtual URL mapped to the specified folder. The resulting URL uses a protocol defined by HostSecurity and a host name derived from the HostOrigin setting.


How it works:

The plugin detects that a Virtual Host should be used when the HostPath option points to a valid folder. Once detected, it initializes the virtual host and generates a new base URL that can be used to access the mapped files.

Example 1: Current Config as Origin (Isolated Storage)

Assume the following code belongs to the Illustro\Clock skin (config):

; HostSecurity:
; 0 = http  (insecure context)
; 1 = https (secure context)
; Using HTTPS allows access to APIs that may be blocked by CORS.
HostSecurity=1

; HostOrigin:
; 0 = current config only
; 1 = root config
; Using current config isolates storage to this skin
HostOrigin=0

; Folder containing the HTML and assets
HostPath=#@#

URL=index.html

In this case:

  • The protocol is set to https

  • The origin is derived from the current config: Illustro\Clock

  • The index.html file resides in the @Resources folder

The generated base URL will be:

https://illustro-clock/

You can navigate to the page explicitly:

URL=https://illustro-clock/index.html

Or implicitly:

URL=index.html

When using a Virtual Host, the URL option automatically resolves relative paths against the generated virtual host URL.

Local Storage will be isolated to the Illustro\Clock skin, meaning it is scoped to the following origin:

https://illustro-clock/
Example 2: Root Config as Origin (Shared Storage)

Assume the following code belongs to the Illustro\Clock skin (config):

HostSecurity=0
HostOrigin=1
HostPath=#@#Clock\

URL=clock.html

In this case:

  • The protocol is set to http

  • The origin is derived from the root config: Illustro

  • The clock.html file resides in the @Resources\Clock folder

The generated base URL will be:

http://illustro/

You can navigate to the page explicitly:

URL=http://illustro/clock.html

Or implicitly:

URL=clock.html

Local Storage will be shared across all skins (configs) that belong to the Illustro root config. In other words, Local Storage is scoped to:

http://illustro/
Practical Example

Assume the following skin structure:

📁 MyRootConfig\
  ├── 📁 @Resources\
  │   └── index.html
  └── MyRootConfigIni.ini
MyRootConfigIni.ini
[Rainmeter]
Update=1000

[WebView2]
Measure=Plugin
Plugin=WebView2
HostPath=#@#
Url=Index.html
W=300
H=300

[WebView2BG]
Meter=Image
W=300
H=300
SolidColor=0,0,0,255
Index.html
<!DOCTYPE html>
<html>
	<style>
	body {
		color: white;
		display: flex;
		flex-direction: column;
		justify-content: center;
		align-items: center;
		height: 100vh;
		margin: 0;
		text-align: center;
	}
	</style>
	<body>
	
		<h1>Hello, Rainmeter!</h1>
		<p>This is a very simple HTML page.</p>
		<p>Hold CTRL to drag the skin</p>
		<p>Press CTRL + RMB to open SkinMenu.</p>
	
	</body>
</html>

After loading the skin, check the skins tab on the About window. You'll see that the measure is returning a URL as its string value, which is https://myrootconfig/Index.html.

By default, HostSecurity=1, therefore, as seen on the URL, it's using the https protocol.

Also, HostOrigin=1 by default, but in this example it doesn't matter given our skin's structure. This skin has only a root config, so that's the only host name we can use. Which is the myrootconfig/ part on the URL.

If we now open DevTools (press F12 inside the WebView window) and go to the Aplication tab, then go to Storage -> Local storage on the left side panel, we'll see that our https://myrootconfig origin is listed.

Clicking on our origin will show all the key-value pairs that are stored. Obviously it is empty if you haven't saved anything yet. Check the storage for YoutubePlayer example skin instead.


Now try using this skin's structure and play with both HostSecurity and HostOrigin.

📁 MyRootConfig\
  ├── 📁 @Resources\
  │   └── index.html
  ├── 📁 MyOtherConfig\
  │    └── MyOtherConfigIni.ini
  └── MyRootConfigIni.ini

Try setting HostOrigin=0 on both configs, you'll see the same https://myrootconfig origin on DevTools for both configs, which means they share local storage.

If you then set HostOrigin=1 on MyOtherConfig, you'll see its origin is now https://myrootconfig-myotherconfig, which means its local storage is not shared.


💡 Examples

The plugin includes ready-to-use example skins:

🕐 Clock
Animated liquid clock with smooth animations
📅 Calendar
Interactive month view calendar
⚙️ Config Reader
Read options from measures and sections
🔧 Utilities
Demonstrate all API functions
▶️ Youtube Player
Youtube Player iFrame API example

To explore examples:

  1. Install the .rmskin package
  2. Check your Rainmeter skins folder
  3. Load example skins from Rainmeter manager

🛠️ Building from Source

For Developers: Build Instructions

Prerequisites

  • Visual Studio 2022 with C++ desktop development
  • Windows SDK
  • PowerShell 5.1+

Build Steps

# Clone repository
git clone https://github.com/yourusername/WebView2.git
cd WebView2

# Open in Visual Studio
start WebView2-Plugin.sln

# Build with PowerShell
powershell -ExecutionPolicy Bypass -Command "& {. .\Build-CPP.ps1; Dist -major 0 -minor 0 -patch 3}"

Build Output

📁 dist/
  ├── 📁 x64/
  │   └── WebView2.dll
  ├── 📁 x32/
  │   └── WebView2.dll
  ├── WebView2_v0.0.3_x64_x86_dll.zip
  └── WebView2_v0.0.3_Alpha.rmskin

🆘 Troubleshooting

❌ "WebView2 Runtime is not installed"

Solution: Install WebView2 Runtime

Windows 11 has it pre-installed. For Windows 10, download and run the installer.

❌ "Failed to create WebView2 controller"

Try these steps:

  1. ✅ Right-click skin → Refresh skin
  2. ✅ Restart Rainmeter completely
  3. ✅ Verify WebView2 Runtime is installed
  4. ✅ Check Windows Event Viewer for detailed errors
❌ "RainmeterAPI is not defined" in JavaScript

Solution: Wait for page to load before accessing API:

document.addEventListener('DOMContentLoaded', () => {
    // Now you can use RainmeterAPI
    RainmeterAPI.Log('Page loaded!', 'DEBUG');
});
❌ WebView not visible

Checklist:

  • ✅ Ensure Hidden=0 in your measure (default is 0)
  • ✅ Check URL path is correct
  • ✅ Verify HTML file exists
  • ✅ Look for errors in Rainmeter log
  • ✅ Try: [!CommandMeasure MeasureName "Open DevTools"] to debug

Transparency tip: The WebView has transparent background by default. Use background: transparent; in your CSS.


📄 License

MIT License - Free to use, modify, and distribute

See LICENSE file for full details


🤝 Contributing

We welcome contributions! Here's how:

1. Fork
🍴
Fork this repo
2. Branch
🌿
Create feature branch
3. Code
💻
Make your changes
4. Commit
📝
Commit with clear message
5. PR
🚀
Open Pull Request
git checkout -b feature/AmazingFeature
git commit -m 'Add some AmazingFeature'
git push origin feature/AmazingFeature

🙏 Acknowledgments

Built with powerful tools and inspired by the community

Microsoft Edge WebView2Rainmeter APIRainmeter Community


💖 Made with love for the Rainmeter community

⬆ Back to Top

Made with Love

About

Embed modern web content directly into your Rainmeter skins with full JavaScript interactivity

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published