mirror of
https://github.com/chibicitiberiu/rainmeter-studio.git
synced 2024-02-24 04:33:31 +00:00
934 lines
23 KiB
C++
934 lines
23 KiB
C++
/*
|
|
Copyright (C) 2013 Rainmeter Team
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "StdAfx.h"
|
|
#include "../Common/PathUtil.h"
|
|
#include "CommandHandler.h"
|
|
#include "ConfigParser.h"
|
|
#include "Measure.h"
|
|
#include "Logger.h"
|
|
#include "Rainmeter.h"
|
|
#include "System.h"
|
|
#include "resource.h"
|
|
|
|
namespace {
|
|
|
|
typedef void (* BangHandlerFunc)(std::vector<std::wstring>& args, MeterWindow* skin);
|
|
|
|
struct BangInfo
|
|
{
|
|
Bang bang;
|
|
WCHAR* name;
|
|
uint8_t argCount;
|
|
};
|
|
|
|
struct CustomBangInfo
|
|
{
|
|
Bang bang;
|
|
WCHAR* name;
|
|
BangHandlerFunc handlerFunc;
|
|
};
|
|
|
|
// Bangs that are to be handled with DoBang().
|
|
const BangInfo s_Bangs[] =
|
|
{
|
|
{ Bang::Refresh, L"Refresh", 0 },
|
|
{ Bang::Redraw, L"Redraw", 0 },
|
|
{ Bang::Update, L"Update", 0 },
|
|
{ Bang::Hide, L"Hide", 0 },
|
|
{ Bang::Show, L"Show", 0 },
|
|
{ Bang::Toggle, L"Toggle", 0 },
|
|
{ Bang::HideFade, L"HideFade", 0 },
|
|
{ Bang::ShowFade, L"ShowFade", 0 },
|
|
{ Bang::ToggleFade, L"ToggleFade", 0 },
|
|
{ Bang::HideMeter, L"HideMeter", 1 },
|
|
{ Bang::ShowMeter, L"ShowMeter", 1 },
|
|
{ Bang::ToggleMeter, L"ToggleMeter", 1 },
|
|
{ Bang::MoveMeter, L"MoveMeter", 3 },
|
|
{ Bang::UpdateMeter, L"UpdateMeter", 1 },
|
|
{ Bang::DisableMeasure, L"DisableMeasure", 1 },
|
|
{ Bang::EnableMeasure, L"EnableMeasure", 1 },
|
|
{ Bang::ToggleMeasure, L"ToggleMeasure", 1 },
|
|
{ Bang::PauseMeasure, L"PauseMeasure", 1 },
|
|
{ Bang::UnpauseMeasure, L"UnpauseMeasure", 1 },
|
|
{ Bang::TogglePauseMeasure, L"TogglePauseMeasure", 1 },
|
|
{ Bang::UpdateMeasure, L"UpdateMeasure", 1 },
|
|
{ Bang::CommandMeasure, L"CommandMeasure", 2 },
|
|
{ Bang::PluginBang, L"PluginBang", 1 },
|
|
{ Bang::ShowBlur, L"ShowBlur", 0 },
|
|
{ Bang::HideBlur, L"HideBlur", 0 },
|
|
{ Bang::ToggleBlur, L"ToggleBlur", 0 },
|
|
{ Bang::AddBlur, L"AddBlur", 1 },
|
|
{ Bang::RemoveBlur, L"RemoveBlur", 1 },
|
|
{ Bang::Move, L"Move", 2 },
|
|
{ Bang::ZPos, L"ZPos", 1 },
|
|
{ Bang::ZPos, L"ChangeZPos", 1 }, // For backwards compatibility.
|
|
{ Bang::ChangeZPos, L"ChangeZPos", 1 },
|
|
{ Bang::ClickThrough, L"ClickThrough", 1 },
|
|
{ Bang::Draggable, L"Draggable", 1 },
|
|
{ Bang::SnapEdges, L"SnapEdges", 1 },
|
|
{ Bang::KeepOnScreen, L"KeepOnScreen", 1 },
|
|
{ Bang::SetTransparency, L"SetTransparency", 1 },
|
|
{ Bang::SetVariable, L"SetVariable", 2 },
|
|
{ Bang::SetOption, L"SetOption", 3 },
|
|
{ Bang::SetOptionGroup, L"SetOptionGroup", 3 },
|
|
{ Bang::HideMeterGroup, L"HideMeterGroup", 1 },
|
|
{ Bang::ShowMeterGroup, L"ShowMeterGroup", 1 },
|
|
{ Bang::ToggleMeterGroup, L"ToggleMeterGroup", 1 },
|
|
{ Bang::UpdateMeterGroup, L"UpdateMeterGroup", 1 },
|
|
{ Bang::DisableMeasureGroup, L"DisableMeasureGroup", 1 },
|
|
{ Bang::EnableMeasureGroup, L"EnableMeasureGroup", 1 },
|
|
{ Bang::ToggleMeasureGroup, L"ToggleMeasureGroup", 1 },
|
|
{ Bang::PauseMeasureGroup, L"PauseMeasureGroup", 1 },
|
|
{ Bang::UnpauseMeasureGroup, L"UnpauseMeasureGroup", 1 },
|
|
{ Bang::TogglePauseMeasureGroup, L"TogglePauseMeasureGroup", 1 },
|
|
{ Bang::UpdateMeasureGroup, L"UpdateMeasureGroup", 1 },
|
|
{ Bang::SkinCustomMenu, L"SkinCustomMenu", 0 }
|
|
};
|
|
|
|
// Bangs that are to be handled with DoGroupBang().
|
|
// TODO: Better handling of Bang-id
|
|
const BangInfo s_GroupBangs[] =
|
|
{
|
|
{ Bang::Refresh, L"RefreshGroup", 0 },
|
|
{ Bang::Update, L"UpdateGroup", 0 },
|
|
{ Bang::Redraw, L"RedrawGroup", 0 },
|
|
{ Bang::Hide, L"HideGroup", 0 },
|
|
{ Bang::Show, L"ShowGroup", 0 },
|
|
{ Bang::Toggle, L"ToggleGroup", 0 },
|
|
{ Bang::HideFade, L"HideFadeGroup", 0 },
|
|
{ Bang::ShowFade, L"ShowFadeGroup", 0 },
|
|
{ Bang::ToggleFade, L"ToggleFadeGroup", 0 },
|
|
{ Bang::ZPos, L"ZPosGroup", 1 },
|
|
{ Bang::ClickThrough, L"ClickThroughGroup", 1 },
|
|
{ Bang::Draggable, L"DraggableGroup", 1 },
|
|
{ Bang::SnapEdges, L"SnapEdgesGroup", 1 },
|
|
{ Bang::KeepOnScreen, L"KeepOnScreenGroup", 1 },
|
|
{ Bang::SetTransparency, L"SetTransparencyGroup", 1 },
|
|
{ Bang::SetVariable, L"SetVariableGroup", 2 }
|
|
};
|
|
|
|
// Bangs that are to be handled using a custom handler function.
|
|
const CustomBangInfo s_CustomBangs[] =
|
|
{
|
|
{ Bang::ActivateConfig, L"ActivateConfig", CommandHandler::DoActivateSkinBang },
|
|
{ Bang::DeactivateConfig, L"DeactivateConfig", CommandHandler::DoDeactivateSkinBang },
|
|
{ Bang::ToggleConfig, L"ToggleConfig", CommandHandler::DoToggleSkinBang },
|
|
{ Bang::DeactivateConfigGroup, L"DeactivateConfigGroup", CommandHandler::DoDeactivateSkinGroupBang },
|
|
{ Bang::WriteKeyValue, L"WriteKeyValue", CommandHandler::DoWriteKeyValueBang },
|
|
{ Bang::LoadLayout, L"LoadLayout", CommandHandler::DoLoadLayoutBang },
|
|
{ Bang::SetClip, L"SetClip", CommandHandler::DoSetClipBang },
|
|
{ Bang::SetWallpaper, L"SetWallpaper", CommandHandler::DoSetWallpaperBang },
|
|
{ Bang::About, L"About", CommandHandler::DoAboutBang },
|
|
{ Bang::Manage, L"Manage", CommandHandler::DoManageBang },
|
|
{ Bang::SkinMenu, L"SkinMenu", CommandHandler::DoSkinMenuBang },
|
|
{ Bang::TrayMenu, L"TrayMenu", CommandHandler::DoTrayMenuBang },
|
|
{ Bang::ResetStats, L"ResetStats", CommandHandler::DoResetStatsBang },
|
|
{ Bang::Log, L"Log", CommandHandler::DoLogBang },
|
|
{ Bang::RefreshApp, L"RefreshApp", CommandHandler::DoRefreshApp },
|
|
{ Bang::Quit, L"Quit", CommandHandler::DoQuitBang },
|
|
{ Bang::LsBoxHook, L"LsBoxHook", CommandHandler::DoLsBoxHookBang }
|
|
};
|
|
|
|
void DoBang(const BangInfo& bangInfo, std::vector<std::wstring>& args, MeterWindow* skin)
|
|
{
|
|
const size_t argsCount = args.size();
|
|
if (argsCount >= bangInfo.argCount)
|
|
{
|
|
if (argsCount == bangInfo.argCount && skin)
|
|
{
|
|
skin->DoBang(bangInfo.bang, args);
|
|
}
|
|
else
|
|
{
|
|
// Use the specified window instead of skin parameter.
|
|
if (argsCount > bangInfo.argCount)
|
|
{
|
|
const std::wstring& folderPath = args[bangInfo.argCount];
|
|
if (!folderPath.empty() && (folderPath.length() != 1 || folderPath[0] != L'*'))
|
|
{
|
|
MeterWindow* meterWindow = GetRainmeter().GetMeterWindow(folderPath);
|
|
if (meterWindow)
|
|
{
|
|
meterWindow->DoBang(bangInfo.bang, args);
|
|
}
|
|
else
|
|
{
|
|
LogErrorF(skin, L"!%s: Skin \"%s\" not found", bangInfo.name, folderPath.c_str());
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// No skin defined -> apply to all.
|
|
for (const auto& ip : GetRainmeter().GetAllMeterWindows())
|
|
{
|
|
ip.second->DoBang(bangInfo.bang, args);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// For backwards compatibility.
|
|
if (bangInfo.bang == Bang::CommandMeasure && argsCount >= 1)
|
|
{
|
|
std::wstring& firstArg = args[0];
|
|
std::wstring::size_type pos = firstArg.find_first_of(L' ');
|
|
if (pos != std::wstring::npos)
|
|
{
|
|
std::wstring newArg = firstArg.substr(0, pos);
|
|
firstArg.erase(0, pos + 1);
|
|
args.insert(args.begin(), newArg);
|
|
|
|
LogWarningF(skin, L"!%s: Two parameters required, only one given", bangInfo.name);
|
|
DoBang(bangInfo, args, skin);
|
|
return;
|
|
}
|
|
}
|
|
|
|
LogErrorF(skin, L"!%s: Incorrect number of arguments", bangInfo.name);
|
|
}
|
|
}
|
|
|
|
void DoGroupBang(const BangInfo& bangInfo, std::vector<std::wstring>& args, MeterWindow* skin)
|
|
{
|
|
if (args.size() > bangInfo.argCount)
|
|
{
|
|
std::multimap<int, MeterWindow*> windows;
|
|
GetRainmeter().GetMeterWindowsByLoadOrder(windows, args[bangInfo.argCount]);
|
|
|
|
// Remove extra parameters (including group).
|
|
args.resize(bangInfo.argCount);
|
|
|
|
for (const auto& ip : windows)
|
|
{
|
|
DoBang(bangInfo, args, ip.second);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogErrorF(skin, L"!%s: Incorrect number of arguments", bangInfo.name);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/*
|
|
** Parses and executes the given command.
|
|
**
|
|
*/
|
|
void CommandHandler::ExecuteCommand(const WCHAR* command, MeterWindow* skin, bool multi)
|
|
{
|
|
if (command[0] == L'!') // Bang
|
|
{
|
|
++command; // Skip "!"
|
|
|
|
if (_wcsnicmp(L"Execute", command, 7) == 0)
|
|
{
|
|
command += 7;
|
|
command = wcschr(command, L'[');
|
|
if (!command) return;
|
|
}
|
|
else
|
|
{
|
|
if (_wcsnicmp(command, L"Rainmeter", 9) == 0)
|
|
{
|
|
// Skip "Rainmeter" for backwards compatibility
|
|
command += 9;
|
|
}
|
|
|
|
std::wstring bang;
|
|
std::vector<std::wstring> args;
|
|
|
|
// Find the first space
|
|
const WCHAR* pos = wcschr(command, L' ');
|
|
if (pos)
|
|
{
|
|
bang.assign(command, 0, pos - command);
|
|
args = ParseString(pos + 1, skin ? &skin->GetParser() : nullptr);
|
|
}
|
|
else
|
|
{
|
|
bang = command;
|
|
}
|
|
|
|
ExecuteBang(bang.c_str(), args, skin);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (multi && command[0] == L'[') // Multi-bang
|
|
{
|
|
std::wstring bangs = command;
|
|
std::wstring::size_type start = std::wstring::npos;
|
|
int count = 0;
|
|
for (size_t i = 0, isize = bangs.size(); i < isize; ++i)
|
|
{
|
|
if (bangs[i] == L'[')
|
|
{
|
|
if (count == 0)
|
|
{
|
|
start = i;
|
|
}
|
|
++count;
|
|
}
|
|
else if (bangs[i] == L']')
|
|
{
|
|
--count;
|
|
|
|
if (count == 0 && start != std::wstring::npos)
|
|
{
|
|
// Change ] to nullptr
|
|
bangs[i] = L'\0';
|
|
|
|
// Skip whitespace
|
|
start = bangs.find_first_not_of(L" \t\r\n", start + 1, 4);
|
|
|
|
ExecuteCommand(bangs.c_str() + start, skin, false);
|
|
}
|
|
}
|
|
else if (bangs[i] == L'"' && isize > (i + 2) && bangs[i + 1] == L'"' && bangs[i + 2] == L'"')
|
|
{
|
|
i += 3;
|
|
|
|
std::wstring::size_type pos = bangs.find(L"\"\"\"", i);
|
|
if (pos != std::wstring::npos)
|
|
{
|
|
i = pos + 2; // Skip "", loop will skip last "
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Check for built-ins
|
|
if (_wcsnicmp(L"PLAY", command, 4) == 0)
|
|
{
|
|
if (command[4] == L' ' || // PLAY
|
|
_wcsnicmp(L"LOOP ", &command[4], 5) == 0) // PLAYLOOP
|
|
{
|
|
command += 4; // Skip PLAY
|
|
|
|
DWORD flags = SND_FILENAME | SND_ASYNC;
|
|
|
|
if (command[0] != L' ')
|
|
{
|
|
flags |= SND_LOOP | SND_NODEFAULT;
|
|
command += 4; // Skip LOOP
|
|
}
|
|
|
|
++command; // Skip the space
|
|
if (command[0] != L'\0')
|
|
{
|
|
std::wstring sound = command;
|
|
|
|
// Strip the quotes
|
|
std::wstring::size_type len = sound.length();
|
|
if (len >= 2 && sound[0] == L'"' && sound[len - 1] == L'"')
|
|
{
|
|
len -= 2;
|
|
sound.assign(sound, 1, len);
|
|
}
|
|
|
|
if (skin)
|
|
{
|
|
skin->GetParser().ReplaceMeasures(sound);
|
|
skin->MakePathAbsolute(sound);
|
|
}
|
|
|
|
PlaySound(sound.c_str(), nullptr, flags);
|
|
}
|
|
return;
|
|
}
|
|
else if (_wcsnicmp(L"STOP", &command[4], 4) == 0) // PLAYSTOP
|
|
{
|
|
PlaySound(nullptr, nullptr, SND_PURGE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Run command
|
|
std::wstring tmpSz = command;
|
|
if (skin)
|
|
{
|
|
skin->GetParser().ReplaceMeasures(tmpSz);
|
|
}
|
|
RunCommand(tmpSz);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Runs the given bang.
|
|
**
|
|
*/
|
|
void CommandHandler::ExecuteBang(const WCHAR* name, std::vector<std::wstring>& args, MeterWindow* skin)
|
|
{
|
|
for (const auto& bangInfo : s_Bangs)
|
|
{
|
|
if (_wcsicmp(bangInfo.name, name) == 0)
|
|
{
|
|
DoBang(bangInfo, args, skin);
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (const auto& bangInfo : s_GroupBangs)
|
|
{
|
|
if (_wcsicmp(bangInfo.name, name) == 0)
|
|
{
|
|
DoGroupBang(bangInfo, args, skin);
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (const auto& bangInfo : s_CustomBangs)
|
|
{
|
|
if (_wcsicmp(bangInfo.name, name) == 0)
|
|
{
|
|
bangInfo.handlerFunc(args, skin);
|
|
return;
|
|
}
|
|
}
|
|
|
|
LogErrorF(skin, L"Invalid bang: !%s", name);
|
|
}
|
|
|
|
/*
|
|
** Parses and runs the given command.
|
|
**
|
|
*/
|
|
void CommandHandler::RunCommand(std::wstring command)
|
|
{
|
|
std::wstring args;
|
|
|
|
size_t notwhite = command.find_first_not_of(L" \t\r\n");
|
|
command.erase(0, notwhite);
|
|
|
|
size_t quotePos = command.find(L'"');
|
|
if (quotePos == 0)
|
|
{
|
|
size_t quotePos2 = command.find(L'"', quotePos + 1);
|
|
if (quotePos2 != std::wstring::npos)
|
|
{
|
|
args.assign(command, quotePos2 + 1, command.length() - (quotePos2 + 1));
|
|
command.assign(command, quotePos + 1, quotePos2 - quotePos - 1);
|
|
}
|
|
else
|
|
{
|
|
command.erase(0, 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
size_t spacePos = command.find(L' ');
|
|
if (spacePos != std::wstring::npos)
|
|
{
|
|
args.assign(command, spacePos + 1, command.length() - (spacePos + 1));
|
|
command.erase(spacePos);
|
|
}
|
|
}
|
|
|
|
if (!command.empty())
|
|
{
|
|
RunFile(command.c_str(), args.c_str());
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Runs a file with the given arguments.
|
|
**
|
|
*/
|
|
void CommandHandler::RunFile(const WCHAR* file, const WCHAR* args)
|
|
{
|
|
SHELLEXECUTEINFO si = {sizeof(SHELLEXECUTEINFO)};
|
|
si.lpVerb = L"open";
|
|
si.lpFile = file;
|
|
si.nShow = SW_SHOWNORMAL;
|
|
|
|
DWORD type = GetFileAttributes(si.lpFile);
|
|
if (type & FILE_ATTRIBUTE_DIRECTORY && type != 0xFFFFFFFF)
|
|
{
|
|
ShellExecute(si.hwnd, si.lpVerb, si.lpFile, nullptr, nullptr, si.nShow);
|
|
}
|
|
else
|
|
{
|
|
std::wstring dir = PathUtil::GetFolderFromFilePath(file);
|
|
si.lpDirectory = dir.c_str();
|
|
si.lpParameters = args;
|
|
si.fMask = SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI;
|
|
ShellExecuteEx(&si);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Splits strings into parts.
|
|
**
|
|
*/
|
|
std::vector<std::wstring> CommandHandler::ParseString(LPCTSTR str, ConfigParser* parser)
|
|
{
|
|
std::vector<std::wstring> result;
|
|
|
|
if (str)
|
|
{
|
|
std::wstring arg = str;
|
|
|
|
// Split the argument between first space.
|
|
// Or if string is in quotes, the after the second quote.
|
|
|
|
auto addResult = [&](std::wstring& string, bool stripQuotes)
|
|
{
|
|
if (stripQuotes)
|
|
{
|
|
size_t pos = 0;
|
|
do
|
|
{
|
|
pos = string.find(L'"', pos);
|
|
if (pos != std::wstring::npos)
|
|
{
|
|
string.erase(pos, 1);
|
|
}
|
|
}
|
|
while (pos != std::wstring::npos);
|
|
}
|
|
|
|
if (parser)
|
|
{
|
|
parser->ReplaceMeasures(string);
|
|
}
|
|
|
|
result.push_back(string);
|
|
};
|
|
|
|
size_t pos;
|
|
std::wstring newStr;
|
|
while ((pos = arg.find_first_not_of(L' ')) != std::wstring::npos)
|
|
{
|
|
size_t extra = 1;
|
|
if (arg[pos] == L'"')
|
|
{
|
|
if (arg.size() > (pos + 2) &&
|
|
arg[pos + 1] == L'"' && arg[pos + 2] == L'"')
|
|
{
|
|
// Eat found quotes and finding ending """
|
|
arg.erase(0, pos + 3);
|
|
|
|
extra = 4;
|
|
if ((pos = arg.find(L"\"\"\" ")) == std::wstring::npos)
|
|
{
|
|
extra = 3;
|
|
pos = arg.rfind(L"\"\"\""); // search backward
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Eat found quote and find ending quote
|
|
arg.erase(0, pos + 1);
|
|
pos = arg.find_first_of(L'"');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pos > 0)
|
|
{
|
|
// Eat everything until non-space (and non-quote) char
|
|
arg.erase(0, pos);
|
|
}
|
|
|
|
// Find the second quote
|
|
pos = arg.find_first_of(L' ');
|
|
}
|
|
|
|
if (pos != std::wstring::npos)
|
|
{
|
|
newStr.assign(arg, 0, pos);
|
|
arg.erase(0, pos + extra);
|
|
|
|
addResult(newStr, extra == 1);
|
|
}
|
|
else // quote or space not found
|
|
{
|
|
addResult(arg, extra == 1);
|
|
arg.clear();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!arg.empty() && result.empty())
|
|
{
|
|
addResult(arg, true);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void CommandHandler::DoActivateSkinBang(std::vector<std::wstring>& args, MeterWindow* skin)
|
|
{
|
|
if (args.size() == 1)
|
|
{
|
|
if (GetRainmeter().ActivateSkin(args[0])) return;
|
|
}
|
|
else if (args.size() > 1)
|
|
{
|
|
if (GetRainmeter().ActivateSkin(args[0], args[1])) return;
|
|
}
|
|
|
|
LogErrorF(skin, L"!ActivateConfig: Invalid parameters");
|
|
}
|
|
|
|
void CommandHandler::DoDeactivateSkinBang(std::vector<std::wstring>& args, MeterWindow* skin)
|
|
{
|
|
if (!args.empty())
|
|
{
|
|
skin = GetRainmeter().GetMeterWindow(args[0]);
|
|
if (!skin)
|
|
{
|
|
LogWarningF(L"!DeactivateConfig: \"%s\" not active", args[0].c_str());
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (skin)
|
|
{
|
|
GetRainmeter().DeactivateSkin(skin, -1);
|
|
}
|
|
else
|
|
{
|
|
LogError(L"!DeactivateConfig: Invalid parameters");
|
|
}
|
|
}
|
|
|
|
void CommandHandler::DoToggleSkinBang(std::vector<std::wstring>& args, MeterWindow* skin)
|
|
{
|
|
if (args.size() >= 2)
|
|
{
|
|
MeterWindow* meterWindow = GetRainmeter().GetMeterWindow(args[0]);
|
|
if (meterWindow)
|
|
{
|
|
GetRainmeter().DeactivateSkin(meterWindow, -1);
|
|
return;
|
|
}
|
|
|
|
// If the skin wasn't active, activate it.
|
|
DoActivateSkinBang(args, nullptr);
|
|
}
|
|
else
|
|
{
|
|
LogErrorF(skin, L"!ToggleConfig: Invalid parameters");
|
|
}
|
|
}
|
|
|
|
void CommandHandler::DoDeactivateSkinGroupBang(std::vector<std::wstring>& args, MeterWindow* skin)
|
|
{
|
|
if (!args.empty())
|
|
{
|
|
std::multimap<int, MeterWindow*> windows;
|
|
GetRainmeter().GetMeterWindowsByLoadOrder(windows, args[0]);
|
|
for (const auto& ip : windows)
|
|
{
|
|
GetRainmeter().DeactivateSkin(ip.second, -1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogErrorF(skin, L"!DeactivateConfigGroup: Invalid parameters");
|
|
}
|
|
}
|
|
|
|
void CommandHandler::DoLoadLayoutBang(std::vector<std::wstring>& args, MeterWindow* skin)
|
|
{
|
|
if (args.size() == 1)
|
|
{
|
|
if (skin)
|
|
{
|
|
// Delay to avoid loading theme in the middle of an update.
|
|
std::wstring command = L"!LoadLayout \"";
|
|
command += args[0];
|
|
command += L'"';
|
|
GetRainmeter().DelayedExecuteCommand(command.c_str());
|
|
}
|
|
else
|
|
{
|
|
// Not called from a skin (or called with delay).
|
|
GetRainmeter().LoadLayout(args[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CommandHandler::DoSetClipBang(std::vector<std::wstring>& args, MeterWindow* skin)
|
|
{
|
|
if (!args.empty())
|
|
{
|
|
System::SetClipboardText(args[0]);
|
|
}
|
|
else
|
|
{
|
|
LogErrorF(skin, L"!SetClip: Invalid parameter");
|
|
}
|
|
}
|
|
|
|
void CommandHandler::DoSetWallpaperBang(std::vector<std::wstring>& args, MeterWindow* skin)
|
|
{
|
|
const size_t argsSize = args.size();
|
|
if (argsSize >= 1 && argsSize <= 2)
|
|
{
|
|
std::wstring& file = args[0];
|
|
const std::wstring& style = (argsSize == 2) ? args[1] : L"";
|
|
|
|
if (skin)
|
|
{
|
|
skin->MakePathAbsolute(file);
|
|
}
|
|
|
|
System::SetWallpaper(file, style);
|
|
}
|
|
else
|
|
{
|
|
LogErrorF(skin, L"!SetWallpaper: Invalid parameters");
|
|
}
|
|
}
|
|
|
|
void CommandHandler::DoAboutBang(std::vector<std::wstring>& args, MeterWindow* meterWindow)
|
|
{
|
|
LogErrorF(L"!About: about bang is unsupported.");
|
|
}
|
|
|
|
void CommandHandler::DoManageBang(std::vector<std::wstring>& args, MeterWindow* meterWindow)
|
|
{
|
|
LogErrorF(L"!Manage: manage bang is unsupported.");
|
|
}
|
|
|
|
void CommandHandler::DoSkinMenuBang(std::vector<std::wstring>& args, MeterWindow* skin)
|
|
{
|
|
if (!args.empty())
|
|
{
|
|
skin = GetRainmeter().GetMeterWindow(args[0]);
|
|
if (!skin)
|
|
{
|
|
LogWarningF(L"!SkinMenu: \"%s\" not active", args[0].c_str());
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (skin)
|
|
{
|
|
POINT pos = System::GetCursorPosition();
|
|
GetRainmeter().ShowContextMenu(pos, skin);
|
|
}
|
|
else
|
|
{
|
|
LogError(L"!SkinMenu: Invalid parameter");
|
|
}
|
|
}
|
|
|
|
void CommandHandler::DoTrayMenuBang(std::vector<std::wstring>& args, MeterWindow* skin)
|
|
{
|
|
POINT pos = System::GetCursorPosition();
|
|
GetRainmeter().ShowContextMenu(pos, nullptr);
|
|
}
|
|
|
|
void CommandHandler::DoResetStatsBang(std::vector<std::wstring>& args, MeterWindow* meterWindow)
|
|
{
|
|
GetRainmeter().ResetStats();
|
|
}
|
|
|
|
void CommandHandler::DoWriteKeyValueBang(std::vector<std::wstring>& args, MeterWindow* skin)
|
|
{
|
|
if (args.size() == 3 && skin)
|
|
{
|
|
// Add the skin file path to the args
|
|
args.push_back(skin->GetFilePath());
|
|
}
|
|
else if (args.size() < 4)
|
|
{
|
|
LogErrorF(skin, L"!WriteKeyValue: Invalid parameters");
|
|
return;
|
|
}
|
|
|
|
std::wstring& strIniFile = args[3];
|
|
if (skin)
|
|
{
|
|
skin->MakePathAbsolute(strIniFile);
|
|
}
|
|
|
|
const WCHAR* iniFile = strIniFile.c_str();
|
|
|
|
if (strIniFile.find(L"..\\") != std::wstring::npos || strIniFile.find(L"../") != std::wstring::npos)
|
|
{
|
|
LogErrorF(skin, L"!WriteKeyValue: Illegal path: %s", iniFile);
|
|
return;
|
|
}
|
|
|
|
if (_wcsnicmp(iniFile, GetRainmeter().m_SkinPath.c_str(), GetRainmeter().m_SkinPath.size()) != 0 &&
|
|
_wcsnicmp(iniFile, GetRainmeter().m_SettingsPath.c_str(), GetRainmeter().m_SettingsPath.size()) != 0)
|
|
{
|
|
LogErrorF(skin, L"!WriteKeyValue: Illegal path: %s", iniFile);
|
|
return;
|
|
}
|
|
|
|
// Verify whether the file exists.
|
|
if (_waccess(iniFile, 0) == -1)
|
|
{
|
|
LogErrorF(skin, L"!WriteKeyValue: File not found: %s", iniFile);
|
|
return;
|
|
}
|
|
|
|
// Verify whether the file is read-only.
|
|
DWORD attr = GetFileAttributes(iniFile);
|
|
if (attr == -1 || (attr & FILE_ATTRIBUTE_READONLY))
|
|
{
|
|
LogWarningF(skin, L"!WriteKeyValue: File is read-only: %s", iniFile);
|
|
return;
|
|
}
|
|
|
|
// Avoid "IniFileMapping"
|
|
System::UpdateIniFileMappingList();
|
|
std::wstring strIniWrite = System::GetTemporaryFile(strIniFile);
|
|
if (strIniWrite.size() == 1 && strIniWrite[0] == L'?') // error occurred
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool temporary = !strIniWrite.empty();
|
|
|
|
if (temporary)
|
|
{
|
|
if (GetRainmeter().GetDebug())
|
|
{
|
|
LogDebugF(skin, L"!WriteKeyValue: Writing to: %s (Temp: %s)", iniFile, strIniWrite.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (GetRainmeter().GetDebug())
|
|
{
|
|
LogDebugF(skin, L"!WriteKeyValue: Writing to: %s", iniFile);
|
|
}
|
|
strIniWrite = strIniFile;
|
|
}
|
|
|
|
const WCHAR* iniWrite = strIniWrite.c_str();
|
|
const WCHAR* section = args[0].c_str();
|
|
const WCHAR* key = args[1].c_str();
|
|
const std::wstring& strValue = args[2];
|
|
|
|
bool formula = false;
|
|
BOOL write = 0;
|
|
|
|
if (skin)
|
|
{
|
|
double value;
|
|
formula = skin->GetParser().ParseFormula(strValue, &value);
|
|
if (formula)
|
|
{
|
|
WCHAR buffer[256];
|
|
int len = _snwprintf_s(buffer, _TRUNCATE, L"%.5f", value);
|
|
Measure::RemoveTrailingZero(buffer, len);
|
|
|
|
write = WritePrivateProfileString(section, key, buffer, iniWrite);
|
|
}
|
|
}
|
|
|
|
if (!formula)
|
|
{
|
|
write = WritePrivateProfileString(section, key, strValue.c_str(), iniWrite);
|
|
}
|
|
|
|
if (temporary)
|
|
{
|
|
if (write != 0)
|
|
{
|
|
WritePrivateProfileString(nullptr, nullptr, nullptr, iniWrite); // FLUSH
|
|
|
|
// Copy the file back.
|
|
if (!System::CopyFiles(strIniWrite, strIniFile))
|
|
{
|
|
LogErrorF(skin, L"!WriteKeyValue: Failed to copy temporary file to original filepath: %s (Temp: %s)", iniFile, iniWrite);
|
|
}
|
|
}
|
|
else // failed
|
|
{
|
|
LogErrorF(skin, L"!WriteKeyValue: Failed to write to: %s (Temp: %s)", iniFile, iniWrite);
|
|
}
|
|
|
|
// Remove the temporary file.
|
|
System::RemoveFile(strIniWrite);
|
|
}
|
|
else
|
|
{
|
|
if (write == 0) // failed
|
|
{
|
|
LogErrorF(skin, L"!WriteKeyValue: Failed to write to: %s", iniFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CommandHandler::DoLogBang(std::vector<std::wstring>& args, MeterWindow* skin)
|
|
{
|
|
if (!args.empty())
|
|
{
|
|
Logger::Level level = Logger::Level::Notice;
|
|
if (args.size() > 1)
|
|
{
|
|
const WCHAR* type = args[1].c_str();
|
|
if (_wcsicmp(type, L"ERROR") == 0)
|
|
{
|
|
level = Logger::Level::Error;
|
|
}
|
|
else if (_wcsicmp(type, L"WARNING") == 0)
|
|
{
|
|
level = Logger::Level::Warning;
|
|
}
|
|
else if (_wcsicmp(type, L"DEBUG") == 0)
|
|
{
|
|
level = Logger::Level::Debug;
|
|
}
|
|
else if (_wcsicmp(type, L"NOTICE") != 0)
|
|
{
|
|
LogErrorF(skin, L"!Log: Invalid type");
|
|
return;
|
|
}
|
|
}
|
|
|
|
std::wstring source;
|
|
if (skin)
|
|
{
|
|
source = skin->GetSkinPath();
|
|
}
|
|
|
|
GetLogger().Log(level, source.c_str(), args[0].c_str());
|
|
}
|
|
}
|
|
|
|
void CommandHandler::DoRefreshApp(std::vector<std::wstring>& args, MeterWindow* meterWindow)
|
|
{
|
|
// Refresh needs to be delayed since it crashes if done during Update().
|
|
PostMessage(GetRainmeter().m_Window, WM_RAINMETER_DELAYED_REFRESH_ALL, 0, 0);
|
|
}
|
|
|
|
void CommandHandler::DoQuitBang(std::vector<std::wstring>& args, MeterWindow* meterWindow)
|
|
{
|
|
// Quit needs to be delayed since it crashes if done during Update().
|
|
PostMessage(GetRainmeter().GetTrayWindow()->GetWindow(), WM_COMMAND, MAKEWPARAM(IDM_QUIT, 0), 0);
|
|
}
|
|
|
|
void CommandHandler::DoLsBoxHookBang(std::vector<std::wstring>& args, MeterWindow* meterWindow)
|
|
{
|
|
// Deprecated.
|
|
}
|