Overview

Overview

The NodeJS plugin for Rainmeter allows you to harness the full power of Node.js directly within your skins. It acts as a bridge, enabling you to execute JavaScript files, run asynchronous operations, and interact with the Rainmeter API from a Node.js environment. This opens up a world of possibilities, from fetching data from complex APIs to performing heavy computations without freezing your desktop.

Key Features

  • Full Node.js Environment: Use any Node.js module (e.g., fs, axios, path) in your scripts.
  • Asynchronous Execution: Run long-running tasks in the background without impacting Rainmeter's performance.
  • Two-Way Communication: Call Rainmeter API functions from your Node.js script and get results back.
  • Inline & File-Based Scripts: Write simple scripts directly in your .ini file or link to external .js files for more complex logic.
  • Dynamic Function Calls: Execute specific JavaScript functions on demand using !CommandMeasure.

Installation

Requirements

  • Rainmeter 4.3 or higher.
  • Node.js installed on your system. The plugin executes the node command, so it must be in your system's PATH.

Steps

  1. Download: Get the latest .rmskin package from the Download section.
  2. Install: Double-click the .rmskin file to install the plugin using the Rainmeter Skin Installer.
  3. Verify: After installation, the NodeJS.dll file should be located in your Rainmeter plugins directory (e.g., C:\Users\YourName\Documents\Rainmeter\Skins\YourSkin\@Resources\Plugins or C:\Program Files\Rainmeter\Plugins).

Basic Usage

Here’s a simple example of a skin that uses the NodeJS plugin to display the current Node.js version.

1. The .ini Skin File

This measure calls the version.js script.


[Rainmeter]
Update=1000

[Metadata]
Name=Node JS Version
Author=nstechbytes
Information=Sample skin demonstrating the NodeJS plugin
Version=v1.0

[MeasureNodeVersion]
Measure=Plugin
Plugin=NodeJS.dll
ScriptFile=#@#scripts/version.js
DynamicVariables=1

[Rectangle_Shape]
Meter=Shape
Shape=Rectangle 0,0,410,200,8 | StrokeWidth 0 | FillColor 255,255,255,150

[MeterNodeVersion]
Meter=String
MeasureName=MeasureNodeVersion
Text="Node.js Version: %1"
StringAlign=CenterCenter
FontSize=15
FontWeight=900
FontColor=10,10,10
AntiAlias=1
X=(410/2)
Y=(200/2)

2. The JavaScript File (scripts/version.js)

This script defines an update() function that returns the Node.js version string.

function update() {
  // process is a global Node.js object
  return process.version;
}

module.exports = {
  update
};

Options / Parameters

These are the options available for a plugin measure.

Plugin
Should be set to NodeJS.dll.
ScriptFile
The path to your external .js file. Relative paths using #@# are recommended.
Line
The first line of an inline JavaScript script. Use this for very short, simple scripts instead of an external file.
Line2, Line3, ...
Subsequent lines for an inline script. The plugin will read LineN options sequentially until it finds one that is not defined.
DynamicVariables
Set to 1 to allow the use of section variables in the measure's string value.

Important Note on require()

The Node.js require() function is fully supported when using ScriptFile. This allows you to import npm modules (like axios, fs, etc.). To use an npm module, you must first install it in the same directory as your script file (e.g., by running npm install axios in your script's folder, which will create a node_modules directory). However, require() is not available for inline scripts defined with the Line, Line2, etc. options.


Core Functions

The plugin looks for specific exported functions within your JavaScript file or inline script. All functions can be synchronous or asynchronous (return a Promise).

initialize()

Called once when the measure is first loaded or refreshed. Useful for setting up initial state or performing one-time setup tasks. The value returned will be set as the measure's initial value.

function initialize() {
  console.log('Script Initialized!');
  return 'Ready';
}
update()

Called on every skin update cycle. This is the main function for fetching and returning data. The returned value (string or number) becomes the measure's new value.

let count = 0;
function update() {
  count++;
  return `Update count: ${count}`;
}
YourCustomFunction()

Any other exported function can be called on demand using bangs. This is powerful for triggering actions without waiting for an update cycle.

function sayHello(name) {
  return `Hello, ${name}!`;
}

module.exports = {
  sayHello
};

You can call this with a bang:

LeftMouseUpAction=[!CommandMeasure  MeasureName  "sayHello('World')"]

RM API (in JS)

The plugin exposes a global RM object in your Node.js script, allowing you to interact with the Rainmeter API. This enables powerful two-way communication where your script can read skin variables, execute bangs, and get information about the skin environment.

Available RM Functions

All functions that read data from Rainmeter are synchronous from the perspective of your script; the plugin handles the two-way communication with the host process.

RM.Execute(command)
Executes a Rainmeter bang. This is a fire-and-forget action.
Parameters: command (string) - The bang to execute (e.g., '!Log "Hello"', '!ToggleMeter "MyMeter"').
RM.GetVariable(name, defaultValue)
Gets the value of a Rainmeter variable.
Parameters: name (string) - The name of the variable to get. defaultValue (string, optional) - The value to return if the variable doesn't exist.
Returns: (string) The variable's value.
RM.ReadString(option, defaultValue)
Reads a string value from the current measure's section in the .ini file.
Parameters: option (string) - The key to read. defaultValue (string, optional) - The default value.
Returns: (string) The option's value.
RM.ReadDouble(option, defaultValue)
Reads a numeric value from the current measure's section.
Parameters: option (string) - The key to read. defaultValue (number, optional) - The default value.
Returns: (number) The option's value, parsed as a float.
RM.ReadInt(option, defaultValue)
Reads an integer value from the current measure's section.
Parameters: option (string) - The key to read. defaultValue (number, optional) - The default value.
Returns: (number) The option's value, parsed as an integer.
RM.ReadStringFromSection(section, option, defaultValue)
Reads a string value from a different section in the .ini file.
Parameters: section (string) - The section to read from. option (string) - The key to read. defaultValue (string, optional) - The default value.
Returns: (string) The option's value.
RM.GetMeasureName()
Gets the name of the current measure.
Returns: (string) The measure's name.
RM.GetSkinName()
Gets the name of the current skin.
Returns: (string) The skin's name.
RM.GetSkin()
Gets a handle (a unique identifier) for the current skin.
Returns: (string) The skin handle.
RM.GetSkinWindow()
Gets a handle (a unique identifier) for the current skin's window.
Returns: (string) The skin window handle.

Section Variables

A powerful feature of the plugin is the ability to call a JavaScript function directly from within a meter's options using a special section variable syntax. This allows you to get dynamic values without needing bangs or separate measures.

The syntax is: [&MeasureName:Call("functionName(arguments)")]

When Rainmeter parses the meter, it will execute the specified JavaScript function from the script associated with MeasureName and substitute the returned value directly into the option.

Example: Direct Function Call in a Meter

This example demonstrates calling a sum() function from an inline script and displaying the result directly in a String meter.


[Rainmeter]
Update=8000

[Metadata]
Name=Sum
Author=nstechbytes
Information=Sample skin demonstrating the NodeJS plugin
Version=v1.0
License=MIT

[MeasureNodeJS]
Measure=Plugin
Plugin=NodeJS
Line=function sum() {
Line2=return 5 + 2;
Line3=}
Line4=module.exports = { sum };

[Rectangle_Shape]
Meter=Shape
Shape=Rectangle 0,0,310,100,8 | StrokeWidth 0 | FillColor 255,255,255,150

[MeterText]
Meter=String
MeasureName=MeasureNodeJS
Text=Sum of 5+2 is: [&MeasureNodeJS:Call("sum(5,2)")]
X=(310/2)
Y=(100/2)
StringAlign=CenterCenter
FontSize=15
FontColor=10,10,10
ClipString=2
AntiAlias=1
DynamicVariables=1

In this case, [&MeasureNodeJS:Call("sum(5,2)")] is executed, the sum(5, 2) function in the script runs and returns 7. The final text of the meter becomes "Sum of 5+2 is: 7".


Examples

Example 1: Asynchronous API Fetch

This example fetches a random joke from an API. Because it's an async operation, it doesn't freeze Rainmeter while waiting for the network response.

joke.ini

[Rainmeter]
Update=5000
DynamicWindowSize=1
SolidColor=255,255,255
BackgroundMode=2

[Metadata]
Name=NodeJS Plugin Sample
Author=nstechbytes
Information=Sample skin demonstrating the NodeJS plugin
Version=
License=

[MeasureJoke]
Measure=Plugin
Plugin=NodeJS.dll
ScriptFile=#@#scripts/joke.js
DynamicVariables=1

[MeterJoke]
Meter=String
MeasureName=MeasureJoke
W=300
H=100
ClipString=1
FontSize=12
FontColor=10,10,10
Text="%1"
AntiAlias=1

scripts/joke.js

You'll need to install axios by running npm install axios in your script's directory.


const axios = require('axios');


function initialize() {
  return 'Joke API Initialized';
}

async function update() {
  try {
    const response = await axios.get('https://v2.jokeapi.dev/joke/Any?type=single');
    if (response.data && !response.data.error) {
      return response.data.joke;
    }
    return 'Could not fetch joke.';
  } catch (error) {
    console.error('Joke API Error:', error.message);
    return 'API request failed.';
  }
}

module.exports = {
  update,
  initialize
};

Example 2: Inline Script with Bangs

This example uses an inline script to manage a simple counter. Bangs are used to increment, decrement, and reset the counter.

counter.ini


[Rainmeter]
Update=100

[Metadata]
Name=Counter
Author=nstechbytes
Information=Sample skin demonstrating the NodeJS plugin
Version=v1.0
License=MIT

[MeasureCounter]
Measure=Plugin
Plugin=NodeJS.dll
Line=let count = 20;
Line2=function incrementValue() { count++; return count; }
Line3=function decrementValue() { count--; return count; }
Line4=function initialize() { console.log("initializing"); count++;  return count; }
Line5=module.exports = { initialize, incrementValue, decrementValue };

[Rectangle_Shape]
Meter=Shape
Shape=Rectangle 0,0,300,100,8 | StrokeWidth 0 | FillColor 255,255,255,150

[MeterCounter]
Meter=String
MeasureName=MeasureCounter
W=300
H=100
X=(200/2)
Y=(100/2)
StringAlign=CenterCenter
FontSize=18
FontWeight=900
FontColor=10,10,10
Text=Count: %1
AntiAlias=1

[MeterIncrement]
Meter=String
Text="+"
W=20
H=20
X=250
Y=-10r
SolidColor=109,240,9,255
FontColor=255,255,255
FontSize=15
StringAlign=CenterCenter
LeftMouseUpAction=[!CommandMeasure MeasureCounter "incrementValue()"]

[MeterDecrement]
Meter=String
Text="-"
W=20
H=20
X=250
Y=2R
StringAlign=CenterCenter
SolidColor=207,12,12,255
FontColor=255,255,255
FontSize=15
LeftMouseUpAction=[!CommandMeasure MeasureCounter "decrementValue()"]


Error Handling

The plugin is designed to report errors clearly in the Rainmeter log.

  • Plugin Errors: Issues like "Node.js not found" or "ScriptFile not specified" will be logged by the plugin itself.
  • Script Errors: Syntax errors or runtime exceptions in your JavaScript code are caught and logged. The plugin uses console.error in the wrapper script to pipe these messages to the Rainmeter log with a (Error) prefix.
  • Logging: You can use console.log(), console.warn(), and console.error() in your script to send messages to the Rainmeter log. They will appear with (Notice), (Warning), and (Error) prefixes, respectively.
// This will appear in the Rainmeter log
console.log('This is a standard log message.');
console.warn('This is a warning.');
console.error('This is an error message.');

Advanced Usage

Calling Functions with Arguments

You can pass arguments to your custom functions via !CommandMeasure. Ensure your arguments are properly quoted if they contain spaces.

LeftMouseUpAction=[!CommandMeasure  MyMeasure "myFunction('some value', 123)"]

Working Directory

When you use ScriptFile, the working directory for the Node.js process is set to the directory containing that script. This means you can easily use relative paths to require other modules or read local files (e.g., require('./lib/helper.js')).

Asynchronous Initialization

The initialize function can also be async. The plugin will wait for the promise to resolve before proceeding. This is useful for setting up connections or loading data that is needed before the first update.


Limitations

  • Node.js Dependency: The user must have Node.js installed and accessible via their system's PATH.
  • Performance: While the plugin is efficient, running very complex, CPU-intensive scripts on every update can still impact system performance. Use asynchronous operations for heavy tasks.
  • Process Overhead: Each plugin measure starts a Node.js process. While lightweight, having hundreds of measures could increase memory usage.
  • Inline Script Size: Inline scripts are practical for small snippets. For anything complex, ScriptFile is the recommended approach. Rainmeter has a limit on the length of an option value.

FAQ

Q: Can I use ES Modules (import/export)?
A: The plugin currently uses a CommonJS (require/module.exports) wrapper. While you might be able to use libraries that transpile ES Modules, the native entry point for your script must be CommonJS.
Q: How do I debug my scripts?
A: Your primary debugging tool is the Rainmeter log. Use console.log() and console.error() liberally in your script to trace execution and inspect variables. You can also run your script directly with node yourscript.js in a terminal to catch errors outside of the Rainmeter environment.
Q: Why is my async function not updating the meter?
A: The update function is called asynchronously in the background. The result of its promise is used to update the measure's value on a subsequent skin cycle. There may be a one-cycle delay before the value appears. Ensure your async function actually returns a value or resolves a promise with a value.

Download

You can download the latest version of the NodeJS plugin from the official GitHub repository or the Rainmeter forums (links to be added).

Download Latest .rmskin

License & Credits

Author: nstechbytes

License: MIT License

This plugin was built using the official Rainmeter Plugin SDK. Special thanks to the Rainmeter community for their support and resources.