It is currently December 10th, 2018, 8:52 pm

ConfigActive 2.1

Share your custom plugins and applications to enhance Rainmeter.
User avatar
jsmorley
Developer
Posts: 18445
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

ConfigActive 2.1

jsmorley » June 22nd, 2018, 1:59 pm

This updates and replaces the current ConfigActive plugin I released a few months ago.
This plugin is a collaborative effort of JSMorley and TheAzack9


This little plugin can address something that you might want from time to time, to detect if a config (a skin) is currently loaded / active in Rainmeter.

This plugin requires Rainmeter 4.2 r3097 or better to return the string value of the .ini name, but will work to simply detect if a config is loaded using any recent version of Rainmeter.

Example skin that installs the plugin in Rainmeter. The 32bit and 64bit versions of the .dll are also included in @Resources for your own use.
ConfigActive_2.1.rmskin
Here are the 32bit and 64bit versions of the .dll if you don't want or need the example skin.
ConfigActivePluginForDistribution.zip

Checking active status of a skin

Using a measure option
ConfigName;;Default: #CURRENTCONFIG#;;The name of a config you wish to test. This is case-insensitive.

Example:

Code: Select all

[MeasureName]
Measure=Plugin
Plugin=ConfigActive
ConfigName=Illustro\System
- The number value of the measure will be 1 if the config is active and -1 if not.
- The string value of the measure will be the currently running skin .ini file name.

Note: All other measure specific options are ignored when ConfigName is set.
Using inline plugin section variables
[&MeasureName:IsActive(ConfigName)];;;;The section variable will be resolved to 1 if the config is active and -1 if not.

Example IfCondition=[&MeasureName:IsActive(Illustro\System)] = 1

Note: DynamicVariables=1 must be set on the measure or meter where this is used.
[&MeasureName:ConfigVariantName(ConfigName)];;;;The section variable will be resolved to the name of the currently running skin .ini file name.

Example IfTrueAction=[!SetOption SomeMeter Text "Variant [&MeasureName:ConfigVariantName(Illustro\System)] is running."]

Note: DynamicVariables=1 must be set on the measure or meter where this is used.
Getting a count of running skins

Using a measure option
Type;;Default: Skin;;In addition to the basic functionality supported by the ConfigName option, the plugin will obtain a "count" of the number of active configs. This will create a numbered "index" of all active configs, ordered by the current z-position (front to back) of the skin windows.

This count can be returned as the number value by setting Type=Count on the measure.

Type can also used in conjunction with Index, to return either the config name or skin .ini file name of the config referenced with Index. This is done with Type=Config or Type=Skin and Index=x

Note: The intent of this is to be able to use Count, and then, perhaps in Lua, iterate through Index from 1 to the value of Count, to obtain a list of configs and / or skins currently active. In and of itself, Index is not terribly useful, as the position of any given config in this list is really unreliable on any given user's system at any given time.
Index;;Default: 1;;Used in conjunction with Type=Config or Type=Skin to return ether the config name or skin .ini name for a particular indexed config.
Using inline plugin section variables
[&MeasureName:LoadedCount()];;;;The section variable will be resolved to the number of currently active configs.

Example Formula=[&MeasureName:LoadedCount()]

Note: DynamicVariables=1 must be set on the measure or meter where this is used.
Skin:

Code: Select all

[Rainmeter]
Update=1000
DynamicWindowSize=1
AccurateText=1

[Metadata]
Name=ConfigActive
Author=JSMorley
Information=Demonstrates the ConfigActive plugin | See comments in the skin
License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0
Version=July 22, 2018

; ConfigActive Plugin

; Returns 1 (Active/Loaded) or -1 (not Active/Loaded) as the number value for a named skin config.
; Returns the "Skin.ini" name of the "variant" running for the named skin config.

; This can be used two ways:

; Set the ConfigName option on the measure, and it will return the state 
; of the named config on each measure update as the measure's number and string value.

; Use the IsActive(config name) function as an inline section variable.
; The return value of 1 or -1 will replace the function call where used.
; DynamicVariables=1 must be used with inline function calls.
; Do not enclose the config name passed to the isActive() function in quotes.
; No ConfigName option is needed with this method.

; Installing this .rmskin will install the proper ConfigActive.dll in Rainmeter.
; The 32bit and 64bit versions of the .dll are also included in the
; @Resources\ConfigActive Plugin for Distribution folder in the skin.

[Variables]
; Set the config names to test here
Config1=illustro\System
Config2=illustro\Clock
Config3=illustro\Network

; Using the [MeasureActive] measure's number and string return values when ConfigName is set
[MeasureActive]
Measure=Plugin
Plugin=ConfigActive
ConfigName=#Config1#
DynamicVariables=1
IfCondition=MeasureActive = 1
IfTrueAction=[!SetOption MeterActive1 Text "[#Config1#] is active, and [MeasureActive] is running."]
IfFalseAction=[!SetOption MeterActive1 Text "[#Config1#] is not active"]

; Using [MeasureActive] as a host for a inline isActive() function call and testing the number value
; Using [MeasureActive] as a host for a inline ConfigVariantName() function call.
[MeasureActive2]
Measure=Calc
DynamicVariables=1
IfCondition=[&MeasureActive:IsActive(#Config2#)] = 1
IfTrueAction=[!SetOption MeterActive2 Text "[#Config2#] is active, and [&MeasureActive:ConfigVariantName(#Config2#)] is running."]
IfFalseAction=[!SetOption MeterActive2 Text "[#Config2#] is not active"]

; Using [MeasureActive] as a host for a inline isActive() function call and Substituting the string value
; Using [MeasureActive] as a host for a inline ConfigVariantName() function call.
[MeasureActive3]
Measure=String
String=[&MeasureActive:IsActive(#Config3#)]
DynamicVariables=1
Substitute="-1":"[#Config3#] is not active","1":"[#Config3#] is active, and [&MeasureActive:ConfigVariantName(#Config3#)] is running."

[MeterActive1]
Meter=String
W=400
FontSize=11
FontWeight=400
FontColor=255,255,255,255
SolidColor=47,47,47,255
Padding=5,5,5,5
AntiAlias=1

[MeterActive2]
Meter=String
Y=5R
W=400
FontSize=11
FontWeight=400
FontColor=255,255,255,255
SolidColor=47,47,47,255
Padding=5,5,5,5
AntiAlias=1

[MeterActive3]
Meter=String
MeasureName=MeasureActive3
Y=5R
W=400
FontSize=11
FontWeight=400
FontColor=255,255,255,255
SolidColor=47,47,47,255
Padding=5,5,5,5
AntiAlias=1
1.png
Here is the plugin C++ code in case you want to see what it is doing. It simply sends a message to Rainmeter, asking if a particular skin config "window" exists. If it does, it gets the window title of the config window, and extracts the skin .ini name from it.

Code: Select all

#include <Windows.h>
#include "../../API/RainmeterAPI.h"
#include <string>

#define RAINMETER_QUERY_ID_SKIN_WINDOWHANDLE	5101
#define RAINMETER_SKIN_CLASSNAME L"RainmeterMeterWindow"
#define RAINMETER_TRAY_CLASSNAME L"RainmeterTrayClass"

enum class Type
{
	SKIN,
	CONFIG,
	LOADEDCOUNT
};

struct Measure
{
	Measure() {}

	std::wstring m_SkinPath;
	std::wstring m_ConfigName;
	std::wstring m_SkinName;
	int m_Index = 0;
	Type m_Type = Type::SKIN;

	std::wstring m_String;
	std::wstring m_SectionVariable;
};

PLUGIN_EXPORT void Initialize(void** data, void* rm)
{
	Measure* measure = new Measure;
	*data = measure;
}

PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue)
{
	Measure* measure = (Measure*)data;

	measure->m_SkinPath = RmReplaceVariables(rm, L"#SKINSPATH#");
	measure->m_ConfigName = RmReadString(rm, L"ConfigName", L"");
	measure->m_Index = RmReadInt(rm, L"Index", 1);

	std::wstring type = RmReadString(rm, L"Type", L"Skin");

	if (_wcsicmp(type.c_str(), L"SKIN") == 0) measure->m_Type = Type::SKIN;
	else if (_wcsicmp(type.c_str(), L"CONFIG") == 0) measure->m_Type = Type::CONFIG;
	else if (_wcsicmp(type.c_str(), L"COUNT") == 0) measure->m_Type = Type::LOADEDCOUNT;
}

HWND GetLoadedConfig(const std::wstring& configName)
{
	HWND trayHWND = FindWindow(RAINMETER_TRAY_CLASSNAME, NULL);
	if (trayHWND == NULL) return NULL;

	std::wstring config = configName;
	for(int i = 0; i < config.size()-1; ++i)
	{
		if (config[i] == L'\\' && config[i + 1] == L'\\') {
			config.erase(i, 1);
		}
	}

	COPYDATASTRUCT cds = {};
	cds.dwData = RAINMETER_QUERY_ID_SKIN_WINDOWHANDLE;
	cds.cbData = (DWORD)(config.size() + 1) * sizeof(WCHAR);
	cds.lpData = (void*)config.c_str();

	return (HWND)SendMessage(trayHWND, WM_COPYDATA, 0, (LPARAM)&cds);;
}

std::wstring GetWindowTitle(HWND hwnd)
{
	const int length = GetWindowTextLength(hwnd);
	std::wstring buff;
	buff.resize(length+1);
	GetWindowText(hwnd, &buff[0], (int)buff.size());
	return buff;
}

HWND QueryConfig(int index)
{
	HWND hwnd = NULL;
	int current = index;
	do
	{
		hwnd = FindWindowEx(NULL, hwnd, RAINMETER_SKIN_CLASSNAME, NULL);
		// limit reached 
		if (hwnd == NULL) break;
		current--;
	} while (current != 0);
	return hwnd;
}

int LoadedSkinCount()
{
	HWND hwnd = NULL;
	int count = 0;
	do
	{
		hwnd = FindWindowEx(NULL, hwnd, RAINMETER_SKIN_CLASSNAME, NULL);
		if (hwnd == NULL) break;
		count++;
	} while (true);
	return count;
}

PLUGIN_EXPORT double Update(void* data)
{
	Measure* measure = (Measure*)data;

	HWND config;
	if(!measure->m_ConfigName.empty())
	{
		// index is irrelevant here as there can only be one config
		config = GetLoadedConfig(measure->m_ConfigName);
	}
	else
	{
		// Fetch the config based on index instead
		config = QueryConfig(measure->m_Index);
	}

	if (config == NULL)
	{
		// Specified config is not loaded, so clear the return string
		measure->m_String.clear();
		return -1;
	}

	std::wstring title = GetWindowTitle(config);
	const size_t last = title.find_last_of(L"\\");

	switch(measure->m_Type)
	{
	case Type::SKIN: 
		// Title is not set correctly, so we can't retrieve the skin. The config is still loaded.
		if (last == std::wstring::npos && last + 1 < title.size()) return 1;
		measure->m_String = title.substr(last + 1);
		break;
	case Type::CONFIG: 
	{
		measure->m_String = measure->m_ConfigName;
		// Title is not set correctly, so we can't retrieve the skin. The config is still loaded.
		if (last == std::wstring::npos && last + 1 < title.size()) return 1;
		if (!measure->m_ConfigName.empty()) return 1;

		size_t start = measure->m_SkinPath.size();
		measure->m_String = title.substr(start, last - start);
	}
		break;
	case Type::LOADEDCOUNT: 
		measure->m_String.clear();
		return LoadedSkinCount();
	}

	return 1;
}

PLUGIN_EXPORT LPCWSTR GetString(void* data)
{
	Measure* measure = (Measure*)data;
	return measure->m_String.c_str();
}

PLUGIN_EXPORT void Finalize(void* data)
{
	Measure* measure = (Measure*)data;
	delete measure;
}

PLUGIN_EXPORT LPCWSTR IsActive(void* data, const int argc, const WCHAR* argv[])
{
	if (argc > 0) return GetLoadedConfig(argv[0]) ? L"1" : L"-1";
	return nullptr;
}

PLUGIN_EXPORT LPCWSTR ConfigVariantName(void* data, const int argc, const WCHAR* argv[])
{
	if (argc > 0)
	{
		Measure* measure = (Measure*)data;
		HWND config = GetLoadedConfig(argv[0]);
		if (!config) return L"";

		std::wstring title = GetWindowTitle(config);
		size_t last = title.find_last_of(L"\\");
		// Title is not set correctly, so we can't retrieve the skin. The config is still loaded.
		if (last == std::wstring::npos && last + 1 < title.size()) return L"";
		measure->m_SectionVariable = title.substr(last + 1);
		return measure->m_SectionVariable.c_str();

	}
	return nullptr;
}

PLUGIN_EXPORT LPCWSTR LoadedCount(void* data, const int argc, const WCHAR* argv[])
{
	Measure* measure = (Measure*)data;
	measure->m_SectionVariable = std::to_wstring(LoadedSkinCount());
	return measure->m_SectionVariable.c_str();
}
Source available at https://github.com/jsmorley/ConfigActive

Edit: Version 2.1 is changed to ignore extra back-slashes in the ConfigName parameter, to allow you to pass "escaped" versions that you might also want to use in Lua pattern matching. So either Illustro\System or Illustro\\System will work.
You do not have the required permissions to view the files attached to this post.
User avatar
raiguard
Posts: 493
Joined: June 25th, 2015, 7:02 pm
Location: The Sky, USA

Re: ConfigActive 2.0

raiguard » June 22nd, 2018, 9:13 pm

It works great, for the most part. Just a small problem though:

Would it be possible to make the config name compatible with double backslashes? Everything else in Rainmeter knows how to deal with double backslashes, but this plugin can't (it will always return the config as being inactive). This is a huge pain because I need to use two separate variables/strings for the config, one to pass to the LUA as a string (with double backslashes), and the other to give to the IsActive() or ConfigVariantName() functions (with single backslashes). It would be a lot nicer if I could just use a single config variable with double backslashes, and have the plugin able to handle that without issues.

I can, and have, already worked around this, but I figured I may as well ask anyway.

For instance, LeftMouseUpAction=[!CommandMeasure MeasureLoadSkinScript "ToggleSkin{configName='[#toggledSkinConfig]', configState='[&MeasureConfigActive:IsActive([#toggledSkinConfig])]'}"] doesn't work, I'd need to use two separate variables...
”We are pretty sure that r2922 resolves the regression in resolution caused by a reversion to a revision.” - jsmorley, 2017
User avatar
jsmorley
Developer
Posts: 18445
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: ConfigActive 2.0

jsmorley » June 22nd, 2018, 9:23 pm

raiguard wrote:It works great, for the most part. Just a small problem though:

Would it be possible to make the config name compatible with double backslashes? Everything else in Rainmeter knows how to deal with double backslashes, but this plugin can't (it will always return the config as being inactive). This is a huge pain because I need to use two separate variables/strings for the config, one to pass to the LUA as a string (with double backslashes), and the other to give to the IsActive() or ConfigVariantName() functions (with single backslashes). It would be a lot nicer if I could just use a single config variable with double backslashes, and have the plugin able to handle that without issues.

I can, and have, already worked around this, but I figured I may as well ask anyway.

For instance, LeftMouseUpAction=[!CommandMeasure MeasureLoadSkinScript "ToggleSkin{configName='[#toggledSkinConfig]', configState='[&MeasureConfigActive:IsActive([#toggledSkinConfig])]'}"] doesn't work, I'd need to use two separate variables...
Why not just replace the single backslashes with double backslashes in the Lua before you apply any pattern matching to them? Feels weird to me to define the ConfigName as Illustro\\System all over the place, just to avoid dealing with the reserved characters in pattern matching. In effect you are asking us to replace the double backslashes with single backslashes in the plugin code, which is what I'm suggesting you do, in reverse, in the Lua code.
User avatar
raiguard
Posts: 493
Joined: June 25th, 2015, 7:02 pm
Location: The Sky, USA

Re: ConfigActive 2.0

raiguard » June 22nd, 2018, 9:31 pm

jsmorley wrote:Why not just replace the single backslashes with double backslashes in the Lua before you apply any pattern matching to them? Feels weird to me to define the ConfigName as Illustro\\System all over the place, just to avoid dealing with the reserved characters in pattern matching.
Wait, you can do that? From my experience, the instant that the LUA gets the string from the function argument the single backslash disappears. How would you go about replacing it before that happens?
”We are pretty sure that r2922 resolves the regression in resolution caused by a reversion to a revision.” - jsmorley, 2017
User avatar
jsmorley
Developer
Posts: 18445
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: ConfigActive 2.0

jsmorley » June 22nd, 2018, 9:42 pm

raiguard wrote:Wait, you can do that? From my experience, the instant that the LUA gets the string from the function argument the single backslash disappears. How would you go about replacing it before that happens?

Code: Select all

Text=[Lua:TestFunc('C:\MyFolder\MySubfolder\MyFile.txt')]

Code: Select all

function TestFunc(inArg)

	newArg = string.gsub(inArg, '\\', '\\\\')
	-- C:\\MyFolder\\MySubfolder\\MyFile.txt
	oldArg = string.gsub(newArg, '\\\\', '\\')
	-- C:\MyFolder\MySubfolder\MyFile.txt
		
end
Basically, Lua is just fine with a string with the escape "\" character in it, unless you try to use it in any context that has a "pattern matching" component to it, or otherwise reacts to "escapes", which by the way string.gsub() itself does, as you can see.
User avatar
raiguard
Posts: 493
Joined: June 25th, 2015, 7:02 pm
Location: The Sky, USA

Re: ConfigActive 2.0

raiguard » June 22nd, 2018, 10:09 pm

That's odd. I've tried that before with no success. I went ahead and made my own version from scratch that is similar to your example, and it still didn't work.

Code: Select all

LeftMouseUpAction=[!CommandMeasure MeasureScript "Test('LoadSkin\ToggledSkin')"]

Code: Select all

function Test(input)

	local newInput = input:gsub('\\', '\\\\')
	local oldInput = newInput:gsub('\\\\', '\\')
	print(input, newInput, oldInput)

end
DBUG (16:07:38.398) : LoadSkinToggledSkin LoadSkinToggledSkin LoadSkinToggledSkin

Am I missing something important?

EDIT: And yes, I tried using the standard string.gsub syntax too, it didn't work either.
”We are pretty sure that r2922 resolves the regression in resolution caused by a reversion to a revision.” - jsmorley, 2017
User avatar
jsmorley
Developer
Posts: 18445
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: ConfigActive 2.0

jsmorley » June 22nd, 2018, 10:17 pm

Huh. Must be something different when passing it using !CommandMeasure opposed to passing it as an inline function call.

I'm not quite sure why it is reacting that way.
User avatar
raiguard
Posts: 493
Joined: June 25th, 2015, 7:02 pm
Location: The Sky, USA

Re: ConfigActive 2.0

raiguard » June 22nd, 2018, 10:20 pm

jsmorley wrote:Huh. Must be something different when passing it using !CommandMeasure opposed to passing it as an inline function call.

I'm not quite sure why it is reacting that way.
You're actually right. When I changed it to inline LUA, it started working...

EDIT:

Code: Select all

Text=[&MeasureScript:Test('LoadSkin\ToggledSkin')]
LeftMouseUpAction=[!CommandMeasure MeasureScript Test('LoadSkin\ToggledSkin')]
It'll return the correct values on every skin update. But when I click it, they're all missing the slashes.

Code: Select all

DBUG (16:24:43.904) : LoadSkin\ToggledSkin    LoadSkin\\ToggledSkin    LoadSkin\ToggledSkin
DBUG (16:24:44.538) : LoadSkinToggledSkin    LoadSkinToggledSkin    LoadSkinToggledSkin
”We are pretty sure that r2922 resolves the regression in resolution caused by a reversion to a revision.” - jsmorley, 2017
User avatar
jsmorley
Developer
Posts: 18445
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: ConfigActive 2.0

jsmorley » June 22nd, 2018, 10:24 pm

raiguard wrote:You're actually right. When I changed it to inline LUA, it started working...
Yeah, I'm not sure at all what is going on, or if it can be fixed if we find it, but we will poke around in it. Trouble is, Inline Lua is not always the best choice when what you want is to execute some "action". Using !CommandMeasure on actions has its place.
User avatar
jsmorley
Developer
Posts: 18445
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: ConfigActive 2.0

jsmorley » June 22nd, 2018, 10:29 pm

LeftMouseUpAction=[&MeasureScript:Test('LoadSkin\ToggledSkin')]

;-)

LoadSkin\ToggledSkin LoadSkin\\ToggledSkin LoadSkin\ToggledSkin

Inline Lua is intended to "return" some value to the section variable, but that doesn't mean you have to use the value it returns... It actually doesn't have to return anything at all. it can just "do stuff".

Code: Select all

function Test(input)

   local newInput = input:gsub('\\', '\\\\')
   local oldInput = newInput:gsub('\\\\', '\\')
   print(input, newInput, oldInput)

end