diff --git a/Library/CommandHandler.cpp b/Library/CommandHandler.cpp index 389fa69d..59789766 100644 --- a/Library/CommandHandler.cpp +++ b/Library/CommandHandler.cpp @@ -1,950 +1,950 @@ -/* - 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 "DialogAbout.h" -#include "DialogManage.h" -#include "Measure.h" -#include "Logger.h" -#include "Rainmeter.h" -#include "System.h" -#include "TrayWindow.h" -#include "resource.h" - -namespace { - -typedef void (* BangHandlerFunc)(std::vector& 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& 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 = Rainmeter::GetInstance().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 : Rainmeter::GetInstance().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& args, MeterWindow* skin) -{ - if (args.size() > bangInfo.argCount) - { - std::multimap windows; - Rainmeter::GetInstance().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 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& 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 CommandHandler::ParseString(LPCTSTR str, ConfigParser* parser) -{ - std::vector 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& args, MeterWindow* skin) -{ - if (args.size() == 1) - { - if (Rainmeter::GetInstance().ActivateSkin(args[0])) return; - } - else if (args.size() > 1) - { - if (Rainmeter::GetInstance().ActivateSkin(args[0], args[1])) return; - } - - LogErrorF(skin, L"!ActivateConfig: Invalid parameters"); -} - -void CommandHandler::DoDeactivateSkinBang(std::vector& args, MeterWindow* skin) -{ - if (!args.empty()) - { - skin = Rainmeter::GetInstance().GetMeterWindow(args[0]); - if (!skin) - { - LogWarningF(L"!DeactivateConfig: \"%s\" not active", args[0].c_str()); - return; - } - } - - if (skin) - { - Rainmeter::GetInstance().DeactivateSkin(skin, -1); - } - else - { - LogError(L"!DeactivateConfig: Invalid parameters"); - } -} - -void CommandHandler::DoToggleSkinBang(std::vector& args, MeterWindow* skin) -{ - if (args.size() >= 2) - { - MeterWindow* meterWindow = Rainmeter::GetInstance().GetMeterWindow(args[0]); - if (meterWindow) - { - Rainmeter::GetInstance().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& args, MeterWindow* skin) -{ - if (!args.empty()) - { - std::multimap windows; - Rainmeter::GetInstance().GetMeterWindowsByLoadOrder(windows, args[0]); - for (const auto& ip : windows) - { - Rainmeter::GetInstance().DeactivateSkin(ip.second, -1); - } - } - else - { - LogErrorF(skin, L"!DeactivateConfigGroup: Invalid parameters"); - } -} - -void CommandHandler::DoLoadLayoutBang(std::vector& 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'"'; - Rainmeter::GetInstance().DelayedExecuteCommand(command.c_str()); - } - else - { - // Not called from a skin (or called with delay). - Rainmeter::GetInstance().LoadLayout(args[0]); - } - } -} - -void CommandHandler::DoSetClipBang(std::vector& args, MeterWindow* skin) -{ - if (!args.empty()) - { - System::SetClipboardText(args[0]); - } - else - { - LogErrorF(skin, L"!SetClip: Invalid parameter"); - } -} - -void CommandHandler::DoSetWallpaperBang(std::vector& 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& args, MeterWindow* meterWindow) -{ - DialogAbout::Open(args.empty() ? L"" : args[0].c_str()); -} - -void CommandHandler::DoManageBang(std::vector& args, MeterWindow* meterWindow) -{ - const size_t argsSize = args.size(); - if (argsSize >= 2 && argsSize <= 3) - { - DialogManage::Open(args[0].c_str(), - args[1].c_str(), - (argsSize == 3) ? args[2].c_str() : L""); - } - else if (argsSize <= 1) - { - DialogManage::Open(args.empty() ? L"" : args[0].c_str()); - } - else - { - LogErrorF(meterWindow, L"!Manage: Invalid parameters"); - } -} - -void CommandHandler::DoSkinMenuBang(std::vector& args, MeterWindow* skin) -{ - if (!args.empty()) - { - skin = Rainmeter::GetInstance().GetMeterWindow(args[0]); - if (!skin) - { - LogWarningF(L"!SkinMenu: \"%s\" not active", args[0].c_str()); - return; - } - } - - if (skin) - { - POINT pos = System::GetCursorPosition(); - Rainmeter::GetInstance().ShowContextMenu(pos, skin); - } - else - { - LogError(L"!SkinMenu: Invalid parameter"); - } -} - -void CommandHandler::DoTrayMenuBang(std::vector& args, MeterWindow* skin) -{ - POINT pos = System::GetCursorPosition(); - Rainmeter::GetInstance().ShowContextMenu(pos, nullptr); -} - -void CommandHandler::DoResetStatsBang(std::vector& args, MeterWindow* meterWindow) -{ - Rainmeter::GetInstance().ResetStats(); -} - -void CommandHandler::DoWriteKeyValueBang(std::vector& 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, Rainmeter::GetInstance().m_SkinPath.c_str(), Rainmeter::GetInstance().m_SkinPath.size()) != 0 && - _wcsnicmp(iniFile, Rainmeter::GetInstance().m_SettingsPath.c_str(), Rainmeter::GetInstance().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 (Rainmeter::GetInstance().GetDebug()) - { - LogDebugF(skin, L"!WriteKeyValue: Writing to: %s (Temp: %s)", iniFile, strIniWrite.c_str()); - } - } - else - { - if (Rainmeter::GetInstance().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& 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& args, MeterWindow* meterWindow) -{ - // Refresh needs to be delayed since it crashes if done during Update(). - PostMessage(Rainmeter::GetInstance().m_Window, WM_RAINMETER_DELAYED_REFRESH_ALL, 0, 0); -} - -void CommandHandler::DoQuitBang(std::vector& args, MeterWindow* meterWindow) -{ - // Quit needs to be delayed since it crashes if done during Update(). - PostMessage(Rainmeter::GetInstance().GetTrayWindow()->GetWindow(), WM_COMMAND, MAKEWPARAM(IDM_QUIT, 0), 0); -} - -void CommandHandler::DoLsBoxHookBang(std::vector& args, MeterWindow* meterWindow) -{ - // Deprecated. -} +/* + 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 "DialogAbout.h" +#include "DialogManage.h" +#include "Measure.h" +#include "Logger.h" +#include "Rainmeter.h" +#include "System.h" +#include "TrayWindow.h" +#include "resource.h" + +namespace { + +typedef void (* BangHandlerFunc)(std::vector& 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& 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& args, MeterWindow* skin) +{ + if (args.size() > bangInfo.argCount) + { + std::multimap 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 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& 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 CommandHandler::ParseString(LPCTSTR str, ConfigParser* parser) +{ + std::vector 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& 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& 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& 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& args, MeterWindow* skin) +{ + if (!args.empty()) + { + std::multimap 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& 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& args, MeterWindow* skin) +{ + if (!args.empty()) + { + System::SetClipboardText(args[0]); + } + else + { + LogErrorF(skin, L"!SetClip: Invalid parameter"); + } +} + +void CommandHandler::DoSetWallpaperBang(std::vector& 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& args, MeterWindow* meterWindow) +{ + DialogAbout::Open(args.empty() ? L"" : args[0].c_str()); +} + +void CommandHandler::DoManageBang(std::vector& args, MeterWindow* meterWindow) +{ + const size_t argsSize = args.size(); + if (argsSize >= 2 && argsSize <= 3) + { + DialogManage::Open(args[0].c_str(), + args[1].c_str(), + (argsSize == 3) ? args[2].c_str() : L""); + } + else if (argsSize <= 1) + { + DialogManage::Open(args.empty() ? L"" : args[0].c_str()); + } + else + { + LogErrorF(meterWindow, L"!Manage: Invalid parameters"); + } +} + +void CommandHandler::DoSkinMenuBang(std::vector& 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& args, MeterWindow* skin) +{ + POINT pos = System::GetCursorPosition(); + GetRainmeter().ShowContextMenu(pos, nullptr); +} + +void CommandHandler::DoResetStatsBang(std::vector& args, MeterWindow* meterWindow) +{ + GetRainmeter().ResetStats(); +} + +void CommandHandler::DoWriteKeyValueBang(std::vector& 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& 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& 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& 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& args, MeterWindow* meterWindow) +{ + // Deprecated. +} diff --git a/Library/ConfigParser.cpp b/Library/ConfigParser.cpp index b2122a4d..b8ff8271 100644 --- a/Library/ConfigParser.cpp +++ b/Library/ConfigParser.cpp @@ -1,1586 +1,1586 @@ -/* - Copyright (C) 2004 Kimmo Pekkola - - 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/MathParser.h" -#include "../Common/PathUtil.h" -#include "ConfigParser.h" -#include "Litestep.h" -#include "Rainmeter.h" -#include "System.h" -#include "Measure.h" -#include "Meter.h" -#include "resource.h" - -using namespace Gdiplus; - -std::unordered_map ConfigParser::c_MonitorVariables; - -/* -** The constructor -** -*/ -ConfigParser::ConfigParser() : - m_LastReplaced(false), - m_LastDefaultUsed(false), - m_LastValueDefined(false), - m_CurrentSection(), - m_MeterWindow() -{ -} - -/* -** The destructor -** -*/ -ConfigParser::~ConfigParser() -{ -} - -void ConfigParser::Initialize(const std::wstring& filename, MeterWindow* meterWindow, LPCTSTR skinSection, const std::wstring* resourcePath) -{ - m_MeterWindow = meterWindow; - - m_Measures.clear(); - m_Sections.clear(); - m_Values.clear(); - m_BuiltInVariables.clear(); - m_Variables.clear(); - - m_StyleTemplate.clear(); - m_LastReplaced = false; - m_LastDefaultUsed = false; - m_LastValueDefined = false; - - m_CurrentSection = nullptr; - m_SectionInsertPos = m_Sections.end(); - - // Set the built-in variables. Do this before the ini file is read so that the paths can be used with @include - SetBuiltInVariables(filename, resourcePath, meterWindow); - ResetMonitorVariables(meterWindow); - - System::UpdateIniFileMappingList(); - - ReadIniFile(filename, skinSection); - ReadVariables(); - - // Clear and minimize - m_FoundSections.clear(); - m_ListVariables.clear(); - m_SectionInsertPos = m_Sections.end(); -} - -void ConfigParser::SetBuiltInVariables(const std::wstring& filename, const std::wstring* resourcePath, MeterWindow* meterWindow) -{ - auto insertVariable = [&](const WCHAR* name, std::wstring value) - { - return m_BuiltInVariables.insert(std::make_pair(name, value)); - }; - - insertVariable(L"PROGRAMPATH", Rainmeter::GetInstance().GetPath()); - insertVariable(L"PROGRAMDRIVE", Rainmeter::GetInstance().GetDrive()); - insertVariable(L"SETTINGSPATH", Rainmeter::GetInstance().GetSettingsPath()); - insertVariable(L"SKINSPATH", Rainmeter::GetInstance().GetSkinPath()); - insertVariable(L"PLUGINSPATH", Rainmeter::GetInstance().GetPluginPath()); - insertVariable(L"CURRENTPATH", PathUtil::GetFolderFromFilePath(filename)); - insertVariable(L"ADDONSPATH", Rainmeter::GetInstance().GetAddonPath()); - - if (meterWindow) - { - insertVariable(L"CURRENTFILE", meterWindow->GetFileName()); - insertVariable(L"CURRENTCONFIG", meterWindow->GetFolderPath()); - insertVariable(L"ROOTCONFIG", meterWindow->GetRootName()); - insertVariable(L"ROOTCONFIGPATH", meterWindow->GetRootPath()); - } - - insertVariable(L"CRLF", L"\n"); - - m_CurrentSection = &(insertVariable(L"CURRENTSECTION", L"").first->second); // shortcut - - if (resourcePath) - { - SetVariable(L"@", *resourcePath); - } -} - -/* -** Sets all user-defined variables. -** -*/ -void ConfigParser::ReadVariables() -{ - std::list::const_iterator iter = m_ListVariables.begin(); - for ( ; iter != m_ListVariables.end(); ++iter) - { - SetVariable((*iter), ReadString(L"Variables", (*iter).c_str(), L"", false)); - } -} - -void ConfigParser::SetVariable(std::wstring strVariable, const std::wstring& strValue) -{ - StrToUpperC(strVariable); - m_Variables[strVariable] = strValue; -} - -void ConfigParser::SetBuiltInVariable(const std::wstring& strVariable, const std::wstring& strValue) -{ - m_BuiltInVariables[strVariable] = strValue; -} - -/* -** Gets a value for the variable. Returns nullptr if not found. -** -*/ -const std::wstring* ConfigParser::GetVariable(const std::wstring& strVariable) -{ - const std::wstring strTmp = StrToUpper(strVariable); - - // #1: Built-in variables - std::unordered_map::const_iterator iter = m_BuiltInVariables.find(strTmp); - if (iter != m_BuiltInVariables.end()) - { - return &(*iter).second; - } - - // #2: Monitor variables - iter = c_MonitorVariables.find(strTmp); - if (iter != c_MonitorVariables.end()) - { - return &(*iter).second; - } - - // #3: User-defined variables - iter = m_Variables.find(strTmp); - if (iter != m_Variables.end()) - { - return &(*iter).second; - } - - return nullptr; -} - -/* -** Gets the value of a section variable. Returns true if strValue is set. -** The selector is stripped from strVariable. -** -*/ -bool ConfigParser::GetSectionVariable(std::wstring& strVariable, std::wstring& strValue) -{ - size_t colonPos = strVariable.find_last_of(L':'); - if (colonPos == std::wstring::npos) - { - return false; - } - - const std::wstring selector = strVariable.substr(colonPos + 1); - const WCHAR* selectorSz = selector.c_str(); - strVariable.resize(colonPos); - - bool isKeySelector = (!selector.empty() && iswalpha(selectorSz[0])); - - if (isKeySelector) - { - // [Meter:X], [Meter:Y], [Meter:W], [Meter:H] - Meter* meter = m_MeterWindow->GetMeter(strVariable); - if (meter) - { - WCHAR buffer[32]; - if (_wcsicmp(selectorSz, L"X") == 0) - { - _itow_s(meter->GetX(), buffer, 10); - } - else if (_wcsicmp(selectorSz, L"Y") == 0) - { - _itow_s(meter->GetY(), buffer, 10); - } - else if (_wcsicmp(selectorSz, L"W") == 0) - { - _itow_s(meter->GetW(), buffer, 10); - } - else if (_wcsicmp(selectorSz, L"H") == 0) - { - _itow_s(meter->GetH(), buffer, 10); - } - else - { - return false; - } - - strValue = buffer; - return true; - } - } - - // Number: [Measure:], [Measure:dec] - // Percentual: [Measure:%], [Measure:%, dec] - // Scale: [Measure:/scale], [Measure:/scale, dec] - // Max/Min: [Measure:MaxValue], [Measure:MaxValue:/scale, dec] ('%' cannot be used) - // EscapeRegExp: [Measure:EscapeRegExp] (Escapes regular expression syntax, used for 'IfMatch') - enum class ValueType - { - Raw, - Percentual, - Max, - Min, - EscapeRegExp, - EncodeUrl - } valueType = ValueType::Raw; - - if (isKeySelector) - { - if (_wcsicmp(selectorSz, L"MaxValue") == 0) - { - valueType = ValueType::Max; - } - else if (_wcsicmp(selectorSz, L"MinValue") == 0) - { - valueType = ValueType::Min; - } - else if (_wcsicmp(selectorSz, L"EscapeRegExp") == 0) - { - valueType = ValueType::EscapeRegExp; - } - else if (_wcsicmp(selectorSz, L"EncodeUrl") == 0) - { - valueType = ValueType::EncodeUrl; - } - else - { - return false; - } - - selectorSz = L""; - } - else - { - colonPos = strVariable.find_last_of(L':'); - if (colonPos != std::wstring::npos) - { - do - { - const WCHAR* keySelectorSz = strVariable.c_str() + colonPos + 1; - - if (_wcsicmp(keySelectorSz, L"MaxValue") == 0) - { - valueType = ValueType::Max; - } - else if (_wcsicmp(keySelectorSz, L"MinValue") == 0) - { - valueType = ValueType::Min; - } - else - { - // Section name contains ':' ? - break; - } - - strVariable.resize(colonPos); - } - while (0); - } - } - - Measure* measure = m_MeterWindow->GetMeasure(strVariable); - if (measure) - { - if (valueType == ValueType::EscapeRegExp) - { - strValue = measure->GetStringValue(); - StringUtil::EscapeRegExp(strValue); - return true; - } - else if (valueType == ValueType::EncodeUrl) - { - strValue = measure->GetStringValue(); - StringUtil::EncodeUrl(strValue); - return true; - } - - int scale = 1; - - const WCHAR* decimalsSz = wcschr(selectorSz, L','); - if (decimalsSz) - { - ++decimalsSz; - } - - if (*selectorSz == L'%') // Percentual - { - if (valueType == ValueType::Max || valueType == ValueType::Min) - { - // '%' cannot be used with Max/Min values. - return false; - } - - valueType = ValueType::Percentual; - } - else if (*selectorSz == L'/') // Scale - { - errno = 0; - scale = _wtoi(selectorSz + 1); - if (errno == EINVAL || scale == 0) - { - // Invalid scale value. - return false; - } - } - else - { - if (decimalsSz) - { - return false; - } - - decimalsSz = selectorSz; - } - - const double value = - (valueType == ValueType::Percentual) ? measure->GetRelativeValue() * 100.0 : - (valueType == ValueType::Max) ? measure->GetMaxValue() / scale : - (valueType == ValueType::Min) ? measure->GetMinValue() / scale : - measure->GetValue() / scale; - int decimals = 10; - if (decimalsSz) - { - while (iswspace(*decimalsSz)) ++decimalsSz; - - if (*decimalsSz) - { - decimals = _wtoi(decimalsSz); - decimals = max(0, decimals); - decimals = min(32, decimals); - } - else - { - decimalsSz = nullptr; - } - } - - WCHAR format[32]; - WCHAR buffer[128]; - _snwprintf_s(format, _TRUNCATE, L"%%.%if", decimals); - int bufferLen = _snwprintf_s(buffer, _TRUNCATE, format, value); - - if (!decimalsSz) - { - // Remove trailing zeros if decimal count was not specified. - measure->RemoveTrailingZero(buffer, bufferLen); - bufferLen = (int)wcslen(buffer); - } - - strValue.assign(buffer, bufferLen); - return true; - } - - return false; -} - -void ConfigParser::ResetMonitorVariables(MeterWindow* meterWindow) -{ - // Set the SCREENAREA/WORKAREA variables - if (c_MonitorVariables.empty()) - { - SetMultiMonitorVariables(true); - } - - // Set the SCREENAREA/WORKAREA variables for present monitor - SetAutoSelectedMonitorVariables(meterWindow); -} - -/* -** Sets new values for the SCREENAREA/WORKAREA variables. -** -*/ -void ConfigParser::SetMultiMonitorVariables(bool reset) -{ - auto setMonitorVariable = [&](const WCHAR* variable, const WCHAR* value) - { - c_MonitorVariables[variable] = value; - }; - - if (!reset && c_MonitorVariables.empty()) - { - reset = true; // Set all variables - } - - const size_t numOfMonitors = System::GetMonitorCount(); // intentional - const MultiMonitorInfo& monitorsInfo = System::GetMultiMonitorInfo(); - const std::vector& monitors = monitorsInfo.monitors; - - WCHAR buffer[32]; - const RECT workArea = monitors[monitorsInfo.primary - 1].work; - const RECT scrArea = monitors[monitorsInfo.primary - 1].screen; - - _itow_s(workArea.left, buffer, 10); - setMonitorVariable(L"WORKAREAX", buffer); - setMonitorVariable(L"PWORKAREAX", buffer); - _itow_s(workArea.top, buffer, 10); - setMonitorVariable(L"WORKAREAY", buffer); - setMonitorVariable(L"PWORKAREAY", buffer); - _itow_s(workArea.right - workArea.left, buffer, 10); - setMonitorVariable(L"WORKAREAWIDTH", buffer); - setMonitorVariable(L"PWORKAREAWIDTH", buffer); - _itow_s(workArea.bottom - workArea.top, buffer, 10); - setMonitorVariable(L"WORKAREAHEIGHT", buffer); - setMonitorVariable(L"PWORKAREAHEIGHT", buffer); - - if (reset) - { - _itow_s(scrArea.left, buffer, 10); - setMonitorVariable(L"SCREENAREAX", buffer); - setMonitorVariable(L"PSCREENAREAX", buffer); - _itow_s(scrArea.top, buffer, 10); - setMonitorVariable(L"SCREENAREAY", buffer); - setMonitorVariable(L"PSCREENAREAY", buffer); - _itow_s(scrArea.right - scrArea.left, buffer, 10); - setMonitorVariable(L"SCREENAREAWIDTH", buffer); - setMonitorVariable(L"PSCREENAREAWIDTH", buffer); - _itow_s(scrArea.bottom - scrArea.top, buffer, 10); - setMonitorVariable(L"SCREENAREAHEIGHT", buffer); - setMonitorVariable(L"PSCREENAREAHEIGHT", buffer); - - _itow_s(monitorsInfo.vsL, buffer, 10); - setMonitorVariable(L"VSCREENAREAX", buffer); - _itow_s(monitorsInfo.vsT, buffer, 10); - setMonitorVariable(L"VSCREENAREAY", buffer); - _itow_s(monitorsInfo.vsW, buffer, 10); - setMonitorVariable(L"VSCREENAREAWIDTH", buffer); - _itow_s(monitorsInfo.vsH, buffer, 10); - setMonitorVariable(L"VSCREENAREAHEIGHT", buffer); - } - - int i = 1; - for (auto iter = monitors.cbegin(); iter != monitors.cend(); ++iter, ++i) - { - WCHAR buffer2[64]; - - const RECT work = ((*iter).active) ? (*iter).work : workArea; - - _itow_s(work.left, buffer, 10); - _snwprintf_s(buffer2, _TRUNCATE, L"WORKAREAX@%i", i); - setMonitorVariable(buffer2, buffer); - _itow_s(work.top, buffer, 10); - _snwprintf_s(buffer2, _TRUNCATE, L"WORKAREAY@%i", i); - setMonitorVariable(buffer2, buffer); - _itow_s(work.right - work.left, buffer, 10); - _snwprintf_s(buffer2, _TRUNCATE, L"WORKAREAWIDTH@%i", i); - setMonitorVariable(buffer2, buffer); - _itow_s(work.bottom - work.top, buffer, 10); - _snwprintf_s(buffer2, _TRUNCATE, L"WORKAREAHEIGHT@%i", i); - setMonitorVariable(buffer2, buffer); - - if (reset) - { - const RECT screen = ((*iter).active) ? (*iter).screen : scrArea; - - _itow_s(screen.left, buffer, 10); - _snwprintf_s(buffer2, _TRUNCATE, L"SCREENAREAX@%i", i); - setMonitorVariable(buffer2, buffer); - _itow_s(screen.top, buffer, 10); - _snwprintf_s(buffer2, _TRUNCATE, L"SCREENAREAY@%i", i); - setMonitorVariable(buffer2, buffer); - _itow_s(screen.right - screen.left, buffer, 10); - _snwprintf_s(buffer2, _TRUNCATE, L"SCREENAREAWIDTH@%i", i); - setMonitorVariable(buffer2, buffer); - _itow_s(screen.bottom - screen.top, buffer, 10); - _snwprintf_s(buffer2, _TRUNCATE, L"SCREENAREAHEIGHT@%i", i); - setMonitorVariable(buffer2, buffer); - } - } -} - -/* -** Sets new SCREENAREA/WORKAREA variables for present monitor. -** -*/ -void ConfigParser::SetAutoSelectedMonitorVariables(MeterWindow* meterWindow) -{ - if (meterWindow) - { - const int numOfMonitors = (int)System::GetMonitorCount(); - const MultiMonitorInfo& monitorsInfo = System::GetMultiMonitorInfo(); - const std::vector& monitors = monitorsInfo.monitors; - - WCHAR buffer[32]; - int w1, w2, s1, s2; - int screenIndex; - - // Set X / WIDTH - screenIndex = monitorsInfo.primary; - if (meterWindow->GetXScreenDefined()) - { - int i = meterWindow->GetXScreen(); - if (i >= 0 && (i == 0 || i <= numOfMonitors && monitors[i - 1].active)) - { - screenIndex = i; - } - } - - if (screenIndex == 0) - { - s1 = w1 = monitorsInfo.vsL; - s2 = w2 = monitorsInfo.vsW; - } - else - { - w1 = monitors[screenIndex - 1].work.left; - w2 = monitors[screenIndex - 1].work.right - monitors[screenIndex - 1].work.left; - s1 = monitors[screenIndex - 1].screen.left; - s2 = monitors[screenIndex - 1].screen.right - monitors[screenIndex - 1].screen.left; - } - - _itow_s(w1, buffer, 10); - SetBuiltInVariable(L"WORKAREAX", buffer); - _itow_s(w2, buffer, 10); - SetBuiltInVariable(L"WORKAREAWIDTH", buffer); - _itow_s(s1, buffer, 10); - SetBuiltInVariable(L"SCREENAREAX", buffer); - _itow_s(s2, buffer, 10); - SetBuiltInVariable(L"SCREENAREAWIDTH", buffer); - - // Set Y / HEIGHT - screenIndex = monitorsInfo.primary; - if (meterWindow->GetYScreenDefined()) - { - int i = meterWindow->GetYScreen(); - if (i >= 0 && (i == 0 || i <= numOfMonitors && monitors[i - 1].active)) - { - screenIndex = i; - } - } - - if (screenIndex == 0) - { - s1 = w1 = monitorsInfo.vsL; - s2 = w2 = monitorsInfo.vsW; - } - else - { - w1 = monitors[screenIndex - 1].work.top; - w2 = monitors[screenIndex - 1].work.bottom - monitors[screenIndex - 1].work.top; - s1 = monitors[screenIndex - 1].screen.top; - s2 = monitors[screenIndex - 1].screen.bottom - monitors[screenIndex - 1].screen.top; - } - - _itow_s(w1, buffer, 10); - SetBuiltInVariable(L"WORKAREAY", buffer); - _itow_s(w2, buffer, 10); - SetBuiltInVariable(L"WORKAREAHEIGHT", buffer); - _itow_s(s1, buffer, 10); - SetBuiltInVariable(L"SCREENAREAY", buffer); - _itow_s(s2, buffer, 10); - SetBuiltInVariable(L"SCREENAREAHEIGHT", buffer); - } -} - -/* -** Replaces environment and internal variables in the given string. -** -*/ -bool ConfigParser::ReplaceVariables(std::wstring& result) -{ - bool replaced = false; - - PathUtil::ExpandEnvironmentVariables(result); - - if (c_MonitorVariables.empty()) - { - SetMultiMonitorVariables(true); - } - - // Check for variables (#VAR#) - size_t start = 0, end; - bool loop = true; - - do - { - start = result.find(L'#', start); - if (start != std::wstring::npos) - { - size_t si = start + 1; - end = result.find(L'#', si); - if (end != std::wstring::npos) - { - size_t ei = end - 1; - if (si != ei && result[si] == L'*' && result[ei] == L'*') - { - result.erase(ei, 1); - result.erase(si, 1); - start = ei; - } - else - { - std::wstring strVariable = result.substr(si, end - si); - const std::wstring* value = GetVariable(strVariable); - if (value) - { - // Variable found, replace it with the value - result.replace(start, end - start + 1, *value); - start += (*value).length(); - replaced = true; - } - else - { - start = end; - } - } - } - else - { - loop = false; - } - } - else - { - loop = false; - } - } - while (loop); - - return replaced; -} - -/* -** Replaces measures in the given string. -** -*/ -bool ConfigParser::ReplaceMeasures(std::wstring& result) -{ - bool replaced = false; - - size_t start = 0; - while ((start = result.find(L'[', start)) != std::wstring::npos) - { - size_t si = start + 1; - size_t end = result.find(L']', si); - if (end == std::wstring::npos) - { - break; - } - - size_t next = result.find(L'[', si); - if (next == std::wstring::npos || end < next) - { - size_t ei = end - 1; - if (si != ei && result[si] == L'*' && result[ei] == L'*') - { - result.erase(ei, 1); - result.erase(si, 1); - start = ei; - } - else - { - std::wstring var = result.substr(si, end - si); - - Measure* measure = GetMeasure(var); - if (measure) - { - const WCHAR* value = measure->GetStringOrFormattedValue(AUTOSCALE_OFF, 1, -1, false); - size_t valueLen = wcslen(value); - - // Measure found, replace it with the value - result.replace(start, end - start + 1, value, valueLen); - start += valueLen; - replaced = true; - } - else - { - std::wstring value; - if (GetSectionVariable(var, value)) - { - // Replace section variable with the value. - result.replace(start, end - start + 1, value); - start += value.length(); - replaced = true; - } - else - { - start = end; - } - } - } - } - else - { - start = next; - } - } - - return replaced; -} - -const std::wstring& ConfigParser::ReadString(LPCTSTR section, LPCTSTR key, LPCTSTR defValue, bool bReplaceMeasures) -{ - static std::wstring result; - - // Clear last status - m_LastReplaced = false; - m_LastDefaultUsed = false; - m_LastValueDefined = false; - - const std::wstring strSection = section; - const std::wstring strKey = key; - const std::wstring strDefault = defValue; - - const std::wstring& strValue = GetValue(strSection, strKey, strDefault); - if (&strValue == &strDefault) - { - bool foundStyleValue = false; - - // If the template is defined read the value from there. - std::vector::const_reverse_iterator iter = m_StyleTemplate.rbegin(); - for ( ; iter != m_StyleTemplate.rend(); ++iter) - { - const std::wstring& strStyleValue = GetValue((*iter), strKey, strDefault); - - //LogDebugF(L"StyleTemplate: [%s] %s (from [%s]) : strDefault=%s (0x%p), strStyleValue=%s (0x%p)", - // section, key, (*iter).c_str(), strDefault.c_str(), &strDefault, strStyleValue.c_str(), &strStyleValue); - - if (&strStyleValue != &strDefault) - { - result = strStyleValue; - foundStyleValue = true; - break; - } - } - - if (!foundStyleValue) - { - result = strDefault; - m_LastDefaultUsed = true; - return result; - } - } - else - { - result = strValue; - } - - if (!result.empty()) - { - m_LastValueDefined = true; - - if (result.size() >= 3) - { - if (result.find(L'#') != std::wstring::npos) - { - m_CurrentSection->assign(strSection); // Set temporarily - - if (ReplaceVariables(result)) - { - m_LastReplaced = true; - } - - m_CurrentSection->clear(); // Reset - } - else - { - PathUtil::ExpandEnvironmentVariables(result); - } - - if (bReplaceMeasures && ReplaceMeasures(result)) - { - m_LastReplaced = true; - } - } - } - - return result; -} - -bool ConfigParser::IsKeyDefined(LPCTSTR section, LPCTSTR key) -{ - ReadString(section, key, L"", false); - return !m_LastDefaultUsed; -} - -bool ConfigParser::IsValueDefined(LPCTSTR section, LPCTSTR key) -{ - ReadString(section, key, L"", false); - return m_LastValueDefined; -} - -void ConfigParser::AddMeasure(Measure* pMeasure) -{ - if (pMeasure) - { - m_Measures[StrToUpper(pMeasure->GetOriginalName())] = pMeasure; - } -} - -Measure* ConfigParser::GetMeasure(const std::wstring& name) -{ - std::unordered_map::const_iterator iter = m_Measures.find(StrToUpper(name)); - if (iter != m_Measures.end()) - { - return (*iter).second; - } - - return nullptr; -} - -std::vector ConfigParser::ReadFloats(LPCTSTR section, LPCTSTR key) -{ - std::vector result; - const std::wstring& str = ReadString(section, key, L""); - if (!str.empty()) - { - // Tokenize and parse the floats - const WCHAR delimiter = L';'; - size_t lastPos, pos = 0; - do - { - lastPos = str.find_first_not_of(delimiter, pos); - if (lastPos == std::wstring::npos) break; - - pos = str.find_first_of(delimiter, lastPos + 1); - - result.push_back((Gdiplus::REAL)ParseDouble(str.substr(lastPos, pos - lastPos).c_str(), 0.0)); // (pos != std::wstring::npos) ? pos - lastPos : pos - if (pos == std::wstring::npos) break; - - ++pos; - } - while (true); - } - return result; -} - -int ConfigParser::ReadInt(LPCTSTR section, LPCTSTR key, int defValue) -{ - const std::wstring& result = ReadString(section, key, L""); - - if (!m_LastDefaultUsed) - { - const WCHAR* string = result.c_str(); - if (*string == L'(') - { - double dblValue; - const WCHAR* errMsg = MathParser::CheckedParse(string, &dblValue); - if (!errMsg) - { - return (int)dblValue; - } - - LogErrorF(m_MeterWindow, L"Formula: %s in key \"%s\" in [%s]", errMsg, key, section); - } - else if (*string) - { - errno = 0; - int intValue = wcstol(string, nullptr, 10); - if (errno != ERANGE) - { - return intValue; - } - } - } - - return defValue; -} - -uint32_t ConfigParser::ReadUInt(LPCTSTR section, LPCTSTR key, uint32_t defValue) -{ - const std::wstring& result = ReadString(section, key, L""); - - if (!m_LastDefaultUsed) - { - const WCHAR* string = result.c_str(); - if (*string == L'(') - { - double dblValue; - const WCHAR* errMsg = MathParser::CheckedParse(string, &dblValue); - if (!errMsg) - { - return (uint32_t)dblValue; - } - - LogErrorF(m_MeterWindow, L"Formula: %s in key \"%s\" in [%s]", errMsg, key, section); - } - else if (*string) - { - errno = 0; - uint32_t uintValue = wcstoul(string, nullptr, 10); - if (errno != ERANGE) - { - return uintValue; - } - } - } - - return defValue; -} - -uint64_t ConfigParser::ReadUInt64(LPCTSTR section, LPCTSTR key, uint64_t defValue) -{ - const std::wstring& result = ReadString(section, key, L""); - - if (!m_LastDefaultUsed) - { - const WCHAR* string = result.c_str(); - if (*string == L'(') - { - double dblValue; - const WCHAR* errMsg = MathParser::CheckedParse(string, &dblValue); - if (!errMsg) - { - return (uint64_t)dblValue; - } - - LogErrorF(m_MeterWindow, L"Formula: %s in key \"%s\" in [%s]", errMsg, key, section); - } - else if (*string) - { - errno = 0; - uint64_t uint64Value = _wcstoui64(string, nullptr, 10); - if (errno != ERANGE) - { - return uint64Value; - } - } - } - - return defValue; -} - -double ConfigParser::ReadFloat(LPCTSTR section, LPCTSTR key, double defValue) -{ - const std::wstring& result = ReadString(section, key, L""); - - if (!m_LastDefaultUsed) - { - double value; - const WCHAR* string = result.c_str(); - if (*string == L'(') - { - const WCHAR* errMsg = MathParser::CheckedParse(string, &value); - if (!errMsg) - { - return value; - } - - LogErrorF(m_MeterWindow, L"Formula: %s in key \"%s\" in [%s]", errMsg, key, section); - } - else if (*string) - { - errno = 0; - value = wcstod(string, nullptr); - if (errno != ERANGE) - { - return value; - } - } - } - - return defValue; -} - -// Returns true if the formula was read successfully, false for failure. -bool ConfigParser::ParseFormula(const std::wstring& formula, double* resultValue) -{ - // Formulas must be surrounded by parenthesis - if (!formula.empty() && formula[0] == L'(' && formula[formula.size() - 1] == L')') - { - const WCHAR* string = formula.c_str(); - const WCHAR* errMsg = MathParser::CheckedParse(string, resultValue); - if (errMsg != nullptr) - { - LogErrorF(m_MeterWindow, L"Formula: %s: %s", errMsg, string); - return false; - } - - return true; - } - - return false; -} - -ARGB ConfigParser::ReadColor(LPCTSTR section, LPCTSTR key, ARGB defValue) -{ - const std::wstring& result = ReadString(section, key, L""); - - return (m_LastDefaultUsed) ? defValue : ParseColor(result.c_str()); -} - -Rect ConfigParser::ReadRect(LPCTSTR section, LPCTSTR key, const Rect& defValue) -{ - const std::wstring& result = ReadString(section, key, L""); - - return (m_LastDefaultUsed) ? defValue : ParseRect(result.c_str()); -} - -RECT ConfigParser::ReadRECT(LPCTSTR section, LPCTSTR key, const RECT& defValue) -{ - const std::wstring& result = ReadString(section, key, L""); - - RECT r; - if (m_LastDefaultUsed) - { - r = defValue; - } - else - { - r = ParseRECT(result.c_str()); - } - return r; -} - -/* -** Splits the string from the delimiters. -** Now trims empty element in vector and white-space in each string. -** -** Modified from http://www.digitalpeer.com/id/simple -*/ -std::vector ConfigParser::Tokenize(const std::wstring& str, const std::wstring& delimiters) -{ - std::vector tokens; - - size_t lastPos, pos = 0; - do - { - lastPos = str.find_first_not_of(delimiters, pos); - if (lastPos == std::wstring::npos) break; - - pos = str.find_first_of(delimiters, lastPos + 1); - std::wstring token = str.substr(lastPos, pos - lastPos); // len = (pos != std::wstring::npos) ? pos - lastPos : pos - - size_t pos2 = token.find_first_not_of(L" \t\r\n"); - if (pos2 != std::wstring::npos) - { - size_t lastPos2 = token.find_last_not_of(L" \t\r\n"); - if (pos2 != 0 || lastPos2 != (token.size() - 1)) - { - // Trim white-space - token.assign(token, pos2, lastPos2 - pos2 + 1); - } - tokens.push_back(token); - } - - if (pos == std::wstring::npos) break; - ++pos; - } - while (true); - - return tokens; -} - -/* -** Helper method that parses the floating-point value from the given string. -** If the given string is invalid format or causes overflow/underflow, returns given default value. -** -*/ -double ConfigParser::ParseDouble(LPCTSTR string, double defValue) -{ - assert(string); - - double value; - if (*string == L'(') - { - const WCHAR* errMsg = MathParser::CheckedParse(string, &value); - if (!errMsg) - { - return value; - } - - LogErrorF(L"Formula: %s: %s", errMsg, string); - } - else if (*string) - { - errno = 0; - double value = wcstod(string, nullptr); - if (errno != ERANGE) - { - return value; - } - } - - return defValue; -} - -/* -** Helper method that parses the integer value from the given string. -** If the given string is invalid format or causes overflow/underflow, returns given default value. -** -*/ -int ConfigParser::ParseInt(LPCTSTR string, int defValue) -{ - assert(string); - - if (*string == L'(') - { - double dblValue; - const WCHAR* errMsg = MathParser::CheckedParse(string, &dblValue); - if (!errMsg) - { - return (int)dblValue; - } - - LogErrorF(L"Formula: %s: %s", errMsg, string); - } - else if (*string) - { - errno = 0; - int intValue = wcstol(string, nullptr, 10); - if (errno != ERANGE) - { - return intValue; - } - } - - return defValue; -} - -/* -** Helper method that parses the unsigned integer value from the given string. -** If the given string is invalid format or causes overflow/underflow, returns given default value. -** -*/ -uint32_t ConfigParser::ParseUInt(LPCTSTR string, uint32_t defValue) -{ - assert(string); - - if (*string == L'(') - { - double dblValue; - const WCHAR* errMsg = MathParser::CheckedParse(string, &dblValue); - if (!errMsg) - { - return (uint32_t)dblValue; - } - - LogErrorF(L"Formula: %s: %s", errMsg, string); - } - else if (*string) - { - errno = 0; - uint32_t uintValue = wcstoul(string, nullptr, 10); - if (errno != ERANGE) - { - return uintValue; - } - } - - return defValue; -} - -/* -** Helper method that parses the 64bit unsigned integer value from the given string. -** If the given string is invalid format or causes overflow/underflow, returns given default value. -** -*/ -uint64_t ConfigParser::ParseUInt64(LPCTSTR string, uint64_t defValue) -{ - assert(string); - - if (*string == L'(') - { - double dblValue; - const WCHAR* errMsg = MathParser::CheckedParse(string, &dblValue); - if (!errMsg) - { - return (uint64_t)dblValue; - } - - LogErrorF(L"Formula: %s: %s", errMsg, string); - } - else if (*string) - { - errno = 0; - uint64_t uint64Value = _wcstoui64(string, nullptr, 10); - if (errno != ERANGE) - { - return uint64Value; - } - } - - return defValue; -} - -/* -** Helper template that parses four comma separated values from the given string. -** -*/ -template -bool ParseInt4(LPCTSTR string, T& v1, T& v2, T& v3, T& v4) -{ - if (wcschr(string, L',')) - { - WCHAR* parseSz = _wcsdup(string); - WCHAR* token; - - token = wcstok(parseSz, L","); - if (token) - { - v1 = ConfigParser::ParseInt(token, 0); - - token = wcstok(nullptr, L","); - if (token) - { - v2 = ConfigParser::ParseInt(token, 0); - - token = wcstok(nullptr, L","); - if (token) - { - v3 = ConfigParser::ParseInt(token, 0); - - token = wcstok(nullptr, L","); - if (token) - { - v4 = ConfigParser::ParseInt(token, 0); - } - } - } - } - free(parseSz); - return true; - } - - return false; -} - -/* -** Helper method that parses the color values from the given string. -** The color can be supplied as three/four comma separated values or as one -** hex-value. -** -*/ -ARGB ConfigParser::ParseColor(LPCTSTR string) -{ - int R = 255, G = 255, B = 255, A = 255; - - if (!ParseInt4(string, R, G, B, A)) - { - if (wcsncmp(string, L"0x", 2) == 0) - { - string += 2; // skip prefix - } - - size_t len = wcslen(string); - if (len >= 8 && !iswspace(string[6])) - { - swscanf(string, L"%02x%02x%02x%02x", &R, &G, &B, &A); - } - else if (len >= 6) - { - swscanf(string, L"%02x%02x%02x", &R, &G, &B); - } - } - - return Color::MakeARGB(A, R, G, B); -} - -/* -** Helper method that parses the Gdiplus::Rect values from the given string. -** The rect can be supplied as four comma separated values (X/Y/Width/Height). -** -*/ -Rect ConfigParser::ParseRect(LPCTSTR string) -{ - Rect r; - ParseInt4(string, r.X, r.Y, r.Width, r.Height); - return r; -} - -/* -** Helper method that parses the RECT values from the given string. -** The rect can be supplied as four comma separated values (left/top/right/bottom). -** -*/ -RECT ConfigParser::ParseRECT(LPCTSTR string) -{ - RECT r = {0}; - ParseInt4(string, r.left, r.top, r.right, r.bottom); - return r; -} - -/* -** Reads the given ini file and fills the m_Values and m_Keys maps. -** -*/ -void ConfigParser::ReadIniFile(const std::wstring& iniFile, LPCTSTR skinSection, int depth) -{ - if (depth > 100) // Is 100 enough to assume the include loop never ends? - { - Rainmeter::GetInstance().ShowMessage(nullptr, GetString(ID_STR_INCLUDEINFINITELOOP), MB_OK | MB_ICONERROR); - return; - } - - // Verify whether the file exists - if (_waccess(iniFile.c_str(), 0) == -1) - { - LogErrorF(m_MeterWindow, L"Unable to read file: %s", iniFile.c_str()); - return; - } - - // Avoid "IniFileMapping" - std::wstring iniRead = System::GetTemporaryFile(iniFile); - bool temporary = (!iniRead.empty() && (iniRead.size() != 1 || iniRead[0] != L'?')); - - if (temporary) - { - if (Rainmeter::GetInstance().GetDebug()) LogDebugF(m_MeterWindow, L"Reading file: %s (Temp: %s)", iniFile.c_str(), iniRead.c_str()); - } - else - { - if (Rainmeter::GetInstance().GetDebug()) LogDebugF(m_MeterWindow, L"Reading file: %s", iniFile.c_str()); - iniRead = iniFile; - } - - // Get all the sections (i.e. different meters) - std::list sections; - std::unordered_set unique; - std::wstring key, value; // buffer - - DWORD itemsSize = MAX_LINE_LENGTH; - WCHAR* items = new WCHAR[itemsSize]; - WCHAR* pos = nullptr; - WCHAR* epos = nullptr; - - if (skinSection == nullptr) - { - // Get all the sections - do - { - items[0] = 0; - DWORD res = GetPrivateProfileSectionNames(items, itemsSize, iniRead.c_str()); - if (res == 0) // File not found - { - delete [] items; - if (temporary) System::RemoveFile(iniRead); - return; - } - if (res < itemsSize - 2) // Fits in the buffer - { - epos = items + res; - break; - } - - delete [] items; - itemsSize *= 2; - items = new WCHAR[itemsSize]; - } - while (true); - - // Read the sections - pos = items; - while (pos < epos) - { - if (*pos) - { - value = pos; // section name - StrToUpperC(key.assign(value)); - if (unique.insert(key).second) - { - if (m_FoundSections.insert(key).second) - { - m_Sections.insert(m_SectionInsertPos, value); - } - sections.push_back(value); - } - pos += value.size() + 1; - } - else // Empty string - { - ++pos; - } - } - } - else - { - // Special case: Read only "Rainmeter" and specified section from "Rainmeter.ini" - const std::wstring strRainmeter = L"Rainmeter"; - const std::wstring strFolder = skinSection; - - sections.push_back(strRainmeter); - sections.push_back(strFolder); - - if (depth == 0) // Add once - { - m_Sections.push_back(strRainmeter); - m_Sections.push_back(strFolder); - } - } - - // Read the keys and values - for (auto it = sections.cbegin(); it != sections.cend(); ++it) - { - unique.clear(); - - const WCHAR* sectionName = (*it).c_str(); - bool isVariables = (_wcsicmp(sectionName, L"Variables") == 0); - bool isMetadata = (skinSection == nullptr && !isVariables && _wcsicmp(sectionName, L"Metadata") == 0); - bool resetInsertPos = true; - - // Read all "key=value" from the section - do - { - items[0] = 0; - DWORD res = GetPrivateProfileSection(sectionName, items, itemsSize, iniRead.c_str()); - if (res < itemsSize - 2) // Fits in the buffer - { - epos = items + res; - break; - } - - delete [] items; - itemsSize *= 2; - items = new WCHAR[itemsSize]; - } - while (true); - - pos = items; - while (pos < epos) - { - if (*pos) - { - size_t len = wcslen(pos); - WCHAR* sep = wmemchr(pos, L'=', len); - if (sep != nullptr && sep != pos) - { - size_t clen = sep - pos; // key's length - - StrToUpperC(key.assign(pos, clen)); - if (unique.insert(key).second) - { - ++sep; - clen = len - (clen + 1); // value's length - - // Trim surrounded quotes from value - if (clen >= 2 && (sep[0] == L'"' || sep[0] == L'\'') && sep[clen - 1] == sep[0]) - { - clen -= 2; - ++sep; - } - - if (wcsncmp(key.c_str(), L"@INCLUDE", 8) == 0) - { - if (clen > 0) - { - value.assign(sep, clen); - ReadVariables(); - ReplaceVariables(value); - if (!PathUtil::IsAbsolute(value)) - { - // Relative to the ini folder - value.insert(0, PathUtil::GetFolderFromFilePath(iniFile)); - } - - if (resetInsertPos) - { - auto jt = it; - if (++jt == sections.end()) // Special case: @include was used in the last section of the current file - { - // Set the insertion place to the last - m_SectionInsertPos = m_Sections.end(); - resetInsertPos = false; - } - else - { - // Find the appropriate insertion place - for (jt = m_Sections.cbegin(); jt != m_Sections.cend(); ++jt) - { - if (_wcsicmp((*jt).c_str(), sectionName) == 0) - { - m_SectionInsertPos = ++jt; - resetInsertPos = false; - break; - } - } - } - } - - ReadIniFile(value, skinSection, depth + 1); - } - } - else - { - if (!isMetadata) // Uncache Metadata's key-value pair in the skin - { - value.assign(sep, clen); - SetValue((*it), key, value); - - if (isVariables) - { - m_ListVariables.push_back(key); - } - } - } - } - } - pos += len + 1; - } - else // Empty string - { - ++pos; - } - } - } - - delete [] items; - if (temporary) System::RemoveFile(iniRead); -} - -/* -** Sets the value for the key under the given section. -** -*/ -void ConfigParser::SetValue(const std::wstring& strSection, const std::wstring& strKey, const std::wstring& strValue) -{ - // LogDebugF(L"[%s] %s=%s (size: %i)", strSection.c_str(), strKey.c_str(), strValue.c_str(), (int)m_Values.size()); - - std::wstring strTmp; - strTmp.reserve(strSection.size() + 1 + strKey.size()); - strTmp = strSection; - strTmp += L'~'; - strTmp += strKey; - - m_Values[StrToUpperC(strTmp)] = strValue; -} - -/* -** Deletes the value for the key under the given section. -** -*/ -void ConfigParser::DeleteValue(const std::wstring& strSection, const std::wstring& strKey) -{ - std::wstring strTmp; - strTmp.reserve(strSection.size() + 1 + strKey.size()); - strTmp = strSection; - strTmp += L'~'; - strTmp += strKey; - - std::unordered_map::const_iterator iter = m_Values.find(StrToUpperC(strTmp)); - if (iter != m_Values.end()) - { - m_Values.erase(iter); - } -} - -/* -** Returns the value for the key under the given section. -** -*/ -const std::wstring& ConfigParser::GetValue(const std::wstring& strSection, const std::wstring& strKey, const std::wstring& strDefault) -{ - std::wstring strTmp; - strTmp.reserve(strSection.size() + 1 + strKey.size()); - strTmp = strSection; - strTmp += L'~'; - strTmp += strKey; - - std::unordered_map::const_iterator iter = m_Values.find(StrToUpperC(strTmp)); - return (iter != m_Values.end()) ? (*iter).second : strDefault; -} +/* + Copyright (C) 2004 Kimmo Pekkola + + 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/MathParser.h" +#include "../Common/PathUtil.h" +#include "ConfigParser.h" +#include "Litestep.h" +#include "Rainmeter.h" +#include "System.h" +#include "Measure.h" +#include "Meter.h" +#include "resource.h" + +using namespace Gdiplus; + +std::unordered_map ConfigParser::c_MonitorVariables; + +/* +** The constructor +** +*/ +ConfigParser::ConfigParser() : + m_LastReplaced(false), + m_LastDefaultUsed(false), + m_LastValueDefined(false), + m_CurrentSection(), + m_MeterWindow() +{ +} + +/* +** The destructor +** +*/ +ConfigParser::~ConfigParser() +{ +} + +void ConfigParser::Initialize(const std::wstring& filename, MeterWindow* meterWindow, LPCTSTR skinSection, const std::wstring* resourcePath) +{ + m_MeterWindow = meterWindow; + + m_Measures.clear(); + m_Sections.clear(); + m_Values.clear(); + m_BuiltInVariables.clear(); + m_Variables.clear(); + + m_StyleTemplate.clear(); + m_LastReplaced = false; + m_LastDefaultUsed = false; + m_LastValueDefined = false; + + m_CurrentSection = nullptr; + m_SectionInsertPos = m_Sections.end(); + + // Set the built-in variables. Do this before the ini file is read so that the paths can be used with @include + SetBuiltInVariables(filename, resourcePath, meterWindow); + ResetMonitorVariables(meterWindow); + + System::UpdateIniFileMappingList(); + + ReadIniFile(filename, skinSection); + ReadVariables(); + + // Clear and minimize + m_FoundSections.clear(); + m_ListVariables.clear(); + m_SectionInsertPos = m_Sections.end(); +} + +void ConfigParser::SetBuiltInVariables(const std::wstring& filename, const std::wstring* resourcePath, MeterWindow* meterWindow) +{ + auto insertVariable = [&](const WCHAR* name, std::wstring value) + { + return m_BuiltInVariables.insert(std::make_pair(name, value)); + }; + + insertVariable(L"PROGRAMPATH", GetRainmeter().GetPath()); + insertVariable(L"PROGRAMDRIVE", GetRainmeter().GetDrive()); + insertVariable(L"SETTINGSPATH", GetRainmeter().GetSettingsPath()); + insertVariable(L"SKINSPATH", GetRainmeter().GetSkinPath()); + insertVariable(L"PLUGINSPATH", GetRainmeter().GetPluginPath()); + insertVariable(L"CURRENTPATH", PathUtil::GetFolderFromFilePath(filename)); + insertVariable(L"ADDONSPATH", GetRainmeter().GetAddonPath()); + + if (meterWindow) + { + insertVariable(L"CURRENTFILE", meterWindow->GetFileName()); + insertVariable(L"CURRENTCONFIG", meterWindow->GetFolderPath()); + insertVariable(L"ROOTCONFIG", meterWindow->GetRootName()); + insertVariable(L"ROOTCONFIGPATH", meterWindow->GetRootPath()); + } + + insertVariable(L"CRLF", L"\n"); + + m_CurrentSection = &(insertVariable(L"CURRENTSECTION", L"").first->second); // shortcut + + if (resourcePath) + { + SetVariable(L"@", *resourcePath); + } +} + +/* +** Sets all user-defined variables. +** +*/ +void ConfigParser::ReadVariables() +{ + std::list::const_iterator iter = m_ListVariables.begin(); + for ( ; iter != m_ListVariables.end(); ++iter) + { + SetVariable((*iter), ReadString(L"Variables", (*iter).c_str(), L"", false)); + } +} + +void ConfigParser::SetVariable(std::wstring strVariable, const std::wstring& strValue) +{ + StrToUpperC(strVariable); + m_Variables[strVariable] = strValue; +} + +void ConfigParser::SetBuiltInVariable(const std::wstring& strVariable, const std::wstring& strValue) +{ + m_BuiltInVariables[strVariable] = strValue; +} + +/* +** Gets a value for the variable. Returns nullptr if not found. +** +*/ +const std::wstring* ConfigParser::GetVariable(const std::wstring& strVariable) +{ + const std::wstring strTmp = StrToUpper(strVariable); + + // #1: Built-in variables + std::unordered_map::const_iterator iter = m_BuiltInVariables.find(strTmp); + if (iter != m_BuiltInVariables.end()) + { + return &(*iter).second; + } + + // #2: Monitor variables + iter = c_MonitorVariables.find(strTmp); + if (iter != c_MonitorVariables.end()) + { + return &(*iter).second; + } + + // #3: User-defined variables + iter = m_Variables.find(strTmp); + if (iter != m_Variables.end()) + { + return &(*iter).second; + } + + return nullptr; +} + +/* +** Gets the value of a section variable. Returns true if strValue is set. +** The selector is stripped from strVariable. +** +*/ +bool ConfigParser::GetSectionVariable(std::wstring& strVariable, std::wstring& strValue) +{ + size_t colonPos = strVariable.find_last_of(L':'); + if (colonPos == std::wstring::npos) + { + return false; + } + + const std::wstring selector = strVariable.substr(colonPos + 1); + const WCHAR* selectorSz = selector.c_str(); + strVariable.resize(colonPos); + + bool isKeySelector = (!selector.empty() && iswalpha(selectorSz[0])); + + if (isKeySelector) + { + // [Meter:X], [Meter:Y], [Meter:W], [Meter:H] + Meter* meter = m_MeterWindow->GetMeter(strVariable); + if (meter) + { + WCHAR buffer[32]; + if (_wcsicmp(selectorSz, L"X") == 0) + { + _itow_s(meter->GetX(), buffer, 10); + } + else if (_wcsicmp(selectorSz, L"Y") == 0) + { + _itow_s(meter->GetY(), buffer, 10); + } + else if (_wcsicmp(selectorSz, L"W") == 0) + { + _itow_s(meter->GetW(), buffer, 10); + } + else if (_wcsicmp(selectorSz, L"H") == 0) + { + _itow_s(meter->GetH(), buffer, 10); + } + else + { + return false; + } + + strValue = buffer; + return true; + } + } + + // Number: [Measure:], [Measure:dec] + // Percentual: [Measure:%], [Measure:%, dec] + // Scale: [Measure:/scale], [Measure:/scale, dec] + // Max/Min: [Measure:MaxValue], [Measure:MaxValue:/scale, dec] ('%' cannot be used) + // EscapeRegExp: [Measure:EscapeRegExp] (Escapes regular expression syntax, used for 'IfMatch') + enum class ValueType + { + Raw, + Percentual, + Max, + Min, + EscapeRegExp, + EncodeUrl + } valueType = ValueType::Raw; + + if (isKeySelector) + { + if (_wcsicmp(selectorSz, L"MaxValue") == 0) + { + valueType = ValueType::Max; + } + else if (_wcsicmp(selectorSz, L"MinValue") == 0) + { + valueType = ValueType::Min; + } + else if (_wcsicmp(selectorSz, L"EscapeRegExp") == 0) + { + valueType = ValueType::EscapeRegExp; + } + else if (_wcsicmp(selectorSz, L"EncodeUrl") == 0) + { + valueType = ValueType::EncodeUrl; + } + else + { + return false; + } + + selectorSz = L""; + } + else + { + colonPos = strVariable.find_last_of(L':'); + if (colonPos != std::wstring::npos) + { + do + { + const WCHAR* keySelectorSz = strVariable.c_str() + colonPos + 1; + + if (_wcsicmp(keySelectorSz, L"MaxValue") == 0) + { + valueType = ValueType::Max; + } + else if (_wcsicmp(keySelectorSz, L"MinValue") == 0) + { + valueType = ValueType::Min; + } + else + { + // Section name contains ':' ? + break; + } + + strVariable.resize(colonPos); + } + while (0); + } + } + + Measure* measure = m_MeterWindow->GetMeasure(strVariable); + if (measure) + { + if (valueType == ValueType::EscapeRegExp) + { + strValue = measure->GetStringValue(); + StringUtil::EscapeRegExp(strValue); + return true; + } + else if (valueType == ValueType::EncodeUrl) + { + strValue = measure->GetStringValue(); + StringUtil::EncodeUrl(strValue); + return true; + } + + int scale = 1; + + const WCHAR* decimalsSz = wcschr(selectorSz, L','); + if (decimalsSz) + { + ++decimalsSz; + } + + if (*selectorSz == L'%') // Percentual + { + if (valueType == ValueType::Max || valueType == ValueType::Min) + { + // '%' cannot be used with Max/Min values. + return false; + } + + valueType = ValueType::Percentual; + } + else if (*selectorSz == L'/') // Scale + { + errno = 0; + scale = _wtoi(selectorSz + 1); + if (errno == EINVAL || scale == 0) + { + // Invalid scale value. + return false; + } + } + else + { + if (decimalsSz) + { + return false; + } + + decimalsSz = selectorSz; + } + + const double value = + (valueType == ValueType::Percentual) ? measure->GetRelativeValue() * 100.0 : + (valueType == ValueType::Max) ? measure->GetMaxValue() / scale : + (valueType == ValueType::Min) ? measure->GetMinValue() / scale : + measure->GetValue() / scale; + int decimals = 10; + if (decimalsSz) + { + while (iswspace(*decimalsSz)) ++decimalsSz; + + if (*decimalsSz) + { + decimals = _wtoi(decimalsSz); + decimals = max(0, decimals); + decimals = min(32, decimals); + } + else + { + decimalsSz = nullptr; + } + } + + WCHAR format[32]; + WCHAR buffer[128]; + _snwprintf_s(format, _TRUNCATE, L"%%.%if", decimals); + int bufferLen = _snwprintf_s(buffer, _TRUNCATE, format, value); + + if (!decimalsSz) + { + // Remove trailing zeros if decimal count was not specified. + measure->RemoveTrailingZero(buffer, bufferLen); + bufferLen = (int)wcslen(buffer); + } + + strValue.assign(buffer, bufferLen); + return true; + } + + return false; +} + +void ConfigParser::ResetMonitorVariables(MeterWindow* meterWindow) +{ + // Set the SCREENAREA/WORKAREA variables + if (c_MonitorVariables.empty()) + { + SetMultiMonitorVariables(true); + } + + // Set the SCREENAREA/WORKAREA variables for present monitor + SetAutoSelectedMonitorVariables(meterWindow); +} + +/* +** Sets new values for the SCREENAREA/WORKAREA variables. +** +*/ +void ConfigParser::SetMultiMonitorVariables(bool reset) +{ + auto setMonitorVariable = [&](const WCHAR* variable, const WCHAR* value) + { + c_MonitorVariables[variable] = value; + }; + + if (!reset && c_MonitorVariables.empty()) + { + reset = true; // Set all variables + } + + const size_t numOfMonitors = System::GetMonitorCount(); // intentional + const MultiMonitorInfo& monitorsInfo = System::GetMultiMonitorInfo(); + const std::vector& monitors = monitorsInfo.monitors; + + WCHAR buffer[32]; + const RECT workArea = monitors[monitorsInfo.primary - 1].work; + const RECT scrArea = monitors[monitorsInfo.primary - 1].screen; + + _itow_s(workArea.left, buffer, 10); + setMonitorVariable(L"WORKAREAX", buffer); + setMonitorVariable(L"PWORKAREAX", buffer); + _itow_s(workArea.top, buffer, 10); + setMonitorVariable(L"WORKAREAY", buffer); + setMonitorVariable(L"PWORKAREAY", buffer); + _itow_s(workArea.right - workArea.left, buffer, 10); + setMonitorVariable(L"WORKAREAWIDTH", buffer); + setMonitorVariable(L"PWORKAREAWIDTH", buffer); + _itow_s(workArea.bottom - workArea.top, buffer, 10); + setMonitorVariable(L"WORKAREAHEIGHT", buffer); + setMonitorVariable(L"PWORKAREAHEIGHT", buffer); + + if (reset) + { + _itow_s(scrArea.left, buffer, 10); + setMonitorVariable(L"SCREENAREAX", buffer); + setMonitorVariable(L"PSCREENAREAX", buffer); + _itow_s(scrArea.top, buffer, 10); + setMonitorVariable(L"SCREENAREAY", buffer); + setMonitorVariable(L"PSCREENAREAY", buffer); + _itow_s(scrArea.right - scrArea.left, buffer, 10); + setMonitorVariable(L"SCREENAREAWIDTH", buffer); + setMonitorVariable(L"PSCREENAREAWIDTH", buffer); + _itow_s(scrArea.bottom - scrArea.top, buffer, 10); + setMonitorVariable(L"SCREENAREAHEIGHT", buffer); + setMonitorVariable(L"PSCREENAREAHEIGHT", buffer); + + _itow_s(monitorsInfo.vsL, buffer, 10); + setMonitorVariable(L"VSCREENAREAX", buffer); + _itow_s(monitorsInfo.vsT, buffer, 10); + setMonitorVariable(L"VSCREENAREAY", buffer); + _itow_s(monitorsInfo.vsW, buffer, 10); + setMonitorVariable(L"VSCREENAREAWIDTH", buffer); + _itow_s(monitorsInfo.vsH, buffer, 10); + setMonitorVariable(L"VSCREENAREAHEIGHT", buffer); + } + + int i = 1; + for (auto iter = monitors.cbegin(); iter != monitors.cend(); ++iter, ++i) + { + WCHAR buffer2[64]; + + const RECT work = ((*iter).active) ? (*iter).work : workArea; + + _itow_s(work.left, buffer, 10); + _snwprintf_s(buffer2, _TRUNCATE, L"WORKAREAX@%i", i); + setMonitorVariable(buffer2, buffer); + _itow_s(work.top, buffer, 10); + _snwprintf_s(buffer2, _TRUNCATE, L"WORKAREAY@%i", i); + setMonitorVariable(buffer2, buffer); + _itow_s(work.right - work.left, buffer, 10); + _snwprintf_s(buffer2, _TRUNCATE, L"WORKAREAWIDTH@%i", i); + setMonitorVariable(buffer2, buffer); + _itow_s(work.bottom - work.top, buffer, 10); + _snwprintf_s(buffer2, _TRUNCATE, L"WORKAREAHEIGHT@%i", i); + setMonitorVariable(buffer2, buffer); + + if (reset) + { + const RECT screen = ((*iter).active) ? (*iter).screen : scrArea; + + _itow_s(screen.left, buffer, 10); + _snwprintf_s(buffer2, _TRUNCATE, L"SCREENAREAX@%i", i); + setMonitorVariable(buffer2, buffer); + _itow_s(screen.top, buffer, 10); + _snwprintf_s(buffer2, _TRUNCATE, L"SCREENAREAY@%i", i); + setMonitorVariable(buffer2, buffer); + _itow_s(screen.right - screen.left, buffer, 10); + _snwprintf_s(buffer2, _TRUNCATE, L"SCREENAREAWIDTH@%i", i); + setMonitorVariable(buffer2, buffer); + _itow_s(screen.bottom - screen.top, buffer, 10); + _snwprintf_s(buffer2, _TRUNCATE, L"SCREENAREAHEIGHT@%i", i); + setMonitorVariable(buffer2, buffer); + } + } +} + +/* +** Sets new SCREENAREA/WORKAREA variables for present monitor. +** +*/ +void ConfigParser::SetAutoSelectedMonitorVariables(MeterWindow* meterWindow) +{ + if (meterWindow) + { + const int numOfMonitors = (int)System::GetMonitorCount(); + const MultiMonitorInfo& monitorsInfo = System::GetMultiMonitorInfo(); + const std::vector& monitors = monitorsInfo.monitors; + + WCHAR buffer[32]; + int w1, w2, s1, s2; + int screenIndex; + + // Set X / WIDTH + screenIndex = monitorsInfo.primary; + if (meterWindow->GetXScreenDefined()) + { + int i = meterWindow->GetXScreen(); + if (i >= 0 && (i == 0 || i <= numOfMonitors && monitors[i - 1].active)) + { + screenIndex = i; + } + } + + if (screenIndex == 0) + { + s1 = w1 = monitorsInfo.vsL; + s2 = w2 = monitorsInfo.vsW; + } + else + { + w1 = monitors[screenIndex - 1].work.left; + w2 = monitors[screenIndex - 1].work.right - monitors[screenIndex - 1].work.left; + s1 = monitors[screenIndex - 1].screen.left; + s2 = monitors[screenIndex - 1].screen.right - monitors[screenIndex - 1].screen.left; + } + + _itow_s(w1, buffer, 10); + SetBuiltInVariable(L"WORKAREAX", buffer); + _itow_s(w2, buffer, 10); + SetBuiltInVariable(L"WORKAREAWIDTH", buffer); + _itow_s(s1, buffer, 10); + SetBuiltInVariable(L"SCREENAREAX", buffer); + _itow_s(s2, buffer, 10); + SetBuiltInVariable(L"SCREENAREAWIDTH", buffer); + + // Set Y / HEIGHT + screenIndex = monitorsInfo.primary; + if (meterWindow->GetYScreenDefined()) + { + int i = meterWindow->GetYScreen(); + if (i >= 0 && (i == 0 || i <= numOfMonitors && monitors[i - 1].active)) + { + screenIndex = i; + } + } + + if (screenIndex == 0) + { + s1 = w1 = monitorsInfo.vsL; + s2 = w2 = monitorsInfo.vsW; + } + else + { + w1 = monitors[screenIndex - 1].work.top; + w2 = monitors[screenIndex - 1].work.bottom - monitors[screenIndex - 1].work.top; + s1 = monitors[screenIndex - 1].screen.top; + s2 = monitors[screenIndex - 1].screen.bottom - monitors[screenIndex - 1].screen.top; + } + + _itow_s(w1, buffer, 10); + SetBuiltInVariable(L"WORKAREAY", buffer); + _itow_s(w2, buffer, 10); + SetBuiltInVariable(L"WORKAREAHEIGHT", buffer); + _itow_s(s1, buffer, 10); + SetBuiltInVariable(L"SCREENAREAY", buffer); + _itow_s(s2, buffer, 10); + SetBuiltInVariable(L"SCREENAREAHEIGHT", buffer); + } +} + +/* +** Replaces environment and internal variables in the given string. +** +*/ +bool ConfigParser::ReplaceVariables(std::wstring& result) +{ + bool replaced = false; + + PathUtil::ExpandEnvironmentVariables(result); + + if (c_MonitorVariables.empty()) + { + SetMultiMonitorVariables(true); + } + + // Check for variables (#VAR#) + size_t start = 0, end; + bool loop = true; + + do + { + start = result.find(L'#', start); + if (start != std::wstring::npos) + { + size_t si = start + 1; + end = result.find(L'#', si); + if (end != std::wstring::npos) + { + size_t ei = end - 1; + if (si != ei && result[si] == L'*' && result[ei] == L'*') + { + result.erase(ei, 1); + result.erase(si, 1); + start = ei; + } + else + { + std::wstring strVariable = result.substr(si, end - si); + const std::wstring* value = GetVariable(strVariable); + if (value) + { + // Variable found, replace it with the value + result.replace(start, end - start + 1, *value); + start += (*value).length(); + replaced = true; + } + else + { + start = end; + } + } + } + else + { + loop = false; + } + } + else + { + loop = false; + } + } + while (loop); + + return replaced; +} + +/* +** Replaces measures in the given string. +** +*/ +bool ConfigParser::ReplaceMeasures(std::wstring& result) +{ + bool replaced = false; + + size_t start = 0; + while ((start = result.find(L'[', start)) != std::wstring::npos) + { + size_t si = start + 1; + size_t end = result.find(L']', si); + if (end == std::wstring::npos) + { + break; + } + + size_t next = result.find(L'[', si); + if (next == std::wstring::npos || end < next) + { + size_t ei = end - 1; + if (si != ei && result[si] == L'*' && result[ei] == L'*') + { + result.erase(ei, 1); + result.erase(si, 1); + start = ei; + } + else + { + std::wstring var = result.substr(si, end - si); + + Measure* measure = GetMeasure(var); + if (measure) + { + const WCHAR* value = measure->GetStringOrFormattedValue(AUTOSCALE_OFF, 1, -1, false); + size_t valueLen = wcslen(value); + + // Measure found, replace it with the value + result.replace(start, end - start + 1, value, valueLen); + start += valueLen; + replaced = true; + } + else + { + std::wstring value; + if (GetSectionVariable(var, value)) + { + // Replace section variable with the value. + result.replace(start, end - start + 1, value); + start += value.length(); + replaced = true; + } + else + { + start = end; + } + } + } + } + else + { + start = next; + } + } + + return replaced; +} + +const std::wstring& ConfigParser::ReadString(LPCTSTR section, LPCTSTR key, LPCTSTR defValue, bool bReplaceMeasures) +{ + static std::wstring result; + + // Clear last status + m_LastReplaced = false; + m_LastDefaultUsed = false; + m_LastValueDefined = false; + + const std::wstring strSection = section; + const std::wstring strKey = key; + const std::wstring strDefault = defValue; + + const std::wstring& strValue = GetValue(strSection, strKey, strDefault); + if (&strValue == &strDefault) + { + bool foundStyleValue = false; + + // If the template is defined read the value from there. + std::vector::const_reverse_iterator iter = m_StyleTemplate.rbegin(); + for ( ; iter != m_StyleTemplate.rend(); ++iter) + { + const std::wstring& strStyleValue = GetValue((*iter), strKey, strDefault); + + //LogDebugF(L"StyleTemplate: [%s] %s (from [%s]) : strDefault=%s (0x%p), strStyleValue=%s (0x%p)", + // section, key, (*iter).c_str(), strDefault.c_str(), &strDefault, strStyleValue.c_str(), &strStyleValue); + + if (&strStyleValue != &strDefault) + { + result = strStyleValue; + foundStyleValue = true; + break; + } + } + + if (!foundStyleValue) + { + result = strDefault; + m_LastDefaultUsed = true; + return result; + } + } + else + { + result = strValue; + } + + if (!result.empty()) + { + m_LastValueDefined = true; + + if (result.size() >= 3) + { + if (result.find(L'#') != std::wstring::npos) + { + m_CurrentSection->assign(strSection); // Set temporarily + + if (ReplaceVariables(result)) + { + m_LastReplaced = true; + } + + m_CurrentSection->clear(); // Reset + } + else + { + PathUtil::ExpandEnvironmentVariables(result); + } + + if (bReplaceMeasures && ReplaceMeasures(result)) + { + m_LastReplaced = true; + } + } + } + + return result; +} + +bool ConfigParser::IsKeyDefined(LPCTSTR section, LPCTSTR key) +{ + ReadString(section, key, L"", false); + return !m_LastDefaultUsed; +} + +bool ConfigParser::IsValueDefined(LPCTSTR section, LPCTSTR key) +{ + ReadString(section, key, L"", false); + return m_LastValueDefined; +} + +void ConfigParser::AddMeasure(Measure* pMeasure) +{ + if (pMeasure) + { + m_Measures[StrToUpper(pMeasure->GetOriginalName())] = pMeasure; + } +} + +Measure* ConfigParser::GetMeasure(const std::wstring& name) +{ + std::unordered_map::const_iterator iter = m_Measures.find(StrToUpper(name)); + if (iter != m_Measures.end()) + { + return (*iter).second; + } + + return nullptr; +} + +std::vector ConfigParser::ReadFloats(LPCTSTR section, LPCTSTR key) +{ + std::vector result; + const std::wstring& str = ReadString(section, key, L""); + if (!str.empty()) + { + // Tokenize and parse the floats + const WCHAR delimiter = L';'; + size_t lastPos, pos = 0; + do + { + lastPos = str.find_first_not_of(delimiter, pos); + if (lastPos == std::wstring::npos) break; + + pos = str.find_first_of(delimiter, lastPos + 1); + + result.push_back((Gdiplus::REAL)ParseDouble(str.substr(lastPos, pos - lastPos).c_str(), 0.0)); // (pos != std::wstring::npos) ? pos - lastPos : pos + if (pos == std::wstring::npos) break; + + ++pos; + } + while (true); + } + return result; +} + +int ConfigParser::ReadInt(LPCTSTR section, LPCTSTR key, int defValue) +{ + const std::wstring& result = ReadString(section, key, L""); + + if (!m_LastDefaultUsed) + { + const WCHAR* string = result.c_str(); + if (*string == L'(') + { + double dblValue; + const WCHAR* errMsg = MathParser::CheckedParse(string, &dblValue); + if (!errMsg) + { + return (int)dblValue; + } + + LogErrorF(m_MeterWindow, L"Formula: %s in key \"%s\" in [%s]", errMsg, key, section); + } + else if (*string) + { + errno = 0; + int intValue = wcstol(string, nullptr, 10); + if (errno != ERANGE) + { + return intValue; + } + } + } + + return defValue; +} + +uint32_t ConfigParser::ReadUInt(LPCTSTR section, LPCTSTR key, uint32_t defValue) +{ + const std::wstring& result = ReadString(section, key, L""); + + if (!m_LastDefaultUsed) + { + const WCHAR* string = result.c_str(); + if (*string == L'(') + { + double dblValue; + const WCHAR* errMsg = MathParser::CheckedParse(string, &dblValue); + if (!errMsg) + { + return (uint32_t)dblValue; + } + + LogErrorF(m_MeterWindow, L"Formula: %s in key \"%s\" in [%s]", errMsg, key, section); + } + else if (*string) + { + errno = 0; + uint32_t uintValue = wcstoul(string, nullptr, 10); + if (errno != ERANGE) + { + return uintValue; + } + } + } + + return defValue; +} + +uint64_t ConfigParser::ReadUInt64(LPCTSTR section, LPCTSTR key, uint64_t defValue) +{ + const std::wstring& result = ReadString(section, key, L""); + + if (!m_LastDefaultUsed) + { + const WCHAR* string = result.c_str(); + if (*string == L'(') + { + double dblValue; + const WCHAR* errMsg = MathParser::CheckedParse(string, &dblValue); + if (!errMsg) + { + return (uint64_t)dblValue; + } + + LogErrorF(m_MeterWindow, L"Formula: %s in key \"%s\" in [%s]", errMsg, key, section); + } + else if (*string) + { + errno = 0; + uint64_t uint64Value = _wcstoui64(string, nullptr, 10); + if (errno != ERANGE) + { + return uint64Value; + } + } + } + + return defValue; +} + +double ConfigParser::ReadFloat(LPCTSTR section, LPCTSTR key, double defValue) +{ + const std::wstring& result = ReadString(section, key, L""); + + if (!m_LastDefaultUsed) + { + double value; + const WCHAR* string = result.c_str(); + if (*string == L'(') + { + const WCHAR* errMsg = MathParser::CheckedParse(string, &value); + if (!errMsg) + { + return value; + } + + LogErrorF(m_MeterWindow, L"Formula: %s in key \"%s\" in [%s]", errMsg, key, section); + } + else if (*string) + { + errno = 0; + value = wcstod(string, nullptr); + if (errno != ERANGE) + { + return value; + } + } + } + + return defValue; +} + +// Returns true if the formula was read successfully, false for failure. +bool ConfigParser::ParseFormula(const std::wstring& formula, double* resultValue) +{ + // Formulas must be surrounded by parenthesis + if (!formula.empty() && formula[0] == L'(' && formula[formula.size() - 1] == L')') + { + const WCHAR* string = formula.c_str(); + const WCHAR* errMsg = MathParser::CheckedParse(string, resultValue); + if (errMsg != nullptr) + { + LogErrorF(m_MeterWindow, L"Formula: %s: %s", errMsg, string); + return false; + } + + return true; + } + + return false; +} + +ARGB ConfigParser::ReadColor(LPCTSTR section, LPCTSTR key, ARGB defValue) +{ + const std::wstring& result = ReadString(section, key, L""); + + return (m_LastDefaultUsed) ? defValue : ParseColor(result.c_str()); +} + +Rect ConfigParser::ReadRect(LPCTSTR section, LPCTSTR key, const Rect& defValue) +{ + const std::wstring& result = ReadString(section, key, L""); + + return (m_LastDefaultUsed) ? defValue : ParseRect(result.c_str()); +} + +RECT ConfigParser::ReadRECT(LPCTSTR section, LPCTSTR key, const RECT& defValue) +{ + const std::wstring& result = ReadString(section, key, L""); + + RECT r; + if (m_LastDefaultUsed) + { + r = defValue; + } + else + { + r = ParseRECT(result.c_str()); + } + return r; +} + +/* +** Splits the string from the delimiters. +** Now trims empty element in vector and white-space in each string. +** +** Modified from http://www.digitalpeer.com/id/simple +*/ +std::vector ConfigParser::Tokenize(const std::wstring& str, const std::wstring& delimiters) +{ + std::vector tokens; + + size_t lastPos, pos = 0; + do + { + lastPos = str.find_first_not_of(delimiters, pos); + if (lastPos == std::wstring::npos) break; + + pos = str.find_first_of(delimiters, lastPos + 1); + std::wstring token = str.substr(lastPos, pos - lastPos); // len = (pos != std::wstring::npos) ? pos - lastPos : pos + + size_t pos2 = token.find_first_not_of(L" \t\r\n"); + if (pos2 != std::wstring::npos) + { + size_t lastPos2 = token.find_last_not_of(L" \t\r\n"); + if (pos2 != 0 || lastPos2 != (token.size() - 1)) + { + // Trim white-space + token.assign(token, pos2, lastPos2 - pos2 + 1); + } + tokens.push_back(token); + } + + if (pos == std::wstring::npos) break; + ++pos; + } + while (true); + + return tokens; +} + +/* +** Helper method that parses the floating-point value from the given string. +** If the given string is invalid format or causes overflow/underflow, returns given default value. +** +*/ +double ConfigParser::ParseDouble(LPCTSTR string, double defValue) +{ + assert(string); + + double value; + if (*string == L'(') + { + const WCHAR* errMsg = MathParser::CheckedParse(string, &value); + if (!errMsg) + { + return value; + } + + LogErrorF(L"Formula: %s: %s", errMsg, string); + } + else if (*string) + { + errno = 0; + double value = wcstod(string, nullptr); + if (errno != ERANGE) + { + return value; + } + } + + return defValue; +} + +/* +** Helper method that parses the integer value from the given string. +** If the given string is invalid format or causes overflow/underflow, returns given default value. +** +*/ +int ConfigParser::ParseInt(LPCTSTR string, int defValue) +{ + assert(string); + + if (*string == L'(') + { + double dblValue; + const WCHAR* errMsg = MathParser::CheckedParse(string, &dblValue); + if (!errMsg) + { + return (int)dblValue; + } + + LogErrorF(L"Formula: %s: %s", errMsg, string); + } + else if (*string) + { + errno = 0; + int intValue = wcstol(string, nullptr, 10); + if (errno != ERANGE) + { + return intValue; + } + } + + return defValue; +} + +/* +** Helper method that parses the unsigned integer value from the given string. +** If the given string is invalid format or causes overflow/underflow, returns given default value. +** +*/ +uint32_t ConfigParser::ParseUInt(LPCTSTR string, uint32_t defValue) +{ + assert(string); + + if (*string == L'(') + { + double dblValue; + const WCHAR* errMsg = MathParser::CheckedParse(string, &dblValue); + if (!errMsg) + { + return (uint32_t)dblValue; + } + + LogErrorF(L"Formula: %s: %s", errMsg, string); + } + else if (*string) + { + errno = 0; + uint32_t uintValue = wcstoul(string, nullptr, 10); + if (errno != ERANGE) + { + return uintValue; + } + } + + return defValue; +} + +/* +** Helper method that parses the 64bit unsigned integer value from the given string. +** If the given string is invalid format or causes overflow/underflow, returns given default value. +** +*/ +uint64_t ConfigParser::ParseUInt64(LPCTSTR string, uint64_t defValue) +{ + assert(string); + + if (*string == L'(') + { + double dblValue; + const WCHAR* errMsg = MathParser::CheckedParse(string, &dblValue); + if (!errMsg) + { + return (uint64_t)dblValue; + } + + LogErrorF(L"Formula: %s: %s", errMsg, string); + } + else if (*string) + { + errno = 0; + uint64_t uint64Value = _wcstoui64(string, nullptr, 10); + if (errno != ERANGE) + { + return uint64Value; + } + } + + return defValue; +} + +/* +** Helper template that parses four comma separated values from the given string. +** +*/ +template +bool ParseInt4(LPCTSTR string, T& v1, T& v2, T& v3, T& v4) +{ + if (wcschr(string, L',')) + { + WCHAR* parseSz = _wcsdup(string); + WCHAR* token; + + token = wcstok(parseSz, L","); + if (token) + { + v1 = ConfigParser::ParseInt(token, 0); + + token = wcstok(nullptr, L","); + if (token) + { + v2 = ConfigParser::ParseInt(token, 0); + + token = wcstok(nullptr, L","); + if (token) + { + v3 = ConfigParser::ParseInt(token, 0); + + token = wcstok(nullptr, L","); + if (token) + { + v4 = ConfigParser::ParseInt(token, 0); + } + } + } + } + free(parseSz); + return true; + } + + return false; +} + +/* +** Helper method that parses the color values from the given string. +** The color can be supplied as three/four comma separated values or as one +** hex-value. +** +*/ +ARGB ConfigParser::ParseColor(LPCTSTR string) +{ + int R = 255, G = 255, B = 255, A = 255; + + if (!ParseInt4(string, R, G, B, A)) + { + if (wcsncmp(string, L"0x", 2) == 0) + { + string += 2; // skip prefix + } + + size_t len = wcslen(string); + if (len >= 8 && !iswspace(string[6])) + { + swscanf(string, L"%02x%02x%02x%02x", &R, &G, &B, &A); + } + else if (len >= 6) + { + swscanf(string, L"%02x%02x%02x", &R, &G, &B); + } + } + + return Color::MakeARGB(A, R, G, B); +} + +/* +** Helper method that parses the Gdiplus::Rect values from the given string. +** The rect can be supplied as four comma separated values (X/Y/Width/Height). +** +*/ +Rect ConfigParser::ParseRect(LPCTSTR string) +{ + Rect r; + ParseInt4(string, r.X, r.Y, r.Width, r.Height); + return r; +} + +/* +** Helper method that parses the RECT values from the given string. +** The rect can be supplied as four comma separated values (left/top/right/bottom). +** +*/ +RECT ConfigParser::ParseRECT(LPCTSTR string) +{ + RECT r = {0}; + ParseInt4(string, r.left, r.top, r.right, r.bottom); + return r; +} + +/* +** Reads the given ini file and fills the m_Values and m_Keys maps. +** +*/ +void ConfigParser::ReadIniFile(const std::wstring& iniFile, LPCTSTR skinSection, int depth) +{ + if (depth > 100) // Is 100 enough to assume the include loop never ends? + { + GetRainmeter().ShowMessage(nullptr, GetString(ID_STR_INCLUDEINFINITELOOP), MB_OK | MB_ICONERROR); + return; + } + + // Verify whether the file exists + if (_waccess(iniFile.c_str(), 0) == -1) + { + LogErrorF(m_MeterWindow, L"Unable to read file: %s", iniFile.c_str()); + return; + } + + // Avoid "IniFileMapping" + std::wstring iniRead = System::GetTemporaryFile(iniFile); + bool temporary = (!iniRead.empty() && (iniRead.size() != 1 || iniRead[0] != L'?')); + + if (temporary) + { + if (GetRainmeter().GetDebug()) LogDebugF(m_MeterWindow, L"Reading file: %s (Temp: %s)", iniFile.c_str(), iniRead.c_str()); + } + else + { + if (GetRainmeter().GetDebug()) LogDebugF(m_MeterWindow, L"Reading file: %s", iniFile.c_str()); + iniRead = iniFile; + } + + // Get all the sections (i.e. different meters) + std::list sections; + std::unordered_set unique; + std::wstring key, value; // buffer + + DWORD itemsSize = MAX_LINE_LENGTH; + WCHAR* items = new WCHAR[itemsSize]; + WCHAR* pos = nullptr; + WCHAR* epos = nullptr; + + if (skinSection == nullptr) + { + // Get all the sections + do + { + items[0] = 0; + DWORD res = GetPrivateProfileSectionNames(items, itemsSize, iniRead.c_str()); + if (res == 0) // File not found + { + delete [] items; + if (temporary) System::RemoveFile(iniRead); + return; + } + if (res < itemsSize - 2) // Fits in the buffer + { + epos = items + res; + break; + } + + delete [] items; + itemsSize *= 2; + items = new WCHAR[itemsSize]; + } + while (true); + + // Read the sections + pos = items; + while (pos < epos) + { + if (*pos) + { + value = pos; // section name + StrToUpperC(key.assign(value)); + if (unique.insert(key).second) + { + if (m_FoundSections.insert(key).second) + { + m_Sections.insert(m_SectionInsertPos, value); + } + sections.push_back(value); + } + pos += value.size() + 1; + } + else // Empty string + { + ++pos; + } + } + } + else + { + // Special case: Read only "Rainmeter" and specified section from "Rainmeter.ini" + const std::wstring strRainmeter = L"Rainmeter"; + const std::wstring strFolder = skinSection; + + sections.push_back(strRainmeter); + sections.push_back(strFolder); + + if (depth == 0) // Add once + { + m_Sections.push_back(strRainmeter); + m_Sections.push_back(strFolder); + } + } + + // Read the keys and values + for (auto it = sections.cbegin(); it != sections.cend(); ++it) + { + unique.clear(); + + const WCHAR* sectionName = (*it).c_str(); + bool isVariables = (_wcsicmp(sectionName, L"Variables") == 0); + bool isMetadata = (skinSection == nullptr && !isVariables && _wcsicmp(sectionName, L"Metadata") == 0); + bool resetInsertPos = true; + + // Read all "key=value" from the section + do + { + items[0] = 0; + DWORD res = GetPrivateProfileSection(sectionName, items, itemsSize, iniRead.c_str()); + if (res < itemsSize - 2) // Fits in the buffer + { + epos = items + res; + break; + } + + delete [] items; + itemsSize *= 2; + items = new WCHAR[itemsSize]; + } + while (true); + + pos = items; + while (pos < epos) + { + if (*pos) + { + size_t len = wcslen(pos); + WCHAR* sep = wmemchr(pos, L'=', len); + if (sep != nullptr && sep != pos) + { + size_t clen = sep - pos; // key's length + + StrToUpperC(key.assign(pos, clen)); + if (unique.insert(key).second) + { + ++sep; + clen = len - (clen + 1); // value's length + + // Trim surrounded quotes from value + if (clen >= 2 && (sep[0] == L'"' || sep[0] == L'\'') && sep[clen - 1] == sep[0]) + { + clen -= 2; + ++sep; + } + + if (wcsncmp(key.c_str(), L"@INCLUDE", 8) == 0) + { + if (clen > 0) + { + value.assign(sep, clen); + ReadVariables(); + ReplaceVariables(value); + if (!PathUtil::IsAbsolute(value)) + { + // Relative to the ini folder + value.insert(0, PathUtil::GetFolderFromFilePath(iniFile)); + } + + if (resetInsertPos) + { + auto jt = it; + if (++jt == sections.end()) // Special case: @include was used in the last section of the current file + { + // Set the insertion place to the last + m_SectionInsertPos = m_Sections.end(); + resetInsertPos = false; + } + else + { + // Find the appropriate insertion place + for (jt = m_Sections.cbegin(); jt != m_Sections.cend(); ++jt) + { + if (_wcsicmp((*jt).c_str(), sectionName) == 0) + { + m_SectionInsertPos = ++jt; + resetInsertPos = false; + break; + } + } + } + } + + ReadIniFile(value, skinSection, depth + 1); + } + } + else + { + if (!isMetadata) // Uncache Metadata's key-value pair in the skin + { + value.assign(sep, clen); + SetValue((*it), key, value); + + if (isVariables) + { + m_ListVariables.push_back(key); + } + } + } + } + } + pos += len + 1; + } + else // Empty string + { + ++pos; + } + } + } + + delete [] items; + if (temporary) System::RemoveFile(iniRead); +} + +/* +** Sets the value for the key under the given section. +** +*/ +void ConfigParser::SetValue(const std::wstring& strSection, const std::wstring& strKey, const std::wstring& strValue) +{ + // LogDebugF(L"[%s] %s=%s (size: %i)", strSection.c_str(), strKey.c_str(), strValue.c_str(), (int)m_Values.size()); + + std::wstring strTmp; + strTmp.reserve(strSection.size() + 1 + strKey.size()); + strTmp = strSection; + strTmp += L'~'; + strTmp += strKey; + + m_Values[StrToUpperC(strTmp)] = strValue; +} + +/* +** Deletes the value for the key under the given section. +** +*/ +void ConfigParser::DeleteValue(const std::wstring& strSection, const std::wstring& strKey) +{ + std::wstring strTmp; + strTmp.reserve(strSection.size() + 1 + strKey.size()); + strTmp = strSection; + strTmp += L'~'; + strTmp += strKey; + + std::unordered_map::const_iterator iter = m_Values.find(StrToUpperC(strTmp)); + if (iter != m_Values.end()) + { + m_Values.erase(iter); + } +} + +/* +** Returns the value for the key under the given section. +** +*/ +const std::wstring& ConfigParser::GetValue(const std::wstring& strSection, const std::wstring& strKey, const std::wstring& strDefault) +{ + std::wstring strTmp; + strTmp.reserve(strSection.size() + 1 + strKey.size()); + strTmp = strSection; + strTmp += L'~'; + strTmp += strKey; + + std::unordered_map::const_iterator iter = m_Values.find(StrToUpperC(strTmp)); + return (iter != m_Values.end()) ? (*iter).second : strDefault; +} diff --git a/Library/ContextMenu.cpp b/Library/ContextMenu.cpp index 22738bc1..1088f4e7 100644 --- a/Library/ContextMenu.cpp +++ b/Library/ContextMenu.cpp @@ -1,690 +1,690 @@ -/* - 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/MenuTemplate.h" -#include "../Common/Gfx/CanvasD2D.h" -#include "ContextMenu.h" -#include "Rainmeter.h" -#include "Litestep.h" -#include "MeterWindow.h" -#include "System.h" -#include "TrayWindow.h" -#include "resource.h" - -ContextMenu::ContextMenu() : - m_MenuActive(false) -{ -} - -/* -** Opens the context menu in given coordinates. -*/ -void ContextMenu::ShowMenu(POINT pos, MeterWindow* meterWindow) -{ - static const MenuTemplate s_Menu[] = - { - MENU_ITEM(IDM_MANAGE, ID_STR_MANAGE), - MENU_ITEM(IDM_ABOUT, ID_STR_ABOUT), - MENU_ITEM(IDM_SHOW_HELP, ID_STR_HELP), - MENU_SEPARATOR(), - MENU_SUBMENU(ID_STR_SKINS, - MENU_ITEM_GRAYED(0, ID_STR_NOSKINS), - MENU_SEPARATOR(), - MENU_ITEM(IDM_OPENSKINSFOLDER, ID_STR_OPENFOLDER), - MENU_ITEM(IDM_DISABLEDRAG, ID_STR_DISABLEDRAGGING)), - MENU_SUBMENU(ID_STR_THEMES, - MENU_ITEM_GRAYED(0, ID_STR_NOTHEMES)), - MENU_SEPARATOR(), - MENU_ITEM(IDM_EDITCONFIG, ID_STR_EDITSETTINGS), - MENU_ITEM(IDM_REFRESH, ID_STR_REFRESHALL), - MENU_SEPARATOR(), - MENU_SUBMENU(ID_STR_LOGGING, - MENU_ITEM(IDM_SHOWLOGFILE, ID_STR_SHOWLOGFILE), - MENU_SEPARATOR(), - MENU_ITEM(IDM_STARTLOG, ID_STR_STARTLOGGING), - MENU_ITEM(IDM_STOPLOG, ID_STR_STOPLOGGING), - MENU_SEPARATOR(), - MENU_ITEM(IDM_DELETELOGFILE, ID_STR_DELETELOGFILE), - MENU_ITEM(IDM_DEBUGLOG, ID_STR_DEBUGMODE)), - MENU_SEPARATOR(), - MENU_ITEM(IDM_QUIT, ID_STR_EXIT) - }; - - if (m_MenuActive || (meterWindow && meterWindow->IsClosing())) return; - - // Show context menu, if no actions were executed - HMENU menu = MenuTemplate::CreateMenu(s_Menu, _countof(s_Menu), GetString); - if (!menu) return; - - m_MenuActive = true; - Rainmeter& rainmeter = Rainmeter::GetInstance(); - - SetMenuDefaultItem(menu, IDM_MANAGE, MF_BYCOMMAND); - - if (_waccess(GetLogger().GetLogFilePath().c_str(), 0) == -1) - { - EnableMenuItem(menu, IDM_SHOWLOGFILE, MF_BYCOMMAND | MF_GRAYED); - EnableMenuItem(menu, IDM_DELETELOGFILE, MF_BYCOMMAND | MF_GRAYED); - EnableMenuItem(menu, IDM_STOPLOG, MF_BYCOMMAND | MF_GRAYED); - } - else - { - EnableMenuItem( - menu, - (GetLogger().IsLogToFile()) ? IDM_STARTLOG : IDM_STOPLOG, - MF_BYCOMMAND | MF_GRAYED); - } - - if (rainmeter.m_Debug) - { - CheckMenuItem(menu, IDM_DEBUGLOG, MF_BYCOMMAND | MF_CHECKED); - } - - HMENU allSkinsMenu = GetSubMenu(menu, 4); - if (allSkinsMenu) - { - if (!rainmeter.m_SkinRegistry.IsEmpty()) - { - DeleteMenu(allSkinsMenu, 0, MF_BYPOSITION); // "No skins available" menuitem - CreateAllSkinsMenu(allSkinsMenu); - } - - if (rainmeter.m_DisableDragging) - { - CheckMenuItem(allSkinsMenu, IDM_DISABLEDRAG, MF_BYCOMMAND | MF_CHECKED); - } - } - - HMENU layoutMenu = GetSubMenu(menu, 5); - if (layoutMenu) - { - if (!rainmeter.m_Layouts.empty()) - { - DeleteMenu(layoutMenu, 0, MF_BYPOSITION); // "No layouts available" menuitem - CreateLayoutMenu(layoutMenu); - } - } - - if (meterWindow) - { - HMENU rainmeterMenu = menu; - menu = CreateSkinMenu(meterWindow, 0, allSkinsMenu); - - InsertMenu(menu, IDM_CLOSESKIN, MF_BYCOMMAND | MF_POPUP, (UINT_PTR)rainmeterMenu, L"Rainmeter"); - InsertMenu(menu, IDM_CLOSESKIN, MF_BYCOMMAND | MF_SEPARATOR, 0, nullptr); - } - else - { - InsertMenu(menu, 12, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); - - // Create a menu for all active skins - int index = 0; - std::map::const_iterator iter = rainmeter.m_MeterWindows.begin(); - for (; iter != rainmeter.m_MeterWindows.end(); ++iter) - { - MeterWindow* mw = ((*iter).second); - HMENU skinMenu = CreateSkinMenu(mw, index, allSkinsMenu); - InsertMenu(menu, 12, MF_BYPOSITION | MF_POPUP, (UINT_PTR)skinMenu, mw->GetFolderPath().c_str()); - ++index; - } - - // Add update notification item - if (rainmeter.m_NewVersion) - { - InsertMenu(menu, 0, MF_BYPOSITION, IDM_NEW_VERSION, GetString(ID_STR_UPDATEAVAILABLE)); - HiliteMenuItem(rainmeter.GetTrayWindow()->GetWindow(), menu, 0, MF_BYPOSITION | MF_HILITE); - InsertMenu(menu, 1, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); - } - } - - HWND hWnd = WindowFromPoint(pos); - if (hWnd != nullptr) - { - MeterWindow* mw = rainmeter.GetMeterWindow(hWnd); - if (mw) - { - // Cancel the mouse event beforehand - mw->SetMouseLeaveEvent(true); - } - } - - DisplayMenu(pos, menu, meterWindow ? meterWindow->GetWindow() : rainmeter.m_TrayWindow->GetWindow()); - DestroyMenu(menu); - - m_MenuActive = false; -} - -void ContextMenu::ShowSkinCustomMenu(POINT pos, MeterWindow* meterWindow) -{ - if (m_MenuActive || meterWindow->IsClosing()) return; - - m_MenuActive = true; - - HMENU menu = CreatePopupMenu(); - AppendSkinCustomMenu(meterWindow, 0, menu, true); - - DisplayMenu(pos, menu, meterWindow->GetWindow()); - DestroyMenu(menu); - - m_MenuActive = false; -} - -void ContextMenu::DisplayMenu(POINT pos, HMENU menu, HWND parentWindow) -{ - // Set the window to foreground - HWND foregroundWindow = GetForegroundWindow(); - if (foregroundWindow != parentWindow) - { - const DWORD foregroundThreadID = GetWindowThreadProcessId(foregroundWindow, nullptr); - const DWORD currentThreadID = GetCurrentThreadId(); - AttachThreadInput(currentThreadID, foregroundThreadID, TRUE); - SetForegroundWindow(parentWindow); - AttachThreadInput(currentThreadID, foregroundThreadID, FALSE); - } - - // Show context menu - TrackPopupMenu( - menu, - TPM_RIGHTBUTTON | TPM_LEFTALIGN | (*GetString(ID_STR_ISRTL) == L'1' ? TPM_LAYOUTRTL : 0), - pos.x, - pos.y, - 0, - parentWindow, - nullptr); -} - -HMENU ContextMenu::CreateSkinMenu(MeterWindow* meterWindow, int index, HMENU menu) -{ - static const MenuTemplate s_Menu[] = - { - MENU_ITEM(IDM_SKIN_OPENSKINSFOLDER, 0), - MENU_SEPARATOR(), - MENU_SUBMENU(ID_STR_VARIANTS, - MENU_SEPARATOR()), - MENU_SEPARATOR(), - MENU_SUBMENU(ID_STR_SETTINGS, - MENU_SUBMENU(ID_STR_POSITION, - MENU_SUBMENU(ID_STR_DISPLAYMONITOR, - MENU_ITEM(IDM_SKIN_MONITOR_PRIMARY, ID_STR_USEDEFAULTMONITOR), - MENU_ITEM(ID_MONITOR_FIRST, ID_STR_VIRTUALSCREEN), - MENU_SEPARATOR(), - MENU_SEPARATOR(), - MENU_ITEM(IDM_SKIN_MONITOR_AUTOSELECT, ID_STR_AUTOSELECTMONITOR)), - MENU_SEPARATOR(), - MENU_ITEM(IDM_SKIN_VERYTOPMOST, ID_STR_STAYTOPMOST), - MENU_ITEM(IDM_SKIN_TOPMOST, ID_STR_TOPMOST), - MENU_ITEM(IDM_SKIN_NORMAL, ID_STR_NORMAL), - MENU_ITEM(IDM_SKIN_BOTTOM, ID_STR_BOTTOM), - MENU_ITEM(IDM_SKIN_ONDESKTOP, ID_STR_ONDESKTOP), - MENU_SEPARATOR(), - MENU_ITEM(IDM_SKIN_FROMRIGHT, ID_STR_FROMRIGHT), - MENU_ITEM(IDM_SKIN_FROMBOTTOM, ID_STR_FROMBOTTOM), - MENU_ITEM(IDM_SKIN_XPERCENTAGE, ID_STR_XASPERCENTAGE), - MENU_ITEM(IDM_SKIN_YPERCENTAGE, ID_STR_YASPERCENTAGE)), - MENU_SUBMENU(ID_STR_TRANSPARENCY, - MENU_ITEM(IDM_SKIN_TRANSPARENCY_0, ID_STR_0PERCENT), - MENU_ITEM(IDM_SKIN_TRANSPARENCY_10, ID_STR_10PERCENT), - MENU_ITEM(IDM_SKIN_TRANSPARENCY_20, ID_STR_20PERCENT), - MENU_ITEM(IDM_SKIN_TRANSPARENCY_30, ID_STR_30PERCENT), - MENU_ITEM(IDM_SKIN_TRANSPARENCY_40, ID_STR_40PERCENT), - MENU_ITEM(IDM_SKIN_TRANSPARENCY_50, ID_STR_50PERCENT), - MENU_ITEM(IDM_SKIN_TRANSPARENCY_60, ID_STR_60PERCENT), - MENU_ITEM(IDM_SKIN_TRANSPARENCY_70, ID_STR_70PERCENT), - MENU_ITEM(IDM_SKIN_TRANSPARENCY_80, ID_STR_80PERCENT), - MENU_ITEM(IDM_SKIN_TRANSPARENCY_90, ID_STR_90PERCENT), - MENU_SEPARATOR(), - MENU_ITEM(IDM_SKIN_TRANSPARENCY_FADEIN, ID_STR_FADEIN), - MENU_ITEM(IDM_SKIN_TRANSPARENCY_FADEOUT, ID_STR_FADEOUT)), - MENU_SEPARATOR(), - MENU_ITEM(IDM_SKIN_HIDEONMOUSE, ID_STR_HIDEONMOUSEOVER), - MENU_ITEM(IDM_SKIN_DRAGGABLE, ID_STR_DRAGGABLE), - MENU_ITEM(IDM_SKIN_REMEMBERPOSITION, ID_STR_SAVEPOSITION), - MENU_ITEM(IDM_SKIN_SNAPTOEDGES, ID_STR_SNAPTOEDGES), - MENU_ITEM(IDM_SKIN_CLICKTHROUGH, ID_STR_CLICKTHROUGH), - MENU_ITEM(IDM_SKIN_KEEPONSCREEN, ID_STR_KEEPONSCREEN), - MENU_ITEM(IDM_SKIN_USED2D, ID_STR_USED2D)), - MENU_SEPARATOR(), - MENU_ITEM(IDM_SKIN_MANAGESKIN, ID_STR_MANAGESKIN), - MENU_ITEM(IDM_SKIN_EDITSKIN, ID_STR_EDITSKIN), - MENU_ITEM(IDM_SKIN_REFRESH, ID_STR_REFRESHSKIN), - MENU_SEPARATOR(), - MENU_ITEM(IDM_CLOSESKIN, ID_STR_UNLOADSKIN) - }; - - HMENU skinMenu = MenuTemplate::CreateMenu(s_Menu, _countof(s_Menu), GetString); - if (!skinMenu) return nullptr; - - // Tick the position - HMENU settingsMenu = GetSubMenu(skinMenu, 4); - if (settingsMenu) - { - HMENU posMenu = GetSubMenu(settingsMenu, 0); - if (posMenu) - { - const UINT checkPos = IDM_SKIN_NORMAL - (UINT)meterWindow->GetWindowZPosition(); - CheckMenuRadioItem(posMenu, checkPos, checkPos, checkPos, MF_BYCOMMAND); - - if (meterWindow->GetXFromRight()) CheckMenuItem(posMenu, IDM_SKIN_FROMRIGHT, MF_BYCOMMAND | MF_CHECKED); - if (meterWindow->GetYFromBottom()) CheckMenuItem(posMenu, IDM_SKIN_FROMBOTTOM, MF_BYCOMMAND | MF_CHECKED); - if (meterWindow->GetXPercentage()) CheckMenuItem(posMenu, IDM_SKIN_XPERCENTAGE, MF_BYCOMMAND | MF_CHECKED); - if (meterWindow->GetYPercentage()) CheckMenuItem(posMenu, IDM_SKIN_YPERCENTAGE, MF_BYCOMMAND | MF_CHECKED); - - HMENU monitorMenu = GetSubMenu(posMenu, 0); - if (monitorMenu) - { - CreateMonitorMenu(monitorMenu, meterWindow); - } - } - - // Tick the transparency - HMENU alphaMenu = GetSubMenu(settingsMenu, 1); - if (alphaMenu) - { - UINT checkPos = (UINT)(10 - meterWindow->GetAlphaValue() / 25.5); - checkPos = min(9, checkPos); - checkPos = max(0, checkPos); - CheckMenuRadioItem(alphaMenu, checkPos, checkPos, checkPos, MF_BYPOSITION); - - switch (meterWindow->GetWindowHide()) - { - case HIDEMODE_FADEIN: - CheckMenuItem(alphaMenu, IDM_SKIN_TRANSPARENCY_FADEIN, MF_BYCOMMAND | MF_CHECKED); - EnableMenuItem(alphaMenu, IDM_SKIN_TRANSPARENCY_FADEOUT, MF_BYCOMMAND | MF_GRAYED); - break; - - case HIDEMODE_FADEOUT: - CheckMenuItem(alphaMenu, IDM_SKIN_TRANSPARENCY_FADEOUT, MF_BYCOMMAND | MF_CHECKED); - EnableMenuItem(alphaMenu, IDM_SKIN_TRANSPARENCY_FADEIN, MF_BYCOMMAND | MF_GRAYED); - break; - - case HIDEMODE_HIDE: - EnableMenuItem(alphaMenu, IDM_SKIN_TRANSPARENCY_FADEIN, MF_BYCOMMAND | MF_GRAYED); - EnableMenuItem(alphaMenu, IDM_SKIN_TRANSPARENCY_FADEOUT, MF_BYCOMMAND | MF_GRAYED); - break; - } - } - - // Tick the settings - switch (meterWindow->GetWindowHide()) - { - case HIDEMODE_HIDE: - CheckMenuItem(settingsMenu, IDM_SKIN_HIDEONMOUSE, MF_BYCOMMAND | MF_CHECKED); - break; - - case HIDEMODE_FADEIN: - case HIDEMODE_FADEOUT: - EnableMenuItem(settingsMenu, IDM_SKIN_HIDEONMOUSE, MF_BYCOMMAND | MF_GRAYED); - break; - } - - if (meterWindow->GetSnapEdges()) - { - CheckMenuItem(settingsMenu, IDM_SKIN_SNAPTOEDGES, MF_BYCOMMAND | MF_CHECKED); - } - - if (meterWindow->GetSavePosition()) - { - CheckMenuItem(settingsMenu, IDM_SKIN_REMEMBERPOSITION, MF_BYCOMMAND | MF_CHECKED); - } - - if (Rainmeter::GetInstance().m_DisableDragging) - { - EnableMenuItem(settingsMenu, IDM_SKIN_DRAGGABLE, MF_BYCOMMAND | MF_GRAYED); - } - else if (meterWindow->GetWindowDraggable()) - { - CheckMenuItem(settingsMenu, IDM_SKIN_DRAGGABLE, MF_BYCOMMAND | MF_CHECKED); - } - - if (meterWindow->GetClickThrough()) - { - CheckMenuItem(settingsMenu, IDM_SKIN_CLICKTHROUGH, MF_BYCOMMAND | MF_CHECKED); - } - - if (meterWindow->GetKeepOnScreen()) - { - CheckMenuItem(settingsMenu, IDM_SKIN_KEEPONSCREEN, MF_BYCOMMAND | MF_CHECKED); - } - - if (Gfx::CanvasD2D::Initialize()) - { - if (!Rainmeter::GetInstance().GetUseD2D()) - { - EnableMenuItem(settingsMenu, IDM_SKIN_USED2D, MF_BYCOMMAND | MF_GRAYED); - } - else if (meterWindow->GetUseD2D()) - { - CheckMenuItem(settingsMenu, IDM_SKIN_USED2D, MF_BYCOMMAND | MF_CHECKED); - } - } - else - { - DeleteMenu(settingsMenu, IDM_SKIN_USED2D, MF_BYCOMMAND); - } - Gfx::CanvasD2D::Finalize(); - } - - // Add the name of the Skin to the menu - const std::wstring& skinName = meterWindow->GetFolderPath(); - ModifyMenu(skinMenu, IDM_SKIN_OPENSKINSFOLDER, MF_BYCOMMAND, IDM_SKIN_OPENSKINSFOLDER, skinName.c_str()); - SetMenuDefaultItem(skinMenu, IDM_SKIN_OPENSKINSFOLDER, FALSE); - - // Remove dummy menuitem from the variants menu - HMENU variantsMenu = GetSubMenu(skinMenu, 2); - if (variantsMenu) - { - DeleteMenu(variantsMenu, 0, MF_BYPOSITION); - } - - // Give the menuitem the unique id that depends on the skin - ChangeSkinIndex(skinMenu, index); - - // Add the variants menu - if (variantsMenu) - { - const SkinRegistry::Folder& skinFolder = *Rainmeter::GetInstance().m_SkinRegistry.FindFolder(skinName); - for (int i = 0, isize = (int)skinFolder.files.size(); i < isize; ++i) - { - InsertMenu(variantsMenu, i, MF_BYPOSITION, skinFolder.baseID + i, skinFolder.files[i].c_str()); - } - - if (skinFolder.active) - { - UINT checkPos = skinFolder.active - 1; - CheckMenuRadioItem(variantsMenu, checkPos, checkPos, checkPos, MF_BYPOSITION); - } - } - - // Add skin root menu - int itemCount = GetMenuItemCount(menu); - if (itemCount > 0) - { - std::wstring root = meterWindow->GetFolderPath(); - std::wstring::size_type pos = root.find_first_of(L'\\'); - if (pos != std::wstring::npos) - { - root.erase(pos); - } - - for (int i = 0; i < itemCount; ++i) - { - const UINT state = GetMenuState(menu, i, MF_BYPOSITION); - if (state == 0xFFFFFFFF || (state & MF_POPUP) == 0) break; - - WCHAR buffer[MAX_PATH]; - if (GetMenuString(menu, i, buffer, MAX_PATH, MF_BYPOSITION)) - { - if (_wcsicmp(root.c_str(), buffer) == 0) - { - HMENU skinRootMenu = GetSubMenu(menu, i); - if (skinRootMenu) - { - InsertMenu(skinMenu, 3, MF_BYPOSITION | MF_POPUP, (UINT_PTR)skinRootMenu, root.c_str()); - } - break; - } - } - } - } - - AppendSkinCustomMenu(meterWindow, index, skinMenu, false); - - return skinMenu; -} - -void ContextMenu::AppendSkinCustomMenu( - MeterWindow* meterWindow, int index, HMENU menu, bool standaloneMenu) -{ - // Add custom actions to the context menu - std::wstring contextTitle = meterWindow->GetParser().ReadString(L"Rainmeter", L"ContextTitle", L""); - if (contextTitle.empty()) - { - return; - } - - auto isTitleSeparator = [](const std::wstring& title) - { - return title.find_first_not_of(L'-') == std::wstring::npos; - }; - - std::wstring contextAction = meterWindow->GetParser().ReadString(L"Rainmeter", L"ContextAction", L""); - if (contextAction.empty() && !isTitleSeparator(contextTitle)) - { - return; - } - - std::vector cTitles; - WCHAR buffer[128]; - int i = 1; - - while (!contextTitle.empty() && - (!contextAction.empty() || isTitleSeparator(contextTitle)) && - (IDM_SKIN_CUSTOMCONTEXTMENU_FIRST + i - 1) <= IDM_SKIN_CUSTOMCONTEXTMENU_LAST) // Set maximum context items in resource.h - { - // Trim long titles - if (contextTitle.size() > 30) - { - contextTitle.replace(27, contextTitle.size() - 27, L"..."); - } - - cTitles.push_back(contextTitle); - - _snwprintf_s(buffer, _TRUNCATE, L"ContextTitle%i", ++i); - contextTitle = meterWindow->GetParser().ReadString(L"Rainmeter", buffer, L""); - _snwprintf_s(buffer, _TRUNCATE, L"ContextAction%i", i); - contextAction = meterWindow->GetParser().ReadString(L"Rainmeter", buffer, L""); - } - - // Build a sub-menu if more than three items - const size_t titleSize = cTitles.size(); - if (titleSize <= 3 || standaloneMenu) - { - size_t position = 0; - for (size_t i = 0; i < titleSize; ++i) - { - if (isTitleSeparator(cTitles[i])) - { - if (standaloneMenu) - { - AppendMenu(menu, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); - } - else - { - // Separators not allowed in main top-level menu - --position; - } - } - else - { - const UINT_PTR id = (index << 16) | (IDM_SKIN_CUSTOMCONTEXTMENU_FIRST + i); - InsertMenu(menu, (UINT)(position + 1), MF_BYPOSITION | MF_STRING, id, cTitles[i].c_str()); - } - - ++position; - } - - if (position != 0 && !standaloneMenu) - { - InsertMenu(menu, 1, MF_BYPOSITION | MF_STRING | MF_GRAYED, 0, L"Custom skin actions"); - InsertMenu(menu, 1, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); - } - } - else - { - HMENU customMenu = CreatePopupMenu(); - InsertMenu(menu, 1, MF_BYPOSITION | MF_POPUP, (UINT_PTR)customMenu, L"Custom skin actions"); - - for (size_t i = 0; i < titleSize; ++i) - { - if (isTitleSeparator(cTitles[i])) - { - AppendMenu(customMenu, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); - } - else - { - const UINT_PTR id = (index << 16) | (IDM_SKIN_CUSTOMCONTEXTMENU_FIRST + i); - AppendMenu(customMenu, MF_BYPOSITION | MF_STRING, id, cTitles[i].c_str()); - } - } - - InsertMenu(menu, 1, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); - } -} - -int ContextMenu::CreateAllSkinsMenuRecursive(HMENU skinMenu, int index) -{ - SkinRegistry& skinRegistry = Rainmeter::GetInstance().m_SkinRegistry; - const int initialLevel = skinRegistry.GetFolder(index).level; - int menuIndex = 0; - - const size_t max = skinRegistry.GetFolderCount(); - while (index < max) - { - const SkinRegistry::Folder& skinFolder = skinRegistry.GetFolder(index); - if (skinFolder.level != initialLevel) - { - return index - 1; - } - - HMENU subMenu = CreatePopupMenu(); - - // Add current folder - InsertMenu(skinMenu, menuIndex, MF_POPUP | MF_BYPOSITION, (UINT_PTR)subMenu, skinFolder.name.c_str()); - - // Add subfolders - const bool hasSubfolder = (index + 1) < max && skinRegistry.GetFolder(index + 1).level == initialLevel + 1; - if (hasSubfolder) - { - index = CreateAllSkinsMenuRecursive(subMenu, index + 1); - } - - // Add files - { - int fileIndex = 0; - const int fileCount = (int)skinFolder.files.size(); - for ( ; fileIndex < fileCount; ++fileIndex) - { - InsertMenu(subMenu, fileIndex, MF_STRING | MF_BYPOSITION, skinFolder.baseID + fileIndex, skinFolder.files[fileIndex].c_str()); - } - - if (skinFolder.active) - { - UINT checkPos = skinFolder.active - 1; - CheckMenuRadioItem(subMenu, checkPos, checkPos, checkPos, MF_BYPOSITION); - } - - if (hasSubfolder && fileIndex != 0) - { - InsertMenu(subMenu, fileIndex, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr); - } - } - - ++menuIndex; - ++index; - } - - return index; -} - -void ContextMenu::CreateLayoutMenu(HMENU layoutMenu) -{ - const auto& layouts = Rainmeter::GetInstance().m_Layouts; - for (size_t i = 0, isize = layouts.size(); i < isize; ++i) - { - InsertMenu(layoutMenu, (UINT)i, MF_BYPOSITION, ID_THEME_FIRST + i, layouts[i].c_str()); - } -} - -void ContextMenu::CreateMonitorMenu(HMENU monitorMenu, MeterWindow* meterWindow) -{ - const bool screenDefined = meterWindow->GetXScreenDefined(); - const int screenIndex = meterWindow->GetXScreen(); - - // for the "Specified monitor" (@n) - const size_t numOfMonitors = System::GetMonitorCount(); // intentional - const std::vector& monitors = System::GetMultiMonitorInfo().monitors; - - int i = 1; - for (auto iter = monitors.cbegin(); iter != monitors.cend(); ++iter, ++i) - { - WCHAR buffer[64]; - size_t len = _snwprintf_s(buffer, _TRUNCATE, L"@%i: ", i); - - std::wstring item(buffer, len); - - if ((*iter).monitorName.size() > 32) - { - item.append((*iter).monitorName, 0, 32); - item += L"..."; - } - else - { - item += (*iter).monitorName; - } - - const UINT flags = - MF_BYPOSITION | - ((screenDefined && screenIndex == i) ? MF_CHECKED : MF_UNCHECKED) | - ((*iter).active ? MF_ENABLED : MF_GRAYED); - InsertMenu(monitorMenu, i + 2, flags, ID_MONITOR_FIRST + i, item.c_str()); - } - - if (!screenDefined) - { - CheckMenuItem(monitorMenu, IDM_SKIN_MONITOR_PRIMARY, MF_BYCOMMAND | MF_CHECKED); - } - - if (screenDefined && screenIndex == 0) - { - CheckMenuItem(monitorMenu, ID_MONITOR_FIRST, MF_BYCOMMAND | MF_CHECKED); - } - - if (meterWindow->GetAutoSelectScreen()) - { - CheckMenuItem(monitorMenu, IDM_SKIN_MONITOR_AUTOSELECT, MF_BYCOMMAND | MF_CHECKED); - } -} - -void ContextMenu::ChangeSkinIndex(HMENU menu, int index) -{ - if (index > 0) - { - const int count = GetMenuItemCount(menu); - for (int i = 0; i < count; ++i) - { - HMENU subMenu = GetSubMenu(menu, i); - if (subMenu) - { - ChangeSkinIndex(subMenu, index); - } - else - { - MENUITEMINFO mii = {sizeof(MENUITEMINFO)}; - mii.fMask = MIIM_FTYPE | MIIM_ID; - GetMenuItemInfo(menu, i, TRUE, &mii); - if ((mii.fType & MFT_SEPARATOR) == 0) - { - mii.wID |= (index << 16); - mii.fMask = MIIM_ID; - SetMenuItemInfo(menu, i, TRUE, &mii); - } - } - } - } -} +/* + 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/MenuTemplate.h" +#include "../Common/Gfx/CanvasD2D.h" +#include "ContextMenu.h" +#include "Rainmeter.h" +#include "Litestep.h" +#include "MeterWindow.h" +#include "System.h" +#include "TrayWindow.h" +#include "resource.h" + +ContextMenu::ContextMenu() : + m_MenuActive(false) +{ +} + +/* +** Opens the context menu in given coordinates. +*/ +void ContextMenu::ShowMenu(POINT pos, MeterWindow* meterWindow) +{ + static const MenuTemplate s_Menu[] = + { + MENU_ITEM(IDM_MANAGE, ID_STR_MANAGE), + MENU_ITEM(IDM_ABOUT, ID_STR_ABOUT), + MENU_ITEM(IDM_SHOW_HELP, ID_STR_HELP), + MENU_SEPARATOR(), + MENU_SUBMENU(ID_STR_SKINS, + MENU_ITEM_GRAYED(0, ID_STR_NOSKINS), + MENU_SEPARATOR(), + MENU_ITEM(IDM_OPENSKINSFOLDER, ID_STR_OPENFOLDER), + MENU_ITEM(IDM_DISABLEDRAG, ID_STR_DISABLEDRAGGING)), + MENU_SUBMENU(ID_STR_THEMES, + MENU_ITEM_GRAYED(0, ID_STR_NOTHEMES)), + MENU_SEPARATOR(), + MENU_ITEM(IDM_EDITCONFIG, ID_STR_EDITSETTINGS), + MENU_ITEM(IDM_REFRESH, ID_STR_REFRESHALL), + MENU_SEPARATOR(), + MENU_SUBMENU(ID_STR_LOGGING, + MENU_ITEM(IDM_SHOWLOGFILE, ID_STR_SHOWLOGFILE), + MENU_SEPARATOR(), + MENU_ITEM(IDM_STARTLOG, ID_STR_STARTLOGGING), + MENU_ITEM(IDM_STOPLOG, ID_STR_STOPLOGGING), + MENU_SEPARATOR(), + MENU_ITEM(IDM_DELETELOGFILE, ID_STR_DELETELOGFILE), + MENU_ITEM(IDM_DEBUGLOG, ID_STR_DEBUGMODE)), + MENU_SEPARATOR(), + MENU_ITEM(IDM_QUIT, ID_STR_EXIT) + }; + + if (m_MenuActive || (meterWindow && meterWindow->IsClosing())) return; + + // Show context menu, if no actions were executed + HMENU menu = MenuTemplate::CreateMenu(s_Menu, _countof(s_Menu), GetString); + if (!menu) return; + + m_MenuActive = true; + Rainmeter& rainmeter = GetRainmeter(); + + SetMenuDefaultItem(menu, IDM_MANAGE, MF_BYCOMMAND); + + if (_waccess(GetLogger().GetLogFilePath().c_str(), 0) == -1) + { + EnableMenuItem(menu, IDM_SHOWLOGFILE, MF_BYCOMMAND | MF_GRAYED); + EnableMenuItem(menu, IDM_DELETELOGFILE, MF_BYCOMMAND | MF_GRAYED); + EnableMenuItem(menu, IDM_STOPLOG, MF_BYCOMMAND | MF_GRAYED); + } + else + { + EnableMenuItem( + menu, + (GetLogger().IsLogToFile()) ? IDM_STARTLOG : IDM_STOPLOG, + MF_BYCOMMAND | MF_GRAYED); + } + + if (rainmeter.m_Debug) + { + CheckMenuItem(menu, IDM_DEBUGLOG, MF_BYCOMMAND | MF_CHECKED); + } + + HMENU allSkinsMenu = GetSubMenu(menu, 4); + if (allSkinsMenu) + { + if (!rainmeter.m_SkinRegistry.IsEmpty()) + { + DeleteMenu(allSkinsMenu, 0, MF_BYPOSITION); // "No skins available" menuitem + CreateAllSkinsMenu(allSkinsMenu); + } + + if (rainmeter.m_DisableDragging) + { + CheckMenuItem(allSkinsMenu, IDM_DISABLEDRAG, MF_BYCOMMAND | MF_CHECKED); + } + } + + HMENU layoutMenu = GetSubMenu(menu, 5); + if (layoutMenu) + { + if (!rainmeter.m_Layouts.empty()) + { + DeleteMenu(layoutMenu, 0, MF_BYPOSITION); // "No layouts available" menuitem + CreateLayoutMenu(layoutMenu); + } + } + + if (meterWindow) + { + HMENU rainmeterMenu = menu; + menu = CreateSkinMenu(meterWindow, 0, allSkinsMenu); + + InsertMenu(menu, IDM_CLOSESKIN, MF_BYCOMMAND | MF_POPUP, (UINT_PTR)rainmeterMenu, L"Rainmeter"); + InsertMenu(menu, IDM_CLOSESKIN, MF_BYCOMMAND | MF_SEPARATOR, 0, nullptr); + } + else + { + InsertMenu(menu, 12, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); + + // Create a menu for all active skins + int index = 0; + std::map::const_iterator iter = rainmeter.m_MeterWindows.begin(); + for (; iter != rainmeter.m_MeterWindows.end(); ++iter) + { + MeterWindow* mw = ((*iter).second); + HMENU skinMenu = CreateSkinMenu(mw, index, allSkinsMenu); + InsertMenu(menu, 12, MF_BYPOSITION | MF_POPUP, (UINT_PTR)skinMenu, mw->GetFolderPath().c_str()); + ++index; + } + + // Add update notification item + if (rainmeter.m_NewVersion) + { + InsertMenu(menu, 0, MF_BYPOSITION, IDM_NEW_VERSION, GetString(ID_STR_UPDATEAVAILABLE)); + HiliteMenuItem(rainmeter.GetTrayWindow()->GetWindow(), menu, 0, MF_BYPOSITION | MF_HILITE); + InsertMenu(menu, 1, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); + } + } + + HWND hWnd = WindowFromPoint(pos); + if (hWnd != nullptr) + { + MeterWindow* mw = rainmeter.GetMeterWindow(hWnd); + if (mw) + { + // Cancel the mouse event beforehand + mw->SetMouseLeaveEvent(true); + } + } + + DisplayMenu(pos, menu, meterWindow ? meterWindow->GetWindow() : rainmeter.m_TrayWindow->GetWindow()); + DestroyMenu(menu); + + m_MenuActive = false; +} + +void ContextMenu::ShowSkinCustomMenu(POINT pos, MeterWindow* meterWindow) +{ + if (m_MenuActive || meterWindow->IsClosing()) return; + + m_MenuActive = true; + + HMENU menu = CreatePopupMenu(); + AppendSkinCustomMenu(meterWindow, 0, menu, true); + + DisplayMenu(pos, menu, meterWindow->GetWindow()); + DestroyMenu(menu); + + m_MenuActive = false; +} + +void ContextMenu::DisplayMenu(POINT pos, HMENU menu, HWND parentWindow) +{ + // Set the window to foreground + HWND foregroundWindow = GetForegroundWindow(); + if (foregroundWindow != parentWindow) + { + const DWORD foregroundThreadID = GetWindowThreadProcessId(foregroundWindow, nullptr); + const DWORD currentThreadID = GetCurrentThreadId(); + AttachThreadInput(currentThreadID, foregroundThreadID, TRUE); + SetForegroundWindow(parentWindow); + AttachThreadInput(currentThreadID, foregroundThreadID, FALSE); + } + + // Show context menu + TrackPopupMenu( + menu, + TPM_RIGHTBUTTON | TPM_LEFTALIGN | (*GetString(ID_STR_ISRTL) == L'1' ? TPM_LAYOUTRTL : 0), + pos.x, + pos.y, + 0, + parentWindow, + nullptr); +} + +HMENU ContextMenu::CreateSkinMenu(MeterWindow* meterWindow, int index, HMENU menu) +{ + static const MenuTemplate s_Menu[] = + { + MENU_ITEM(IDM_SKIN_OPENSKINSFOLDER, 0), + MENU_SEPARATOR(), + MENU_SUBMENU(ID_STR_VARIANTS, + MENU_SEPARATOR()), + MENU_SEPARATOR(), + MENU_SUBMENU(ID_STR_SETTINGS, + MENU_SUBMENU(ID_STR_POSITION, + MENU_SUBMENU(ID_STR_DISPLAYMONITOR, + MENU_ITEM(IDM_SKIN_MONITOR_PRIMARY, ID_STR_USEDEFAULTMONITOR), + MENU_ITEM(ID_MONITOR_FIRST, ID_STR_VIRTUALSCREEN), + MENU_SEPARATOR(), + MENU_SEPARATOR(), + MENU_ITEM(IDM_SKIN_MONITOR_AUTOSELECT, ID_STR_AUTOSELECTMONITOR)), + MENU_SEPARATOR(), + MENU_ITEM(IDM_SKIN_VERYTOPMOST, ID_STR_STAYTOPMOST), + MENU_ITEM(IDM_SKIN_TOPMOST, ID_STR_TOPMOST), + MENU_ITEM(IDM_SKIN_NORMAL, ID_STR_NORMAL), + MENU_ITEM(IDM_SKIN_BOTTOM, ID_STR_BOTTOM), + MENU_ITEM(IDM_SKIN_ONDESKTOP, ID_STR_ONDESKTOP), + MENU_SEPARATOR(), + MENU_ITEM(IDM_SKIN_FROMRIGHT, ID_STR_FROMRIGHT), + MENU_ITEM(IDM_SKIN_FROMBOTTOM, ID_STR_FROMBOTTOM), + MENU_ITEM(IDM_SKIN_XPERCENTAGE, ID_STR_XASPERCENTAGE), + MENU_ITEM(IDM_SKIN_YPERCENTAGE, ID_STR_YASPERCENTAGE)), + MENU_SUBMENU(ID_STR_TRANSPARENCY, + MENU_ITEM(IDM_SKIN_TRANSPARENCY_0, ID_STR_0PERCENT), + MENU_ITEM(IDM_SKIN_TRANSPARENCY_10, ID_STR_10PERCENT), + MENU_ITEM(IDM_SKIN_TRANSPARENCY_20, ID_STR_20PERCENT), + MENU_ITEM(IDM_SKIN_TRANSPARENCY_30, ID_STR_30PERCENT), + MENU_ITEM(IDM_SKIN_TRANSPARENCY_40, ID_STR_40PERCENT), + MENU_ITEM(IDM_SKIN_TRANSPARENCY_50, ID_STR_50PERCENT), + MENU_ITEM(IDM_SKIN_TRANSPARENCY_60, ID_STR_60PERCENT), + MENU_ITEM(IDM_SKIN_TRANSPARENCY_70, ID_STR_70PERCENT), + MENU_ITEM(IDM_SKIN_TRANSPARENCY_80, ID_STR_80PERCENT), + MENU_ITEM(IDM_SKIN_TRANSPARENCY_90, ID_STR_90PERCENT), + MENU_SEPARATOR(), + MENU_ITEM(IDM_SKIN_TRANSPARENCY_FADEIN, ID_STR_FADEIN), + MENU_ITEM(IDM_SKIN_TRANSPARENCY_FADEOUT, ID_STR_FADEOUT)), + MENU_SEPARATOR(), + MENU_ITEM(IDM_SKIN_HIDEONMOUSE, ID_STR_HIDEONMOUSEOVER), + MENU_ITEM(IDM_SKIN_DRAGGABLE, ID_STR_DRAGGABLE), + MENU_ITEM(IDM_SKIN_REMEMBERPOSITION, ID_STR_SAVEPOSITION), + MENU_ITEM(IDM_SKIN_SNAPTOEDGES, ID_STR_SNAPTOEDGES), + MENU_ITEM(IDM_SKIN_CLICKTHROUGH, ID_STR_CLICKTHROUGH), + MENU_ITEM(IDM_SKIN_KEEPONSCREEN, ID_STR_KEEPONSCREEN), + MENU_ITEM(IDM_SKIN_USED2D, ID_STR_USED2D)), + MENU_SEPARATOR(), + MENU_ITEM(IDM_SKIN_MANAGESKIN, ID_STR_MANAGESKIN), + MENU_ITEM(IDM_SKIN_EDITSKIN, ID_STR_EDITSKIN), + MENU_ITEM(IDM_SKIN_REFRESH, ID_STR_REFRESHSKIN), + MENU_SEPARATOR(), + MENU_ITEM(IDM_CLOSESKIN, ID_STR_UNLOADSKIN) + }; + + HMENU skinMenu = MenuTemplate::CreateMenu(s_Menu, _countof(s_Menu), GetString); + if (!skinMenu) return nullptr; + + // Tick the position + HMENU settingsMenu = GetSubMenu(skinMenu, 4); + if (settingsMenu) + { + HMENU posMenu = GetSubMenu(settingsMenu, 0); + if (posMenu) + { + const UINT checkPos = IDM_SKIN_NORMAL - (UINT)meterWindow->GetWindowZPosition(); + CheckMenuRadioItem(posMenu, checkPos, checkPos, checkPos, MF_BYCOMMAND); + + if (meterWindow->GetXFromRight()) CheckMenuItem(posMenu, IDM_SKIN_FROMRIGHT, MF_BYCOMMAND | MF_CHECKED); + if (meterWindow->GetYFromBottom()) CheckMenuItem(posMenu, IDM_SKIN_FROMBOTTOM, MF_BYCOMMAND | MF_CHECKED); + if (meterWindow->GetXPercentage()) CheckMenuItem(posMenu, IDM_SKIN_XPERCENTAGE, MF_BYCOMMAND | MF_CHECKED); + if (meterWindow->GetYPercentage()) CheckMenuItem(posMenu, IDM_SKIN_YPERCENTAGE, MF_BYCOMMAND | MF_CHECKED); + + HMENU monitorMenu = GetSubMenu(posMenu, 0); + if (monitorMenu) + { + CreateMonitorMenu(monitorMenu, meterWindow); + } + } + + // Tick the transparency + HMENU alphaMenu = GetSubMenu(settingsMenu, 1); + if (alphaMenu) + { + UINT checkPos = (UINT)(10 - meterWindow->GetAlphaValue() / 25.5); + checkPos = min(9, checkPos); + checkPos = max(0, checkPos); + CheckMenuRadioItem(alphaMenu, checkPos, checkPos, checkPos, MF_BYPOSITION); + + switch (meterWindow->GetWindowHide()) + { + case HIDEMODE_FADEIN: + CheckMenuItem(alphaMenu, IDM_SKIN_TRANSPARENCY_FADEIN, MF_BYCOMMAND | MF_CHECKED); + EnableMenuItem(alphaMenu, IDM_SKIN_TRANSPARENCY_FADEOUT, MF_BYCOMMAND | MF_GRAYED); + break; + + case HIDEMODE_FADEOUT: + CheckMenuItem(alphaMenu, IDM_SKIN_TRANSPARENCY_FADEOUT, MF_BYCOMMAND | MF_CHECKED); + EnableMenuItem(alphaMenu, IDM_SKIN_TRANSPARENCY_FADEIN, MF_BYCOMMAND | MF_GRAYED); + break; + + case HIDEMODE_HIDE: + EnableMenuItem(alphaMenu, IDM_SKIN_TRANSPARENCY_FADEIN, MF_BYCOMMAND | MF_GRAYED); + EnableMenuItem(alphaMenu, IDM_SKIN_TRANSPARENCY_FADEOUT, MF_BYCOMMAND | MF_GRAYED); + break; + } + } + + // Tick the settings + switch (meterWindow->GetWindowHide()) + { + case HIDEMODE_HIDE: + CheckMenuItem(settingsMenu, IDM_SKIN_HIDEONMOUSE, MF_BYCOMMAND | MF_CHECKED); + break; + + case HIDEMODE_FADEIN: + case HIDEMODE_FADEOUT: + EnableMenuItem(settingsMenu, IDM_SKIN_HIDEONMOUSE, MF_BYCOMMAND | MF_GRAYED); + break; + } + + if (meterWindow->GetSnapEdges()) + { + CheckMenuItem(settingsMenu, IDM_SKIN_SNAPTOEDGES, MF_BYCOMMAND | MF_CHECKED); + } + + if (meterWindow->GetSavePosition()) + { + CheckMenuItem(settingsMenu, IDM_SKIN_REMEMBERPOSITION, MF_BYCOMMAND | MF_CHECKED); + } + + if (GetRainmeter().m_DisableDragging) + { + EnableMenuItem(settingsMenu, IDM_SKIN_DRAGGABLE, MF_BYCOMMAND | MF_GRAYED); + } + else if (meterWindow->GetWindowDraggable()) + { + CheckMenuItem(settingsMenu, IDM_SKIN_DRAGGABLE, MF_BYCOMMAND | MF_CHECKED); + } + + if (meterWindow->GetClickThrough()) + { + CheckMenuItem(settingsMenu, IDM_SKIN_CLICKTHROUGH, MF_BYCOMMAND | MF_CHECKED); + } + + if (meterWindow->GetKeepOnScreen()) + { + CheckMenuItem(settingsMenu, IDM_SKIN_KEEPONSCREEN, MF_BYCOMMAND | MF_CHECKED); + } + + if (Gfx::CanvasD2D::Initialize()) + { + if (!GetRainmeter().GetUseD2D()) + { + EnableMenuItem(settingsMenu, IDM_SKIN_USED2D, MF_BYCOMMAND | MF_GRAYED); + } + else if (meterWindow->GetUseD2D()) + { + CheckMenuItem(settingsMenu, IDM_SKIN_USED2D, MF_BYCOMMAND | MF_CHECKED); + } + } + else + { + DeleteMenu(settingsMenu, IDM_SKIN_USED2D, MF_BYCOMMAND); + } + Gfx::CanvasD2D::Finalize(); + } + + // Add the name of the Skin to the menu + const std::wstring& skinName = meterWindow->GetFolderPath(); + ModifyMenu(skinMenu, IDM_SKIN_OPENSKINSFOLDER, MF_BYCOMMAND, IDM_SKIN_OPENSKINSFOLDER, skinName.c_str()); + SetMenuDefaultItem(skinMenu, IDM_SKIN_OPENSKINSFOLDER, FALSE); + + // Remove dummy menuitem from the variants menu + HMENU variantsMenu = GetSubMenu(skinMenu, 2); + if (variantsMenu) + { + DeleteMenu(variantsMenu, 0, MF_BYPOSITION); + } + + // Give the menuitem the unique id that depends on the skin + ChangeSkinIndex(skinMenu, index); + + // Add the variants menu + if (variantsMenu) + { + const SkinRegistry::Folder& skinFolder = *GetRainmeter().m_SkinRegistry.FindFolder(skinName); + for (int i = 0, isize = (int)skinFolder.files.size(); i < isize; ++i) + { + InsertMenu(variantsMenu, i, MF_BYPOSITION, skinFolder.baseID + i, skinFolder.files[i].c_str()); + } + + if (skinFolder.active) + { + UINT checkPos = skinFolder.active - 1; + CheckMenuRadioItem(variantsMenu, checkPos, checkPos, checkPos, MF_BYPOSITION); + } + } + + // Add skin root menu + int itemCount = GetMenuItemCount(menu); + if (itemCount > 0) + { + std::wstring root = meterWindow->GetFolderPath(); + std::wstring::size_type pos = root.find_first_of(L'\\'); + if (pos != std::wstring::npos) + { + root.erase(pos); + } + + for (int i = 0; i < itemCount; ++i) + { + const UINT state = GetMenuState(menu, i, MF_BYPOSITION); + if (state == 0xFFFFFFFF || (state & MF_POPUP) == 0) break; + + WCHAR buffer[MAX_PATH]; + if (GetMenuString(menu, i, buffer, MAX_PATH, MF_BYPOSITION)) + { + if (_wcsicmp(root.c_str(), buffer) == 0) + { + HMENU skinRootMenu = GetSubMenu(menu, i); + if (skinRootMenu) + { + InsertMenu(skinMenu, 3, MF_BYPOSITION | MF_POPUP, (UINT_PTR)skinRootMenu, root.c_str()); + } + break; + } + } + } + } + + AppendSkinCustomMenu(meterWindow, index, skinMenu, false); + + return skinMenu; +} + +void ContextMenu::AppendSkinCustomMenu( + MeterWindow* meterWindow, int index, HMENU menu, bool standaloneMenu) +{ + // Add custom actions to the context menu + std::wstring contextTitle = meterWindow->GetParser().ReadString(L"Rainmeter", L"ContextTitle", L""); + if (contextTitle.empty()) + { + return; + } + + auto isTitleSeparator = [](const std::wstring& title) + { + return title.find_first_not_of(L'-') == std::wstring::npos; + }; + + std::wstring contextAction = meterWindow->GetParser().ReadString(L"Rainmeter", L"ContextAction", L""); + if (contextAction.empty() && !isTitleSeparator(contextTitle)) + { + return; + } + + std::vector cTitles; + WCHAR buffer[128]; + int i = 1; + + while (!contextTitle.empty() && + (!contextAction.empty() || isTitleSeparator(contextTitle)) && + (IDM_SKIN_CUSTOMCONTEXTMENU_FIRST + i - 1) <= IDM_SKIN_CUSTOMCONTEXTMENU_LAST) // Set maximum context items in resource.h + { + // Trim long titles + if (contextTitle.size() > 30) + { + contextTitle.replace(27, contextTitle.size() - 27, L"..."); + } + + cTitles.push_back(contextTitle); + + _snwprintf_s(buffer, _TRUNCATE, L"ContextTitle%i", ++i); + contextTitle = meterWindow->GetParser().ReadString(L"Rainmeter", buffer, L""); + _snwprintf_s(buffer, _TRUNCATE, L"ContextAction%i", i); + contextAction = meterWindow->GetParser().ReadString(L"Rainmeter", buffer, L""); + } + + // Build a sub-menu if more than three items + const size_t titleSize = cTitles.size(); + if (titleSize <= 3 || standaloneMenu) + { + size_t position = 0; + for (size_t i = 0; i < titleSize; ++i) + { + if (isTitleSeparator(cTitles[i])) + { + if (standaloneMenu) + { + AppendMenu(menu, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); + } + else + { + // Separators not allowed in main top-level menu + --position; + } + } + else + { + const UINT_PTR id = (index << 16) | (IDM_SKIN_CUSTOMCONTEXTMENU_FIRST + i); + InsertMenu(menu, (UINT)(position + 1), MF_BYPOSITION | MF_STRING, id, cTitles[i].c_str()); + } + + ++position; + } + + if (position != 0 && !standaloneMenu) + { + InsertMenu(menu, 1, MF_BYPOSITION | MF_STRING | MF_GRAYED, 0, L"Custom skin actions"); + InsertMenu(menu, 1, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); + } + } + else + { + HMENU customMenu = CreatePopupMenu(); + InsertMenu(menu, 1, MF_BYPOSITION | MF_POPUP, (UINT_PTR)customMenu, L"Custom skin actions"); + + for (size_t i = 0; i < titleSize; ++i) + { + if (isTitleSeparator(cTitles[i])) + { + AppendMenu(customMenu, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); + } + else + { + const UINT_PTR id = (index << 16) | (IDM_SKIN_CUSTOMCONTEXTMENU_FIRST + i); + AppendMenu(customMenu, MF_BYPOSITION | MF_STRING, id, cTitles[i].c_str()); + } + } + + InsertMenu(menu, 1, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); + } +} + +int ContextMenu::CreateAllSkinsMenuRecursive(HMENU skinMenu, int index) +{ + SkinRegistry& skinRegistry = GetRainmeter().m_SkinRegistry; + const int initialLevel = skinRegistry.GetFolder(index).level; + int menuIndex = 0; + + const size_t max = skinRegistry.GetFolderCount(); + while (index < max) + { + const SkinRegistry::Folder& skinFolder = skinRegistry.GetFolder(index); + if (skinFolder.level != initialLevel) + { + return index - 1; + } + + HMENU subMenu = CreatePopupMenu(); + + // Add current folder + InsertMenu(skinMenu, menuIndex, MF_POPUP | MF_BYPOSITION, (UINT_PTR)subMenu, skinFolder.name.c_str()); + + // Add subfolders + const bool hasSubfolder = (index + 1) < max && skinRegistry.GetFolder(index + 1).level == initialLevel + 1; + if (hasSubfolder) + { + index = CreateAllSkinsMenuRecursive(subMenu, index + 1); + } + + // Add files + { + int fileIndex = 0; + const int fileCount = (int)skinFolder.files.size(); + for ( ; fileIndex < fileCount; ++fileIndex) + { + InsertMenu(subMenu, fileIndex, MF_STRING | MF_BYPOSITION, skinFolder.baseID + fileIndex, skinFolder.files[fileIndex].c_str()); + } + + if (skinFolder.active) + { + UINT checkPos = skinFolder.active - 1; + CheckMenuRadioItem(subMenu, checkPos, checkPos, checkPos, MF_BYPOSITION); + } + + if (hasSubfolder && fileIndex != 0) + { + InsertMenu(subMenu, fileIndex, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr); + } + } + + ++menuIndex; + ++index; + } + + return index; +} + +void ContextMenu::CreateLayoutMenu(HMENU layoutMenu) +{ + const auto& layouts = GetRainmeter().m_Layouts; + for (size_t i = 0, isize = layouts.size(); i < isize; ++i) + { + InsertMenu(layoutMenu, (UINT)i, MF_BYPOSITION, ID_THEME_FIRST + i, layouts[i].c_str()); + } +} + +void ContextMenu::CreateMonitorMenu(HMENU monitorMenu, MeterWindow* meterWindow) +{ + const bool screenDefined = meterWindow->GetXScreenDefined(); + const int screenIndex = meterWindow->GetXScreen(); + + // for the "Specified monitor" (@n) + const size_t numOfMonitors = System::GetMonitorCount(); // intentional + const std::vector& monitors = System::GetMultiMonitorInfo().monitors; + + int i = 1; + for (auto iter = monitors.cbegin(); iter != monitors.cend(); ++iter, ++i) + { + WCHAR buffer[64]; + size_t len = _snwprintf_s(buffer, _TRUNCATE, L"@%i: ", i); + + std::wstring item(buffer, len); + + if ((*iter).monitorName.size() > 32) + { + item.append((*iter).monitorName, 0, 32); + item += L"..."; + } + else + { + item += (*iter).monitorName; + } + + const UINT flags = + MF_BYPOSITION | + ((screenDefined && screenIndex == i) ? MF_CHECKED : MF_UNCHECKED) | + ((*iter).active ? MF_ENABLED : MF_GRAYED); + InsertMenu(monitorMenu, i + 2, flags, ID_MONITOR_FIRST + i, item.c_str()); + } + + if (!screenDefined) + { + CheckMenuItem(monitorMenu, IDM_SKIN_MONITOR_PRIMARY, MF_BYCOMMAND | MF_CHECKED); + } + + if (screenDefined && screenIndex == 0) + { + CheckMenuItem(monitorMenu, ID_MONITOR_FIRST, MF_BYCOMMAND | MF_CHECKED); + } + + if (meterWindow->GetAutoSelectScreen()) + { + CheckMenuItem(monitorMenu, IDM_SKIN_MONITOR_AUTOSELECT, MF_BYCOMMAND | MF_CHECKED); + } +} + +void ContextMenu::ChangeSkinIndex(HMENU menu, int index) +{ + if (index > 0) + { + const int count = GetMenuItemCount(menu); + for (int i = 0; i < count; ++i) + { + HMENU subMenu = GetSubMenu(menu, i); + if (subMenu) + { + ChangeSkinIndex(subMenu, index); + } + else + { + MENUITEMINFO mii = {sizeof(MENUITEMINFO)}; + mii.fMask = MIIM_FTYPE | MIIM_ID; + GetMenuItemInfo(menu, i, TRUE, &mii); + if ((mii.fType & MFT_SEPARATOR) == 0) + { + mii.wID |= (index << 16); + mii.fMask = MIIM_ID; + SetMenuItemInfo(menu, i, TRUE, &mii); + } + } + } + } +} diff --git a/Library/DialogAbout.cpp b/Library/DialogAbout.cpp index 9f3a3940..cda90bab 100644 --- a/Library/DialogAbout.cpp +++ b/Library/DialogAbout.cpp @@ -1,1331 +1,1331 @@ -/* - Copyright (C) 2011 Birunthan Mohanathas, Kimmo Pekkola - - 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 "Rainmeter.h" -#include "System.h" -#include "MeterWindow.h" -#include "TrayWindow.h" -#include "Measure.h" -#include "resource.h" -#include "DialogAbout.h" -#include "../Version.h" -#include "../Common/Platform.h" - -WINDOWPLACEMENT DialogAbout::c_WindowPlacement = {0}; -DialogAbout* DialogAbout::c_Dialog = nullptr; - -/* -** Constructor. -** -*/ -DialogAbout::DialogAbout() : Dialog() -{ -} - -/* -** Destructor. -** -*/ -DialogAbout::~DialogAbout() -{ -} - -/* -** Opens the About dialog. -** -*/ -void DialogAbout::Open(int tab) -{ - if (!c_Dialog) - { - c_Dialog = new DialogAbout(); - } - - c_Dialog->ShowDialogWindow( - GetString(ID_STR_ABOUTRAINMETER), - 0, 0, 400, 210, - DS_CENTER | WS_POPUP | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME, - WS_EX_APPWINDOW | WS_EX_CONTROLPARENT | ((*GetString(ID_STR_ISRTL) == L'1') ? WS_EX_LAYOUTRTL : 0), - Rainmeter::GetInstance().GetWindow()); - - // Fake WM_NOTIFY to change tab - NMHDR nm; - nm.code = TCN_SELCHANGE; - nm.idFrom = Id_Tab; - nm.hwndFrom = c_Dialog->GetControl(Id_Tab); - TabCtrl_SetCurSel(nm.hwndFrom, tab); - c_Dialog->OnNotify(0, (LPARAM)&nm); -} - -/* -** Opens the About dialog by tab name. -** -*/ -void DialogAbout::Open(const WCHAR* name) -{ - int tab = 0; - - if (name) - { - if (_wcsicmp(name, L"Skins") == 0 || - _wcsicmp(name, L"Measures") == 0) // For backwards compatibility - { - tab = 1; - } - else if (_wcsicmp(name, L"Plugins") == 0) - { - tab = 2; - } - else if (_wcsicmp(name, L"Version") == 0) - { - tab = 3; - } - } - - Open(tab); -} - -/* -** Shows log if dialog isn't already open. -** -*/ -void DialogAbout::ShowAboutLog() -{ - if (!c_Dialog) - { - Open(); - } -} - -void DialogAbout::AddLogItem(Logger::Level level, LPCWSTR time, LPCWSTR source, LPCWSTR message) -{ - if (c_Dialog && c_Dialog->m_TabLog.IsInitialized()) - { - c_Dialog->m_TabLog.AddItem(level, time, source, message); - } -} - -void DialogAbout::UpdateSkins() -{ - if (c_Dialog && c_Dialog->m_TabSkins.IsInitialized()) - { - c_Dialog->m_TabSkins.UpdateSkinList(); - } -} - -void DialogAbout::UpdateMeasures(MeterWindow* meterWindow) -{ - if (c_Dialog && c_Dialog->m_TabSkins.IsInitialized()) - { - c_Dialog->m_TabSkins.UpdateMeasureList(meterWindow); - } -} - -Dialog::Tab& DialogAbout::GetActiveTab() -{ - int sel = TabCtrl_GetCurSel(GetControl(Id_Tab)); - if (sel == 0) - { - return m_TabLog; - } - else if (sel == 1) - { - return m_TabSkins; - } - else if (sel == 2) - { - return m_TabPlugins; - } - else // if (sel == 3) - { - return m_TabVersion; - } -} - -INT_PTR DialogAbout::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) - { - case WM_INITDIALOG: - return OnInitDialog(wParam, lParam); - - case WM_ACTIVATE: - return OnActivate(wParam, lParam); - - case WM_COMMAND: - return OnCommand(wParam, lParam); - - case WM_NOTIFY: - return OnNotify(wParam, lParam); - - case WM_GETMINMAXINFO: - { - MINMAXINFO* mmi = (MINMAXINFO*)lParam; - mmi->ptMinTrackSize.x = 700; - mmi->ptMinTrackSize.y = 350; - } - return TRUE; - - case WM_SIZE: - { - if (wParam != SIZE_MINIMIZED) - { - int w = LOWORD(lParam); - int h = HIWORD(lParam); - RECT r; - - HWND item = GetControl(Id_Tab); - SetWindowPos(item, nullptr, 0, 0, w - 18, h - 47, SWP_NOMOVE | SWP_NOZORDER); - - item = GetControl(Id_CloseButton); - GetClientRect(item, &r); - SetWindowPos(item, nullptr, w - r.right - 9, h - r.bottom - 8, 0, 0, SWP_NOSIZE | SWP_NOZORDER); - - w -= 48; - h -= 100; - m_TabLog.Resize(w, h); - m_TabSkins.Resize(w, h); - m_TabPlugins.Resize(w, h); - m_TabVersion.Resize(w, h); - } - } - return TRUE; - - case WM_CLOSE: - { - GetWindowPlacement(m_Window, &c_WindowPlacement); - if (c_WindowPlacement.showCmd == SW_SHOWMINIMIZED) - { - c_WindowPlacement.showCmd = SW_SHOWNORMAL; - } - - delete c_Dialog; - c_Dialog = nullptr; - } - return TRUE; - } - - return FALSE; -} - -INT_PTR DialogAbout::OnInitDialog(WPARAM wParam, LPARAM lParam) -{ - static const ControlTemplate::Control s_Controls[] = - { - CT_BUTTON(Id_CloseButton, ID_STR_CLOSE, - 344, 191, 50, 14, - WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 0), - CT_TAB(Id_Tab, 0, - 6, 6, 388, 181, - WS_VISIBLE | WS_TABSTOP | TCS_FIXEDWIDTH, 0) // Last for correct tab order. - }; - - CreateControls(s_Controls, _countof(s_Controls), m_Font, GetString); - - HWND item = GetControl(Id_Tab); - m_TabLog.Create(m_Window); - m_TabSkins.Create(m_Window); - m_TabPlugins.Create(m_Window); - m_TabVersion.Create(m_Window); - - TCITEM tci = {0}; - tci.mask = TCIF_TEXT; - tci.pszText = GetString(ID_STR_LOG); - TabCtrl_InsertItem(item, 0, &tci); - tci.pszText = GetString(ID_STR_SKINS); - TabCtrl_InsertItem(item, 1, &tci); - tci.pszText = GetString(ID_STR_PLUGINS); - TabCtrl_InsertItem(item, 2, &tci); - tci.pszText = GetString(ID_STR_VERSION); - TabCtrl_InsertItem(item, 3, &tci); - - HICON hIcon = GetIcon(IDI_RAINMETER); - SendMessage(m_Window, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); - - item = GetControl(Id_CloseButton); - SendMessage(m_Window, WM_NEXTDLGCTL, (WPARAM)item, TRUE); - - if (IsWindowsVistaOrGreater()) - { - item = m_TabLog.GetControl(TabLog::Id_ItemsListView); - SetWindowTheme(item, L"explorer", nullptr); - item = m_TabSkins.GetControl(TabSkins::Id_ItemsListView); - SetWindowTheme(item, L"explorer", nullptr); - } - - if (c_WindowPlacement.length == 0) - { - c_WindowPlacement.length = sizeof(WINDOWPLACEMENT); - GetWindowPlacement(m_Window, &c_WindowPlacement); - } - SetWindowPlacement(m_Window, &c_WindowPlacement); - - return TRUE; -} - -INT_PTR DialogAbout::OnCommand(WPARAM wParam, LPARAM lParam) -{ - switch (LOWORD(wParam)) - { - case Id_CloseButton: - PostMessage(m_Window, WM_CLOSE, 0, 0); - break; - - default: - return FALSE; - } - - return TRUE; -} - -INT_PTR DialogAbout::OnNotify(WPARAM wParam, LPARAM lParam) -{ - LPNMHDR nm = (LPNMHDR)lParam; - switch (nm->idFrom) - { - case Id_Tab: - if (nm->code == TCN_SELCHANGE) - { - // Disable all tab windows first - EnableWindow(m_TabLog.GetWindow(), FALSE); - EnableWindow(m_TabSkins.GetWindow(), FALSE); - EnableWindow(m_TabPlugins.GetWindow(), FALSE); - EnableWindow(m_TabVersion.GetWindow(), FALSE); - - GetActiveTab().Activate(); - } - break; - - default: - return 1; - } - - return 0; -} - -// ----------------------------------------------------------------------------------------------- -// -// Log tab -// -// ----------------------------------------------------------------------------------------------- - -/* -** Constructor. -** -*/ -DialogAbout::TabLog::TabLog() : Tab(), - m_Error(true), - m_Warning(true), - m_Notice(true), - m_Debug(true) -{ -} - -void DialogAbout::TabLog::Create(HWND owner) -{ - Tab::CreateTabWindow(15, 30, 370, 148, owner); - - // FIXME: Temporary hack. - short buttonWidth = (short)_wtoi(GetString(ID_STR_NUM_BUTTONWIDTH)); - - static const ControlTemplate::Control s_Controls[] = - { - CT_LISTVIEW(Id_ItemsListView, 0, - 0, 0, 368, 135, - WS_VISIBLE | WS_TABSTOP | WS_BORDER | LVS_ICON | LVS_REPORT | LVS_SINGLESEL | LVS_NOSORTHEADER, 0), - CT_CHECKBOX(Id_ErrorCheckBox, ID_STR_ERROR, - 0, 139, 70, 9, - WS_VISIBLE | WS_TABSTOP, 0), - CT_CHECKBOX(Id_WarningCheckBox, ID_STR_WARNING, - 70, 139, 70, 9, - WS_VISIBLE | WS_TABSTOP, 0), - CT_CHECKBOX(Id_NoticeCheckBox, ID_STR_NOTICE, - 140, 139, 70, 9, - WS_VISIBLE | WS_TABSTOP, 0), - CT_CHECKBOX(Id_DebugCheckBox, ID_STR_DEBUG, - 210, 139, 70, 9, - WS_VISIBLE | WS_TABSTOP, 0), - CT_BUTTON(Id_ClearButton, ID_STR_CLEAR, - (368 - buttonWidth), 139, buttonWidth, 14, - WS_VISIBLE | WS_TABSTOP, 0) - }; - - CreateControls(s_Controls, _countof(s_Controls), c_Dialog->m_Font, GetString); -} - -/* -** Called when tab is displayed. -** -*/ -void DialogAbout::TabLog::Initialize() -{ - // Add columns to the list view - HWND item = GetControl(Id_ItemsListView); - ListView_SetExtendedListViewStyleEx(item, 0, LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER); - - // Set folder/.ini icons for tree list - HIMAGELIST hImageList = ImageList_Create(16, 16, ILC_COLOR32, 3, 1); - HMODULE hDLL = GetModuleHandle(L"user32"); - - HICON hIcon = (HICON)LoadImage(hDLL, MAKEINTRESOURCE(103), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); - ImageList_AddIcon(hImageList, hIcon); - DeleteObject(hIcon); - - hIcon = (HICON)LoadImage(hDLL, MAKEINTRESOURCE(101), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); - ImageList_AddIcon(hImageList, hIcon); - DeleteObject(hIcon); - - hIcon = (HICON)LoadImage(hDLL, MAKEINTRESOURCE(104), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); - ImageList_AddIcon(hImageList, hIcon); - DeleteObject(hIcon); - - ListView_SetImageList(item, (WPARAM)hImageList, LVSIL_SMALL); - - LVCOLUMN lvc; - lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; - lvc.fmt = LVCFMT_LEFT; // left-aligned column - lvc.iSubItem = 0; - lvc.cx = 75; - lvc.pszText = GetString(ID_STR_TYPE); - ListView_InsertColumn(item, 0, &lvc); - lvc.iSubItem = 1; - lvc.cx = 85; - lvc.pszText = GetString(ID_STR_TIME); - ListView_InsertColumn(item, 1, &lvc); - lvc.iSubItem = 2; - lvc.cx = 225; - lvc.pszText = GetString(ID_STR_SOURCE); - ListView_InsertColumn(item, 2, &lvc); - lvc.iSubItem = 4; - - // Start 4th column at max width - RECT rect; - lvc.cx = GetWindowRect(item, &rect) ? (rect.right - rect.left - 405) : 180; - lvc.pszText = GetString(ID_STR_MESSAGE); - ListView_InsertColumn(item, 3, &lvc); - - // Add stored entires - for (const auto& entry : GetLogger().GetEntries()) - { - AddItem(entry.level, entry.timestamp.c_str(), entry.source.c_str(), entry.message.c_str()); - } - - item = GetControl(Id_ErrorCheckBox); - Button_SetCheck(item, BST_CHECKED); - - item = GetControl(Id_WarningCheckBox); - Button_SetCheck(item, BST_CHECKED); - - item = GetControl(Id_NoticeCheckBox); - Button_SetCheck(item, BST_CHECKED); - - item = GetControl(Id_DebugCheckBox); - Button_SetCheck(item, BST_CHECKED); - - m_Initialized = true; -} - -/* -** Resizes window and repositions controls. -** -*/ -void DialogAbout::TabLog::Resize(int w, int h) -{ - SetWindowPos(m_Window, nullptr, 0, 0, w, h, SWP_NOMOVE | SWP_NOZORDER); - - // FIXME: Temporary hack. - short buttonWidth = (short)_wtoi(GetString(ID_STR_NUM_BUTTONWIDTH)); - - RECT r; - LONG bottom; - HWND item = GetControl(Id_ClearButton); - GetClientRect(item, &r); - bottom = r.bottom; - - SetWindowPos(item, nullptr, w - r.right, h - bottom, 0, 0, SWP_NOSIZE | SWP_NOZORDER); - - item = GetControl(Id_ErrorCheckBox); - GetClientRect(item, &r); - SetWindowPos(item, nullptr, 0, h - bottom, 0, 0, SWP_NOSIZE | SWP_NOZORDER); - - item = GetControl(Id_WarningCheckBox); - SetWindowPos(item, nullptr, r.right, h - bottom, 0, 0, SWP_NOSIZE | SWP_NOZORDER); - - item = GetControl(Id_NoticeCheckBox); - SetWindowPos(item, nullptr, r.right * 2, h - bottom, 0, 0, SWP_NOSIZE | SWP_NOZORDER); - - item = GetControl(Id_DebugCheckBox); - SetWindowPos(item, nullptr, r.right * 3, h - bottom, 0, 0, SWP_NOSIZE | SWP_NOZORDER); - - item = GetControl(Id_ItemsListView); - SetWindowPos(item, nullptr, 0, 0, w, h - bottom - 10, SWP_NOMOVE | SWP_NOZORDER); - - // Adjust 4th colum - LVCOLUMN lvc; - lvc.mask = LVCF_WIDTH; - lvc.cx = w - 20 - - (ListView_GetColumnWidth(item, 0) + - ListView_GetColumnWidth(item, 1) + - ListView_GetColumnWidth(item, 2)); - ListView_SetColumn(item, 3, &lvc); -} - -/* -** Adds item to log. -** -*/ -void DialogAbout::TabLog::AddItem(Logger::Level level, LPCWSTR time, LPCWSTR source, LPCWSTR message) -{ - WCHAR buffer[32]; - LVITEM vitem; - vitem.mask = LVIF_IMAGE | LVIF_TEXT; - vitem.iItem = 0; - vitem.iSubItem = 0; - vitem.pszText = buffer; - HWND item; - - switch (level) - { - case Logger::Level::Error: - if (!m_Error) return; - item = GetControl(Id_ErrorCheckBox); - vitem.iImage = 0; - break; - - case Logger::Level::Warning: - if (!m_Warning) return; - item = GetControl(Id_WarningCheckBox); - vitem.iImage = 1; - break; - - case Logger::Level::Notice: - if (!m_Notice) return; - item = GetControl(Id_NoticeCheckBox); - vitem.iImage = 2; - break; - - case Logger::Level::Debug: - if (!m_Debug) return; - item = GetControl(Id_DebugCheckBox); - vitem.iImage = I_IMAGENONE; - break; - } - - GetWindowText(item, buffer, 32); - item = GetControl(Id_ItemsListView); - ListView_InsertItem(item, &vitem); - ListView_SetItemText(item, vitem.iItem, 1, (WCHAR*)time); - ListView_SetItemText(item, vitem.iItem, 2, (WCHAR*)source); - ListView_SetItemText(item, vitem.iItem, 3, (WCHAR*)message); - if (!ListView_IsItemVisible(item, 0)) - { - ListView_Scroll(item, 0, 16); - } -} - -INT_PTR DialogAbout::TabLog::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) - { - case WM_COMMAND: - return OnCommand(wParam, lParam); - - case WM_NOTIFY: - return OnNotify(wParam, lParam); - } - - return FALSE; -} - -INT_PTR DialogAbout::TabLog::OnCommand(WPARAM wParam, LPARAM lParam) -{ - switch (LOWORD(wParam)) - { - case Id_ErrorCheckBox: - if (HIWORD(wParam) == BN_CLICKED) - { - m_Error = !m_Error; - } - break; - - case Id_WarningCheckBox: - if (HIWORD(wParam) == BN_CLICKED) - { - m_Warning = !m_Warning; - } - break; - - case Id_NoticeCheckBox: - if (HIWORD(wParam) == BN_CLICKED) - { - m_Notice = !m_Notice; - } - break; - - case Id_DebugCheckBox: - if (HIWORD(wParam) == BN_CLICKED) - { - m_Debug = !m_Debug; - } - break; - - case Id_ClearButton: - if (HIWORD(wParam) == BN_CLICKED) - { - HWND item = GetControl(Id_ItemsListView); - ListView_DeleteAllItems(item); - } - break; - - default: - return 1; - } - - return 0; -} - -INT_PTR DialogAbout::TabLog::OnNotify(WPARAM wParam, LPARAM lParam) -{ - LPNMHDR nm = (LPNMHDR)lParam; - switch (nm->code) - { - case LVN_KEYDOWN: - { - NMLVKEYDOWN* lvkd = (NMLVKEYDOWN*)nm; - if (lvkd->wVKey == 0x43 && // C key. - IsCtrlKeyDown()) - { - const int sel = ListView_GetNextItem(nm->hwndFrom, -1, LVNI_FOCUSED | LVNI_SELECTED); - if (sel != -1) - { - WCHAR buffer[512]; - - // Get message. - ListView_GetItemText(nm->hwndFrom, sel, 3, buffer, 512); - std::wstring message = buffer; - - // Get source (if any). - ListView_GetItemText(nm->hwndFrom, sel, 2, buffer, 512); - if (*buffer) - { - message += L" ("; - message += buffer; - message += L')'; - } - - System::SetClipboardText(message); - } - } - } - break; - - default: - return FALSE; - } - - return TRUE; -} - -// ----------------------------------------------------------------------------------------------- -// -// Measures tab -// -// ----------------------------------------------------------------------------------------------- - -/* -** Constructor. -** -*/ -DialogAbout::TabSkins::TabSkins() : Tab(), - m_SkinWindow() -{ -} - -void DialogAbout::TabSkins::Create(HWND owner) -{ - Tab::CreateTabWindow(15, 30, 370, 148, owner); - - static const ControlTemplate::Control s_Controls[] = - { - CT_LISTBOX(Id_SkinsListBox, 0, - 0, 0, 120, 148, - WS_VISIBLE | WS_TABSTOP | LBS_NOTIFY | LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_HSCROLL, WS_EX_CLIENTEDGE), - CT_LISTVIEW(Id_ItemsListView, 0, - 125, 0, 242, 148, - WS_VISIBLE | WS_TABSTOP | WS_BORDER | LVS_REPORT | LVS_SINGLESEL | LVS_NOSORTHEADER, 0) - }; - - CreateControls(s_Controls, _countof(s_Controls), c_Dialog->m_Font, GetString); -} - -void DialogAbout::TabSkins::Initialize() -{ - // Add columns to the list view - HWND item = GetControl(Id_ItemsListView); - ListView_SetExtendedListViewStyleEx(item, 0, LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER); - - LVGROUP lvg; - lvg.cbSize = sizeof(LVGROUP); - lvg.mask = LVGF_HEADER | LVGF_GROUPID | LVGF_STATE; - lvg.state = (IsWindowsVistaOrGreater()) ? LVGS_COLLAPSIBLE : LVGS_NORMAL; - lvg.iGroupId = 0; - lvg.pszHeader = GetString(ID_STR_MEASURES); - ListView_InsertGroup(item, 0, &lvg); - lvg.iGroupId = 1; - lvg.pszHeader = GetString(ID_STR_VARIABLES); - ListView_InsertGroup(item, 1, &lvg); - - ListView_EnableGroupView(item, TRUE); - - LVCOLUMN lvc; - lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; - lvc.fmt = LVCFMT_LEFT; - lvc.iSubItem = 0; - lvc.cx = 120; - lvc.pszText = GetString(ID_STR_NAME); - ListView_InsertColumn(item, 0, &lvc); - lvc.iSubItem = 1; - lvc.cx = 90; - lvc.pszText = GetString(ID_STR_RANGE); - ListView_InsertColumn(item, 1, &lvc); - lvc.iSubItem = 2; - - // Start 3rd column at max width - RECT rect; - lvc.cx = GetWindowRect(item, &rect) ? (rect.right - rect.left - 230) : 130; - lvc.pszText = GetString(ID_STR_VALUE); - ListView_InsertColumn(item, 2, &lvc); - - UpdateSkinList(); - - m_Initialized = true; -} - -/* -** Resizes window and repositions controls. -** -*/ -void DialogAbout::TabSkins::Resize(int w, int h) -{ - SetWindowPos(m_Window, nullptr, 0, 0, w, h, SWP_NOMOVE | SWP_NOZORDER); - - HWND item = GetControl(Id_SkinsListBox); - SetWindowPos(item, nullptr, 0, 0, 265, h, SWP_NOMOVE | SWP_NOZORDER); - - item = GetControl(Id_ItemsListView); - SetWindowPos(item, nullptr, 275, 0, w - 275, h, SWP_NOZORDER); - - // Adjust third column - LVCOLUMN lvc; - lvc.mask = LVCF_WIDTH; - lvc.cx = w - 275 - 20 - - (ListView_GetColumnWidth(item, 0) + - ListView_GetColumnWidth(item, 1)); - ListView_SetColumn(item, 2, &lvc); -} - -/* -** Updates the list of skins. -** -*/ -void DialogAbout::TabSkins::UpdateSkinList() -{ - // Delete all entries - HWND item = GetControl(Id_SkinsListBox); - ListBox_ResetContent(item); - - // Add entries for each skin - std::wstring::size_type maxLength = 0; - const std::map& windows = Rainmeter::GetInstance().GetAllMeterWindows(); - std::map::const_iterator iter = windows.begin(); - bool found = false; - for ( ; iter != windows.end(); ++iter) - { - const std::wstring& skinName = (*iter).first; - std::wstring::size_type curLength = skinName.length(); - if (curLength > maxLength) - { - maxLength = curLength; - } - - const WCHAR* name = skinName.c_str(); - int index = ListBox_AddString(item, name); - if (!found && m_SkinWindow == (*iter).second) - { - found = true; - m_SkinWindow = (*iter).second; - ListBox_SetCurSel(item, index); - } - } - - ListBox_SetHorizontalExtent(item, 6 * maxLength); - - if (!found) - { - if (windows.empty()) - { - m_SkinWindow = nullptr; - item = GetControl(Id_ItemsListView); - ListView_DeleteAllItems(item); - } - else - { - // Default to first skin - m_SkinWindow = (*windows.begin()).second; - ListBox_SetCurSel(item, 0); - UpdateMeasureList(m_SkinWindow); - } - } -} - -/* -** Updates the list of measures and values. -** -*/ -void DialogAbout::TabSkins::UpdateMeasureList(MeterWindow* meterWindow) -{ - if (!meterWindow) - { - // Find selected skin - HWND item = GetControl(Id_SkinsListBox); - int selected = (int)SendMessage(item, LB_GETCURSEL, 0, 0); - - const std::map& windows = Rainmeter::GetInstance().GetAllMeterWindows(); - std::map::const_iterator iter = windows.begin(); - while (selected && iter != windows.end()) - { - ++iter; - --selected; - } - - m_SkinWindow = (*iter).second; - } - else if (meterWindow != m_SkinWindow) - { - // Called by a skin other than currently visible one, so return - return; - } - - HWND item = GetControl(Id_ItemsListView); - SendMessage(item, WM_SETREDRAW, FALSE, 0); - int count = ListView_GetItemCount(item); - - LVITEM lvi; - lvi.mask = LVIF_TEXT | LVIF_GROUPID | LVIF_PARAM; - lvi.iSubItem = 0; - lvi.iItem = 0; - lvi.lParam = 0; - - lvi.iGroupId = 0; - const std::vector& measures = m_SkinWindow->GetMeasures(); - std::vector::const_iterator j = measures.begin(); - for ( ; j != measures.end(); ++j) - { - lvi.pszText = (WCHAR*)(*j)->GetName(); - - if (lvi.iItem < count) - { - ListView_SetItem(item, &lvi); - } - else - { - ListView_InsertItem(item, &lvi); - } - - WCHAR buffer[256]; - Measure::GetScaledValue(AUTOSCALE_ON, 1, (*j)->GetMinValue(), buffer, _countof(buffer)); - std::wstring range = buffer; - range += L" - "; - Measure::GetScaledValue(AUTOSCALE_ON, 1, (*j)->GetMaxValue(), buffer, _countof(buffer)); - range += buffer; - - ListView_SetItemText(item, lvi.iItem, 1, (WCHAR*)range.c_str()); - ListView_SetItemText(item, lvi.iItem, 2, (WCHAR*)(*j)->GetStringOrFormattedValue( - AUTOSCALE_OFF, 1, -1, false)); - ++lvi.iItem; - } - - lvi.iGroupId = 1; - const auto& variables = m_SkinWindow->GetParser().GetVariables(); - for (auto iter = variables.cbegin(); iter != variables.cend(); ++iter) - { - const WCHAR* name = (*iter).first.c_str(); - lvi.lParam = (LPARAM)name; - - if (wcscmp(name, L"@") == 0) - { - // Ignore reserved variables - continue; - } - - std::wstring tmpStr = (*iter).first; - _wcslwr(&tmpStr[0]); - lvi.pszText = (WCHAR*)tmpStr.c_str(); - - if (lvi.iItem < count) - { - ListView_SetItem(item, &lvi); - } - else - { - ListView_InsertItem(item, &lvi); - } - - ListView_SetItemText(item, lvi.iItem, 1, L""); - ListView_SetItemText(item, lvi.iItem, 2, (WCHAR*)(*iter).second.c_str()); - ++lvi.iItem; - } - - // Delete unnecessary items - while (count > lvi.iItem) - { - ListView_DeleteItem(item, lvi.iItem); - --count; - } - - int selIndex = ListView_GetNextItem(item, -1, LVNI_FOCUSED | LVNI_SELECTED); - - ListView_SortItems(item, ListSortProc, 0); - - if (selIndex != -1) - { - // Re-select previously selected item - ListView_SetItemState(item, selIndex, LVIS_FOCUSED | LVNI_SELECTED, LVIS_FOCUSED | LVNI_SELECTED); - } - - SendMessage(item, WM_SETREDRAW, TRUE, 0); -} - -int CALLBACK DialogAbout::TabSkins::ListSortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) -{ - // Measures - if (!lParam1 && !lParam2) return 0; - if (!lParam1) return -1; - if (!lParam2) return 1; - - // Variables - return wcscmp((const WCHAR*)lParam1, (const WCHAR*)lParam2); -} - -INT_PTR DialogAbout::TabSkins::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) - { - case WM_COMMAND: - return OnCommand(wParam, lParam); - - case WM_NOTIFY: - return OnNotify(wParam, lParam); - } - - return FALSE; -} - -INT_PTR DialogAbout::TabSkins::OnCommand(WPARAM wParam, LPARAM lParam) -{ - switch (LOWORD(wParam)) - { - case Id_SkinsListBox: - if (HIWORD(wParam) == LBN_SELCHANGE) - { - UpdateMeasureList(nullptr); - } - break; - - default: - return 1; - } - - return 0; -} - -INT_PTR DialogAbout::TabSkins::OnNotify(WPARAM wParam, LPARAM lParam) -{ - LPNMHDR nm = (LPNMHDR)lParam; - switch (nm->code) - { - case LVN_KEYDOWN: - { - NMLVKEYDOWN* lvkd = (NMLVKEYDOWN*)nm; - if (lvkd->wVKey == 0x43 && IsCtrlKeyDown()) // CTRL + C. - { - int sel = ListView_GetNextItem(nm->hwndFrom, -1, LVNI_FOCUSED | LVNI_SELECTED); - if (sel != -1) - { - std::wstring tmpSz(512, L'0'); - ListView_GetItemText(nm->hwndFrom, sel, 2, &tmpSz[0], 512); - System::SetClipboardText(tmpSz); - } - } - } - break; - - default: - return FALSE; - } - - return TRUE; -} - -// ----------------------------------------------------------------------------------------------- -// -// Plugins tab -// -// ----------------------------------------------------------------------------------------------- - -DialogAbout::TabPlugins::TabPlugins() : Tab() -{ -} - -void DialogAbout::TabPlugins::Create(HWND owner) -{ - Tab::CreateTabWindow(15, 30, 370, 148, owner); - - static const ControlTemplate::Control s_Controls[] = - { - CT_LISTVIEW(Id_ItemsListView, 0, - 0, 0, 368, 148, - WS_VISIBLE | WS_TABSTOP | WS_BORDER | LVS_REPORT | LVS_SINGLESEL | LVS_NOSORTHEADER, 0) - }; - - CreateControls(s_Controls, _countof(s_Controls), c_Dialog->m_Font, GetString); -} - -void DialogAbout::TabPlugins::Initialize() -{ - // Add columns to the list view - HWND item = GetControl(Id_ItemsListView); - - LVCOLUMN lvc; - lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; - lvc.fmt = LVCFMT_LEFT; // left-aligned column - lvc.iSubItem = 0; - lvc.cx = 140; - lvc.pszText = GetString(ID_STR_NAME); - ListView_InsertColumn(item, 0, &lvc); - lvc.iSubItem = 1; - lvc.cx = 80; - lvc.pszText = GetString(ID_STR_VERSION); - ListView_InsertColumn(item, 1, &lvc); - lvc.iSubItem = 2; - - // Start 3rd column at max width - RECT rect; - lvc.cx = GetWindowRect(item, &rect) ? (rect.right - rect.left - 193) : 290; - lvc.pszText = GetString(ID_STR_AUTHOR); - ListView_InsertColumn(item, 2, &lvc); - - LVITEM vitem; - vitem.mask = LVIF_TEXT; - vitem.iItem = 0; - vitem.iSubItem = 0; - - auto findPlugins = [&](const std::wstring& path) - { - std::wstring filter = path + L"*.dll"; - - WIN32_FIND_DATA fd; - HANDLE hSearch = FindFirstFile(filter.c_str(), &fd); - if (hSearch == INVALID_HANDLE_VALUE) - { - return; - } - - int index = 0; - do - { - // Try to get the version and author - std::wstring tmpSz = path + fd.cFileName; - const WCHAR* path = tmpSz.c_str(); - - vitem.iItem = index; - vitem.pszText = fd.cFileName; - - // Try to get version and author from file resources first - DWORD handle; - DWORD versionSize = GetFileVersionInfoSize(path, &handle); - if (versionSize) - { - bool found = false; - void* data = new BYTE[versionSize]; - if (GetFileVersionInfo(path, 0, versionSize, data)) - { - UINT len; - struct LANGCODEPAGE - { - WORD wLanguage; - WORD wCodePage; - } *lcp; - - if (VerQueryValue(data, L"\\VarFileInfo\\Translation", (LPVOID*)&lcp, &len)) - { - WCHAR key[64]; - LPWSTR value; - - _snwprintf_s(key, _TRUNCATE, L"\\StringFileInfo\\%04x%04x\\ProductName", lcp[0].wLanguage, lcp[0].wCodePage); - if (VerQueryValue(data, (LPTSTR)(LPCTSTR)key, (void**)&value, &len) && - wcscmp(value, L"Rainmeter") == 0) - { - ListView_InsertItem(item, &vitem); - ++index; - found = true; - - _snwprintf_s(key, _TRUNCATE, L"\\StringFileInfo\\%04x%04x\\FileVersion", lcp[0].wLanguage, lcp[0].wCodePage); - if (VerQueryValue(data, (LPTSTR)(LPCTSTR)key, (void**)&value, &len)) - { - ListView_SetItemText(item, vitem.iItem, 1, value); - } - - _snwprintf_s(key, _TRUNCATE, L"\\StringFileInfo\\%04x%04x\\LegalCopyright", lcp[0].wLanguage, lcp[0].wCodePage); - if (VerQueryValue(data, (LPTSTR)(LPCTSTR)key, (void**)&value, &len)) - { - ListView_SetItemText(item, vitem.iItem, 2, value); - } - } - } - } - - delete [] data; - if (found) continue; - } - - // Try old calling GetPluginVersion/GetPluginAuthor for backwards compatibility - DWORD err = 0; - HMODULE dll = System::RmLoadLibrary(path, &err); - if (dll) - { - ListView_InsertItem(item, &vitem); - ++index; - - GETPLUGINVERSION GetVersionFunc = (GETPLUGINVERSION)GetProcAddress(dll, "GetPluginVersion"); - if (GetVersionFunc) - { - UINT version = GetVersionFunc(); - WCHAR buffer[64]; - _snwprintf_s(buffer, _TRUNCATE, L"%u.%u", version / 1000, version % 1000); - ListView_SetItemText(item, vitem.iItem, 1, buffer); - } - - GETPLUGINAUTHOR GetAuthorFunc = (GETPLUGINAUTHOR)GetProcAddress(dll, "GetPluginAuthor"); - if (GetAuthorFunc) - { - LPCTSTR author = GetAuthorFunc(); - if (author && *author) - { - ListView_SetItemText(item, vitem.iItem, 2, (LPWSTR)author); - } - } - - FreeLibrary(dll); - } - else - { - LogErrorF(L"About Dialog - Unable to load plugin: %s (%u)", tmpSz.c_str(), err); - } - } - while (FindNextFile(hSearch, &fd)); - FindClose(hSearch); - }; - - findPlugins(Rainmeter::GetInstance().GetPluginPath()); - if (Rainmeter::GetInstance().HasUserPluginPath()) - { - findPlugins(Rainmeter::GetInstance().GetUserPluginPath()); - } - - m_Initialized = true; -} - -/* -** Resizes window and repositions controls. -** -*/ -void DialogAbout::TabPlugins::Resize(int w, int h) -{ - SetWindowPos(m_Window, nullptr, 0, 0, w, h, SWP_NOMOVE | SWP_NOZORDER); - - HWND item = GetControl(Id_ItemsListView); - SetWindowPos(item, nullptr, 0, 0, w, h, SWP_NOMOVE | SWP_NOZORDER); - - // Adjust third colum - LVCOLUMN lvc; - lvc.mask = LVCF_WIDTH; - lvc.cx = w - 20 - - (ListView_GetColumnWidth(item, 0) + - ListView_GetColumnWidth(item, 1)); - ListView_SetColumn(item, 2, &lvc); -} - -// ----------------------------------------------------------------------------------------------- -// -// Version tab -// -// ----------------------------------------------------------------------------------------------- - -/* -** Constructor. -** -*/ -DialogAbout::TabVersion::TabVersion() : Tab() -{ -} - -void DialogAbout::TabVersion::Create(HWND owner) -{ - Tab::CreateTabWindow(15, 30, 370, 148, owner); - - // FIXME: Temporary hack. - short buttonWidth = (short)_wtoi(GetString(ID_STR_NUM_BUTTONWIDTH)); - - const ControlTemplate::Control s_Controls[] = - { - CT_ICON(Id_AppIcon, 0, - 0, 8, 24, 24, - WS_VISIBLE, 0), - CT_LABEL(Id_VersionLabel, 0, - 28, 0, 300, 9, - WS_VISIBLE, 0), - CT_LINKLABEL(Id_HomeLink, ID_STR_GETLATESTVERSION, - 28, 13, 300, 9, - WS_VISIBLE, 0), - CT_LINKLABEL(Id_LicenseLink, ID_STR_COPYRIGHTNOTICE, - 28, 26, 300, 9, - WS_VISIBLE, 0), - CT_LABEL(Id_WinVerLabel, 0, - 0, 43, 360, 9, - WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), - CT_LABEL(Id_PathLabel, 0, - 0, 56, 360, 9, - WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), - CT_LABEL(Id_IniFileLabel, 0, - 0, 69, 360, 9, - WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), - CT_LABEL(Id_SkinPathLabel, 0, - 0, 82, 360, 9, - WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), - CT_BUTTON(Id_CopyButton, ID_STR_COPYTOCLIPBOARD, - 0, 98, buttonWidth + 25, 14, - WS_VISIBLE | WS_TABSTOP, 0) - }; - - CreateControls(s_Controls, _countof(s_Controls), c_Dialog->m_Font, GetString); -} - -void DialogAbout::TabVersion::Initialize() -{ - HWND item = GetControl(Id_AppIcon); - HICON icon = GetIcon(IDI_RAINMETER, true); - Static_SetIcon(item, icon); - - item = GetControl(Id_VersionLabel); - WCHAR tmpSz[64]; - _snwprintf_s(tmpSz, _TRUNCATE, L"%s%s r%i %s (%s)", APPVERSION, revision_beta ? L" beta" : L"", revision_number, APPBITS, APPDATE); - SetWindowText(item, tmpSz); - - item = GetControl(Id_WinVerLabel); - SetWindowText(item, Platform::GetPlatformFriendlyName().c_str()); - - item = GetControl(Id_PathLabel); - std::wstring text = L"Path: " + Rainmeter::GetInstance().GetPath(); - SetWindowText(item, text.c_str()); - - item = GetControl(Id_IniFileLabel); - text = L"IniFile: " + Rainmeter::GetInstance().GetIniFile(); - SetWindowText(item, text.c_str()); - - item = GetControl(Id_SkinPathLabel); - text = L"SkinPath: " + Rainmeter::GetInstance().GetSkinPath(); - SetWindowText(item, text.c_str()); - - m_Initialized = true; -} - -/* -** Resizes window and repositions controls. -** -*/ -void DialogAbout::TabVersion::Resize(int w, int h) -{ - SetWindowPos(m_Window, nullptr, 0, 0, w, h, SWP_NOMOVE | SWP_NOZORDER); -} - -INT_PTR DialogAbout::TabVersion::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) - { - case WM_COMMAND: - return OnCommand(wParam, lParam); - - case WM_NOTIFY: - return OnNotify(wParam, lParam); - } - - return FALSE; -} - -INT_PTR DialogAbout::TabVersion::OnCommand(WPARAM wParam, LPARAM lParam) -{ - switch (LOWORD(wParam)) - { - case Id_CopyButton: - { - WCHAR tmpSz[64]; - int len = _snwprintf_s(tmpSz, _TRUNCATE, L"%s%s r%i %s (%s)", APPVERSION, revision_beta ? L" beta" : L"", revision_number, APPBITS, APPDATE); - std::wstring text(tmpSz, len); - text += L'\n'; - text += Platform::GetPlatformFriendlyName(); - text += L"\nPath: "; - text += Rainmeter::GetInstance().GetPath(); - text += L"\nIniFile: "; - text += Rainmeter::GetInstance().GetIniFile(); - text += L"\nSkinPath: "; - text += Rainmeter::GetInstance().GetSkinPath(); - System::SetClipboardText(text); - } - break; - - default: - return 1; - } - - return 0; -} - -INT_PTR DialogAbout::TabVersion::OnNotify(WPARAM wParam, LPARAM lParam) -{ - LPNMHDR nm = (LPNMHDR)lParam; - switch (nm->code) - { - case NM_CLICK: - if (nm->idFrom == Id_HomeLink) - { - CommandHandler::RunFile(L"http://rainmeter.net"); - } - else if (nm->idFrom == Id_HomeLink) - { - CommandHandler::RunFile(L"http://gnu.org/licenses"); - } - break; - - default: - return FALSE; - } - - return TRUE; -} +/* + Copyright (C) 2011 Birunthan Mohanathas, Kimmo Pekkola + + 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 "Rainmeter.h" +#include "System.h" +#include "MeterWindow.h" +#include "TrayWindow.h" +#include "Measure.h" +#include "resource.h" +#include "DialogAbout.h" +#include "../Version.h" +#include "../Common/Platform.h" + +WINDOWPLACEMENT DialogAbout::c_WindowPlacement = {0}; +DialogAbout* DialogAbout::c_Dialog = nullptr; + +/* +** Constructor. +** +*/ +DialogAbout::DialogAbout() : Dialog() +{ +} + +/* +** Destructor. +** +*/ +DialogAbout::~DialogAbout() +{ +} + +/* +** Opens the About dialog. +** +*/ +void DialogAbout::Open(int tab) +{ + if (!c_Dialog) + { + c_Dialog = new DialogAbout(); + } + + c_Dialog->ShowDialogWindow( + GetString(ID_STR_ABOUTRAINMETER), + 0, 0, 400, 210, + DS_CENTER | WS_POPUP | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME, + WS_EX_APPWINDOW | WS_EX_CONTROLPARENT | ((*GetString(ID_STR_ISRTL) == L'1') ? WS_EX_LAYOUTRTL : 0), + GetRainmeter().GetWindow()); + + // Fake WM_NOTIFY to change tab + NMHDR nm; + nm.code = TCN_SELCHANGE; + nm.idFrom = Id_Tab; + nm.hwndFrom = c_Dialog->GetControl(Id_Tab); + TabCtrl_SetCurSel(nm.hwndFrom, tab); + c_Dialog->OnNotify(0, (LPARAM)&nm); +} + +/* +** Opens the About dialog by tab name. +** +*/ +void DialogAbout::Open(const WCHAR* name) +{ + int tab = 0; + + if (name) + { + if (_wcsicmp(name, L"Skins") == 0 || + _wcsicmp(name, L"Measures") == 0) // For backwards compatibility + { + tab = 1; + } + else if (_wcsicmp(name, L"Plugins") == 0) + { + tab = 2; + } + else if (_wcsicmp(name, L"Version") == 0) + { + tab = 3; + } + } + + Open(tab); +} + +/* +** Shows log if dialog isn't already open. +** +*/ +void DialogAbout::ShowAboutLog() +{ + if (!c_Dialog) + { + Open(); + } +} + +void DialogAbout::AddLogItem(Logger::Level level, LPCWSTR time, LPCWSTR source, LPCWSTR message) +{ + if (c_Dialog && c_Dialog->m_TabLog.IsInitialized()) + { + c_Dialog->m_TabLog.AddItem(level, time, source, message); + } +} + +void DialogAbout::UpdateSkins() +{ + if (c_Dialog && c_Dialog->m_TabSkins.IsInitialized()) + { + c_Dialog->m_TabSkins.UpdateSkinList(); + } +} + +void DialogAbout::UpdateMeasures(MeterWindow* meterWindow) +{ + if (c_Dialog && c_Dialog->m_TabSkins.IsInitialized()) + { + c_Dialog->m_TabSkins.UpdateMeasureList(meterWindow); + } +} + +Dialog::Tab& DialogAbout::GetActiveTab() +{ + int sel = TabCtrl_GetCurSel(GetControl(Id_Tab)); + if (sel == 0) + { + return m_TabLog; + } + else if (sel == 1) + { + return m_TabSkins; + } + else if (sel == 2) + { + return m_TabPlugins; + } + else // if (sel == 3) + { + return m_TabVersion; + } +} + +INT_PTR DialogAbout::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + return OnInitDialog(wParam, lParam); + + case WM_ACTIVATE: + return OnActivate(wParam, lParam); + + case WM_COMMAND: + return OnCommand(wParam, lParam); + + case WM_NOTIFY: + return OnNotify(wParam, lParam); + + case WM_GETMINMAXINFO: + { + MINMAXINFO* mmi = (MINMAXINFO*)lParam; + mmi->ptMinTrackSize.x = 700; + mmi->ptMinTrackSize.y = 350; + } + return TRUE; + + case WM_SIZE: + { + if (wParam != SIZE_MINIMIZED) + { + int w = LOWORD(lParam); + int h = HIWORD(lParam); + RECT r; + + HWND item = GetControl(Id_Tab); + SetWindowPos(item, nullptr, 0, 0, w - 18, h - 47, SWP_NOMOVE | SWP_NOZORDER); + + item = GetControl(Id_CloseButton); + GetClientRect(item, &r); + SetWindowPos(item, nullptr, w - r.right - 9, h - r.bottom - 8, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + + w -= 48; + h -= 100; + m_TabLog.Resize(w, h); + m_TabSkins.Resize(w, h); + m_TabPlugins.Resize(w, h); + m_TabVersion.Resize(w, h); + } + } + return TRUE; + + case WM_CLOSE: + { + GetWindowPlacement(m_Window, &c_WindowPlacement); + if (c_WindowPlacement.showCmd == SW_SHOWMINIMIZED) + { + c_WindowPlacement.showCmd = SW_SHOWNORMAL; + } + + delete c_Dialog; + c_Dialog = nullptr; + } + return TRUE; + } + + return FALSE; +} + +INT_PTR DialogAbout::OnInitDialog(WPARAM wParam, LPARAM lParam) +{ + static const ControlTemplate::Control s_Controls[] = + { + CT_BUTTON(Id_CloseButton, ID_STR_CLOSE, + 344, 191, 50, 14, + WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 0), + CT_TAB(Id_Tab, 0, + 6, 6, 388, 181, + WS_VISIBLE | WS_TABSTOP | TCS_FIXEDWIDTH, 0) // Last for correct tab order. + }; + + CreateControls(s_Controls, _countof(s_Controls), m_Font, GetString); + + HWND item = GetControl(Id_Tab); + m_TabLog.Create(m_Window); + m_TabSkins.Create(m_Window); + m_TabPlugins.Create(m_Window); + m_TabVersion.Create(m_Window); + + TCITEM tci = {0}; + tci.mask = TCIF_TEXT; + tci.pszText = GetString(ID_STR_LOG); + TabCtrl_InsertItem(item, 0, &tci); + tci.pszText = GetString(ID_STR_SKINS); + TabCtrl_InsertItem(item, 1, &tci); + tci.pszText = GetString(ID_STR_PLUGINS); + TabCtrl_InsertItem(item, 2, &tci); + tci.pszText = GetString(ID_STR_VERSION); + TabCtrl_InsertItem(item, 3, &tci); + + HICON hIcon = GetIcon(IDI_RAINMETER); + SendMessage(m_Window, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); + + item = GetControl(Id_CloseButton); + SendMessage(m_Window, WM_NEXTDLGCTL, (WPARAM)item, TRUE); + + if (IsWindowsVistaOrGreater()) + { + item = m_TabLog.GetControl(TabLog::Id_ItemsListView); + SetWindowTheme(item, L"explorer", nullptr); + item = m_TabSkins.GetControl(TabSkins::Id_ItemsListView); + SetWindowTheme(item, L"explorer", nullptr); + } + + if (c_WindowPlacement.length == 0) + { + c_WindowPlacement.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(m_Window, &c_WindowPlacement); + } + SetWindowPlacement(m_Window, &c_WindowPlacement); + + return TRUE; +} + +INT_PTR DialogAbout::OnCommand(WPARAM wParam, LPARAM lParam) +{ + switch (LOWORD(wParam)) + { + case Id_CloseButton: + PostMessage(m_Window, WM_CLOSE, 0, 0); + break; + + default: + return FALSE; + } + + return TRUE; +} + +INT_PTR DialogAbout::OnNotify(WPARAM wParam, LPARAM lParam) +{ + LPNMHDR nm = (LPNMHDR)lParam; + switch (nm->idFrom) + { + case Id_Tab: + if (nm->code == TCN_SELCHANGE) + { + // Disable all tab windows first + EnableWindow(m_TabLog.GetWindow(), FALSE); + EnableWindow(m_TabSkins.GetWindow(), FALSE); + EnableWindow(m_TabPlugins.GetWindow(), FALSE); + EnableWindow(m_TabVersion.GetWindow(), FALSE); + + GetActiveTab().Activate(); + } + break; + + default: + return 1; + } + + return 0; +} + +// ----------------------------------------------------------------------------------------------- +// +// Log tab +// +// ----------------------------------------------------------------------------------------------- + +/* +** Constructor. +** +*/ +DialogAbout::TabLog::TabLog() : Tab(), + m_Error(true), + m_Warning(true), + m_Notice(true), + m_Debug(true) +{ +} + +void DialogAbout::TabLog::Create(HWND owner) +{ + Tab::CreateTabWindow(15, 30, 370, 148, owner); + + // FIXME: Temporary hack. + short buttonWidth = (short)_wtoi(GetString(ID_STR_NUM_BUTTONWIDTH)); + + static const ControlTemplate::Control s_Controls[] = + { + CT_LISTVIEW(Id_ItemsListView, 0, + 0, 0, 368, 135, + WS_VISIBLE | WS_TABSTOP | WS_BORDER | LVS_ICON | LVS_REPORT | LVS_SINGLESEL | LVS_NOSORTHEADER, 0), + CT_CHECKBOX(Id_ErrorCheckBox, ID_STR_ERROR, + 0, 139, 70, 9, + WS_VISIBLE | WS_TABSTOP, 0), + CT_CHECKBOX(Id_WarningCheckBox, ID_STR_WARNING, + 70, 139, 70, 9, + WS_VISIBLE | WS_TABSTOP, 0), + CT_CHECKBOX(Id_NoticeCheckBox, ID_STR_NOTICE, + 140, 139, 70, 9, + WS_VISIBLE | WS_TABSTOP, 0), + CT_CHECKBOX(Id_DebugCheckBox, ID_STR_DEBUG, + 210, 139, 70, 9, + WS_VISIBLE | WS_TABSTOP, 0), + CT_BUTTON(Id_ClearButton, ID_STR_CLEAR, + (368 - buttonWidth), 139, buttonWidth, 14, + WS_VISIBLE | WS_TABSTOP, 0) + }; + + CreateControls(s_Controls, _countof(s_Controls), c_Dialog->m_Font, GetString); +} + +/* +** Called when tab is displayed. +** +*/ +void DialogAbout::TabLog::Initialize() +{ + // Add columns to the list view + HWND item = GetControl(Id_ItemsListView); + ListView_SetExtendedListViewStyleEx(item, 0, LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER); + + // Set folder/.ini icons for tree list + HIMAGELIST hImageList = ImageList_Create(16, 16, ILC_COLOR32, 3, 1); + HMODULE hDLL = GetModuleHandle(L"user32"); + + HICON hIcon = (HICON)LoadImage(hDLL, MAKEINTRESOURCE(103), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); + ImageList_AddIcon(hImageList, hIcon); + DeleteObject(hIcon); + + hIcon = (HICON)LoadImage(hDLL, MAKEINTRESOURCE(101), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); + ImageList_AddIcon(hImageList, hIcon); + DeleteObject(hIcon); + + hIcon = (HICON)LoadImage(hDLL, MAKEINTRESOURCE(104), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); + ImageList_AddIcon(hImageList, hIcon); + DeleteObject(hIcon); + + ListView_SetImageList(item, (WPARAM)hImageList, LVSIL_SMALL); + + LVCOLUMN lvc; + lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; + lvc.fmt = LVCFMT_LEFT; // left-aligned column + lvc.iSubItem = 0; + lvc.cx = 75; + lvc.pszText = GetString(ID_STR_TYPE); + ListView_InsertColumn(item, 0, &lvc); + lvc.iSubItem = 1; + lvc.cx = 85; + lvc.pszText = GetString(ID_STR_TIME); + ListView_InsertColumn(item, 1, &lvc); + lvc.iSubItem = 2; + lvc.cx = 225; + lvc.pszText = GetString(ID_STR_SOURCE); + ListView_InsertColumn(item, 2, &lvc); + lvc.iSubItem = 4; + + // Start 4th column at max width + RECT rect; + lvc.cx = GetWindowRect(item, &rect) ? (rect.right - rect.left - 405) : 180; + lvc.pszText = GetString(ID_STR_MESSAGE); + ListView_InsertColumn(item, 3, &lvc); + + // Add stored entires + for (const auto& entry : GetLogger().GetEntries()) + { + AddItem(entry.level, entry.timestamp.c_str(), entry.source.c_str(), entry.message.c_str()); + } + + item = GetControl(Id_ErrorCheckBox); + Button_SetCheck(item, BST_CHECKED); + + item = GetControl(Id_WarningCheckBox); + Button_SetCheck(item, BST_CHECKED); + + item = GetControl(Id_NoticeCheckBox); + Button_SetCheck(item, BST_CHECKED); + + item = GetControl(Id_DebugCheckBox); + Button_SetCheck(item, BST_CHECKED); + + m_Initialized = true; +} + +/* +** Resizes window and repositions controls. +** +*/ +void DialogAbout::TabLog::Resize(int w, int h) +{ + SetWindowPos(m_Window, nullptr, 0, 0, w, h, SWP_NOMOVE | SWP_NOZORDER); + + // FIXME: Temporary hack. + short buttonWidth = (short)_wtoi(GetString(ID_STR_NUM_BUTTONWIDTH)); + + RECT r; + LONG bottom; + HWND item = GetControl(Id_ClearButton); + GetClientRect(item, &r); + bottom = r.bottom; + + SetWindowPos(item, nullptr, w - r.right, h - bottom, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + + item = GetControl(Id_ErrorCheckBox); + GetClientRect(item, &r); + SetWindowPos(item, nullptr, 0, h - bottom, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + + item = GetControl(Id_WarningCheckBox); + SetWindowPos(item, nullptr, r.right, h - bottom, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + + item = GetControl(Id_NoticeCheckBox); + SetWindowPos(item, nullptr, r.right * 2, h - bottom, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + + item = GetControl(Id_DebugCheckBox); + SetWindowPos(item, nullptr, r.right * 3, h - bottom, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + + item = GetControl(Id_ItemsListView); + SetWindowPos(item, nullptr, 0, 0, w, h - bottom - 10, SWP_NOMOVE | SWP_NOZORDER); + + // Adjust 4th colum + LVCOLUMN lvc; + lvc.mask = LVCF_WIDTH; + lvc.cx = w - 20 - + (ListView_GetColumnWidth(item, 0) + + ListView_GetColumnWidth(item, 1) + + ListView_GetColumnWidth(item, 2)); + ListView_SetColumn(item, 3, &lvc); +} + +/* +** Adds item to log. +** +*/ +void DialogAbout::TabLog::AddItem(Logger::Level level, LPCWSTR time, LPCWSTR source, LPCWSTR message) +{ + WCHAR buffer[32]; + LVITEM vitem; + vitem.mask = LVIF_IMAGE | LVIF_TEXT; + vitem.iItem = 0; + vitem.iSubItem = 0; + vitem.pszText = buffer; + HWND item; + + switch (level) + { + case Logger::Level::Error: + if (!m_Error) return; + item = GetControl(Id_ErrorCheckBox); + vitem.iImage = 0; + break; + + case Logger::Level::Warning: + if (!m_Warning) return; + item = GetControl(Id_WarningCheckBox); + vitem.iImage = 1; + break; + + case Logger::Level::Notice: + if (!m_Notice) return; + item = GetControl(Id_NoticeCheckBox); + vitem.iImage = 2; + break; + + case Logger::Level::Debug: + if (!m_Debug) return; + item = GetControl(Id_DebugCheckBox); + vitem.iImage = I_IMAGENONE; + break; + } + + GetWindowText(item, buffer, 32); + item = GetControl(Id_ItemsListView); + ListView_InsertItem(item, &vitem); + ListView_SetItemText(item, vitem.iItem, 1, (WCHAR*)time); + ListView_SetItemText(item, vitem.iItem, 2, (WCHAR*)source); + ListView_SetItemText(item, vitem.iItem, 3, (WCHAR*)message); + if (!ListView_IsItemVisible(item, 0)) + { + ListView_Scroll(item, 0, 16); + } +} + +INT_PTR DialogAbout::TabLog::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_COMMAND: + return OnCommand(wParam, lParam); + + case WM_NOTIFY: + return OnNotify(wParam, lParam); + } + + return FALSE; +} + +INT_PTR DialogAbout::TabLog::OnCommand(WPARAM wParam, LPARAM lParam) +{ + switch (LOWORD(wParam)) + { + case Id_ErrorCheckBox: + if (HIWORD(wParam) == BN_CLICKED) + { + m_Error = !m_Error; + } + break; + + case Id_WarningCheckBox: + if (HIWORD(wParam) == BN_CLICKED) + { + m_Warning = !m_Warning; + } + break; + + case Id_NoticeCheckBox: + if (HIWORD(wParam) == BN_CLICKED) + { + m_Notice = !m_Notice; + } + break; + + case Id_DebugCheckBox: + if (HIWORD(wParam) == BN_CLICKED) + { + m_Debug = !m_Debug; + } + break; + + case Id_ClearButton: + if (HIWORD(wParam) == BN_CLICKED) + { + HWND item = GetControl(Id_ItemsListView); + ListView_DeleteAllItems(item); + } + break; + + default: + return 1; + } + + return 0; +} + +INT_PTR DialogAbout::TabLog::OnNotify(WPARAM wParam, LPARAM lParam) +{ + LPNMHDR nm = (LPNMHDR)lParam; + switch (nm->code) + { + case LVN_KEYDOWN: + { + NMLVKEYDOWN* lvkd = (NMLVKEYDOWN*)nm; + if (lvkd->wVKey == 0x43 && // C key. + IsCtrlKeyDown()) + { + const int sel = ListView_GetNextItem(nm->hwndFrom, -1, LVNI_FOCUSED | LVNI_SELECTED); + if (sel != -1) + { + WCHAR buffer[512]; + + // Get message. + ListView_GetItemText(nm->hwndFrom, sel, 3, buffer, 512); + std::wstring message = buffer; + + // Get source (if any). + ListView_GetItemText(nm->hwndFrom, sel, 2, buffer, 512); + if (*buffer) + { + message += L" ("; + message += buffer; + message += L')'; + } + + System::SetClipboardText(message); + } + } + } + break; + + default: + return FALSE; + } + + return TRUE; +} + +// ----------------------------------------------------------------------------------------------- +// +// Measures tab +// +// ----------------------------------------------------------------------------------------------- + +/* +** Constructor. +** +*/ +DialogAbout::TabSkins::TabSkins() : Tab(), + m_SkinWindow() +{ +} + +void DialogAbout::TabSkins::Create(HWND owner) +{ + Tab::CreateTabWindow(15, 30, 370, 148, owner); + + static const ControlTemplate::Control s_Controls[] = + { + CT_LISTBOX(Id_SkinsListBox, 0, + 0, 0, 120, 148, + WS_VISIBLE | WS_TABSTOP | LBS_NOTIFY | LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_HSCROLL, WS_EX_CLIENTEDGE), + CT_LISTVIEW(Id_ItemsListView, 0, + 125, 0, 242, 148, + WS_VISIBLE | WS_TABSTOP | WS_BORDER | LVS_REPORT | LVS_SINGLESEL | LVS_NOSORTHEADER, 0) + }; + + CreateControls(s_Controls, _countof(s_Controls), c_Dialog->m_Font, GetString); +} + +void DialogAbout::TabSkins::Initialize() +{ + // Add columns to the list view + HWND item = GetControl(Id_ItemsListView); + ListView_SetExtendedListViewStyleEx(item, 0, LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER); + + LVGROUP lvg; + lvg.cbSize = sizeof(LVGROUP); + lvg.mask = LVGF_HEADER | LVGF_GROUPID | LVGF_STATE; + lvg.state = (IsWindowsVistaOrGreater()) ? LVGS_COLLAPSIBLE : LVGS_NORMAL; + lvg.iGroupId = 0; + lvg.pszHeader = GetString(ID_STR_MEASURES); + ListView_InsertGroup(item, 0, &lvg); + lvg.iGroupId = 1; + lvg.pszHeader = GetString(ID_STR_VARIABLES); + ListView_InsertGroup(item, 1, &lvg); + + ListView_EnableGroupView(item, TRUE); + + LVCOLUMN lvc; + lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; + lvc.fmt = LVCFMT_LEFT; + lvc.iSubItem = 0; + lvc.cx = 120; + lvc.pszText = GetString(ID_STR_NAME); + ListView_InsertColumn(item, 0, &lvc); + lvc.iSubItem = 1; + lvc.cx = 90; + lvc.pszText = GetString(ID_STR_RANGE); + ListView_InsertColumn(item, 1, &lvc); + lvc.iSubItem = 2; + + // Start 3rd column at max width + RECT rect; + lvc.cx = GetWindowRect(item, &rect) ? (rect.right - rect.left - 230) : 130; + lvc.pszText = GetString(ID_STR_VALUE); + ListView_InsertColumn(item, 2, &lvc); + + UpdateSkinList(); + + m_Initialized = true; +} + +/* +** Resizes window and repositions controls. +** +*/ +void DialogAbout::TabSkins::Resize(int w, int h) +{ + SetWindowPos(m_Window, nullptr, 0, 0, w, h, SWP_NOMOVE | SWP_NOZORDER); + + HWND item = GetControl(Id_SkinsListBox); + SetWindowPos(item, nullptr, 0, 0, 265, h, SWP_NOMOVE | SWP_NOZORDER); + + item = GetControl(Id_ItemsListView); + SetWindowPos(item, nullptr, 275, 0, w - 275, h, SWP_NOZORDER); + + // Adjust third column + LVCOLUMN lvc; + lvc.mask = LVCF_WIDTH; + lvc.cx = w - 275 - 20 - + (ListView_GetColumnWidth(item, 0) + + ListView_GetColumnWidth(item, 1)); + ListView_SetColumn(item, 2, &lvc); +} + +/* +** Updates the list of skins. +** +*/ +void DialogAbout::TabSkins::UpdateSkinList() +{ + // Delete all entries + HWND item = GetControl(Id_SkinsListBox); + ListBox_ResetContent(item); + + // Add entries for each skin + std::wstring::size_type maxLength = 0; + const std::map& windows = GetRainmeter().GetAllMeterWindows(); + std::map::const_iterator iter = windows.begin(); + bool found = false; + for ( ; iter != windows.end(); ++iter) + { + const std::wstring& skinName = (*iter).first; + std::wstring::size_type curLength = skinName.length(); + if (curLength > maxLength) + { + maxLength = curLength; + } + + const WCHAR* name = skinName.c_str(); + int index = ListBox_AddString(item, name); + if (!found && m_SkinWindow == (*iter).second) + { + found = true; + m_SkinWindow = (*iter).second; + ListBox_SetCurSel(item, index); + } + } + + ListBox_SetHorizontalExtent(item, 6 * maxLength); + + if (!found) + { + if (windows.empty()) + { + m_SkinWindow = nullptr; + item = GetControl(Id_ItemsListView); + ListView_DeleteAllItems(item); + } + else + { + // Default to first skin + m_SkinWindow = (*windows.begin()).second; + ListBox_SetCurSel(item, 0); + UpdateMeasureList(m_SkinWindow); + } + } +} + +/* +** Updates the list of measures and values. +** +*/ +void DialogAbout::TabSkins::UpdateMeasureList(MeterWindow* meterWindow) +{ + if (!meterWindow) + { + // Find selected skin + HWND item = GetControl(Id_SkinsListBox); + int selected = (int)SendMessage(item, LB_GETCURSEL, 0, 0); + + const std::map& windows = GetRainmeter().GetAllMeterWindows(); + std::map::const_iterator iter = windows.begin(); + while (selected && iter != windows.end()) + { + ++iter; + --selected; + } + + m_SkinWindow = (*iter).second; + } + else if (meterWindow != m_SkinWindow) + { + // Called by a skin other than currently visible one, so return + return; + } + + HWND item = GetControl(Id_ItemsListView); + SendMessage(item, WM_SETREDRAW, FALSE, 0); + int count = ListView_GetItemCount(item); + + LVITEM lvi; + lvi.mask = LVIF_TEXT | LVIF_GROUPID | LVIF_PARAM; + lvi.iSubItem = 0; + lvi.iItem = 0; + lvi.lParam = 0; + + lvi.iGroupId = 0; + const std::vector& measures = m_SkinWindow->GetMeasures(); + std::vector::const_iterator j = measures.begin(); + for ( ; j != measures.end(); ++j) + { + lvi.pszText = (WCHAR*)(*j)->GetName(); + + if (lvi.iItem < count) + { + ListView_SetItem(item, &lvi); + } + else + { + ListView_InsertItem(item, &lvi); + } + + WCHAR buffer[256]; + Measure::GetScaledValue(AUTOSCALE_ON, 1, (*j)->GetMinValue(), buffer, _countof(buffer)); + std::wstring range = buffer; + range += L" - "; + Measure::GetScaledValue(AUTOSCALE_ON, 1, (*j)->GetMaxValue(), buffer, _countof(buffer)); + range += buffer; + + ListView_SetItemText(item, lvi.iItem, 1, (WCHAR*)range.c_str()); + ListView_SetItemText(item, lvi.iItem, 2, (WCHAR*)(*j)->GetStringOrFormattedValue( + AUTOSCALE_OFF, 1, -1, false)); + ++lvi.iItem; + } + + lvi.iGroupId = 1; + const auto& variables = m_SkinWindow->GetParser().GetVariables(); + for (auto iter = variables.cbegin(); iter != variables.cend(); ++iter) + { + const WCHAR* name = (*iter).first.c_str(); + lvi.lParam = (LPARAM)name; + + if (wcscmp(name, L"@") == 0) + { + // Ignore reserved variables + continue; + } + + std::wstring tmpStr = (*iter).first; + _wcslwr(&tmpStr[0]); + lvi.pszText = (WCHAR*)tmpStr.c_str(); + + if (lvi.iItem < count) + { + ListView_SetItem(item, &lvi); + } + else + { + ListView_InsertItem(item, &lvi); + } + + ListView_SetItemText(item, lvi.iItem, 1, L""); + ListView_SetItemText(item, lvi.iItem, 2, (WCHAR*)(*iter).second.c_str()); + ++lvi.iItem; + } + + // Delete unnecessary items + while (count > lvi.iItem) + { + ListView_DeleteItem(item, lvi.iItem); + --count; + } + + int selIndex = ListView_GetNextItem(item, -1, LVNI_FOCUSED | LVNI_SELECTED); + + ListView_SortItems(item, ListSortProc, 0); + + if (selIndex != -1) + { + // Re-select previously selected item + ListView_SetItemState(item, selIndex, LVIS_FOCUSED | LVNI_SELECTED, LVIS_FOCUSED | LVNI_SELECTED); + } + + SendMessage(item, WM_SETREDRAW, TRUE, 0); +} + +int CALLBACK DialogAbout::TabSkins::ListSortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) +{ + // Measures + if (!lParam1 && !lParam2) return 0; + if (!lParam1) return -1; + if (!lParam2) return 1; + + // Variables + return wcscmp((const WCHAR*)lParam1, (const WCHAR*)lParam2); +} + +INT_PTR DialogAbout::TabSkins::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_COMMAND: + return OnCommand(wParam, lParam); + + case WM_NOTIFY: + return OnNotify(wParam, lParam); + } + + return FALSE; +} + +INT_PTR DialogAbout::TabSkins::OnCommand(WPARAM wParam, LPARAM lParam) +{ + switch (LOWORD(wParam)) + { + case Id_SkinsListBox: + if (HIWORD(wParam) == LBN_SELCHANGE) + { + UpdateMeasureList(nullptr); + } + break; + + default: + return 1; + } + + return 0; +} + +INT_PTR DialogAbout::TabSkins::OnNotify(WPARAM wParam, LPARAM lParam) +{ + LPNMHDR nm = (LPNMHDR)lParam; + switch (nm->code) + { + case LVN_KEYDOWN: + { + NMLVKEYDOWN* lvkd = (NMLVKEYDOWN*)nm; + if (lvkd->wVKey == 0x43 && IsCtrlKeyDown()) // CTRL + C. + { + int sel = ListView_GetNextItem(nm->hwndFrom, -1, LVNI_FOCUSED | LVNI_SELECTED); + if (sel != -1) + { + std::wstring tmpSz(512, L'0'); + ListView_GetItemText(nm->hwndFrom, sel, 2, &tmpSz[0], 512); + System::SetClipboardText(tmpSz); + } + } + } + break; + + default: + return FALSE; + } + + return TRUE; +} + +// ----------------------------------------------------------------------------------------------- +// +// Plugins tab +// +// ----------------------------------------------------------------------------------------------- + +DialogAbout::TabPlugins::TabPlugins() : Tab() +{ +} + +void DialogAbout::TabPlugins::Create(HWND owner) +{ + Tab::CreateTabWindow(15, 30, 370, 148, owner); + + static const ControlTemplate::Control s_Controls[] = + { + CT_LISTVIEW(Id_ItemsListView, 0, + 0, 0, 368, 148, + WS_VISIBLE | WS_TABSTOP | WS_BORDER | LVS_REPORT | LVS_SINGLESEL | LVS_NOSORTHEADER, 0) + }; + + CreateControls(s_Controls, _countof(s_Controls), c_Dialog->m_Font, GetString); +} + +void DialogAbout::TabPlugins::Initialize() +{ + // Add columns to the list view + HWND item = GetControl(Id_ItemsListView); + + LVCOLUMN lvc; + lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; + lvc.fmt = LVCFMT_LEFT; // left-aligned column + lvc.iSubItem = 0; + lvc.cx = 140; + lvc.pszText = GetString(ID_STR_NAME); + ListView_InsertColumn(item, 0, &lvc); + lvc.iSubItem = 1; + lvc.cx = 80; + lvc.pszText = GetString(ID_STR_VERSION); + ListView_InsertColumn(item, 1, &lvc); + lvc.iSubItem = 2; + + // Start 3rd column at max width + RECT rect; + lvc.cx = GetWindowRect(item, &rect) ? (rect.right - rect.left - 193) : 290; + lvc.pszText = GetString(ID_STR_AUTHOR); + ListView_InsertColumn(item, 2, &lvc); + + LVITEM vitem; + vitem.mask = LVIF_TEXT; + vitem.iItem = 0; + vitem.iSubItem = 0; + + auto findPlugins = [&](const std::wstring& path) + { + std::wstring filter = path + L"*.dll"; + + WIN32_FIND_DATA fd; + HANDLE hSearch = FindFirstFile(filter.c_str(), &fd); + if (hSearch == INVALID_HANDLE_VALUE) + { + return; + } + + int index = 0; + do + { + // Try to get the version and author + std::wstring tmpSz = path + fd.cFileName; + const WCHAR* path = tmpSz.c_str(); + + vitem.iItem = index; + vitem.pszText = fd.cFileName; + + // Try to get version and author from file resources first + DWORD handle; + DWORD versionSize = GetFileVersionInfoSize(path, &handle); + if (versionSize) + { + bool found = false; + void* data = new BYTE[versionSize]; + if (GetFileVersionInfo(path, 0, versionSize, data)) + { + UINT len; + struct LANGCODEPAGE + { + WORD wLanguage; + WORD wCodePage; + } *lcp; + + if (VerQueryValue(data, L"\\VarFileInfo\\Translation", (LPVOID*)&lcp, &len)) + { + WCHAR key[64]; + LPWSTR value; + + _snwprintf_s(key, _TRUNCATE, L"\\StringFileInfo\\%04x%04x\\ProductName", lcp[0].wLanguage, lcp[0].wCodePage); + if (VerQueryValue(data, (LPTSTR)(LPCTSTR)key, (void**)&value, &len) && + wcscmp(value, L"Rainmeter") == 0) + { + ListView_InsertItem(item, &vitem); + ++index; + found = true; + + _snwprintf_s(key, _TRUNCATE, L"\\StringFileInfo\\%04x%04x\\FileVersion", lcp[0].wLanguage, lcp[0].wCodePage); + if (VerQueryValue(data, (LPTSTR)(LPCTSTR)key, (void**)&value, &len)) + { + ListView_SetItemText(item, vitem.iItem, 1, value); + } + + _snwprintf_s(key, _TRUNCATE, L"\\StringFileInfo\\%04x%04x\\LegalCopyright", lcp[0].wLanguage, lcp[0].wCodePage); + if (VerQueryValue(data, (LPTSTR)(LPCTSTR)key, (void**)&value, &len)) + { + ListView_SetItemText(item, vitem.iItem, 2, value); + } + } + } + } + + delete [] data; + if (found) continue; + } + + // Try old calling GetPluginVersion/GetPluginAuthor for backwards compatibility + DWORD err = 0; + HMODULE dll = System::RmLoadLibrary(path, &err); + if (dll) + { + ListView_InsertItem(item, &vitem); + ++index; + + GETPLUGINVERSION GetVersionFunc = (GETPLUGINVERSION)GetProcAddress(dll, "GetPluginVersion"); + if (GetVersionFunc) + { + UINT version = GetVersionFunc(); + WCHAR buffer[64]; + _snwprintf_s(buffer, _TRUNCATE, L"%u.%u", version / 1000, version % 1000); + ListView_SetItemText(item, vitem.iItem, 1, buffer); + } + + GETPLUGINAUTHOR GetAuthorFunc = (GETPLUGINAUTHOR)GetProcAddress(dll, "GetPluginAuthor"); + if (GetAuthorFunc) + { + LPCTSTR author = GetAuthorFunc(); + if (author && *author) + { + ListView_SetItemText(item, vitem.iItem, 2, (LPWSTR)author); + } + } + + FreeLibrary(dll); + } + else + { + LogErrorF(L"About Dialog - Unable to load plugin: %s (%u)", tmpSz.c_str(), err); + } + } + while (FindNextFile(hSearch, &fd)); + FindClose(hSearch); + }; + + findPlugins(GetRainmeter().GetPluginPath()); + if (GetRainmeter().HasUserPluginPath()) + { + findPlugins(GetRainmeter().GetUserPluginPath()); + } + + m_Initialized = true; +} + +/* +** Resizes window and repositions controls. +** +*/ +void DialogAbout::TabPlugins::Resize(int w, int h) +{ + SetWindowPos(m_Window, nullptr, 0, 0, w, h, SWP_NOMOVE | SWP_NOZORDER); + + HWND item = GetControl(Id_ItemsListView); + SetWindowPos(item, nullptr, 0, 0, w, h, SWP_NOMOVE | SWP_NOZORDER); + + // Adjust third colum + LVCOLUMN lvc; + lvc.mask = LVCF_WIDTH; + lvc.cx = w - 20 - + (ListView_GetColumnWidth(item, 0) + + ListView_GetColumnWidth(item, 1)); + ListView_SetColumn(item, 2, &lvc); +} + +// ----------------------------------------------------------------------------------------------- +// +// Version tab +// +// ----------------------------------------------------------------------------------------------- + +/* +** Constructor. +** +*/ +DialogAbout::TabVersion::TabVersion() : Tab() +{ +} + +void DialogAbout::TabVersion::Create(HWND owner) +{ + Tab::CreateTabWindow(15, 30, 370, 148, owner); + + // FIXME: Temporary hack. + short buttonWidth = (short)_wtoi(GetString(ID_STR_NUM_BUTTONWIDTH)); + + const ControlTemplate::Control s_Controls[] = + { + CT_ICON(Id_AppIcon, 0, + 0, 8, 24, 24, + WS_VISIBLE, 0), + CT_LABEL(Id_VersionLabel, 0, + 28, 0, 300, 9, + WS_VISIBLE, 0), + CT_LINKLABEL(Id_HomeLink, ID_STR_GETLATESTVERSION, + 28, 13, 300, 9, + WS_VISIBLE, 0), + CT_LINKLABEL(Id_LicenseLink, ID_STR_COPYRIGHTNOTICE, + 28, 26, 300, 9, + WS_VISIBLE, 0), + CT_LABEL(Id_WinVerLabel, 0, + 0, 43, 360, 9, + WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), + CT_LABEL(Id_PathLabel, 0, + 0, 56, 360, 9, + WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), + CT_LABEL(Id_IniFileLabel, 0, + 0, 69, 360, 9, + WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), + CT_LABEL(Id_SkinPathLabel, 0, + 0, 82, 360, 9, + WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), + CT_BUTTON(Id_CopyButton, ID_STR_COPYTOCLIPBOARD, + 0, 98, buttonWidth + 25, 14, + WS_VISIBLE | WS_TABSTOP, 0) + }; + + CreateControls(s_Controls, _countof(s_Controls), c_Dialog->m_Font, GetString); +} + +void DialogAbout::TabVersion::Initialize() +{ + HWND item = GetControl(Id_AppIcon); + HICON icon = GetIcon(IDI_RAINMETER, true); + Static_SetIcon(item, icon); + + item = GetControl(Id_VersionLabel); + WCHAR tmpSz[64]; + _snwprintf_s(tmpSz, _TRUNCATE, L"%s%s r%i %s (%s)", APPVERSION, revision_beta ? L" beta" : L"", revision_number, APPBITS, APPDATE); + SetWindowText(item, tmpSz); + + item = GetControl(Id_WinVerLabel); + SetWindowText(item, Platform::GetPlatformFriendlyName().c_str()); + + item = GetControl(Id_PathLabel); + std::wstring text = L"Path: " + GetRainmeter().GetPath(); + SetWindowText(item, text.c_str()); + + item = GetControl(Id_IniFileLabel); + text = L"IniFile: " + GetRainmeter().GetIniFile(); + SetWindowText(item, text.c_str()); + + item = GetControl(Id_SkinPathLabel); + text = L"SkinPath: " + GetRainmeter().GetSkinPath(); + SetWindowText(item, text.c_str()); + + m_Initialized = true; +} + +/* +** Resizes window and repositions controls. +** +*/ +void DialogAbout::TabVersion::Resize(int w, int h) +{ + SetWindowPos(m_Window, nullptr, 0, 0, w, h, SWP_NOMOVE | SWP_NOZORDER); +} + +INT_PTR DialogAbout::TabVersion::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_COMMAND: + return OnCommand(wParam, lParam); + + case WM_NOTIFY: + return OnNotify(wParam, lParam); + } + + return FALSE; +} + +INT_PTR DialogAbout::TabVersion::OnCommand(WPARAM wParam, LPARAM lParam) +{ + switch (LOWORD(wParam)) + { + case Id_CopyButton: + { + WCHAR tmpSz[64]; + int len = _snwprintf_s(tmpSz, _TRUNCATE, L"%s%s r%i %s (%s)", APPVERSION, revision_beta ? L" beta" : L"", revision_number, APPBITS, APPDATE); + std::wstring text(tmpSz, len); + text += L'\n'; + text += Platform::GetPlatformFriendlyName(); + text += L"\nPath: "; + text += GetRainmeter().GetPath(); + text += L"\nIniFile: "; + text += GetRainmeter().GetIniFile(); + text += L"\nSkinPath: "; + text += GetRainmeter().GetSkinPath(); + System::SetClipboardText(text); + } + break; + + default: + return 1; + } + + return 0; +} + +INT_PTR DialogAbout::TabVersion::OnNotify(WPARAM wParam, LPARAM lParam) +{ + LPNMHDR nm = (LPNMHDR)lParam; + switch (nm->code) + { + case NM_CLICK: + if (nm->idFrom == Id_HomeLink) + { + CommandHandler::RunFile(L"http://rainmeter.net"); + } + else if (nm->idFrom == Id_HomeLink) + { + CommandHandler::RunFile(L"http://gnu.org/licenses"); + } + break; + + default: + return FALSE; + } + + return TRUE; +} diff --git a/Library/DialogManage.cpp b/Library/DialogManage.cpp index ad48debd..eff83e47 100644 --- a/Library/DialogManage.cpp +++ b/Library/DialogManage.cpp @@ -1,2137 +1,2137 @@ -/* - Copyright (C) 2011 Birunthan Mohanathas - - 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/MenuTemplate.h" -#include "Rainmeter.h" -#include "System.h" -#include "MeterWindow.h" -#include "TrayWindow.h" -#include "Measure.h" -#include "resource.h" -#include "DialogManage.h" -#include "DialogAbout.h" -#include "../Version.h" -#include - -WINDOWPLACEMENT DialogManage::c_WindowPlacement = {0}; -DialogManage* DialogManage::c_Dialog = nullptr; - -/* -** Constructor. -** -*/ -DialogManage::DialogManage() : Dialog() -{ -} - -/* -** Destructor. -** -*/ -DialogManage::~DialogManage() -{ -} - -/* -** Opens the Manage dialog by tab name. -** -*/ -void DialogManage::Open(const WCHAR* name) -{ - int tab = 0; - - if (name) - { - if (_wcsicmp(name, L"Layouts") == 0 || - _wcsicmp(name, L"Themes") == 0) // For backwards compatibility. - { - tab = 1; - } - else if (_wcsicmp(name, L"Settings") == 0) - { - tab = 2; - } - } - - Open(tab); -} - -/* -** Opens the Manage dialog. -** -*/ -void DialogManage::Open(int tab) -{ - if (!c_Dialog) - { - c_Dialog = new DialogManage(); - } - - c_Dialog->ShowDialogWindow( - GetString(ID_STR_MANAGERAINMETER), - 0, 0, 500, 322, - DS_CENTER | WS_POPUP | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU, - WS_EX_APPWINDOW | WS_EX_CONTROLPARENT | ((*GetString(ID_STR_ISRTL) == L'1') ? WS_EX_LAYOUTRTL : 0), - Rainmeter::GetInstance().GetWindow()); - - // Fake WM_NOTIFY to change tab - NMHDR nm; - nm.code = TCN_SELCHANGE; - nm.idFrom = Id_Tab; - nm.hwndFrom = c_Dialog->GetControl(Id_Tab); - TabCtrl_SetCurSel(nm.hwndFrom, tab); - c_Dialog->OnNotify(0, (LPARAM)&nm); -} - -/* -** Opens the Manage dialog Skins tab with skin selected. -** -*/ -void DialogManage::OpenSkin(MeterWindow* meterWindow) -{ - Open(); - - if (c_Dialog) - { - std::wstring name = meterWindow->GetFolderPath() + L'\\'; - name += meterWindow->GetFileName(); - - HWND item = c_Dialog->m_TabSkins.GetControl(TabSkins::Id_SkinsTreeView); - c_Dialog->m_TabSkins.SelectTreeItem(item, TreeView_GetRoot(item), name.c_str()); - } -} - -/* -** Opens the Manage dialog tab with parameters -** -*/ -void DialogManage::Open(const WCHAR* tabName, const WCHAR* param1, const WCHAR* param2) -{ - Open(tabName); - - if (c_Dialog) - { - // "Skins" tab - if (_wcsicmp(tabName, L"Skins") == 0) - { - // |param1| represents the config (ie. "illustro\Clock") - // |param2| represents the file (ie. "Clock.ini") - - std::wstring name = param1; - - if (param2) - { - name += L'\\'; - name += param2; - } - - HWND item = c_Dialog->m_TabSkins.GetControl(TabSkins::Id_SkinsTreeView); - c_Dialog->m_TabSkins.SelectTreeItem(item, TreeView_GetRoot(item), name.c_str()); - } - // Future use: Allow optional params for different tabs - //else if (_wcsicmp(tabName, L"Layouts") == 0) - } -} - -void DialogManage::UpdateSkins(MeterWindow* meterWindow, bool deleted) -{ - if (c_Dialog && c_Dialog->m_TabSkins.IsInitialized()) - { - c_Dialog->m_TabSkins.Update(meterWindow, deleted); - } -} - -void DialogManage::UpdateLayouts() -{ - if (c_Dialog && c_Dialog->m_TabLayouts.IsInitialized()) - { - c_Dialog->m_TabLayouts.Update(); - } -} - -Dialog::Tab& DialogManage::GetActiveTab() -{ - int sel = TabCtrl_GetCurSel(GetControl(Id_Tab)); - if (sel == 0) - { - return m_TabSkins; - } - else if (sel == 1) - { - return m_TabLayouts; - } - else // if (sel == 2) - { - return m_TabSettings; - } -} - -INT_PTR DialogManage::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) - { - case WM_INITDIALOG: - return OnInitDialog(wParam, lParam); - - case WM_ACTIVATE: - return OnActivate(wParam, lParam); - - case WM_COMMAND: - return OnCommand(wParam, lParam); - - case WM_NOTIFY: - return OnNotify(wParam, lParam); - - case WM_CLOSE: - { - GetWindowPlacement(m_Window, &c_WindowPlacement); - if (c_WindowPlacement.showCmd == SW_SHOWMINIMIZED) - { - c_WindowPlacement.showCmd = SW_SHOWNORMAL; - } - - delete c_Dialog; - c_Dialog = nullptr; - } - return TRUE; - } - - return FALSE; -} - -INT_PTR DialogManage::OnInitDialog(WPARAM wParam, LPARAM lParam) -{ - // FIXME: Temporary hack. - short buttonWidth = (short)_wtoi(GetString(ID_STR_NUM_BUTTONWIDTH)); - - const ControlTemplate::Control s_Controls[] = - { - CT_BUTTON(Id_RefreshAllButton, ID_STR_REFRESHALL, - 5, 303, buttonWidth, 14, - WS_VISIBLE | WS_TABSTOP, 0), - CT_BUTTON(Id_EditSettingsButton, ID_STR_EDITSETTINGS, - buttonWidth + 9, 303, buttonWidth, 14, - WS_VISIBLE | WS_TABSTOP, 0), - CT_BUTTON(Id_OpenLogButton, ID_STR_OPENLOG, - buttonWidth + buttonWidth + 13, 303, buttonWidth, 14, - WS_VISIBLE | WS_TABSTOP, 0), - CT_BUTTON(Id_HelpButton, ID_STR_HELP, - 389, 303, 50, 14, - WS_VISIBLE | WS_TABSTOP, 0), - CT_BUTTON(Id_CloseButton, ID_STR_CLOSE, - 444, 303, 50, 14, - WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 0), - CT_TAB(Id_Tab, 0, - 6, 6, 488, 293, - WS_VISIBLE | WS_TABSTOP | TCS_FIXEDWIDTH, 0) // Last for correct tab order. - }; - - CreateControls(s_Controls, _countof(s_Controls), m_Font, GetString); - - HWND item = GetControl(Id_Tab); - m_TabSkins.Create(m_Window); - m_TabLayouts.Create(m_Window); - m_TabSettings.Create(m_Window); - - TCITEM tci = {0}; - tci.mask = TCIF_TEXT; - tci.pszText = GetString(ID_STR_SKINS); - TabCtrl_InsertItem(item, 0, &tci); - tci.pszText = GetString(ID_STR_THEMES); - TabCtrl_InsertItem(item, 1, &tci); - tci.pszText = GetString(ID_STR_SETTINGS); - TabCtrl_InsertItem(item, 2, &tci); - - HICON hIcon = GetIcon(IDI_RAINMETER); - SendMessage(m_Window, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); - - item = GetControl(Id_CloseButton); - SendMessage(m_Window, WM_NEXTDLGCTL, (WPARAM)item, TRUE); - - item = m_TabSkins.GetControl(TabSkins::Id_FileLabel); - SendMessage(item, WM_SETFONT, (WPARAM)m_FontBold, 0); - - if (IsWindowsVistaOrGreater()) - { - // Use arrows instead of plus/minus in the tree for Vista+ - item = m_TabSkins.GetControl(TabSkins::Id_SkinsTreeView); - SetWindowTheme(item, L"explorer", nullptr); - } - - if (c_WindowPlacement.length == 0) - { - c_WindowPlacement.length = sizeof(WINDOWPLACEMENT); - GetWindowPlacement(m_Window, &c_WindowPlacement); - } - - SetWindowPlacement(m_Window, &c_WindowPlacement); - - return FALSE; -} - -INT_PTR DialogManage::OnCommand(WPARAM wParam, LPARAM lParam) -{ - switch (LOWORD(wParam)) - { - case Id_RefreshAllButton: - Rainmeter::GetInstance().RefreshAll(); - break; - - case Id_EditSettingsButton: - Rainmeter::GetInstance().EditSettings(); - break; - - case Id_OpenLogButton: - DialogAbout::Open(); - break; - - case Id_CloseButton: - HandleMessage(WM_CLOSE, 0, 0); - break; - - case Id_HelpButton: - { - std::wstring url = L"http://docs.rainmeter.net/manual/user-interface/manage#"; - - Tab& tab = GetActiveTab(); - if (&tab == &m_TabSkins) - { - url += L"Skins"; - } - else if (&tab == &m_TabLayouts) - { - url += L"Layouts"; - } - else // if (&tab == &m_TabSettings) - { - url += L"Settings"; - } - - url += L"Tab"; - ShellExecute(m_Window, L"open", url.c_str(), nullptr, nullptr, SW_SHOWNORMAL); - } - break; - - default: - return FALSE; - } - - return TRUE; -} - -INT_PTR DialogManage::OnNotify(WPARAM wParam, LPARAM lParam) -{ - LPNMHDR nm = (LPNMHDR)lParam; - switch (nm->idFrom) - { - case Id_Tab: - if (nm->code == TCN_SELCHANGE) - { - // Disable all tab windows first - EnableWindow(m_TabSkins.GetWindow(), FALSE); - EnableWindow(m_TabLayouts.GetWindow(), FALSE); - EnableWindow(m_TabSettings.GetWindow(), FALSE); - - GetActiveTab().Activate(); - } - break; - - default: - return 1; - } - - return 0; -} - -// ----------------------------------------------------------------------------------------------- -// -// Skins tab -// -// ----------------------------------------------------------------------------------------------- - -/* -** Constructor. -** -*/ -DialogManage::TabSkins::TabSkins() : Tab(), - m_SkinWindow(), - m_HandleCommands(false), - m_IgnoreUpdate(false) -{ -} - -void DialogManage::TabSkins::Create(HWND owner) -{ - Tab::CreateTabWindow(15, 30, 470, 260, owner); - - // FIXME: Temporary hack. - short labelWidth = (short)_wtoi(GetString(ID_STR_NUM_LABELWIDTH)); - - const ControlTemplate::Control s_Controls[] = - { - CT_BUTTON(Id_ActiveSkinsButton, ID_STR_ACTIVESKINS, - 0, 0, 146, 14, - WS_VISIBLE | WS_TABSTOP, 0), - CT_TREEVIEW(Id_SkinsTreeView, 0, - 0, 18, 145, 221, - WS_VISIBLE | WS_TABSTOP | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_SHOWSELALWAYS | WS_VSCROLL, WS_EX_CLIENTEDGE), - CT_BUTTON(Id_CreateSkinPackageButton, ID_STR_CREATERMSKINPACKAGE, - 0, 244, 146, 14, - WS_VISIBLE | WS_TABSTOP, 0), - - CT_LABEL(Id_FileLabel, ID_STR_ELLIPSIS, - 165, 0, 130, 14, - WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), - CT_LABEL(Id_ConfigLabel, 0, - 165, 15, 130, 9, - WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), - CT_BUTTON(Id_LoadButton, ID_STR_LOAD, - 310, 0, 50, 14, - WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), - CT_BUTTON(Id_RefreshButton, ID_STR_REFRESH, - 364, 0, 50, 14, - WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), - CT_BUTTON(Id_EditButton, ID_STR_EDIT, - 418, 0, 50, 14, - WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), - - CT_LABEL(-1, ID_STR_AUTHORSC, - 165, 30, 80, 9, - WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), - CT_LABEL(Id_AuthorLabel, 0, - 230, 30, 245, 9, - WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), - CT_LABEL(-1, ID_STR_VERSIONSC, - 165, 43, 80, 9, - WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), - CT_LABEL(Id_VersionLabel, 0, - 230, 43, 245, 9, - WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), - CT_LABEL(-1, ID_STR_LICENSESC, - 165, 56, 80, 9, - WS_VISIBLE | WS_TABSTOP | SS_NOPREFIX, 0), - CT_LABEL(Id_LicenseLabel, 0, - 230, 56, 245, 9, - WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), - CT_LABEL(-1, ID_STR_INFORMATIONSC, - 165, 69, 80, 9, - WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), - CT_EDIT(Id_DescriptionLabel, 0, - 228, 69, 238, 64, - WS_VISIBLE | ES_MULTILINE | ES_READONLY, 0), - CT_LINKLABEL(Id_AddMetadataLink, ID_STR_ADDMETADATA, - 165, 142, 150, 9, - 0, 0), - - CT_LINEH(-1, ID_STR_COORDINATESSC, - 165, 156, 304, 1, - WS_VISIBLE, 0), - - CT_LABEL(-1, ID_STR_COORDINATESSC, - 165, 169, labelWidth, 9, - WS_VISIBLE, 0), - CT_EDIT(Id_XPositionEdit, 0, - 165 + labelWidth, 166, 38, 14, - WS_VISIBLE | WS_TABSTOP | WS_DISABLED, WS_EX_CLIENTEDGE), - CT_EDIT(Id_YPositionEdit, 0, - 165 + labelWidth + 42, 166, 38, 14, - WS_VISIBLE | WS_TABSTOP | WS_DISABLED, WS_EX_CLIENTEDGE), - CT_LABEL(-1, ID_STR_POSITIONSC, - 165, 190, labelWidth, 9, - WS_VISIBLE, 0), - CT_COMBOBOX(Id_ZPositionDropDownList, 0, - 165 + labelWidth, 187, 80, 14, - WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL | WS_DISABLED, 0), - CT_LABEL(-1, ID_STR_LOADORDERSC, - 165, 208, labelWidth, 9, - WS_VISIBLE, 0), - CT_EDIT(Id_LoadOrderEdit, 0, - 165 + labelWidth, 205, 80, 14, - WS_VISIBLE | WS_TABSTOP | WS_DISABLED, WS_EX_CLIENTEDGE), - CT_LABEL(-1, ID_STR_TRANSPARENCYSC, - 165, 229, labelWidth, 9, - WS_VISIBLE, 0), - CT_COMBOBOX(Id_TransparencyDropDownList, 0, - 165 + labelWidth, 226, 80, 14, - WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL | WS_DISABLED, 0), - CT_LABEL(-1, ID_STR_ONHOVERSC, - 165, 247, labelWidth, 9, - WS_VISIBLE, 0), - CT_COMBOBOX(Id_OnHoverDropDownList, 0, - 165 + labelWidth, 244, 80, 14, - WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL | WS_DISABLED, 0), - - CT_BUTTON(Id_DisplayMonitorButton, ID_STR_DISPLAYMONITOR, - 350, 166, 118, 14, - WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), - CT_CHECKBOX(Id_DraggableCheckBox, ID_STR_DRAGGABLE, - 350, 190, 118, 9, - WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), - CT_CHECKBOX(Id_ClickThroughCheckBox, ID_STR_CLICKTHROUGH, - 350, 203, 118, 9, - WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), - CT_CHECKBOX(Id_KeepOnScreenCheckBox, ID_STR_KEEPONSCREEN, - 350, 216, 118, 9, - WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), - CT_CHECKBOX(Id_SavePositionCheckBox, ID_STR_SAVEPOSITION, - 350, 229, 118, 9, - WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), - CT_CHECKBOX(Id_SnapToEdgesCheckBox, ID_STR_SNAPTOEDGES, - 350, 242, 118, 9, - WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0) - }; - - CreateControls(s_Controls, _countof(s_Controls), c_Dialog->m_Font, GetString); -} - -void DialogManage::TabSkins::Initialize() -{ - BUTTON_SPLITINFO bsi; - bsi.mask = BCSIF_SIZE; - bsi.size.cx = 20; - bsi.size.cy = 14; - - HWND item = GetControl(Id_ActiveSkinsButton); - Dialog::SetMenuButton(item); - - item = GetControl(Id_DisplayMonitorButton); - Dialog::SetMenuButton(item); - - // Load folder/.ini icons from shell32 - HIMAGELIST hImageList = ImageList_Create(16, 16, ILC_COLOR32, 2, 10); - HMODULE hDLL = GetModuleHandle(L"shell32"); - - HICON hIcon = (HICON)LoadImage(hDLL, MAKEINTRESOURCE(4), IMAGE_ICON, 16, 16, LR_SHARED); - ImageList_AddIcon(hImageList, hIcon); - hIcon = (HICON)LoadImage(hDLL, MAKEINTRESOURCE(151), IMAGE_ICON, 16, 16, LR_SHARED); - ImageList_AddIcon(hImageList, hIcon); - - // Apply icons and populate tree - item = GetControl(Id_SkinsTreeView); - TreeView_SetImageList(item, hImageList, TVSIL_NORMAL); - Update(nullptr, false); - - // Get rid of the EDITTEXT control border - item = GetControl(Id_DescriptionLabel); - SetWindowLongPtr(item, GWL_EXSTYLE, GetWindowLongPtr(item, GWL_EXSTYLE) &~ WS_EX_CLIENTEDGE); - SetWindowPos(item, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER); - - item = GetControl(Id_TransparencyDropDownList); - ComboBox_AddString(item, L"0%"); - ComboBox_AddString(item, L"10%"); - ComboBox_AddString(item, L"20%"); - ComboBox_AddString(item, L"30%"); - ComboBox_AddString(item, L"40%"); - ComboBox_AddString(item, L"50%"); - ComboBox_AddString(item, L"60%"); - ComboBox_AddString(item, L"70%"); - ComboBox_AddString(item, L"80%"); - ComboBox_AddString(item, L"90%"); - - item = GetControl(Id_ZPositionDropDownList); - ComboBox_AddString(item, GetString(ID_STR_ONDESKTOP)); - ComboBox_AddString(item, GetString(ID_STR_BOTTOM)); - ComboBox_AddString(item, GetString(ID_STR_NORMAL)); - ComboBox_AddString(item, GetString(ID_STR_TOPMOST)); - ComboBox_AddString(item, GetString(ID_STR_STAYTOPMOST)); - - item = GetControl(Id_OnHoverDropDownList); - ComboBox_AddString(item, GetString(ID_STR_DONOTHING)); - ComboBox_AddString(item, GetString(ID_STR_HIDE)); - ComboBox_AddString(item, GetString(ID_STR_FADEIN)); - ComboBox_AddString(item, GetString(ID_STR_FADEOUT)); - - m_Initialized = true; - m_HandleCommands = true; -} - -/* -** Updates metadata and settings when changed. -** -*/ -void DialogManage::TabSkins::Update(MeterWindow* meterWindow, bool deleted) -{ - if (meterWindow) - { - if (!deleted && m_IgnoreUpdate) - { - // Changed setting from dialog, no need to update - m_IgnoreUpdate = false; - } - else if (m_SkinWindow && m_SkinWindow == meterWindow) - { - // Update from currently open skin - m_HandleCommands = false; - if (deleted) - { - DisableControls(); - m_SkinWindow = nullptr; - } - else - { - SetControls(); - } - m_HandleCommands = true; - } - else if (wcscmp(meterWindow->GetFolderPath().c_str(), m_SkinFolderPath.c_str()) == 0 && - wcscmp(meterWindow->GetFileName().c_str(), m_SkinFileName.c_str()) == 0) - { - ReadSkin(); - } - } - else - { - // Populate tree - HWND item = GetControl(Id_SkinsTreeView); - TreeView_DeleteAllItems(item); - - TVINSERTSTRUCT tvi = {0}; - tvi.hInsertAfter = TVI_LAST; - tvi.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE; - tvi.item.iImage = tvi.item.iSelectedImage = 0; - - if (!Rainmeter::GetInstance().m_SkinRegistry.IsEmpty()) - { - PopulateTree(item, tvi); - } - } -} - -void DialogManage::TabSkins::SetControls() -{ - WCHAR buffer[64]; - - HWND item = GetControl(Id_EditButton); - EnableWindow(item, TRUE); - - item = GetControl(Id_LoadButton); - EnableWindow(item, TRUE); - - if (m_SkinWindow) - { - SetWindowText(item, GetString(ID_STR_UNLOAD)); - - item = GetControl(Id_RefreshButton); - EnableWindow(item, TRUE); - - item = GetControl(Id_XPositionEdit); - EnableWindow(item, TRUE); - _itow_s(m_SkinWindow->GetX(), buffer, 10); - SetWindowText(item, buffer); - - item = GetControl(Id_YPositionEdit); - EnableWindow(item, TRUE); - _itow_s(m_SkinWindow->GetY(), buffer, 10); - SetWindowText(item, buffer); - - item = GetControl(Id_DisplayMonitorButton); - EnableWindow(item, TRUE); - - item = GetControl(Id_DraggableCheckBox); - if (Rainmeter::GetInstance().GetDisableDragging()) - { - EnableWindow(item, FALSE); - Button_SetCheck(item, BST_UNCHECKED); - } - else - { - EnableWindow(item, TRUE); - Button_SetCheck(item, m_SkinWindow->GetWindowDraggable()); - } - - item = GetControl(Id_ClickThroughCheckBox); - EnableWindow(item, TRUE); - Button_SetCheck(item, m_SkinWindow->GetClickThrough()); - - item = GetControl(Id_KeepOnScreenCheckBox); - EnableWindow(item, TRUE); - Button_SetCheck(item, m_SkinWindow->GetKeepOnScreen()); - - item = GetControl(Id_SavePositionCheckBox); - EnableWindow(item, TRUE); - Button_SetCheck(item, m_SkinWindow->GetSavePosition()); - - item = GetControl(Id_SnapToEdgesCheckBox); - EnableWindow(item, TRUE); - Button_SetCheck(item, m_SkinWindow->GetSnapEdges()); - - item = GetControl(Id_TransparencyDropDownList); - EnableWindow(item, TRUE); - int value = (int)(10 - m_SkinWindow->GetAlphaValue() / 25.5); - value = min(9, value); - value = max(0, value); - ComboBox_SetCurSel(item, value); - - item = GetControl(Id_ZPositionDropDownList); - EnableWindow(item, TRUE); - ComboBox_SetCurSel(item, m_SkinWindow->GetWindowZPosition() + 2); - - item = GetControl(Id_LoadOrderEdit); - EnableWindow(item, TRUE); - _itow_s(Rainmeter::GetInstance().GetLoadOrder(m_SkinFolderPath), buffer, 10); - SetWindowText(item, buffer); - - item = GetControl(Id_OnHoverDropDownList); - EnableWindow(item, TRUE); - ComboBox_SetCurSel(item, m_SkinWindow->GetWindowHide()); - } - else - { - SetWindowText(item, GetString(ID_STR_LOAD)); - } -} - -void DialogManage::TabSkins::DisableControls(bool clear) -{ - HWND item = GetControl(Id_LoadButton); - SetWindowText(item, GetString(ID_STR_LOAD)); - - if (clear) - { - EnableWindow(item, FALSE); - - item = GetControl(Id_EditButton); - EnableWindow(item, FALSE); - - item = GetControl(Id_FileLabel); - SetWindowText(item, GetString(ID_STR_ELLIPSIS)); - - item = GetControl(Id_ConfigLabel); - SetWindowText(item, L""); - - item = GetControl(Id_AuthorLabel); - SetWindowText(item, L""); - - item = GetControl(Id_VersionLabel); - SetWindowText(item, L""); - - item = GetControl(Id_LicenseLabel); - SetWindowText(item, L""); - - item = GetControl(Id_DescriptionLabel); - SetWindowText(item, L""); - ShowScrollBar(item, SB_VERT, FALSE); - - item = GetControl(Id_AddMetadataLink); - ShowWindow(item, SW_HIDE); - } - else - { - EnableWindow(item, TRUE); - } - - item = GetControl(Id_RefreshButton); - EnableWindow(item, FALSE); - - item = GetControl(Id_XPositionEdit); - SetWindowText(item, L""); - EnableWindow(item, FALSE); - - item = GetControl(Id_YPositionEdit); - SetWindowText(item, L""); - EnableWindow(item, FALSE); - - item = GetControl(Id_DisplayMonitorButton); - EnableWindow(item, FALSE); - - item = GetControl(Id_DraggableCheckBox); - EnableWindow(item, FALSE); - Button_SetCheck(item, BST_UNCHECKED); - - item = GetControl(Id_ClickThroughCheckBox); - EnableWindow(item, FALSE); - Button_SetCheck(item, BST_UNCHECKED); - - item = GetControl(Id_KeepOnScreenCheckBox); - EnableWindow(item, FALSE); - Button_SetCheck(item, BST_UNCHECKED); - - item = GetControl(Id_SavePositionCheckBox); - EnableWindow(item, FALSE); - Button_SetCheck(item, BST_UNCHECKED); - - item = GetControl(Id_SnapToEdgesCheckBox); - EnableWindow(item, FALSE); - Button_SetCheck(item, BST_UNCHECKED); - - item = GetControl(Id_TransparencyDropDownList); - EnableWindow(item, FALSE); - ComboBox_SetCurSel(item, -1); - - item = GetControl(Id_ZPositionDropDownList); - EnableWindow(item, FALSE); - ComboBox_SetCurSel(item, -1); - - item = GetControl(Id_LoadOrderEdit); - SetWindowText(item, L""); - EnableWindow(item, FALSE); - - item = GetControl(Id_OnHoverDropDownList); - EnableWindow(item, FALSE); - ComboBox_SetCurSel(item, -1); -} - -void DialogManage::TabSkins::ReadSkin() -{ - HWND item = GetControl(Id_FileLabel); - SetWindowText(item, m_SkinFileName.c_str()); - - PathSetDlgItemPath(m_Window, Id_ConfigLabel, m_SkinFolderPath.c_str()); - - item = GetControl(Id_EditButton); - EnableWindow(item, TRUE); - - std::wstring file = Rainmeter::GetInstance().GetSkinPath() + m_SkinFolderPath; - file += L'\\'; - file += m_SkinFileName; - m_SkinWindow = Rainmeter::GetInstance().GetMeterWindowByINI(file); - if (!m_SkinWindow) - { - DisableControls(); - } - - SetControls(); - - WCHAR* buffer = new WCHAR[MAX_LINE_LENGTH]; - const WCHAR* fileSz = file.c_str(); - - item = GetControl(Id_AuthorLabel); - if (GetPrivateProfileString(L"Metadata", L"Author", nullptr, buffer, MAX_LINE_LENGTH, fileSz) == 0) - { - // For backwards compatibility. - GetPrivateProfileString(L"Rainmeter", L"Author", nullptr, buffer, MAX_LINE_LENGTH, fileSz); - } - SetWindowText(item, buffer); - - item = GetControl(Id_AddMetadataLink); - if (GetPrivateProfileSection(L"Metadata", buffer, 8, fileSz) > 0) - { - ShowWindow(item, SW_HIDE); - - // Set metadata - item = GetControl(Id_VersionLabel); - GetPrivateProfileString(L"Metadata", L"Version", nullptr, buffer, MAX_LINE_LENGTH, fileSz); - SetWindowText(item, buffer); - - item = GetControl(Id_LicenseLabel); - GetPrivateProfileString(L"Metadata", L"License", nullptr, buffer, MAX_LINE_LENGTH, fileSz); - SetWindowText(item, buffer); - - item = GetControl(Id_DescriptionLabel); - std::wstring text; - if (GetPrivateProfileString(L"Metadata", L"Information", nullptr, buffer, MAX_LINE_LENGTH, fileSz) > 0) - { - text = buffer; - } - else - { - // For backwards compatibility - GetPrivateProfileString(L"Metadata", L"Description", nullptr, buffer, MAX_LINE_LENGTH, fileSz); - text = buffer; - - if (GetPrivateProfileString(L"Metadata", L"Instructions", nullptr, buffer, MAX_LINE_LENGTH, fileSz) > 0) - { - text += L"\r\n\r\n"; - text += buffer; - } - } - - // Replace | with newline - std::wstring::size_type pos; - while ((pos = text.find_first_of(L'|')) != std::wstring::npos) - { - size_t count = (pos + 1 < text.length() && text[pos + 1] == L' ') ? 2 : 1; - if (text[pos - 1] == L' ') - { - --pos; - count += 1; - } - text.replace(pos, count, L"\r\n"); - } - - SetWindowText(item, text.c_str()); - - int lines = Edit_GetLineCount(item); - ShowScrollBar(item, SB_VERT, (BOOL)(lines > 6)); - } - else - { - ShowWindow(item, SW_SHOWNORMAL); - - item = GetControl(Id_VersionLabel); - SetWindowText(item, L""); - - item = GetControl(Id_LicenseLabel); - SetWindowText(item, L""); - - item = GetControl(Id_DescriptionLabel); - SetWindowText(item, L""); - ShowScrollBar(item, SB_VERT, FALSE); - } - - delete [] buffer; -} - -std::wstring DialogManage::TabSkins::GetTreeSelectionPath(HWND tree) -{ - WCHAR buffer[MAX_PATH]; - - // Get current selection name - TVITEM tvi = {0}; - tvi.hItem = TreeView_GetSelection(tree); - tvi.mask = TVIF_TEXT; - tvi.pszText = buffer; - tvi.cchTextMax = MAX_PATH; - TreeView_GetItem(tree, &tvi); - - std::wstring path = buffer; - while ((tvi.hItem = TreeView_GetParent(tree, tvi.hItem)) != nullptr) - { - TreeView_GetItem(tree, &tvi); - path.insert(0, 1, L'\\'); - path.insert(0, buffer); - } - - return path; -} - -/* -** Populates the treeview with folders and skins. -** -*/ -int DialogManage::TabSkins::PopulateTree(HWND tree, TVINSERTSTRUCT& tvi, int index) -{ - int initialLevel = Rainmeter::GetInstance().m_SkinRegistry.GetFolder(index).level; - - const size_t max = Rainmeter::GetInstance().m_SkinRegistry.GetFolderCount(); - while (index < max) - { - const auto& skinFolder = Rainmeter::GetInstance().m_SkinRegistry.GetFolder(index); - if (skinFolder.level != initialLevel) - { - return index - 1; - } - - HTREEITEM oldParent = tvi.hParent; - - // Add folder - tvi.item.iImage = tvi.item.iSelectedImage = 0; - tvi.item.pszText = (WCHAR*)skinFolder.name.c_str(); - tvi.hParent = TreeView_InsertItem(tree, &tvi); - - // Add subfolders - if ((index + 1) < max && - Rainmeter::GetInstance().m_SkinRegistry.GetFolder(index + 1).level == initialLevel + 1) - { - index = PopulateTree(tree, tvi, index + 1); - } - - // Add files - tvi.item.iImage = tvi.item.iSelectedImage = 1; - for (int i = 0, isize = (int)skinFolder.files.size(); i < isize; ++i) - { - tvi.item.pszText = (WCHAR*)skinFolder.files[i].c_str(); - TreeView_InsertItem(tree, &tvi); - } - - tvi.hParent = oldParent; - - ++index; - } - - return index; -} - -/* -** Selects an item in the treeview. -** -*/ -void DialogManage::TabSkins::SelectTreeItem(HWND tree, HTREEITEM item, LPCWSTR name) -{ - WCHAR buffer[MAX_PATH]; - TVITEM tvi = {0}; - tvi.mask = TVIF_TEXT; - tvi.hItem = item; - tvi.pszText = buffer; - - const WCHAR* pos = wcschr(name, L'\\'); - if (pos) - { - const int folderLen = (int)(pos - name); - tvi.cchTextMax = folderLen + 1; // Length of folder name plus 1 for nullptr - - // Find and expand the folder - do - { - TreeView_GetItem(tree, &tvi); - if (wcsncmp(buffer, name, folderLen) == 0) - { - if ((item = TreeView_GetChild(tree, tvi.hItem)) != nullptr) - { - TreeView_Expand(tree, tvi.hItem, TVE_EXPAND); - TreeView_Select(tree, tvi.hItem, TVGN_CARET); - ++pos; // Skip the slash - SelectTreeItem(tree, item, pos); - } - - break; - } - } - while ((tvi.hItem = TreeView_GetNextSibling(tree, tvi.hItem)) != nullptr); - } - else - { - tvi.cchTextMax = MAX_PATH; - - // Find and select the file - do - { - TreeView_GetItem(tree, &tvi); - if (wcscmp(buffer, name) == 0) - { - TreeView_Select(tree, tvi.hItem, TVGN_CARET); - break; - } - } - while ((tvi.hItem = TreeView_GetNextSibling(tree, tvi.hItem)) != nullptr); - } -} - -INT_PTR DialogManage::TabSkins::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) - { - case WM_COMMAND: - return OnCommand(wParam, lParam); - - case WM_NOTIFY: - return OnNotify(wParam, lParam); - } - - return FALSE; -} - -INT_PTR DialogManage::TabSkins::OnCommand(WPARAM wParam, LPARAM lParam) -{ - if (!m_HandleCommands) - { - // Values are being changed/reset, no need to apply changes. - return FALSE; - } - - switch (LOWORD(wParam)) - { - case Id_ActiveSkinsButton: - { - HMENU menu = CreatePopupMenu(); - - // Add active skins to menu - std::map::const_iterator iter = Rainmeter::GetInstance().GetAllMeterWindows().begin(); - int index = 0; - for ( ; iter != Rainmeter::GetInstance().GetAllMeterWindows().end(); ++iter) - { - std::wstring name = ((*iter).second)->GetFolderPath() + L'\\'; - name += ((*iter).second)->GetFileName(); - InsertMenu(menu, index, MF_BYPOSITION, ID_CONFIG_FIRST + index, name.c_str()); - ++index; - } - - if (index > 0) - { - RECT r; - GetWindowRect((HWND)lParam, &r); - - // Show context menu - TrackPopupMenu( - menu, - TPM_RIGHTBUTTON | TPM_LEFTALIGN, - (*GetString(ID_STR_ISRTL) == L'1') ? r.right : r.left, - --r.bottom, - 0, - m_Window, - nullptr - ); - } - - DestroyMenu(menu); - } - break; - - case Id_CreateSkinPackageButton: - { - std::wstring file = Rainmeter::GetInstance().GetPath() + L"SkinInstaller.exe"; - CommandHandler::RunFile(file.c_str(), L"/Packager"); - } - break; - - case Id_LoadButton: - { - if (!m_SkinWindow) - { - // Skin not active, load - const SkinRegistry::Indexes indexes = - Rainmeter::GetInstance().m_SkinRegistry.FindIndexes(m_SkinFolderPath, m_SkinFileName); - if (indexes.IsValid()) - { - m_HandleCommands = false; - Rainmeter::GetInstance().ActivateSkin(indexes.folder, indexes.file); - m_HandleCommands = true; - - // Fake selection change to update controls - NMHDR nm; - nm.code = TVN_SELCHANGED; - nm.idFrom = Id_SkinsTreeView; - nm.hwndFrom = GetControl(Id_SkinsTreeView); - OnNotify(0, (LPARAM)&nm); - } - } - else - { - m_HandleCommands = false; - Rainmeter::GetInstance().DeactivateSkin(m_SkinWindow, -1); - } - } - break; - - case Id_RefreshButton: - if (m_SkinWindow) - { - m_SkinWindow->Refresh(false); - } - break; - - case Id_EditButton: - Rainmeter::GetInstance().EditSkinFile(m_SkinFolderPath, m_SkinFileName); - break; - - case Id_XPositionEdit: - if (HIWORD(wParam) == EN_CHANGE) - { - WCHAR buffer[32]; - m_IgnoreUpdate = true; - int x = (GetWindowText((HWND)lParam, buffer, 32) > 0) ? _wtoi(buffer) : 0; - m_SkinWindow->MoveWindow(x, m_SkinWindow->GetY()); - - if (x > m_SkinWindow->GetX()) - { - _itow_s(m_SkinWindow->GetX(), buffer, 10); - Edit_SetText((HWND)lParam, buffer); - } - } - break; - - case Id_YPositionEdit: - if (HIWORD(wParam) == EN_CHANGE) - { - WCHAR buffer[32]; - m_IgnoreUpdate = true; - int y = (GetWindowText((HWND)lParam, buffer, 32) > 0) ? _wtoi(buffer) : 0; - m_SkinWindow->MoveWindow(m_SkinWindow->GetX(), y); - - if (y > m_SkinWindow->GetY()) - { - _itow_s(m_SkinWindow->GetY(), buffer, 10); - Edit_SetText((HWND)lParam, buffer); - } - } - break; - - case Id_LoadOrderEdit: - if (HIWORD(wParam) == EN_CHANGE) - { - if (m_IgnoreUpdate) - { - // To avoid infinite loop after setting value below - m_IgnoreUpdate = false; - } - else - { - // Convert text to number and set it to get rid of extra chars - WCHAR buffer[32]; - int len = GetWindowText((HWND)lParam, buffer, 32); - if ((len == 0) || (len == 1 && buffer[0] == L'-')) - { - // Ignore if empty or if - is only char - break; - } - - // Get selection - DWORD sel = Edit_GetSel((HWND)lParam); - - // Reset value (to get rid of invalid chars) - m_IgnoreUpdate = true; - int value = _wtoi(buffer); - - _itow_s(value, buffer, 10); - SetWindowText((HWND)lParam, buffer); - - // Reset selection - Edit_SetSel((HWND)lParam, LOWORD(sel), HIWORD(sel)); - - WritePrivateProfileString(m_SkinFolderPath.c_str(), L"LoadOrder", buffer, Rainmeter::GetInstance().GetIniFile().c_str()); - const SkinRegistry::Indexes indexes = Rainmeter::GetInstance().m_SkinRegistry.FindIndexes( - m_SkinWindow->GetFolderPath(), m_SkinWindow->GetFileName()); - if (indexes.IsValid()) - { - Rainmeter::GetInstance().SetLoadOrder(indexes.folder, value); - - std::multimap windows; - Rainmeter::GetInstance().GetMeterWindowsByLoadOrder(windows); - - System::PrepareHelperWindow(); - - // Reorder window z-position to reflect load order - std::multimap::const_iterator iter = windows.begin(); - for ( ; iter != windows.end(); ++iter) - { - MeterWindow* mw = (*iter).second; - mw->ChangeZPos(mw->GetWindowZPosition(), true); - } - } - } - } - break; - - case Id_DisplayMonitorButton: - { - static const MenuTemplate s_Menu[] = - { - MENU_ITEM(IDM_SKIN_MONITOR_PRIMARY, ID_STR_USEDEFAULTMONITOR), - MENU_ITEM(ID_MONITOR_FIRST, ID_STR_VIRTUALSCREEN), - MENU_SEPARATOR(), - MENU_SEPARATOR(), - MENU_ITEM(IDM_SKIN_MONITOR_AUTOSELECT, ID_STR_AUTOSELECTMONITOR) - }; - - HMENU menu = MenuTemplate::CreateMenu(s_Menu, _countof(s_Menu), GetString); - if (menu) - { - ContextMenu::CreateMonitorMenu(menu, m_SkinWindow); - - RECT r; - GetWindowRect((HWND)lParam, &r); - - // Show context menu - TrackPopupMenu( - menu, - TPM_RIGHTBUTTON | TPM_LEFTALIGN, - (*GetString(ID_STR_ISRTL) == L'1') ? r.right : r.left, - --r.bottom, - 0, - m_Window, - nullptr - ); - - DestroyMenu(menu); - } - } - break; - - case Id_DraggableCheckBox: - m_IgnoreUpdate = true; - m_SkinWindow->SetWindowDraggable(!m_SkinWindow->GetWindowDraggable()); - break; - - case Id_ClickThroughCheckBox: - m_IgnoreUpdate = true; - m_SkinWindow->SetClickThrough(!m_SkinWindow->GetClickThrough()); - break; - - case Id_KeepOnScreenCheckBox: - m_IgnoreUpdate = true; - m_SkinWindow->SetKeepOnScreen(!m_SkinWindow->GetKeepOnScreen()); - break; - - case Id_SavePositionCheckBox: - m_IgnoreUpdate = true; - m_SkinWindow->SetSavePosition(!m_SkinWindow->GetSavePosition()); - break; - - case Id_SnapToEdgesCheckBox: - m_IgnoreUpdate = true; - m_SkinWindow->SetSnapEdges(!m_SkinWindow->GetSnapEdges()); - break; - - case Id_ZPositionDropDownList: - if (HIWORD(wParam) == CBN_SELCHANGE) - { - m_IgnoreUpdate = true; - ZPOSITION zpos = (ZPOSITION)(ComboBox_GetCurSel((HWND)lParam) - 2); - m_SkinWindow->SetWindowZPosition(zpos); - } - break; - - case Id_TransparencyDropDownList: - if (HIWORD(wParam) == CBN_SELCHANGE) - { - m_IgnoreUpdate = true; - int sel = ComboBox_GetCurSel((HWND)lParam) + IDM_SKIN_TRANSPARENCY_0; - SendMessage(m_SkinWindow->GetWindow(), WM_COMMAND, sel, 0); - } - break; - - case Id_OnHoverDropDownList: - if (HIWORD(wParam) == CBN_SELCHANGE) - { - m_IgnoreUpdate = true; - HIDEMODE hide = (HIDEMODE)ComboBox_GetCurSel((HWND)lParam); - m_SkinWindow->SetWindowHide(hide); - } - break; - - case IDM_MANAGESKINSMENU_EXPAND: - { - HWND tree = GetControl(Id_SkinsTreeView); - HTREEITEM item = TreeView_GetSelection(tree); - TreeView_Expand(tree, item, TVE_TOGGLE); - } - break; - - case IDM_MANAGESKINSMENU_OPENFOLDER: - { - HWND tree = GetControl(Id_SkinsTreeView); - Rainmeter::GetInstance().OpenSkinFolder(GetTreeSelectionPath(tree)); - } - break; - - default: - if (wParam >= ID_CONFIG_FIRST && wParam <= ID_CONFIG_LAST) - { - std::map::const_iterator iter = Rainmeter::GetInstance().GetAllMeterWindows().begin(); - int index = (int)wParam - ID_CONFIG_FIRST; - int i = 0; - for ( ; iter != Rainmeter::GetInstance().GetAllMeterWindows().end(); ++iter) - { - if (i == index) - { - std::wstring name = ((*iter).second)->GetFolderPath() + L'\\'; - name += ((*iter).second)->GetFileName(); - - HWND item = GetControl(Id_SkinsTreeView); - SelectTreeItem(item, TreeView_GetRoot(item), name.c_str()); - break; - } - - ++i; - } - } - else if (wParam == IDM_SKIN_MONITOR_AUTOSELECT || - wParam == IDM_SKIN_MONITOR_PRIMARY || - wParam >= ID_MONITOR_FIRST && wParam <= ID_MONITOR_LAST) - { - if (m_SkinWindow) - { - SendMessage(m_SkinWindow->GetWindow(), WM_COMMAND, wParam, 0); - } - break; - } - - return 1; - } - - return 0; -} - -INT_PTR DialogManage::TabSkins::OnNotify(WPARAM wParam, LPARAM lParam) -{ - LPNMHDR nm = (LPNMHDR)lParam; - switch (nm->code) - { - case NM_CLICK: - if (nm->idFrom == Id_AddMetadataLink) - { - std::wstring file = Rainmeter::GetInstance().GetSkinPath() + m_SkinFolderPath; - file += L'\\'; - file += m_SkinFileName; - const WCHAR* str = L"\r\n" // Hack to add below [Rainmeter]. - L"[Metadata]\r\n" - L"Name=\r\n" - L"Author=\r\n" - L"Information=\r\n" - L"License=\r\n" - L"Version"; - WritePrivateProfileString(L"Rainmeter", str, L"", file.c_str()); - SendMessage(m_Window, WM_COMMAND, MAKEWPARAM(Id_EditButton, 0), 0); - ShowWindow(nm->hwndFrom, SW_HIDE); - } - break; - - case NM_DBLCLK: - if (nm->idFrom == Id_SkinsTreeView && !m_SkinFileName.empty()) - { - OnCommand(MAKEWPARAM(Id_LoadButton, 0), 0); - } - break; - - case NM_RCLICK: - if (nm->idFrom == Id_SkinsTreeView) - { - POINT pt = System::GetCursorPosition(); - - TVHITTESTINFO ht; - ht.pt = pt; - ScreenToClient(nm->hwndFrom, &ht.pt); - - if (TreeView_HitTest(nm->hwndFrom, &ht) && !(ht.flags & TVHT_NOWHERE)) - { - TreeView_SelectItem(nm->hwndFrom, ht.hItem); - - TVITEM tvi = {0}; - tvi.hItem = TreeView_GetSelection(nm->hwndFrom); - tvi.mask = TVIF_STATE; - - if (TreeView_GetItem(nm->hwndFrom, &tvi)) - { - HMENU menu = nullptr; - MENUITEMINFO mii = {0}; - mii.cbSize = sizeof(MENUITEMINFO); - mii.fMask = MIIM_STRING; - - if (m_SkinFileName.empty()) - { - // Folder menu. - static const MenuTemplate s_Menu[] = - { - MENU_ITEM(IDM_MANAGESKINSMENU_EXPAND, ID_STR_EXPAND), - MENU_ITEM(IDM_MANAGESKINSMENU_OPENFOLDER, ID_STR_OPENFOLDER), - }; - - menu = MenuTemplate::CreateMenu(s_Menu, _countof(s_Menu), GetString); - SetMenuDefaultItem(menu, IDM_MANAGESKINSMENU_EXPAND, MF_BYCOMMAND); - - if (tvi.state & TVIS_EXPANDED) - { - mii.dwTypeData = GetString(ID_STR_COLLAPSE); - SetMenuItemInfo(menu, IDM_MANAGESKINSMENU_EXPAND, MF_BYCOMMAND, &mii); - } - } - else - { - // Skin menu. - static const MenuTemplate s_Menu[] = - { - MENU_ITEM(IDM_MANAGESKINSMENU_LOAD, ID_STR_LOAD), - MENU_ITEM(IDM_MANAGESKINSMENU_REFRESH, ID_STR_REFRESH), - MENU_ITEM(IDM_MANAGESKINSMENU_EDIT, ID_STR_EDIT), - }; - - menu = MenuTemplate::CreateMenu(s_Menu, _countof(s_Menu), GetString); - SetMenuDefaultItem(menu, IDM_MANAGESKINSMENU_LOAD, MF_BYCOMMAND); - - if (m_SkinWindow) - { - mii.dwTypeData = GetString(ID_STR_UNLOAD); - SetMenuItemInfo(menu, IDM_MANAGESKINSMENU_LOAD, MF_BYCOMMAND, &mii); - } - else - { - EnableMenuItem(menu, IDM_MANAGESKINSMENU_REFRESH, MF_BYCOMMAND | MF_GRAYED); - } - } - - // Show context menu - TrackPopupMenu( - menu, - TPM_RIGHTBUTTON | TPM_LEFTALIGN, - pt.x, - pt.y, - 0, - m_Window, - nullptr - ); - - DestroyMenu(menu); - } - } - } - break; - - case TVN_SELCHANGED: - if (nm->idFrom == Id_SkinsTreeView) - { - m_SkinWindow = nullptr; - m_SkinFileName.clear(); - m_SkinFolderPath.clear(); - - // Temporarily disable handling commands - m_HandleCommands = false; - - WCHAR buffer[MAX_PATH]; - - // Get current selection name - TVITEM tvi = {0}; - tvi.hItem = TreeView_GetSelection(nm->hwndFrom); - tvi.mask = TVIF_TEXT | TVIF_CHILDREN; - tvi.pszText = buffer; - tvi.cchTextMax = MAX_PATH; - TreeView_GetItem(nm->hwndFrom, &tvi); - - if (tvi.cChildren == 0) - { - // Current selection is file - m_SkinFileName = buffer; - tvi.mask = TVIF_TEXT; - - // Loop through parents to get skin folder - m_SkinFolderPath.clear(); - while ((tvi.hItem = TreeView_GetParent(nm->hwndFrom, tvi.hItem)) != nullptr) - { - TreeView_GetItem(nm->hwndFrom, &tvi); - m_SkinFolderPath.insert(0, 1, L'\\'); - m_SkinFolderPath.insert(0, buffer); - } - - m_SkinFolderPath.resize(m_SkinFolderPath.length() - 1); // Get rid of trailing slash - - ReadSkin(); - } - else - { - DisableControls(true); - } - - m_HandleCommands = true; - } - break; - - default: - return FALSE; - } - - return TRUE; -} - -// ----------------------------------------------------------------------------------------------- -// -// Layouts tab -// -// ----------------------------------------------------------------------------------------------- - -/* -** Constructor. -** -*/ -DialogManage::TabLayouts::TabLayouts() : Tab() -{ -} - -void DialogManage::TabLayouts::Create(HWND owner) -{ - Tab::CreateTabWindow(15, 30, 470, 260, owner); - - static const ControlTemplate::Control s_Controls[] = - { - CT_GROUPBOX(-1, ID_STR_SAVENEWTHEME, - 0, 0, 230, 150, - WS_VISIBLE, 0), - CT_LABEL(-1, ID_STR_THEMEDESCRIPTION, - 6, 16, 205, 44, - WS_VISIBLE, 0), - CT_CHECKBOX(Id_SaveEmptyThemeCheckBox, ID_STR_SAVEASEMPTYTHEME, - 6, 70, 220, 9, - WS_VISIBLE | WS_TABSTOP, 0), - CT_CHECKBOX(Id_ExcludeUnusedSkinsCheckBox, ID_STR_EXCLUDEUNUSEDSKINS, - 6, 83, 220, 9, - WS_VISIBLE | WS_TABSTOP, 0), - CT_CHECKBOX(Id_IncludeWallpaperCheckBox, ID_STR_INCLUDEWALLPAPER, - 6, 96, 220, 9, - WS_VISIBLE | WS_TABSTOP, 0), - CT_LABEL(-1, ID_STR_NAMESC, - 6, 115, 100, 9, - WS_VISIBLE, 0), - CT_EDIT(Id_NameLabel, 0, - 6, 128, 162, 14, - WS_VISIBLE | WS_TABSTOP, WS_EX_CLIENTEDGE), - CT_BUTTON(Id_SaveButton, ID_STR_SAVE, - 172, 128, 50, 14, - WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), - - CT_GROUPBOX(-1, ID_STR_SAVEDTHEMES, - 238, 0, 230, 150, - WS_VISIBLE, 0), - CT_LISTBOX(Id_List, 0, - 244, 16, 160, 125, - WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | LBS_SORT | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT, WS_EX_CLIENTEDGE), - CT_BUTTON(Id_LoadButton, ID_STR_LOAD, - 410, 16, 50, 14, - WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), - CT_BUTTON(Id_EditButton, ID_STR_EDIT, - 410, 34, 50, 14, - WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), - CT_BUTTON(Id_DeleteButton, ID_STR_DELETE, - 410, 52, 50, 14, - WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0) - }; - - CreateControls(s_Controls, _countof(s_Controls), c_Dialog->m_Font, GetString); -} - -void DialogManage::TabLayouts::Initialize() -{ - HWND item = GetControl(Id_List); - ListBox_ResetContent(item); - const std::vector& layouts = Rainmeter::GetInstance().GetAllLayouts(); - for (size_t i = 0, isize = layouts.size(); i < isize; ++i) - { - ListBox_AddString(item, layouts[i].c_str()); - } - - m_Initialized = true; -} - -void DialogManage::TabLayouts::Update() -{ - Initialize(); -} - -INT_PTR DialogManage::TabLayouts::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) - { - case WM_COMMAND: - return OnCommand(wParam, lParam); - } - - return FALSE; -} - -INT_PTR DialogManage::TabLayouts::OnCommand(WPARAM wParam, LPARAM lParam) -{ - switch (LOWORD(wParam)) - { - case Id_SaveEmptyThemeCheckBox: - { - BOOL state = !(Button_GetCheck((HWND)lParam) == BST_CHECKED); - - HWND item = GetControl(Id_ExcludeUnusedSkinsCheckBox); - EnableWindow(item, state); - Button_SetCheck(item, BST_UNCHECKED); - - item = GetControl(Id_IncludeWallpaperCheckBox); - EnableWindow(item, state); - Button_SetCheck(item, BST_UNCHECKED); - } - break; - - case Id_NameLabel: - if (HIWORD(wParam) == EN_CHANGE) - { - WCHAR buffer[32]; - int len = Edit_GetText((HWND)lParam, buffer, 32); - - // Disable save button if no text or if backup - BOOL state = (len > 0 && _wcsicmp(buffer, L"@Backup") != 0); - EnableWindow(GetControl(Id_SaveButton), state); - } - break; - - case Id_List: - if (HIWORD(wParam) == LBN_SELCHANGE) - { - // Ignore clicks that don't hit items - if (ListBox_GetCurSel((HWND)lParam) != LB_ERR) - { - HWND item = GetControl(Id_LoadButton); - EnableWindow(item, TRUE); - item = GetControl(Id_DeleteButton); - EnableWindow(item, TRUE); - item = GetControl(Id_EditButton); - EnableWindow(item, TRUE); - - const std::vector& layouts = Rainmeter::GetInstance().GetAllLayouts(); - item = GetControl(Id_List); - int sel = ListBox_GetCurSel(item); - - item = GetControl(Id_NameLabel); - Edit_SetText(item, layouts[sel].c_str()); - } - } - break; - - case Id_SaveButton: - { - WCHAR buffer[MAX_PATH]; - HWND item = GetControl(Id_NameLabel); - Edit_GetText(item, buffer, MAX_PATH); - - std::wstring layout = buffer; - std::wstring path = Rainmeter::GetInstance().GetLayoutPath(); - CreateDirectory(path.c_str(), 0); - - path += layout; - bool alreadyExists = (_waccess(path.c_str(), 0) != -1); - if (alreadyExists) - { - std::wstring text = GetFormattedString(ID_STR_THEMEALREADYEXISTS, layout.c_str()); - if (Rainmeter::GetInstance().ShowMessage(m_Window, text.c_str(), MB_ICONWARNING | MB_YESNO) != IDYES) - { - // Cancel - break; - } - } - else - { - // Make sure path exists - CreateDirectory(path.c_str(), nullptr); - } - - path += L"\\Rainmeter.ini"; - - item = GetControl(Id_SaveEmptyThemeCheckBox); - if (Button_GetCheck(item) != BST_CHECKED) - { - if (!System::CopyFiles(Rainmeter::GetInstance().GetIniFile(), path)) - { - std::wstring text = GetFormattedString(ID_STR_THEMESAVEFAIL, path.c_str()); - Rainmeter::GetInstance().ShowMessage(m_Window, text.c_str(), MB_OK | MB_ICONERROR); - break; - } - - // Exclude unused skins - item = GetControl(Id_ExcludeUnusedSkinsCheckBox); - if (Button_GetCheck(item) == BST_CHECKED) - { - ConfigParser parser; - parser.Initialize(path); - - // Remove sections with Active=0 - std::list::const_iterator iter = parser.GetSections().begin(); - for ( ; iter != parser.GetSections().end(); ++iter) - { - if (parser.GetValue(*iter, L"Active", L"") == L"0") - { - WritePrivateProfileString((*iter).c_str(), nullptr, nullptr, path.c_str()); - } - } - } - - // Save wallpaper - item = GetControl(Id_IncludeWallpaperCheckBox); - if (Button_GetCheck(item) == BST_CHECKED) - { - // Get current wallpaper - if (SystemParametersInfo(SPI_GETDESKWALLPAPER, MAX_PATH, &buffer, 0)) - { - std::wstring::size_type pos = path.find_last_of(L'\\'); - path.replace(pos + 1, path.length() - pos - 1, L"Wallpaper.bmp"); - System::CopyFiles((std::wstring)buffer, path); - } - } - } - else - { - // Create empty layout - HANDLE file = CreateFile(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - if (file == INVALID_HANDLE_VALUE) - { - std::wstring text = GetFormattedString(ID_STR_THEMESAVEFAIL, path.c_str()); - Rainmeter::GetInstance().ShowMessage(m_Window, text.c_str(), MB_OK | MB_ICONERROR); - break; - } - - CloseHandle(file); - } - - if (!alreadyExists) - { - item = GetControl(Id_List); - ListBox_AddString(item, layout.c_str()); - - Rainmeter::GetInstance().ScanForLayouts(); - } - } - break; - - case Id_LoadButton: - { - HWND item = GetControl(Id_List); - int sel = ListBox_GetCurSel(item); - Rainmeter::GetInstance().LoadLayout(Rainmeter::GetInstance().m_Layouts[sel]); - } - break; - - case Id_EditButton: - { - HWND item = GetControl(Id_List); - int sel = ListBox_GetCurSel(item); - const std::vector& layouts = Rainmeter::GetInstance().GetAllLayouts(); - - std::wstring args = L"\"" + Rainmeter::GetInstance().GetLayoutPath(); - args += layouts[sel]; - args += L"\\Rainmeter.ini"; - args += L'"'; - CommandHandler::RunFile(Rainmeter::GetInstance().GetSkinEditor().c_str(), args.c_str()); - } - break; - - case Id_DeleteButton: - { - HWND item = GetControl(Id_List); - int sel = ListBox_GetCurSel(item); - std::vector& layouts = const_cast&>(Rainmeter::GetInstance().GetAllLayouts()); - - std::wstring text = GetFormattedString(ID_STR_THEMEDELETE, layouts[sel].c_str()); - if (Rainmeter::GetInstance().ShowMessage(m_Window, text.c_str(), MB_ICONQUESTION | MB_YESNO) != IDYES) - { - // Cancel - break; - } - - std::wstring folder = Rainmeter::GetInstance().GetLayoutPath(); - folder += layouts[sel]; - - if (System::RemoveFolder(folder)) - { - ListBox_DeleteString(item, sel); - - // Remove layout from vector - std::vector::iterator iter = layouts.begin(); - for ( ; iter != layouts.end(); ++iter) - { - if (wcscmp(layouts[sel].c_str(), (*iter).c_str()) == 0) - { - layouts.erase(iter); - break; - } - } - - EnableWindow(GetControl(Id_LoadButton), FALSE); - EnableWindow(GetControl(Id_DeleteButton), FALSE); - EnableWindow(GetControl(Id_EditButton), FALSE); - } - } - break; - - default: - return 1; - } - - return 0; -} - -// ----------------------------------------------------------------------------------------------- -// -// Settings tab -// -// ----------------------------------------------------------------------------------------------- - -/* -** Constructor. -** -*/ -DialogManage::TabSettings::TabSettings() : Tab() -{ -} - -void DialogManage::TabSettings::Create(HWND owner) -{ - Tab::CreateTabWindow(15, 30, 470, 260, owner); - - // FIXME: Temporary hack. - short buttonWidth = (short)_wtoi(GetString(ID_STR_NUM_BUTTONWIDTH)); - - const ControlTemplate::Control s_Controls[] = - { - CT_GROUPBOX(-1, ID_STR_GENERAL, - 0, 0, 468, 131, - WS_VISIBLE, 0), - CT_LABEL(-1, ID_STR_LANGUAGESC, - 6, 16, 107, 14, - WS_VISIBLE, 0), - CT_COMBOBOX(Id_LanguageDropDownList, 0, - 107, 13, 222, 14, - WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL, 0), - CT_LABEL(-1, ID_STR_EDITORSC, - 6, 37, 107, 9, - WS_VISIBLE, 0), - CT_EDIT(Id_EditorEdit, 0, - 107, 34, 222, 14, - WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_READONLY, WS_EX_CLIENTEDGE), - CT_BUTTON(Id_EditorBrowseButton, ID_STR_ELLIPSIS, - 333, 34, 25, 14, - WS_VISIBLE | WS_TABSTOP, 0), - CT_CHECKBOX(Id_CheckForUpdatesCheckBox, ID_STR_CHECKFORUPDATES, - 6, 55, 200, 9, - WS_VISIBLE | WS_TABSTOP, 0), - CT_CHECKBOX(Id_LockSkinsCheckBox, ID_STR_DISABLEDRAGGING, - 6, 68, 200, 9, - WS_VISIBLE | WS_TABSTOP, 0), - CT_CHECKBOX(Id_ShowTrayIconCheckBox, ID_STR_SHOWNOTIFICATIONAREAICON, - 6, 81, 200, 9, - WS_VISIBLE | WS_TABSTOP, 0), - CT_CHECKBOX(Id_UseD2DCheckBox, ID_STR_USED2D, - 6, 94, 200, 9, - WS_VISIBLE | WS_TABSTOP, 0), - CT_BUTTON(Id_ResetStatisticsButton, ID_STR_RESETSTATISTICS, - 6, 110, buttonWidth + 20, 14, - WS_VISIBLE | WS_TABSTOP, 0), - - CT_GROUPBOX(-1, ID_STR_LOGGING, - 0, 138, 468, 66, - WS_VISIBLE, 0), - CT_CHECKBOX(Id_VerboseLoggingCheckbox, ID_STR_DEBUGMODE, - 6, 154, 200, 9, - WS_VISIBLE | WS_TABSTOP, 0), - CT_CHECKBOX(Id_LogToFileCheckBox, ID_STR_LOGTOFILE, - 6, 167, 200, 9, - WS_VISIBLE | WS_TABSTOP, 0), - CT_BUTTON(Id_ShowLogFileButton, ID_STR_SHOWLOGFILE, - 6, 183, buttonWidth + 20, 14, - WS_VISIBLE | WS_TABSTOP, 0), - CT_BUTTON(Id_DeleteLogFileButton, ID_STR_DELETELOGFILE, - buttonWidth + 30, 183, buttonWidth + 20, 14, - WS_VISIBLE | WS_TABSTOP, 0) - }; - - CreateControls(s_Controls, _countof(s_Controls), c_Dialog->m_Font, GetString); -} - -void DialogManage::TabSettings::Initialize() -{ - // Scan for languages - HWND item = GetControl(Id_LanguageDropDownList); - - std::wstring files = Rainmeter::GetInstance().GetPath() + L"Languages\\*.dll"; - WIN32_FIND_DATA fd; - HANDLE hSearch = FindFirstFile(files.c_str(), &fd); - if (hSearch != INVALID_HANDLE_VALUE) - { - do - { - WCHAR* pos = wcschr(fd.cFileName, L'.'); - if (pos) - { - LCID lcid = (LCID)wcstoul(fd.cFileName, &pos, 10); - if (pos != fd.cFileName && - _wcsicmp(pos, L".dll") == 0 && - GetLocaleInfo(lcid, LOCALE_SENGLISHLANGUAGENAME, fd.cFileName, MAX_PATH) > 0) - { - // Strip brackets in language name - std::wstring text = fd.cFileName; - text += L" - "; - - GetLocaleInfo(lcid, LOCALE_SNATIVEDISPLAYNAME, fd.cFileName, MAX_PATH); - text += fd.cFileName; - - int index = ComboBox_AddString(item, text.c_str()); - ComboBox_SetItemData(item, index, (LPARAM)lcid); - - if (lcid == Rainmeter::GetInstance().GetResourceLCID()) - { - ComboBox_SetCurSel(item, index); - } - } - } - } - while (FindNextFile(hSearch, &fd)); - - FindClose(hSearch); - } - - Button_SetCheck(GetControl(Id_CheckForUpdatesCheckBox), !Rainmeter::GetInstance().GetDisableVersionCheck()); - Button_SetCheck(GetControl(Id_LockSkinsCheckBox), Rainmeter::GetInstance().GetDisableDragging()); - Button_SetCheck(GetControl(Id_LogToFileCheckBox), GetLogger().IsLogToFile()); - Button_SetCheck(GetControl(Id_VerboseLoggingCheckbox), Rainmeter::GetInstance().GetDebug()); - - BOOL isLogFile = (_waccess(GetLogger().GetLogFilePath().c_str(), 0) != -1); - EnableWindow(GetControl(Id_ShowLogFileButton), isLogFile); - EnableWindow(GetControl(Id_DeleteLogFileButton), isLogFile); - - Edit_SetText(GetControl(Id_EditorEdit), Rainmeter::GetInstance().GetSkinEditor().c_str()); - - bool iconEnabled = Rainmeter::GetInstance().GetTrayWindow()->IsTrayIconEnabled(); - Button_SetCheck(GetControl(Id_ShowTrayIconCheckBox), iconEnabled); - - if (IsWindowsVistaOrGreater()) - { - Button_SetCheck(GetControl(Id_UseD2DCheckBox), Rainmeter::GetInstance().GetUseD2D()); - } - else - { - Button_Enable(GetControl(Id_UseD2DCheckBox), FALSE); - } - - m_Initialized = true; -} - -INT_PTR DialogManage::TabSettings::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) - { - case WM_COMMAND: - return OnCommand(wParam, lParam); - } - - return FALSE; -} - -INT_PTR DialogManage::TabSettings::OnCommand(WPARAM wParam, LPARAM lParam) -{ - if (!m_Initialized) - { - return FALSE; - } - - switch (LOWORD(wParam)) - { - case Id_LanguageDropDownList: - if (HIWORD(wParam) == CBN_SELCHANGE) - { - int sel = ComboBox_GetCurSel((HWND)lParam); - LCID lcid = (LCID)ComboBox_GetItemData((HWND)lParam, sel); - if (lcid != Rainmeter::GetInstance().m_ResourceLCID) - { - WCHAR buffer[16]; - _ultow(lcid, buffer, 10); - WritePrivateProfileString(L"Rainmeter", L"Language", buffer, Rainmeter::GetInstance().GetIniFile().c_str()); - - std::wstring resource = Rainmeter::GetInstance().GetPath() + L"Languages\\"; - resource += buffer; - resource += L".dll"; - FreeLibrary(Rainmeter::GetInstance().m_ResourceInstance); - Rainmeter::GetInstance().m_ResourceInstance = LoadLibraryEx(resource.c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE); - Rainmeter::GetInstance().m_ResourceLCID = lcid; - - if (DialogAbout::GetDialog()) - { - int sel = TabCtrl_GetCurSel(DialogAbout::GetDialog()->GetControl(DialogManage::Id_Tab)); - SendMessage(DialogAbout::GetDialog()->GetWindow(), WM_CLOSE, 0, 0); - if (sel == 0) - { - Rainmeter::GetInstance().DelayedExecuteCommand(L"!About"); - } - else if (sel == 1) - { - Rainmeter::GetInstance().DelayedExecuteCommand(L"!About Skins"); - } - else if (sel == 2) - { - Rainmeter::GetInstance().DelayedExecuteCommand(L"!About Plugins"); - } - else //if (sel == 3) - { - Rainmeter::GetInstance().DelayedExecuteCommand(L"!About Version"); - } - } - - SendMessage(c_Dialog->GetWindow(), WM_CLOSE, 0, 0); - Rainmeter::GetInstance().DelayedExecuteCommand(L"!Manage Settings"); - } - } - break; - - case Id_CheckForUpdatesCheckBox: - Rainmeter::GetInstance().SetDisableVersionCheck(!Rainmeter::GetInstance().GetDisableVersionCheck()); - break; - - case Id_LockSkinsCheckBox: - Rainmeter::GetInstance().SetDisableDragging(!Rainmeter::GetInstance().GetDisableDragging()); - break; - - case Id_ResetStatisticsButton: - Rainmeter::GetInstance().ResetStats(); - break; - - case Id_ShowLogFileButton: - Rainmeter::GetInstance().ShowLogFile(); - break; - - case Id_DeleteLogFileButton: - GetLogger().DeleteLogFile(); - if (_waccess(GetLogger().GetLogFilePath().c_str(), 0) == -1) - { - Button_SetCheck(GetControl(Id_LogToFileCheckBox), BST_UNCHECKED); - EnableWindow(GetControl(Id_ShowLogFileButton), FALSE); - EnableWindow(GetControl(Id_DeleteLogFileButton), FALSE); - } - break; - - case Id_LogToFileCheckBox: - if (GetLogger().IsLogToFile()) - { - GetLogger().StopLogFile(); - } - else - { - GetLogger().StartLogFile(); - if (_waccess(GetLogger().GetLogFilePath().c_str(), 0) != -1) - { - EnableWindow(GetControl(Id_ShowLogFileButton), TRUE); - EnableWindow(GetControl(Id_DeleteLogFileButton), TRUE); - } - } - break; - - case Id_VerboseLoggingCheckbox: - Rainmeter::GetInstance().SetDebug(!Rainmeter::GetInstance().GetDebug()); - break; - - case Id_EditorEdit: - if (HIWORD(wParam) == EN_CHANGE) - { - WCHAR buffer[MAX_PATH]; - if (GetWindowText((HWND)lParam, buffer, _countof(buffer)) > 0) - { - Rainmeter::GetInstance().SetSkinEditor(buffer); - } - } - break; - - case Id_EditorBrowseButton: - { - WCHAR buffer[MAX_PATH]; - buffer[0] = L'\0'; - - std::wstring editor = Rainmeter::GetInstance().GetSkinEditor(); - editor = editor.substr(0, editor.find_last_of(L"/\\")).c_str(); - - OPENFILENAME ofn = { sizeof(OPENFILENAME) }; - ofn.Flags = OFN_FILEMUSTEXIST; - ofn.lpstrFilter = L"Executable File (.exe)\0*.exe"; - ofn.lpstrTitle = L"Select executable file"; - ofn.lpstrDefExt = L"exe"; - ofn.lpstrInitialDir = editor.c_str(); - ofn.nFilterIndex = 0; - ofn.lpstrFile = buffer; - ofn.nMaxFile = _countof(buffer); - ofn.hwndOwner = c_Dialog->GetWindow(); - - if (!GetOpenFileName(&ofn)) - { - break; - } - - Edit_SetText(GetControl(Id_EditorEdit), buffer); - } - break; - - case Id_ShowTrayIconCheckBox: - Rainmeter::GetInstance().GetTrayWindow()->SetTrayIcon(!Rainmeter::GetInstance().GetTrayWindow()->IsTrayIconEnabled()); - break; - - case Id_UseD2DCheckBox: - Rainmeter::GetInstance().SetUseD2D(!Rainmeter::GetInstance().GetUseD2D()); - break; - - default: - return 1; - } - - return 0; +/* + Copyright (C) 2011 Birunthan Mohanathas + + 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/MenuTemplate.h" +#include "Rainmeter.h" +#include "System.h" +#include "MeterWindow.h" +#include "TrayWindow.h" +#include "Measure.h" +#include "resource.h" +#include "DialogManage.h" +#include "DialogAbout.h" +#include "../Version.h" +#include + +WINDOWPLACEMENT DialogManage::c_WindowPlacement = {0}; +DialogManage* DialogManage::c_Dialog = nullptr; + +/* +** Constructor. +** +*/ +DialogManage::DialogManage() : Dialog() +{ +} + +/* +** Destructor. +** +*/ +DialogManage::~DialogManage() +{ +} + +/* +** Opens the Manage dialog by tab name. +** +*/ +void DialogManage::Open(const WCHAR* name) +{ + int tab = 0; + + if (name) + { + if (_wcsicmp(name, L"Layouts") == 0 || + _wcsicmp(name, L"Themes") == 0) // For backwards compatibility. + { + tab = 1; + } + else if (_wcsicmp(name, L"Settings") == 0) + { + tab = 2; + } + } + + Open(tab); +} + +/* +** Opens the Manage dialog. +** +*/ +void DialogManage::Open(int tab) +{ + if (!c_Dialog) + { + c_Dialog = new DialogManage(); + } + + c_Dialog->ShowDialogWindow( + GetString(ID_STR_MANAGERAINMETER), + 0, 0, 500, 322, + DS_CENTER | WS_POPUP | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU, + WS_EX_APPWINDOW | WS_EX_CONTROLPARENT | ((*GetString(ID_STR_ISRTL) == L'1') ? WS_EX_LAYOUTRTL : 0), + GetRainmeter().GetWindow()); + + // Fake WM_NOTIFY to change tab + NMHDR nm; + nm.code = TCN_SELCHANGE; + nm.idFrom = Id_Tab; + nm.hwndFrom = c_Dialog->GetControl(Id_Tab); + TabCtrl_SetCurSel(nm.hwndFrom, tab); + c_Dialog->OnNotify(0, (LPARAM)&nm); +} + +/* +** Opens the Manage dialog Skins tab with skin selected. +** +*/ +void DialogManage::OpenSkin(MeterWindow* meterWindow) +{ + Open(); + + if (c_Dialog) + { + std::wstring name = meterWindow->GetFolderPath() + L'\\'; + name += meterWindow->GetFileName(); + + HWND item = c_Dialog->m_TabSkins.GetControl(TabSkins::Id_SkinsTreeView); + c_Dialog->m_TabSkins.SelectTreeItem(item, TreeView_GetRoot(item), name.c_str()); + } +} + +/* +** Opens the Manage dialog tab with parameters +** +*/ +void DialogManage::Open(const WCHAR* tabName, const WCHAR* param1, const WCHAR* param2) +{ + Open(tabName); + + if (c_Dialog) + { + // "Skins" tab + if (_wcsicmp(tabName, L"Skins") == 0) + { + // |param1| represents the config (ie. "illustro\Clock") + // |param2| represents the file (ie. "Clock.ini") + + std::wstring name = param1; + + if (param2) + { + name += L'\\'; + name += param2; + } + + HWND item = c_Dialog->m_TabSkins.GetControl(TabSkins::Id_SkinsTreeView); + c_Dialog->m_TabSkins.SelectTreeItem(item, TreeView_GetRoot(item), name.c_str()); + } + // Future use: Allow optional params for different tabs + //else if (_wcsicmp(tabName, L"Layouts") == 0) + } +} + +void DialogManage::UpdateSkins(MeterWindow* meterWindow, bool deleted) +{ + if (c_Dialog && c_Dialog->m_TabSkins.IsInitialized()) + { + c_Dialog->m_TabSkins.Update(meterWindow, deleted); + } +} + +void DialogManage::UpdateLayouts() +{ + if (c_Dialog && c_Dialog->m_TabLayouts.IsInitialized()) + { + c_Dialog->m_TabLayouts.Update(); + } +} + +Dialog::Tab& DialogManage::GetActiveTab() +{ + int sel = TabCtrl_GetCurSel(GetControl(Id_Tab)); + if (sel == 0) + { + return m_TabSkins; + } + else if (sel == 1) + { + return m_TabLayouts; + } + else // if (sel == 2) + { + return m_TabSettings; + } +} + +INT_PTR DialogManage::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + return OnInitDialog(wParam, lParam); + + case WM_ACTIVATE: + return OnActivate(wParam, lParam); + + case WM_COMMAND: + return OnCommand(wParam, lParam); + + case WM_NOTIFY: + return OnNotify(wParam, lParam); + + case WM_CLOSE: + { + GetWindowPlacement(m_Window, &c_WindowPlacement); + if (c_WindowPlacement.showCmd == SW_SHOWMINIMIZED) + { + c_WindowPlacement.showCmd = SW_SHOWNORMAL; + } + + delete c_Dialog; + c_Dialog = nullptr; + } + return TRUE; + } + + return FALSE; +} + +INT_PTR DialogManage::OnInitDialog(WPARAM wParam, LPARAM lParam) +{ + // FIXME: Temporary hack. + short buttonWidth = (short)_wtoi(GetString(ID_STR_NUM_BUTTONWIDTH)); + + const ControlTemplate::Control s_Controls[] = + { + CT_BUTTON(Id_RefreshAllButton, ID_STR_REFRESHALL, + 5, 303, buttonWidth, 14, + WS_VISIBLE | WS_TABSTOP, 0), + CT_BUTTON(Id_EditSettingsButton, ID_STR_EDITSETTINGS, + buttonWidth + 9, 303, buttonWidth, 14, + WS_VISIBLE | WS_TABSTOP, 0), + CT_BUTTON(Id_OpenLogButton, ID_STR_OPENLOG, + buttonWidth + buttonWidth + 13, 303, buttonWidth, 14, + WS_VISIBLE | WS_TABSTOP, 0), + CT_BUTTON(Id_HelpButton, ID_STR_HELP, + 389, 303, 50, 14, + WS_VISIBLE | WS_TABSTOP, 0), + CT_BUTTON(Id_CloseButton, ID_STR_CLOSE, + 444, 303, 50, 14, + WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 0), + CT_TAB(Id_Tab, 0, + 6, 6, 488, 293, + WS_VISIBLE | WS_TABSTOP | TCS_FIXEDWIDTH, 0) // Last for correct tab order. + }; + + CreateControls(s_Controls, _countof(s_Controls), m_Font, GetString); + + HWND item = GetControl(Id_Tab); + m_TabSkins.Create(m_Window); + m_TabLayouts.Create(m_Window); + m_TabSettings.Create(m_Window); + + TCITEM tci = {0}; + tci.mask = TCIF_TEXT; + tci.pszText = GetString(ID_STR_SKINS); + TabCtrl_InsertItem(item, 0, &tci); + tci.pszText = GetString(ID_STR_THEMES); + TabCtrl_InsertItem(item, 1, &tci); + tci.pszText = GetString(ID_STR_SETTINGS); + TabCtrl_InsertItem(item, 2, &tci); + + HICON hIcon = GetIcon(IDI_RAINMETER); + SendMessage(m_Window, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); + + item = GetControl(Id_CloseButton); + SendMessage(m_Window, WM_NEXTDLGCTL, (WPARAM)item, TRUE); + + item = m_TabSkins.GetControl(TabSkins::Id_FileLabel); + SendMessage(item, WM_SETFONT, (WPARAM)m_FontBold, 0); + + if (IsWindowsVistaOrGreater()) + { + // Use arrows instead of plus/minus in the tree for Vista+ + item = m_TabSkins.GetControl(TabSkins::Id_SkinsTreeView); + SetWindowTheme(item, L"explorer", nullptr); + } + + if (c_WindowPlacement.length == 0) + { + c_WindowPlacement.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(m_Window, &c_WindowPlacement); + } + + SetWindowPlacement(m_Window, &c_WindowPlacement); + + return FALSE; +} + +INT_PTR DialogManage::OnCommand(WPARAM wParam, LPARAM lParam) +{ + switch (LOWORD(wParam)) + { + case Id_RefreshAllButton: + GetRainmeter().RefreshAll(); + break; + + case Id_EditSettingsButton: + GetRainmeter().EditSettings(); + break; + + case Id_OpenLogButton: + DialogAbout::Open(); + break; + + case Id_CloseButton: + HandleMessage(WM_CLOSE, 0, 0); + break; + + case Id_HelpButton: + { + std::wstring url = L"http://docs.rainmeter.net/manual/user-interface/manage#"; + + Tab& tab = GetActiveTab(); + if (&tab == &m_TabSkins) + { + url += L"Skins"; + } + else if (&tab == &m_TabLayouts) + { + url += L"Layouts"; + } + else // if (&tab == &m_TabSettings) + { + url += L"Settings"; + } + + url += L"Tab"; + ShellExecute(m_Window, L"open", url.c_str(), nullptr, nullptr, SW_SHOWNORMAL); + } + break; + + default: + return FALSE; + } + + return TRUE; +} + +INT_PTR DialogManage::OnNotify(WPARAM wParam, LPARAM lParam) +{ + LPNMHDR nm = (LPNMHDR)lParam; + switch (nm->idFrom) + { + case Id_Tab: + if (nm->code == TCN_SELCHANGE) + { + // Disable all tab windows first + EnableWindow(m_TabSkins.GetWindow(), FALSE); + EnableWindow(m_TabLayouts.GetWindow(), FALSE); + EnableWindow(m_TabSettings.GetWindow(), FALSE); + + GetActiveTab().Activate(); + } + break; + + default: + return 1; + } + + return 0; +} + +// ----------------------------------------------------------------------------------------------- +// +// Skins tab +// +// ----------------------------------------------------------------------------------------------- + +/* +** Constructor. +** +*/ +DialogManage::TabSkins::TabSkins() : Tab(), + m_SkinWindow(), + m_HandleCommands(false), + m_IgnoreUpdate(false) +{ +} + +void DialogManage::TabSkins::Create(HWND owner) +{ + Tab::CreateTabWindow(15, 30, 470, 260, owner); + + // FIXME: Temporary hack. + short labelWidth = (short)_wtoi(GetString(ID_STR_NUM_LABELWIDTH)); + + const ControlTemplate::Control s_Controls[] = + { + CT_BUTTON(Id_ActiveSkinsButton, ID_STR_ACTIVESKINS, + 0, 0, 146, 14, + WS_VISIBLE | WS_TABSTOP, 0), + CT_TREEVIEW(Id_SkinsTreeView, 0, + 0, 18, 145, 221, + WS_VISIBLE | WS_TABSTOP | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_SHOWSELALWAYS | WS_VSCROLL, WS_EX_CLIENTEDGE), + CT_BUTTON(Id_CreateSkinPackageButton, ID_STR_CREATERMSKINPACKAGE, + 0, 244, 146, 14, + WS_VISIBLE | WS_TABSTOP, 0), + + CT_LABEL(Id_FileLabel, ID_STR_ELLIPSIS, + 165, 0, 130, 14, + WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), + CT_LABEL(Id_ConfigLabel, 0, + 165, 15, 130, 9, + WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), + CT_BUTTON(Id_LoadButton, ID_STR_LOAD, + 310, 0, 50, 14, + WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), + CT_BUTTON(Id_RefreshButton, ID_STR_REFRESH, + 364, 0, 50, 14, + WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), + CT_BUTTON(Id_EditButton, ID_STR_EDIT, + 418, 0, 50, 14, + WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), + + CT_LABEL(-1, ID_STR_AUTHORSC, + 165, 30, 80, 9, + WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), + CT_LABEL(Id_AuthorLabel, 0, + 230, 30, 245, 9, + WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), + CT_LABEL(-1, ID_STR_VERSIONSC, + 165, 43, 80, 9, + WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), + CT_LABEL(Id_VersionLabel, 0, + 230, 43, 245, 9, + WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), + CT_LABEL(-1, ID_STR_LICENSESC, + 165, 56, 80, 9, + WS_VISIBLE | WS_TABSTOP | SS_NOPREFIX, 0), + CT_LABEL(Id_LicenseLabel, 0, + 230, 56, 245, 9, + WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), + CT_LABEL(-1, ID_STR_INFORMATIONSC, + 165, 69, 80, 9, + WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), + CT_EDIT(Id_DescriptionLabel, 0, + 228, 69, 238, 64, + WS_VISIBLE | ES_MULTILINE | ES_READONLY, 0), + CT_LINKLABEL(Id_AddMetadataLink, ID_STR_ADDMETADATA, + 165, 142, 150, 9, + 0, 0), + + CT_LINEH(-1, ID_STR_COORDINATESSC, + 165, 156, 304, 1, + WS_VISIBLE, 0), + + CT_LABEL(-1, ID_STR_COORDINATESSC, + 165, 169, labelWidth, 9, + WS_VISIBLE, 0), + CT_EDIT(Id_XPositionEdit, 0, + 165 + labelWidth, 166, 38, 14, + WS_VISIBLE | WS_TABSTOP | WS_DISABLED, WS_EX_CLIENTEDGE), + CT_EDIT(Id_YPositionEdit, 0, + 165 + labelWidth + 42, 166, 38, 14, + WS_VISIBLE | WS_TABSTOP | WS_DISABLED, WS_EX_CLIENTEDGE), + CT_LABEL(-1, ID_STR_POSITIONSC, + 165, 190, labelWidth, 9, + WS_VISIBLE, 0), + CT_COMBOBOX(Id_ZPositionDropDownList, 0, + 165 + labelWidth, 187, 80, 14, + WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL | WS_DISABLED, 0), + CT_LABEL(-1, ID_STR_LOADORDERSC, + 165, 208, labelWidth, 9, + WS_VISIBLE, 0), + CT_EDIT(Id_LoadOrderEdit, 0, + 165 + labelWidth, 205, 80, 14, + WS_VISIBLE | WS_TABSTOP | WS_DISABLED, WS_EX_CLIENTEDGE), + CT_LABEL(-1, ID_STR_TRANSPARENCYSC, + 165, 229, labelWidth, 9, + WS_VISIBLE, 0), + CT_COMBOBOX(Id_TransparencyDropDownList, 0, + 165 + labelWidth, 226, 80, 14, + WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL | WS_DISABLED, 0), + CT_LABEL(-1, ID_STR_ONHOVERSC, + 165, 247, labelWidth, 9, + WS_VISIBLE, 0), + CT_COMBOBOX(Id_OnHoverDropDownList, 0, + 165 + labelWidth, 244, 80, 14, + WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL | WS_DISABLED, 0), + + CT_BUTTON(Id_DisplayMonitorButton, ID_STR_DISPLAYMONITOR, + 350, 166, 118, 14, + WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), + CT_CHECKBOX(Id_DraggableCheckBox, ID_STR_DRAGGABLE, + 350, 190, 118, 9, + WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), + CT_CHECKBOX(Id_ClickThroughCheckBox, ID_STR_CLICKTHROUGH, + 350, 203, 118, 9, + WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), + CT_CHECKBOX(Id_KeepOnScreenCheckBox, ID_STR_KEEPONSCREEN, + 350, 216, 118, 9, + WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), + CT_CHECKBOX(Id_SavePositionCheckBox, ID_STR_SAVEPOSITION, + 350, 229, 118, 9, + WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), + CT_CHECKBOX(Id_SnapToEdgesCheckBox, ID_STR_SNAPTOEDGES, + 350, 242, 118, 9, + WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0) + }; + + CreateControls(s_Controls, _countof(s_Controls), c_Dialog->m_Font, GetString); +} + +void DialogManage::TabSkins::Initialize() +{ + BUTTON_SPLITINFO bsi; + bsi.mask = BCSIF_SIZE; + bsi.size.cx = 20; + bsi.size.cy = 14; + + HWND item = GetControl(Id_ActiveSkinsButton); + Dialog::SetMenuButton(item); + + item = GetControl(Id_DisplayMonitorButton); + Dialog::SetMenuButton(item); + + // Load folder/.ini icons from shell32 + HIMAGELIST hImageList = ImageList_Create(16, 16, ILC_COLOR32, 2, 10); + HMODULE hDLL = GetModuleHandle(L"shell32"); + + HICON hIcon = (HICON)LoadImage(hDLL, MAKEINTRESOURCE(4), IMAGE_ICON, 16, 16, LR_SHARED); + ImageList_AddIcon(hImageList, hIcon); + hIcon = (HICON)LoadImage(hDLL, MAKEINTRESOURCE(151), IMAGE_ICON, 16, 16, LR_SHARED); + ImageList_AddIcon(hImageList, hIcon); + + // Apply icons and populate tree + item = GetControl(Id_SkinsTreeView); + TreeView_SetImageList(item, hImageList, TVSIL_NORMAL); + Update(nullptr, false); + + // Get rid of the EDITTEXT control border + item = GetControl(Id_DescriptionLabel); + SetWindowLongPtr(item, GWL_EXSTYLE, GetWindowLongPtr(item, GWL_EXSTYLE) &~ WS_EX_CLIENTEDGE); + SetWindowPos(item, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER); + + item = GetControl(Id_TransparencyDropDownList); + ComboBox_AddString(item, L"0%"); + ComboBox_AddString(item, L"10%"); + ComboBox_AddString(item, L"20%"); + ComboBox_AddString(item, L"30%"); + ComboBox_AddString(item, L"40%"); + ComboBox_AddString(item, L"50%"); + ComboBox_AddString(item, L"60%"); + ComboBox_AddString(item, L"70%"); + ComboBox_AddString(item, L"80%"); + ComboBox_AddString(item, L"90%"); + + item = GetControl(Id_ZPositionDropDownList); + ComboBox_AddString(item, GetString(ID_STR_ONDESKTOP)); + ComboBox_AddString(item, GetString(ID_STR_BOTTOM)); + ComboBox_AddString(item, GetString(ID_STR_NORMAL)); + ComboBox_AddString(item, GetString(ID_STR_TOPMOST)); + ComboBox_AddString(item, GetString(ID_STR_STAYTOPMOST)); + + item = GetControl(Id_OnHoverDropDownList); + ComboBox_AddString(item, GetString(ID_STR_DONOTHING)); + ComboBox_AddString(item, GetString(ID_STR_HIDE)); + ComboBox_AddString(item, GetString(ID_STR_FADEIN)); + ComboBox_AddString(item, GetString(ID_STR_FADEOUT)); + + m_Initialized = true; + m_HandleCommands = true; +} + +/* +** Updates metadata and settings when changed. +** +*/ +void DialogManage::TabSkins::Update(MeterWindow* meterWindow, bool deleted) +{ + if (meterWindow) + { + if (!deleted && m_IgnoreUpdate) + { + // Changed setting from dialog, no need to update + m_IgnoreUpdate = false; + } + else if (m_SkinWindow && m_SkinWindow == meterWindow) + { + // Update from currently open skin + m_HandleCommands = false; + if (deleted) + { + DisableControls(); + m_SkinWindow = nullptr; + } + else + { + SetControls(); + } + m_HandleCommands = true; + } + else if (wcscmp(meterWindow->GetFolderPath().c_str(), m_SkinFolderPath.c_str()) == 0 && + wcscmp(meterWindow->GetFileName().c_str(), m_SkinFileName.c_str()) == 0) + { + ReadSkin(); + } + } + else + { + // Populate tree + HWND item = GetControl(Id_SkinsTreeView); + TreeView_DeleteAllItems(item); + + TVINSERTSTRUCT tvi = {0}; + tvi.hInsertAfter = TVI_LAST; + tvi.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE; + tvi.item.iImage = tvi.item.iSelectedImage = 0; + + if (!GetRainmeter().m_SkinRegistry.IsEmpty()) + { + PopulateTree(item, tvi); + } + } +} + +void DialogManage::TabSkins::SetControls() +{ + WCHAR buffer[64]; + + HWND item = GetControl(Id_EditButton); + EnableWindow(item, TRUE); + + item = GetControl(Id_LoadButton); + EnableWindow(item, TRUE); + + if (m_SkinWindow) + { + SetWindowText(item, GetString(ID_STR_UNLOAD)); + + item = GetControl(Id_RefreshButton); + EnableWindow(item, TRUE); + + item = GetControl(Id_XPositionEdit); + EnableWindow(item, TRUE); + _itow_s(m_SkinWindow->GetX(), buffer, 10); + SetWindowText(item, buffer); + + item = GetControl(Id_YPositionEdit); + EnableWindow(item, TRUE); + _itow_s(m_SkinWindow->GetY(), buffer, 10); + SetWindowText(item, buffer); + + item = GetControl(Id_DisplayMonitorButton); + EnableWindow(item, TRUE); + + item = GetControl(Id_DraggableCheckBox); + if (GetRainmeter().GetDisableDragging()) + { + EnableWindow(item, FALSE); + Button_SetCheck(item, BST_UNCHECKED); + } + else + { + EnableWindow(item, TRUE); + Button_SetCheck(item, m_SkinWindow->GetWindowDraggable()); + } + + item = GetControl(Id_ClickThroughCheckBox); + EnableWindow(item, TRUE); + Button_SetCheck(item, m_SkinWindow->GetClickThrough()); + + item = GetControl(Id_KeepOnScreenCheckBox); + EnableWindow(item, TRUE); + Button_SetCheck(item, m_SkinWindow->GetKeepOnScreen()); + + item = GetControl(Id_SavePositionCheckBox); + EnableWindow(item, TRUE); + Button_SetCheck(item, m_SkinWindow->GetSavePosition()); + + item = GetControl(Id_SnapToEdgesCheckBox); + EnableWindow(item, TRUE); + Button_SetCheck(item, m_SkinWindow->GetSnapEdges()); + + item = GetControl(Id_TransparencyDropDownList); + EnableWindow(item, TRUE); + int value = (int)(10 - m_SkinWindow->GetAlphaValue() / 25.5); + value = min(9, value); + value = max(0, value); + ComboBox_SetCurSel(item, value); + + item = GetControl(Id_ZPositionDropDownList); + EnableWindow(item, TRUE); + ComboBox_SetCurSel(item, m_SkinWindow->GetWindowZPosition() + 2); + + item = GetControl(Id_LoadOrderEdit); + EnableWindow(item, TRUE); + _itow_s(GetRainmeter().GetLoadOrder(m_SkinFolderPath), buffer, 10); + SetWindowText(item, buffer); + + item = GetControl(Id_OnHoverDropDownList); + EnableWindow(item, TRUE); + ComboBox_SetCurSel(item, m_SkinWindow->GetWindowHide()); + } + else + { + SetWindowText(item, GetString(ID_STR_LOAD)); + } +} + +void DialogManage::TabSkins::DisableControls(bool clear) +{ + HWND item = GetControl(Id_LoadButton); + SetWindowText(item, GetString(ID_STR_LOAD)); + + if (clear) + { + EnableWindow(item, FALSE); + + item = GetControl(Id_EditButton); + EnableWindow(item, FALSE); + + item = GetControl(Id_FileLabel); + SetWindowText(item, GetString(ID_STR_ELLIPSIS)); + + item = GetControl(Id_ConfigLabel); + SetWindowText(item, L""); + + item = GetControl(Id_AuthorLabel); + SetWindowText(item, L""); + + item = GetControl(Id_VersionLabel); + SetWindowText(item, L""); + + item = GetControl(Id_LicenseLabel); + SetWindowText(item, L""); + + item = GetControl(Id_DescriptionLabel); + SetWindowText(item, L""); + ShowScrollBar(item, SB_VERT, FALSE); + + item = GetControl(Id_AddMetadataLink); + ShowWindow(item, SW_HIDE); + } + else + { + EnableWindow(item, TRUE); + } + + item = GetControl(Id_RefreshButton); + EnableWindow(item, FALSE); + + item = GetControl(Id_XPositionEdit); + SetWindowText(item, L""); + EnableWindow(item, FALSE); + + item = GetControl(Id_YPositionEdit); + SetWindowText(item, L""); + EnableWindow(item, FALSE); + + item = GetControl(Id_DisplayMonitorButton); + EnableWindow(item, FALSE); + + item = GetControl(Id_DraggableCheckBox); + EnableWindow(item, FALSE); + Button_SetCheck(item, BST_UNCHECKED); + + item = GetControl(Id_ClickThroughCheckBox); + EnableWindow(item, FALSE); + Button_SetCheck(item, BST_UNCHECKED); + + item = GetControl(Id_KeepOnScreenCheckBox); + EnableWindow(item, FALSE); + Button_SetCheck(item, BST_UNCHECKED); + + item = GetControl(Id_SavePositionCheckBox); + EnableWindow(item, FALSE); + Button_SetCheck(item, BST_UNCHECKED); + + item = GetControl(Id_SnapToEdgesCheckBox); + EnableWindow(item, FALSE); + Button_SetCheck(item, BST_UNCHECKED); + + item = GetControl(Id_TransparencyDropDownList); + EnableWindow(item, FALSE); + ComboBox_SetCurSel(item, -1); + + item = GetControl(Id_ZPositionDropDownList); + EnableWindow(item, FALSE); + ComboBox_SetCurSel(item, -1); + + item = GetControl(Id_LoadOrderEdit); + SetWindowText(item, L""); + EnableWindow(item, FALSE); + + item = GetControl(Id_OnHoverDropDownList); + EnableWindow(item, FALSE); + ComboBox_SetCurSel(item, -1); +} + +void DialogManage::TabSkins::ReadSkin() +{ + HWND item = GetControl(Id_FileLabel); + SetWindowText(item, m_SkinFileName.c_str()); + + PathSetDlgItemPath(m_Window, Id_ConfigLabel, m_SkinFolderPath.c_str()); + + item = GetControl(Id_EditButton); + EnableWindow(item, TRUE); + + std::wstring file = GetRainmeter().GetSkinPath() + m_SkinFolderPath; + file += L'\\'; + file += m_SkinFileName; + m_SkinWindow = GetRainmeter().GetMeterWindowByINI(file); + if (!m_SkinWindow) + { + DisableControls(); + } + + SetControls(); + + WCHAR* buffer = new WCHAR[MAX_LINE_LENGTH]; + const WCHAR* fileSz = file.c_str(); + + item = GetControl(Id_AuthorLabel); + if (GetPrivateProfileString(L"Metadata", L"Author", nullptr, buffer, MAX_LINE_LENGTH, fileSz) == 0) + { + // For backwards compatibility. + GetPrivateProfileString(L"Rainmeter", L"Author", nullptr, buffer, MAX_LINE_LENGTH, fileSz); + } + SetWindowText(item, buffer); + + item = GetControl(Id_AddMetadataLink); + if (GetPrivateProfileSection(L"Metadata", buffer, 8, fileSz) > 0) + { + ShowWindow(item, SW_HIDE); + + // Set metadata + item = GetControl(Id_VersionLabel); + GetPrivateProfileString(L"Metadata", L"Version", nullptr, buffer, MAX_LINE_LENGTH, fileSz); + SetWindowText(item, buffer); + + item = GetControl(Id_LicenseLabel); + GetPrivateProfileString(L"Metadata", L"License", nullptr, buffer, MAX_LINE_LENGTH, fileSz); + SetWindowText(item, buffer); + + item = GetControl(Id_DescriptionLabel); + std::wstring text; + if (GetPrivateProfileString(L"Metadata", L"Information", nullptr, buffer, MAX_LINE_LENGTH, fileSz) > 0) + { + text = buffer; + } + else + { + // For backwards compatibility + GetPrivateProfileString(L"Metadata", L"Description", nullptr, buffer, MAX_LINE_LENGTH, fileSz); + text = buffer; + + if (GetPrivateProfileString(L"Metadata", L"Instructions", nullptr, buffer, MAX_LINE_LENGTH, fileSz) > 0) + { + text += L"\r\n\r\n"; + text += buffer; + } + } + + // Replace | with newline + std::wstring::size_type pos; + while ((pos = text.find_first_of(L'|')) != std::wstring::npos) + { + size_t count = (pos + 1 < text.length() && text[pos + 1] == L' ') ? 2 : 1; + if (text[pos - 1] == L' ') + { + --pos; + count += 1; + } + text.replace(pos, count, L"\r\n"); + } + + SetWindowText(item, text.c_str()); + + int lines = Edit_GetLineCount(item); + ShowScrollBar(item, SB_VERT, (BOOL)(lines > 6)); + } + else + { + ShowWindow(item, SW_SHOWNORMAL); + + item = GetControl(Id_VersionLabel); + SetWindowText(item, L""); + + item = GetControl(Id_LicenseLabel); + SetWindowText(item, L""); + + item = GetControl(Id_DescriptionLabel); + SetWindowText(item, L""); + ShowScrollBar(item, SB_VERT, FALSE); + } + + delete [] buffer; +} + +std::wstring DialogManage::TabSkins::GetTreeSelectionPath(HWND tree) +{ + WCHAR buffer[MAX_PATH]; + + // Get current selection name + TVITEM tvi = {0}; + tvi.hItem = TreeView_GetSelection(tree); + tvi.mask = TVIF_TEXT; + tvi.pszText = buffer; + tvi.cchTextMax = MAX_PATH; + TreeView_GetItem(tree, &tvi); + + std::wstring path = buffer; + while ((tvi.hItem = TreeView_GetParent(tree, tvi.hItem)) != nullptr) + { + TreeView_GetItem(tree, &tvi); + path.insert(0, 1, L'\\'); + path.insert(0, buffer); + } + + return path; +} + +/* +** Populates the treeview with folders and skins. +** +*/ +int DialogManage::TabSkins::PopulateTree(HWND tree, TVINSERTSTRUCT& tvi, int index) +{ + int initialLevel = GetRainmeter().m_SkinRegistry.GetFolder(index).level; + + const size_t max = GetRainmeter().m_SkinRegistry.GetFolderCount(); + while (index < max) + { + const auto& skinFolder = GetRainmeter().m_SkinRegistry.GetFolder(index); + if (skinFolder.level != initialLevel) + { + return index - 1; + } + + HTREEITEM oldParent = tvi.hParent; + + // Add folder + tvi.item.iImage = tvi.item.iSelectedImage = 0; + tvi.item.pszText = (WCHAR*)skinFolder.name.c_str(); + tvi.hParent = TreeView_InsertItem(tree, &tvi); + + // Add subfolders + if ((index + 1) < max && + GetRainmeter().m_SkinRegistry.GetFolder(index + 1).level == initialLevel + 1) + { + index = PopulateTree(tree, tvi, index + 1); + } + + // Add files + tvi.item.iImage = tvi.item.iSelectedImage = 1; + for (int i = 0, isize = (int)skinFolder.files.size(); i < isize; ++i) + { + tvi.item.pszText = (WCHAR*)skinFolder.files[i].c_str(); + TreeView_InsertItem(tree, &tvi); + } + + tvi.hParent = oldParent; + + ++index; + } + + return index; +} + +/* +** Selects an item in the treeview. +** +*/ +void DialogManage::TabSkins::SelectTreeItem(HWND tree, HTREEITEM item, LPCWSTR name) +{ + WCHAR buffer[MAX_PATH]; + TVITEM tvi = {0}; + tvi.mask = TVIF_TEXT; + tvi.hItem = item; + tvi.pszText = buffer; + + const WCHAR* pos = wcschr(name, L'\\'); + if (pos) + { + const int folderLen = (int)(pos - name); + tvi.cchTextMax = folderLen + 1; // Length of folder name plus 1 for nullptr + + // Find and expand the folder + do + { + TreeView_GetItem(tree, &tvi); + if (wcsncmp(buffer, name, folderLen) == 0) + { + if ((item = TreeView_GetChild(tree, tvi.hItem)) != nullptr) + { + TreeView_Expand(tree, tvi.hItem, TVE_EXPAND); + TreeView_Select(tree, tvi.hItem, TVGN_CARET); + ++pos; // Skip the slash + SelectTreeItem(tree, item, pos); + } + + break; + } + } + while ((tvi.hItem = TreeView_GetNextSibling(tree, tvi.hItem)) != nullptr); + } + else + { + tvi.cchTextMax = MAX_PATH; + + // Find and select the file + do + { + TreeView_GetItem(tree, &tvi); + if (wcscmp(buffer, name) == 0) + { + TreeView_Select(tree, tvi.hItem, TVGN_CARET); + break; + } + } + while ((tvi.hItem = TreeView_GetNextSibling(tree, tvi.hItem)) != nullptr); + } +} + +INT_PTR DialogManage::TabSkins::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_COMMAND: + return OnCommand(wParam, lParam); + + case WM_NOTIFY: + return OnNotify(wParam, lParam); + } + + return FALSE; +} + +INT_PTR DialogManage::TabSkins::OnCommand(WPARAM wParam, LPARAM lParam) +{ + if (!m_HandleCommands) + { + // Values are being changed/reset, no need to apply changes. + return FALSE; + } + + switch (LOWORD(wParam)) + { + case Id_ActiveSkinsButton: + { + HMENU menu = CreatePopupMenu(); + + // Add active skins to menu + std::map::const_iterator iter = GetRainmeter().GetAllMeterWindows().begin(); + int index = 0; + for ( ; iter != GetRainmeter().GetAllMeterWindows().end(); ++iter) + { + std::wstring name = ((*iter).second)->GetFolderPath() + L'\\'; + name += ((*iter).second)->GetFileName(); + InsertMenu(menu, index, MF_BYPOSITION, ID_CONFIG_FIRST + index, name.c_str()); + ++index; + } + + if (index > 0) + { + RECT r; + GetWindowRect((HWND)lParam, &r); + + // Show context menu + TrackPopupMenu( + menu, + TPM_RIGHTBUTTON | TPM_LEFTALIGN, + (*GetString(ID_STR_ISRTL) == L'1') ? r.right : r.left, + --r.bottom, + 0, + m_Window, + nullptr + ); + } + + DestroyMenu(menu); + } + break; + + case Id_CreateSkinPackageButton: + { + std::wstring file = GetRainmeter().GetPath() + L"SkinInstaller.exe"; + CommandHandler::RunFile(file.c_str(), L"/Packager"); + } + break; + + case Id_LoadButton: + { + if (!m_SkinWindow) + { + // Skin not active, load + const SkinRegistry::Indexes indexes = + GetRainmeter().m_SkinRegistry.FindIndexes(m_SkinFolderPath, m_SkinFileName); + if (indexes.IsValid()) + { + m_HandleCommands = false; + GetRainmeter().ActivateSkin(indexes.folder, indexes.file); + m_HandleCommands = true; + + // Fake selection change to update controls + NMHDR nm; + nm.code = TVN_SELCHANGED; + nm.idFrom = Id_SkinsTreeView; + nm.hwndFrom = GetControl(Id_SkinsTreeView); + OnNotify(0, (LPARAM)&nm); + } + } + else + { + m_HandleCommands = false; + GetRainmeter().DeactivateSkin(m_SkinWindow, -1); + } + } + break; + + case Id_RefreshButton: + if (m_SkinWindow) + { + m_SkinWindow->Refresh(false); + } + break; + + case Id_EditButton: + GetRainmeter().EditSkinFile(m_SkinFolderPath, m_SkinFileName); + break; + + case Id_XPositionEdit: + if (HIWORD(wParam) == EN_CHANGE) + { + WCHAR buffer[32]; + m_IgnoreUpdate = true; + int x = (GetWindowText((HWND)lParam, buffer, 32) > 0) ? _wtoi(buffer) : 0; + m_SkinWindow->MoveWindow(x, m_SkinWindow->GetY()); + + if (x > m_SkinWindow->GetX()) + { + _itow_s(m_SkinWindow->GetX(), buffer, 10); + Edit_SetText((HWND)lParam, buffer); + } + } + break; + + case Id_YPositionEdit: + if (HIWORD(wParam) == EN_CHANGE) + { + WCHAR buffer[32]; + m_IgnoreUpdate = true; + int y = (GetWindowText((HWND)lParam, buffer, 32) > 0) ? _wtoi(buffer) : 0; + m_SkinWindow->MoveWindow(m_SkinWindow->GetX(), y); + + if (y > m_SkinWindow->GetY()) + { + _itow_s(m_SkinWindow->GetY(), buffer, 10); + Edit_SetText((HWND)lParam, buffer); + } + } + break; + + case Id_LoadOrderEdit: + if (HIWORD(wParam) == EN_CHANGE) + { + if (m_IgnoreUpdate) + { + // To avoid infinite loop after setting value below + m_IgnoreUpdate = false; + } + else + { + // Convert text to number and set it to get rid of extra chars + WCHAR buffer[32]; + int len = GetWindowText((HWND)lParam, buffer, 32); + if ((len == 0) || (len == 1 && buffer[0] == L'-')) + { + // Ignore if empty or if - is only char + break; + } + + // Get selection + DWORD sel = Edit_GetSel((HWND)lParam); + + // Reset value (to get rid of invalid chars) + m_IgnoreUpdate = true; + int value = _wtoi(buffer); + + _itow_s(value, buffer, 10); + SetWindowText((HWND)lParam, buffer); + + // Reset selection + Edit_SetSel((HWND)lParam, LOWORD(sel), HIWORD(sel)); + + WritePrivateProfileString(m_SkinFolderPath.c_str(), L"LoadOrder", buffer, GetRainmeter().GetIniFile().c_str()); + const SkinRegistry::Indexes indexes = GetRainmeter().m_SkinRegistry.FindIndexes( + m_SkinWindow->GetFolderPath(), m_SkinWindow->GetFileName()); + if (indexes.IsValid()) + { + GetRainmeter().SetLoadOrder(indexes.folder, value); + + std::multimap windows; + GetRainmeter().GetMeterWindowsByLoadOrder(windows); + + System::PrepareHelperWindow(); + + // Reorder window z-position to reflect load order + std::multimap::const_iterator iter = windows.begin(); + for ( ; iter != windows.end(); ++iter) + { + MeterWindow* mw = (*iter).second; + mw->ChangeZPos(mw->GetWindowZPosition(), true); + } + } + } + } + break; + + case Id_DisplayMonitorButton: + { + static const MenuTemplate s_Menu[] = + { + MENU_ITEM(IDM_SKIN_MONITOR_PRIMARY, ID_STR_USEDEFAULTMONITOR), + MENU_ITEM(ID_MONITOR_FIRST, ID_STR_VIRTUALSCREEN), + MENU_SEPARATOR(), + MENU_SEPARATOR(), + MENU_ITEM(IDM_SKIN_MONITOR_AUTOSELECT, ID_STR_AUTOSELECTMONITOR) + }; + + HMENU menu = MenuTemplate::CreateMenu(s_Menu, _countof(s_Menu), GetString); + if (menu) + { + ContextMenu::CreateMonitorMenu(menu, m_SkinWindow); + + RECT r; + GetWindowRect((HWND)lParam, &r); + + // Show context menu + TrackPopupMenu( + menu, + TPM_RIGHTBUTTON | TPM_LEFTALIGN, + (*GetString(ID_STR_ISRTL) == L'1') ? r.right : r.left, + --r.bottom, + 0, + m_Window, + nullptr + ); + + DestroyMenu(menu); + } + } + break; + + case Id_DraggableCheckBox: + m_IgnoreUpdate = true; + m_SkinWindow->SetWindowDraggable(!m_SkinWindow->GetWindowDraggable()); + break; + + case Id_ClickThroughCheckBox: + m_IgnoreUpdate = true; + m_SkinWindow->SetClickThrough(!m_SkinWindow->GetClickThrough()); + break; + + case Id_KeepOnScreenCheckBox: + m_IgnoreUpdate = true; + m_SkinWindow->SetKeepOnScreen(!m_SkinWindow->GetKeepOnScreen()); + break; + + case Id_SavePositionCheckBox: + m_IgnoreUpdate = true; + m_SkinWindow->SetSavePosition(!m_SkinWindow->GetSavePosition()); + break; + + case Id_SnapToEdgesCheckBox: + m_IgnoreUpdate = true; + m_SkinWindow->SetSnapEdges(!m_SkinWindow->GetSnapEdges()); + break; + + case Id_ZPositionDropDownList: + if (HIWORD(wParam) == CBN_SELCHANGE) + { + m_IgnoreUpdate = true; + ZPOSITION zpos = (ZPOSITION)(ComboBox_GetCurSel((HWND)lParam) - 2); + m_SkinWindow->SetWindowZPosition(zpos); + } + break; + + case Id_TransparencyDropDownList: + if (HIWORD(wParam) == CBN_SELCHANGE) + { + m_IgnoreUpdate = true; + int sel = ComboBox_GetCurSel((HWND)lParam) + IDM_SKIN_TRANSPARENCY_0; + SendMessage(m_SkinWindow->GetWindow(), WM_COMMAND, sel, 0); + } + break; + + case Id_OnHoverDropDownList: + if (HIWORD(wParam) == CBN_SELCHANGE) + { + m_IgnoreUpdate = true; + HIDEMODE hide = (HIDEMODE)ComboBox_GetCurSel((HWND)lParam); + m_SkinWindow->SetWindowHide(hide); + } + break; + + case IDM_MANAGESKINSMENU_EXPAND: + { + HWND tree = GetControl(Id_SkinsTreeView); + HTREEITEM item = TreeView_GetSelection(tree); + TreeView_Expand(tree, item, TVE_TOGGLE); + } + break; + + case IDM_MANAGESKINSMENU_OPENFOLDER: + { + HWND tree = GetControl(Id_SkinsTreeView); + GetRainmeter().OpenSkinFolder(GetTreeSelectionPath(tree)); + } + break; + + default: + if (wParam >= ID_CONFIG_FIRST && wParam <= ID_CONFIG_LAST) + { + std::map::const_iterator iter = GetRainmeter().GetAllMeterWindows().begin(); + int index = (int)wParam - ID_CONFIG_FIRST; + int i = 0; + for ( ; iter != GetRainmeter().GetAllMeterWindows().end(); ++iter) + { + if (i == index) + { + std::wstring name = ((*iter).second)->GetFolderPath() + L'\\'; + name += ((*iter).second)->GetFileName(); + + HWND item = GetControl(Id_SkinsTreeView); + SelectTreeItem(item, TreeView_GetRoot(item), name.c_str()); + break; + } + + ++i; + } + } + else if (wParam == IDM_SKIN_MONITOR_AUTOSELECT || + wParam == IDM_SKIN_MONITOR_PRIMARY || + wParam >= ID_MONITOR_FIRST && wParam <= ID_MONITOR_LAST) + { + if (m_SkinWindow) + { + SendMessage(m_SkinWindow->GetWindow(), WM_COMMAND, wParam, 0); + } + break; + } + + return 1; + } + + return 0; +} + +INT_PTR DialogManage::TabSkins::OnNotify(WPARAM wParam, LPARAM lParam) +{ + LPNMHDR nm = (LPNMHDR)lParam; + switch (nm->code) + { + case NM_CLICK: + if (nm->idFrom == Id_AddMetadataLink) + { + std::wstring file = GetRainmeter().GetSkinPath() + m_SkinFolderPath; + file += L'\\'; + file += m_SkinFileName; + const WCHAR* str = L"\r\n" // Hack to add below [Rainmeter]. + L"[Metadata]\r\n" + L"Name=\r\n" + L"Author=\r\n" + L"Information=\r\n" + L"License=\r\n" + L"Version"; + WritePrivateProfileString(L"Rainmeter", str, L"", file.c_str()); + SendMessage(m_Window, WM_COMMAND, MAKEWPARAM(Id_EditButton, 0), 0); + ShowWindow(nm->hwndFrom, SW_HIDE); + } + break; + + case NM_DBLCLK: + if (nm->idFrom == Id_SkinsTreeView && !m_SkinFileName.empty()) + { + OnCommand(MAKEWPARAM(Id_LoadButton, 0), 0); + } + break; + + case NM_RCLICK: + if (nm->idFrom == Id_SkinsTreeView) + { + POINT pt = System::GetCursorPosition(); + + TVHITTESTINFO ht; + ht.pt = pt; + ScreenToClient(nm->hwndFrom, &ht.pt); + + if (TreeView_HitTest(nm->hwndFrom, &ht) && !(ht.flags & TVHT_NOWHERE)) + { + TreeView_SelectItem(nm->hwndFrom, ht.hItem); + + TVITEM tvi = {0}; + tvi.hItem = TreeView_GetSelection(nm->hwndFrom); + tvi.mask = TVIF_STATE; + + if (TreeView_GetItem(nm->hwndFrom, &tvi)) + { + HMENU menu = nullptr; + MENUITEMINFO mii = {0}; + mii.cbSize = sizeof(MENUITEMINFO); + mii.fMask = MIIM_STRING; + + if (m_SkinFileName.empty()) + { + // Folder menu. + static const MenuTemplate s_Menu[] = + { + MENU_ITEM(IDM_MANAGESKINSMENU_EXPAND, ID_STR_EXPAND), + MENU_ITEM(IDM_MANAGESKINSMENU_OPENFOLDER, ID_STR_OPENFOLDER), + }; + + menu = MenuTemplate::CreateMenu(s_Menu, _countof(s_Menu), GetString); + SetMenuDefaultItem(menu, IDM_MANAGESKINSMENU_EXPAND, MF_BYCOMMAND); + + if (tvi.state & TVIS_EXPANDED) + { + mii.dwTypeData = GetString(ID_STR_COLLAPSE); + SetMenuItemInfo(menu, IDM_MANAGESKINSMENU_EXPAND, MF_BYCOMMAND, &mii); + } + } + else + { + // Skin menu. + static const MenuTemplate s_Menu[] = + { + MENU_ITEM(IDM_MANAGESKINSMENU_LOAD, ID_STR_LOAD), + MENU_ITEM(IDM_MANAGESKINSMENU_REFRESH, ID_STR_REFRESH), + MENU_ITEM(IDM_MANAGESKINSMENU_EDIT, ID_STR_EDIT), + }; + + menu = MenuTemplate::CreateMenu(s_Menu, _countof(s_Menu), GetString); + SetMenuDefaultItem(menu, IDM_MANAGESKINSMENU_LOAD, MF_BYCOMMAND); + + if (m_SkinWindow) + { + mii.dwTypeData = GetString(ID_STR_UNLOAD); + SetMenuItemInfo(menu, IDM_MANAGESKINSMENU_LOAD, MF_BYCOMMAND, &mii); + } + else + { + EnableMenuItem(menu, IDM_MANAGESKINSMENU_REFRESH, MF_BYCOMMAND | MF_GRAYED); + } + } + + // Show context menu + TrackPopupMenu( + menu, + TPM_RIGHTBUTTON | TPM_LEFTALIGN, + pt.x, + pt.y, + 0, + m_Window, + nullptr + ); + + DestroyMenu(menu); + } + } + } + break; + + case TVN_SELCHANGED: + if (nm->idFrom == Id_SkinsTreeView) + { + m_SkinWindow = nullptr; + m_SkinFileName.clear(); + m_SkinFolderPath.clear(); + + // Temporarily disable handling commands + m_HandleCommands = false; + + WCHAR buffer[MAX_PATH]; + + // Get current selection name + TVITEM tvi = {0}; + tvi.hItem = TreeView_GetSelection(nm->hwndFrom); + tvi.mask = TVIF_TEXT | TVIF_CHILDREN; + tvi.pszText = buffer; + tvi.cchTextMax = MAX_PATH; + TreeView_GetItem(nm->hwndFrom, &tvi); + + if (tvi.cChildren == 0) + { + // Current selection is file + m_SkinFileName = buffer; + tvi.mask = TVIF_TEXT; + + // Loop through parents to get skin folder + m_SkinFolderPath.clear(); + while ((tvi.hItem = TreeView_GetParent(nm->hwndFrom, tvi.hItem)) != nullptr) + { + TreeView_GetItem(nm->hwndFrom, &tvi); + m_SkinFolderPath.insert(0, 1, L'\\'); + m_SkinFolderPath.insert(0, buffer); + } + + m_SkinFolderPath.resize(m_SkinFolderPath.length() - 1); // Get rid of trailing slash + + ReadSkin(); + } + else + { + DisableControls(true); + } + + m_HandleCommands = true; + } + break; + + default: + return FALSE; + } + + return TRUE; +} + +// ----------------------------------------------------------------------------------------------- +// +// Layouts tab +// +// ----------------------------------------------------------------------------------------------- + +/* +** Constructor. +** +*/ +DialogManage::TabLayouts::TabLayouts() : Tab() +{ +} + +void DialogManage::TabLayouts::Create(HWND owner) +{ + Tab::CreateTabWindow(15, 30, 470, 260, owner); + + static const ControlTemplate::Control s_Controls[] = + { + CT_GROUPBOX(-1, ID_STR_SAVENEWTHEME, + 0, 0, 230, 150, + WS_VISIBLE, 0), + CT_LABEL(-1, ID_STR_THEMEDESCRIPTION, + 6, 16, 205, 44, + WS_VISIBLE, 0), + CT_CHECKBOX(Id_SaveEmptyThemeCheckBox, ID_STR_SAVEASEMPTYTHEME, + 6, 70, 220, 9, + WS_VISIBLE | WS_TABSTOP, 0), + CT_CHECKBOX(Id_ExcludeUnusedSkinsCheckBox, ID_STR_EXCLUDEUNUSEDSKINS, + 6, 83, 220, 9, + WS_VISIBLE | WS_TABSTOP, 0), + CT_CHECKBOX(Id_IncludeWallpaperCheckBox, ID_STR_INCLUDEWALLPAPER, + 6, 96, 220, 9, + WS_VISIBLE | WS_TABSTOP, 0), + CT_LABEL(-1, ID_STR_NAMESC, + 6, 115, 100, 9, + WS_VISIBLE, 0), + CT_EDIT(Id_NameLabel, 0, + 6, 128, 162, 14, + WS_VISIBLE | WS_TABSTOP, WS_EX_CLIENTEDGE), + CT_BUTTON(Id_SaveButton, ID_STR_SAVE, + 172, 128, 50, 14, + WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), + + CT_GROUPBOX(-1, ID_STR_SAVEDTHEMES, + 238, 0, 230, 150, + WS_VISIBLE, 0), + CT_LISTBOX(Id_List, 0, + 244, 16, 160, 125, + WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | LBS_SORT | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT, WS_EX_CLIENTEDGE), + CT_BUTTON(Id_LoadButton, ID_STR_LOAD, + 410, 16, 50, 14, + WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), + CT_BUTTON(Id_EditButton, ID_STR_EDIT, + 410, 34, 50, 14, + WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0), + CT_BUTTON(Id_DeleteButton, ID_STR_DELETE, + 410, 52, 50, 14, + WS_VISIBLE | WS_TABSTOP | WS_DISABLED, 0) + }; + + CreateControls(s_Controls, _countof(s_Controls), c_Dialog->m_Font, GetString); +} + +void DialogManage::TabLayouts::Initialize() +{ + HWND item = GetControl(Id_List); + ListBox_ResetContent(item); + const std::vector& layouts = GetRainmeter().GetAllLayouts(); + for (size_t i = 0, isize = layouts.size(); i < isize; ++i) + { + ListBox_AddString(item, layouts[i].c_str()); + } + + m_Initialized = true; +} + +void DialogManage::TabLayouts::Update() +{ + Initialize(); +} + +INT_PTR DialogManage::TabLayouts::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_COMMAND: + return OnCommand(wParam, lParam); + } + + return FALSE; +} + +INT_PTR DialogManage::TabLayouts::OnCommand(WPARAM wParam, LPARAM lParam) +{ + switch (LOWORD(wParam)) + { + case Id_SaveEmptyThemeCheckBox: + { + BOOL state = !(Button_GetCheck((HWND)lParam) == BST_CHECKED); + + HWND item = GetControl(Id_ExcludeUnusedSkinsCheckBox); + EnableWindow(item, state); + Button_SetCheck(item, BST_UNCHECKED); + + item = GetControl(Id_IncludeWallpaperCheckBox); + EnableWindow(item, state); + Button_SetCheck(item, BST_UNCHECKED); + } + break; + + case Id_NameLabel: + if (HIWORD(wParam) == EN_CHANGE) + { + WCHAR buffer[32]; + int len = Edit_GetText((HWND)lParam, buffer, 32); + + // Disable save button if no text or if backup + BOOL state = (len > 0 && _wcsicmp(buffer, L"@Backup") != 0); + EnableWindow(GetControl(Id_SaveButton), state); + } + break; + + case Id_List: + if (HIWORD(wParam) == LBN_SELCHANGE) + { + // Ignore clicks that don't hit items + if (ListBox_GetCurSel((HWND)lParam) != LB_ERR) + { + HWND item = GetControl(Id_LoadButton); + EnableWindow(item, TRUE); + item = GetControl(Id_DeleteButton); + EnableWindow(item, TRUE); + item = GetControl(Id_EditButton); + EnableWindow(item, TRUE); + + const std::vector& layouts = GetRainmeter().GetAllLayouts(); + item = GetControl(Id_List); + int sel = ListBox_GetCurSel(item); + + item = GetControl(Id_NameLabel); + Edit_SetText(item, layouts[sel].c_str()); + } + } + break; + + case Id_SaveButton: + { + WCHAR buffer[MAX_PATH]; + HWND item = GetControl(Id_NameLabel); + Edit_GetText(item, buffer, MAX_PATH); + + std::wstring layout = buffer; + std::wstring path = GetRainmeter().GetLayoutPath(); + CreateDirectory(path.c_str(), 0); + + path += layout; + bool alreadyExists = (_waccess(path.c_str(), 0) != -1); + if (alreadyExists) + { + std::wstring text = GetFormattedString(ID_STR_THEMEALREADYEXISTS, layout.c_str()); + if (GetRainmeter().ShowMessage(m_Window, text.c_str(), MB_ICONWARNING | MB_YESNO) != IDYES) + { + // Cancel + break; + } + } + else + { + // Make sure path exists + CreateDirectory(path.c_str(), nullptr); + } + + path += L"\\Rainmeter.ini"; + + item = GetControl(Id_SaveEmptyThemeCheckBox); + if (Button_GetCheck(item) != BST_CHECKED) + { + if (!System::CopyFiles(GetRainmeter().GetIniFile(), path)) + { + std::wstring text = GetFormattedString(ID_STR_THEMESAVEFAIL, path.c_str()); + GetRainmeter().ShowMessage(m_Window, text.c_str(), MB_OK | MB_ICONERROR); + break; + } + + // Exclude unused skins + item = GetControl(Id_ExcludeUnusedSkinsCheckBox); + if (Button_GetCheck(item) == BST_CHECKED) + { + ConfigParser parser; + parser.Initialize(path); + + // Remove sections with Active=0 + std::list::const_iterator iter = parser.GetSections().begin(); + for ( ; iter != parser.GetSections().end(); ++iter) + { + if (parser.GetValue(*iter, L"Active", L"") == L"0") + { + WritePrivateProfileString((*iter).c_str(), nullptr, nullptr, path.c_str()); + } + } + } + + // Save wallpaper + item = GetControl(Id_IncludeWallpaperCheckBox); + if (Button_GetCheck(item) == BST_CHECKED) + { + // Get current wallpaper + if (SystemParametersInfo(SPI_GETDESKWALLPAPER, MAX_PATH, &buffer, 0)) + { + std::wstring::size_type pos = path.find_last_of(L'\\'); + path.replace(pos + 1, path.length() - pos - 1, L"Wallpaper.bmp"); + System::CopyFiles((std::wstring)buffer, path); + } + } + } + else + { + // Create empty layout + HANDLE file = CreateFile(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) + { + std::wstring text = GetFormattedString(ID_STR_THEMESAVEFAIL, path.c_str()); + GetRainmeter().ShowMessage(m_Window, text.c_str(), MB_OK | MB_ICONERROR); + break; + } + + CloseHandle(file); + } + + if (!alreadyExists) + { + item = GetControl(Id_List); + ListBox_AddString(item, layout.c_str()); + + GetRainmeter().ScanForLayouts(); + } + } + break; + + case Id_LoadButton: + { + HWND item = GetControl(Id_List); + int sel = ListBox_GetCurSel(item); + GetRainmeter().LoadLayout(GetRainmeter().m_Layouts[sel]); + } + break; + + case Id_EditButton: + { + HWND item = GetControl(Id_List); + int sel = ListBox_GetCurSel(item); + const std::vector& layouts = GetRainmeter().GetAllLayouts(); + + std::wstring args = L"\"" + GetRainmeter().GetLayoutPath(); + args += layouts[sel]; + args += L"\\Rainmeter.ini"; + args += L'"'; + CommandHandler::RunFile(GetRainmeter().GetSkinEditor().c_str(), args.c_str()); + } + break; + + case Id_DeleteButton: + { + HWND item = GetControl(Id_List); + int sel = ListBox_GetCurSel(item); + std::vector& layouts = const_cast&>(GetRainmeter().GetAllLayouts()); + + std::wstring text = GetFormattedString(ID_STR_THEMEDELETE, layouts[sel].c_str()); + if (GetRainmeter().ShowMessage(m_Window, text.c_str(), MB_ICONQUESTION | MB_YESNO) != IDYES) + { + // Cancel + break; + } + + std::wstring folder = GetRainmeter().GetLayoutPath(); + folder += layouts[sel]; + + if (System::RemoveFolder(folder)) + { + ListBox_DeleteString(item, sel); + + // Remove layout from vector + std::vector::iterator iter = layouts.begin(); + for ( ; iter != layouts.end(); ++iter) + { + if (wcscmp(layouts[sel].c_str(), (*iter).c_str()) == 0) + { + layouts.erase(iter); + break; + } + } + + EnableWindow(GetControl(Id_LoadButton), FALSE); + EnableWindow(GetControl(Id_DeleteButton), FALSE); + EnableWindow(GetControl(Id_EditButton), FALSE); + } + } + break; + + default: + return 1; + } + + return 0; +} + +// ----------------------------------------------------------------------------------------------- +// +// Settings tab +// +// ----------------------------------------------------------------------------------------------- + +/* +** Constructor. +** +*/ +DialogManage::TabSettings::TabSettings() : Tab() +{ +} + +void DialogManage::TabSettings::Create(HWND owner) +{ + Tab::CreateTabWindow(15, 30, 470, 260, owner); + + // FIXME: Temporary hack. + short buttonWidth = (short)_wtoi(GetString(ID_STR_NUM_BUTTONWIDTH)); + + const ControlTemplate::Control s_Controls[] = + { + CT_GROUPBOX(-1, ID_STR_GENERAL, + 0, 0, 468, 131, + WS_VISIBLE, 0), + CT_LABEL(-1, ID_STR_LANGUAGESC, + 6, 16, 107, 14, + WS_VISIBLE, 0), + CT_COMBOBOX(Id_LanguageDropDownList, 0, + 107, 13, 222, 14, + WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL, 0), + CT_LABEL(-1, ID_STR_EDITORSC, + 6, 37, 107, 9, + WS_VISIBLE, 0), + CT_EDIT(Id_EditorEdit, 0, + 107, 34, 222, 14, + WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_READONLY, WS_EX_CLIENTEDGE), + CT_BUTTON(Id_EditorBrowseButton, ID_STR_ELLIPSIS, + 333, 34, 25, 14, + WS_VISIBLE | WS_TABSTOP, 0), + CT_CHECKBOX(Id_CheckForUpdatesCheckBox, ID_STR_CHECKFORUPDATES, + 6, 55, 200, 9, + WS_VISIBLE | WS_TABSTOP, 0), + CT_CHECKBOX(Id_LockSkinsCheckBox, ID_STR_DISABLEDRAGGING, + 6, 68, 200, 9, + WS_VISIBLE | WS_TABSTOP, 0), + CT_CHECKBOX(Id_ShowTrayIconCheckBox, ID_STR_SHOWNOTIFICATIONAREAICON, + 6, 81, 200, 9, + WS_VISIBLE | WS_TABSTOP, 0), + CT_CHECKBOX(Id_UseD2DCheckBox, ID_STR_USED2D, + 6, 94, 200, 9, + WS_VISIBLE | WS_TABSTOP, 0), + CT_BUTTON(Id_ResetStatisticsButton, ID_STR_RESETSTATISTICS, + 6, 110, buttonWidth + 20, 14, + WS_VISIBLE | WS_TABSTOP, 0), + + CT_GROUPBOX(-1, ID_STR_LOGGING, + 0, 138, 468, 66, + WS_VISIBLE, 0), + CT_CHECKBOX(Id_VerboseLoggingCheckbox, ID_STR_DEBUGMODE, + 6, 154, 200, 9, + WS_VISIBLE | WS_TABSTOP, 0), + CT_CHECKBOX(Id_LogToFileCheckBox, ID_STR_LOGTOFILE, + 6, 167, 200, 9, + WS_VISIBLE | WS_TABSTOP, 0), + CT_BUTTON(Id_ShowLogFileButton, ID_STR_SHOWLOGFILE, + 6, 183, buttonWidth + 20, 14, + WS_VISIBLE | WS_TABSTOP, 0), + CT_BUTTON(Id_DeleteLogFileButton, ID_STR_DELETELOGFILE, + buttonWidth + 30, 183, buttonWidth + 20, 14, + WS_VISIBLE | WS_TABSTOP, 0) + }; + + CreateControls(s_Controls, _countof(s_Controls), c_Dialog->m_Font, GetString); +} + +void DialogManage::TabSettings::Initialize() +{ + // Scan for languages + HWND item = GetControl(Id_LanguageDropDownList); + + std::wstring files = GetRainmeter().GetPath() + L"Languages\\*.dll"; + WIN32_FIND_DATA fd; + HANDLE hSearch = FindFirstFile(files.c_str(), &fd); + if (hSearch != INVALID_HANDLE_VALUE) + { + do + { + WCHAR* pos = wcschr(fd.cFileName, L'.'); + if (pos) + { + LCID lcid = (LCID)wcstoul(fd.cFileName, &pos, 10); + if (pos != fd.cFileName && + _wcsicmp(pos, L".dll") == 0 && + GetLocaleInfo(lcid, LOCALE_SENGLISHLANGUAGENAME, fd.cFileName, MAX_PATH) > 0) + { + // Strip brackets in language name + std::wstring text = fd.cFileName; + text += L" - "; + + GetLocaleInfo(lcid, LOCALE_SNATIVEDISPLAYNAME, fd.cFileName, MAX_PATH); + text += fd.cFileName; + + int index = ComboBox_AddString(item, text.c_str()); + ComboBox_SetItemData(item, index, (LPARAM)lcid); + + if (lcid == GetRainmeter().GetResourceLCID()) + { + ComboBox_SetCurSel(item, index); + } + } + } + } + while (FindNextFile(hSearch, &fd)); + + FindClose(hSearch); + } + + Button_SetCheck(GetControl(Id_CheckForUpdatesCheckBox), !GetRainmeter().GetDisableVersionCheck()); + Button_SetCheck(GetControl(Id_LockSkinsCheckBox), GetRainmeter().GetDisableDragging()); + Button_SetCheck(GetControl(Id_LogToFileCheckBox), GetLogger().IsLogToFile()); + Button_SetCheck(GetControl(Id_VerboseLoggingCheckbox), GetRainmeter().GetDebug()); + + BOOL isLogFile = (_waccess(GetLogger().GetLogFilePath().c_str(), 0) != -1); + EnableWindow(GetControl(Id_ShowLogFileButton), isLogFile); + EnableWindow(GetControl(Id_DeleteLogFileButton), isLogFile); + + Edit_SetText(GetControl(Id_EditorEdit), GetRainmeter().GetSkinEditor().c_str()); + + bool iconEnabled = GetRainmeter().GetTrayWindow()->IsTrayIconEnabled(); + Button_SetCheck(GetControl(Id_ShowTrayIconCheckBox), iconEnabled); + + if (IsWindowsVistaOrGreater()) + { + Button_SetCheck(GetControl(Id_UseD2DCheckBox), GetRainmeter().GetUseD2D()); + } + else + { + Button_Enable(GetControl(Id_UseD2DCheckBox), FALSE); + } + + m_Initialized = true; +} + +INT_PTR DialogManage::TabSettings::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_COMMAND: + return OnCommand(wParam, lParam); + } + + return FALSE; +} + +INT_PTR DialogManage::TabSettings::OnCommand(WPARAM wParam, LPARAM lParam) +{ + if (!m_Initialized) + { + return FALSE; + } + + switch (LOWORD(wParam)) + { + case Id_LanguageDropDownList: + if (HIWORD(wParam) == CBN_SELCHANGE) + { + int sel = ComboBox_GetCurSel((HWND)lParam); + LCID lcid = (LCID)ComboBox_GetItemData((HWND)lParam, sel); + if (lcid != GetRainmeter().m_ResourceLCID) + { + WCHAR buffer[16]; + _ultow(lcid, buffer, 10); + WritePrivateProfileString(L"Rainmeter", L"Language", buffer, GetRainmeter().GetIniFile().c_str()); + + std::wstring resource = GetRainmeter().GetPath() + L"Languages\\"; + resource += buffer; + resource += L".dll"; + FreeLibrary(GetRainmeter().m_ResourceInstance); + GetRainmeter().m_ResourceInstance = LoadLibraryEx(resource.c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE); + GetRainmeter().m_ResourceLCID = lcid; + + if (DialogAbout::GetDialog()) + { + int sel = TabCtrl_GetCurSel(DialogAbout::GetDialog()->GetControl(DialogManage::Id_Tab)); + SendMessage(DialogAbout::GetDialog()->GetWindow(), WM_CLOSE, 0, 0); + if (sel == 0) + { + GetRainmeter().DelayedExecuteCommand(L"!About"); + } + else if (sel == 1) + { + GetRainmeter().DelayedExecuteCommand(L"!About Skins"); + } + else if (sel == 2) + { + GetRainmeter().DelayedExecuteCommand(L"!About Plugins"); + } + else //if (sel == 3) + { + GetRainmeter().DelayedExecuteCommand(L"!About Version"); + } + } + + SendMessage(c_Dialog->GetWindow(), WM_CLOSE, 0, 0); + GetRainmeter().DelayedExecuteCommand(L"!Manage Settings"); + } + } + break; + + case Id_CheckForUpdatesCheckBox: + GetRainmeter().SetDisableVersionCheck(!GetRainmeter().GetDisableVersionCheck()); + break; + + case Id_LockSkinsCheckBox: + GetRainmeter().SetDisableDragging(!GetRainmeter().GetDisableDragging()); + break; + + case Id_ResetStatisticsButton: + GetRainmeter().ResetStats(); + break; + + case Id_ShowLogFileButton: + GetRainmeter().ShowLogFile(); + break; + + case Id_DeleteLogFileButton: + GetLogger().DeleteLogFile(); + if (_waccess(GetLogger().GetLogFilePath().c_str(), 0) == -1) + { + Button_SetCheck(GetControl(Id_LogToFileCheckBox), BST_UNCHECKED); + EnableWindow(GetControl(Id_ShowLogFileButton), FALSE); + EnableWindow(GetControl(Id_DeleteLogFileButton), FALSE); + } + break; + + case Id_LogToFileCheckBox: + if (GetLogger().IsLogToFile()) + { + GetLogger().StopLogFile(); + } + else + { + GetLogger().StartLogFile(); + if (_waccess(GetLogger().GetLogFilePath().c_str(), 0) != -1) + { + EnableWindow(GetControl(Id_ShowLogFileButton), TRUE); + EnableWindow(GetControl(Id_DeleteLogFileButton), TRUE); + } + } + break; + + case Id_VerboseLoggingCheckbox: + GetRainmeter().SetDebug(!GetRainmeter().GetDebug()); + break; + + case Id_EditorEdit: + if (HIWORD(wParam) == EN_CHANGE) + { + WCHAR buffer[MAX_PATH]; + if (GetWindowText((HWND)lParam, buffer, _countof(buffer)) > 0) + { + GetRainmeter().SetSkinEditor(buffer); + } + } + break; + + case Id_EditorBrowseButton: + { + WCHAR buffer[MAX_PATH]; + buffer[0] = L'\0'; + + std::wstring editor = GetRainmeter().GetSkinEditor(); + editor = editor.substr(0, editor.find_last_of(L"/\\")).c_str(); + + OPENFILENAME ofn = { sizeof(OPENFILENAME) }; + ofn.Flags = OFN_FILEMUSTEXIST; + ofn.lpstrFilter = L"Executable File (.exe)\0*.exe"; + ofn.lpstrTitle = L"Select executable file"; + ofn.lpstrDefExt = L"exe"; + ofn.lpstrInitialDir = editor.c_str(); + ofn.nFilterIndex = 0; + ofn.lpstrFile = buffer; + ofn.nMaxFile = _countof(buffer); + ofn.hwndOwner = c_Dialog->GetWindow(); + + if (!GetOpenFileName(&ofn)) + { + break; + } + + Edit_SetText(GetControl(Id_EditorEdit), buffer); + } + break; + + case Id_ShowTrayIconCheckBox: + GetRainmeter().GetTrayWindow()->SetTrayIcon(!GetRainmeter().GetTrayWindow()->IsTrayIconEnabled()); + break; + + case Id_UseD2DCheckBox: + GetRainmeter().SetUseD2D(!GetRainmeter().GetUseD2D()); + break; + + default: + return 1; + } + + return 0; } \ No newline at end of file diff --git a/Library/Export.cpp b/Library/Export.cpp index ab82057b..75fa7dea 100644 --- a/Library/Export.cpp +++ b/Library/Export.cpp @@ -1,262 +1,262 @@ -/* - Copyright (C) 2011 Birunthan Mohanathas, Peter Souza - - 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 "Rainmeter.h" -#include "Export.h" -#include "MeterWindow.h" -#include "Measure.h" -#include "MeasurePlugin.h" - -#define NULLCHECK(str) { if ((str) == nullptr) { (str) = L""; } } - -static std::wstring g_Buffer; - -LPCWSTR __stdcall RmReadString(void* rm, LPCWSTR option, LPCWSTR defValue, BOOL replaceMeasures) -{ - NULLCHECK(option); - NULLCHECK(defValue); - - MeasurePlugin* measure = (MeasurePlugin*)rm; - ConfigParser& parser = measure->GetMeterWindow()->GetParser(); - return parser.ReadString(measure->GetName(), option, defValue, replaceMeasures != FALSE).c_str(); -} - -double __stdcall RmReadFormula(void* rm, LPCWSTR option, double defValue) -{ - NULLCHECK(option); - - MeasurePlugin* measure = (MeasurePlugin*)rm; - ConfigParser& parser = measure->GetMeterWindow()->GetParser(); - return parser.ReadFloat(measure->GetName(), option, defValue); -} - -LPCWSTR __stdcall RmReplaceVariables(void* rm, LPCWSTR str) -{ - NULLCHECK(str); - - MeasurePlugin* measure = (MeasurePlugin*)rm; - ConfigParser& parser = measure->GetMeterWindow()->GetParser(); - g_Buffer = str; - parser.ReplaceVariables(g_Buffer); - parser.ReplaceMeasures(g_Buffer); - return g_Buffer.c_str(); -} - -LPCWSTR __stdcall RmPathToAbsolute(void* rm, LPCWSTR relativePath) -{ - NULLCHECK(relativePath); - - MeasurePlugin* measure = (MeasurePlugin*)rm; - g_Buffer = relativePath; - measure->GetMeterWindow()->MakePathAbsolute(g_Buffer); - return g_Buffer.c_str(); -} - -void* __stdcall RmGet(void* rm, int type) -{ - MeasurePlugin* measure = (MeasurePlugin*)rm; - - switch (type) - { - case RMG_MEASURENAME: - { - return (void*)measure->GetName(); - } - - case RMG_SKIN: - { - return (void*)measure->GetMeterWindow(); - } - - case RMG_SETTINGSFILE: - { - return (void*)Rainmeter::GetInstance().GetDataFile().c_str(); - } - - case RMG_SKINNAME: - { - MeterWindow* window = measure->GetMeterWindow(); - if (!window) break; - return (void*)window->GetFolderPath().c_str(); - } - - case RMG_SKINWINDOWHANDLE: - { - MeterWindow* window = measure->GetMeterWindow(); - if (!window) break; - return (void*)window->GetWindow(); - } - } - - return nullptr; -} - -void __stdcall RmExecute(void* skin, LPCWSTR command) -{ - MeterWindow* mw = (MeterWindow*)skin; - if (command) - { - // WM_RAINMETER_EXECUTE used instead of ExecuteCommand for thread-safety - SendMessage(Rainmeter::GetInstance().GetWindow(), WM_RAINMETER_EXECUTE, (WPARAM)mw, (LPARAM)command); - } -} - -BOOL LSLog(int level, LPCWSTR unused, LPCWSTR message) -{ - NULLCHECK(message); - - // Ignore Debug messages from plugins unless in debug mode. - if (level != (int)Logger::Level::Debug || Rainmeter::GetInstance().GetDebug()) - { - GetLogger().Log((Logger::Level)level, L"", message); - } - - return TRUE; -} - -void __stdcall RmLog(void* rm, int level, LPCWSTR message) -{ - NULLCHECK(message); - - MeasurePlugin* measure = (MeasurePlugin*)rm; - - // Ignore Debug messages from plugins unless in debug mode. - if (level != (int)Logger::Level::Debug || Rainmeter::GetInstance().GetDebug()) - { - GetLogger().LogSection((Logger::Level)level, measure, message); - } -} - -void RmLogF(void* rm, int level, LPCWSTR format, ...) -{ - NULLCHECK(format); - - MeasurePlugin* measure = (MeasurePlugin*)rm; - - // Ignore Debug messages from plugins unless in debug mode. - if (level != (int)Logger::Level::Debug || Rainmeter::GetInstance().GetDebug()) - { - va_list args; - va_start(args, format); - GetLogger().LogSectionVF((Logger::Level)level, measure, format, args); - va_end(args); - } -} - -// Deprecated! -LPCWSTR ReadConfigString(LPCWSTR section, LPCWSTR option, LPCWSTR defValue) -{ - NULLCHECK(section); - NULLCHECK(option); - NULLCHECK(defValue); - - ConfigParser* parser = Rainmeter::GetInstance().GetCurrentParser(); - if (parser) - { - return parser->ReadString(section, option, defValue, false).c_str(); - } - - return defValue; -} - -// Deprecated! -LPCWSTR PluginBridge(LPCWSTR command, LPCWSTR data) -{ - if (command == nullptr || *command == L'\0') - { - return L"noop"; - } - - NULLCHECK(data); - - if (_wcsicmp(command, L"GetConfig") == 0) - { - MeterWindow* meterWindow = Rainmeter::GetInstance().GetMeterWindowByINI(data); - if (meterWindow) - { - g_Buffer = L"\""; - g_Buffer += meterWindow->GetFolderPath(); - g_Buffer += L"\""; - return g_Buffer.c_str(); - } - - return L""; - } - else if (_wcsicmp(command, L"GetWindow") == 0) - { - std::vector subStrings = CommandHandler::ParseString(data); - - if (subStrings.size() >= 1) - { - const std::wstring& config = subStrings[0]; - - MeterWindow* meterWindow = Rainmeter::GetInstance().GetMeterWindow(config); - if (meterWindow) - { - WCHAR buf1[64]; - _snwprintf_s(buf1, _TRUNCATE, L"%lu", PtrToUlong(meterWindow->GetWindow())); - g_Buffer = buf1; - return g_Buffer.c_str(); - } - } - - return L"error"; - } - else if (_wcsicmp(command, L"GetVariable") == 0) - { - std::vector subStrings = CommandHandler::ParseString(data); - - if (subStrings.size() >= 2) - { - const std::wstring& config = subStrings[0]; - - MeterWindow* meterWindow = Rainmeter::GetInstance().GetMeterWindow(config); - if (meterWindow) - { - const std::wstring& variable = subStrings[1]; - - const std::wstring* value = meterWindow->GetParser().GetVariable(variable); - if (value) - { - return (*value).c_str(); - } - } - } - - return L""; - } - else if (_wcsicmp(command, L"SetVariable") == 0) - { - std::vector subStrings = CommandHandler::ParseString(data); - - if (subStrings.size() == 3) - { - MeterWindow* meterWindow = Rainmeter::GetInstance().GetMeterWindow(subStrings[0]); - if (meterWindow) - { - meterWindow->SetVariable(subStrings[1], subStrings[2]); - return L"success"; - } - } - - return L"error"; - } - - return L"noop"; -} +/* + Copyright (C) 2011 Birunthan Mohanathas, Peter Souza + + 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 "Rainmeter.h" +#include "Export.h" +#include "MeterWindow.h" +#include "Measure.h" +#include "MeasurePlugin.h" + +#define NULLCHECK(str) { if ((str) == nullptr) { (str) = L""; } } + +static std::wstring g_Buffer; + +LPCWSTR __stdcall RmReadString(void* rm, LPCWSTR option, LPCWSTR defValue, BOOL replaceMeasures) +{ + NULLCHECK(option); + NULLCHECK(defValue); + + MeasurePlugin* measure = (MeasurePlugin*)rm; + ConfigParser& parser = measure->GetMeterWindow()->GetParser(); + return parser.ReadString(measure->GetName(), option, defValue, replaceMeasures != FALSE).c_str(); +} + +double __stdcall RmReadFormula(void* rm, LPCWSTR option, double defValue) +{ + NULLCHECK(option); + + MeasurePlugin* measure = (MeasurePlugin*)rm; + ConfigParser& parser = measure->GetMeterWindow()->GetParser(); + return parser.ReadFloat(measure->GetName(), option, defValue); +} + +LPCWSTR __stdcall RmReplaceVariables(void* rm, LPCWSTR str) +{ + NULLCHECK(str); + + MeasurePlugin* measure = (MeasurePlugin*)rm; + ConfigParser& parser = measure->GetMeterWindow()->GetParser(); + g_Buffer = str; + parser.ReplaceVariables(g_Buffer); + parser.ReplaceMeasures(g_Buffer); + return g_Buffer.c_str(); +} + +LPCWSTR __stdcall RmPathToAbsolute(void* rm, LPCWSTR relativePath) +{ + NULLCHECK(relativePath); + + MeasurePlugin* measure = (MeasurePlugin*)rm; + g_Buffer = relativePath; + measure->GetMeterWindow()->MakePathAbsolute(g_Buffer); + return g_Buffer.c_str(); +} + +void* __stdcall RmGet(void* rm, int type) +{ + MeasurePlugin* measure = (MeasurePlugin*)rm; + + switch (type) + { + case RMG_MEASURENAME: + { + return (void*)measure->GetName(); + } + + case RMG_SKIN: + { + return (void*)measure->GetMeterWindow(); + } + + case RMG_SETTINGSFILE: + { + return (void*)GetRainmeter().GetDataFile().c_str(); + } + + case RMG_SKINNAME: + { + MeterWindow* window = measure->GetMeterWindow(); + if (!window) break; + return (void*)window->GetFolderPath().c_str(); + } + + case RMG_SKINWINDOWHANDLE: + { + MeterWindow* window = measure->GetMeterWindow(); + if (!window) break; + return (void*)window->GetWindow(); + } + } + + return nullptr; +} + +void __stdcall RmExecute(void* skin, LPCWSTR command) +{ + MeterWindow* mw = (MeterWindow*)skin; + if (command) + { + // WM_RAINMETER_EXECUTE used instead of ExecuteCommand for thread-safety + SendMessage(GetRainmeter().GetWindow(), WM_RAINMETER_EXECUTE, (WPARAM)mw, (LPARAM)command); + } +} + +BOOL LSLog(int level, LPCWSTR unused, LPCWSTR message) +{ + NULLCHECK(message); + + // Ignore Debug messages from plugins unless in debug mode. + if (level != (int)Logger::Level::Debug || GetRainmeter().GetDebug()) + { + GetLogger().Log((Logger::Level)level, L"", message); + } + + return TRUE; +} + +void __stdcall RmLog(void* rm, int level, LPCWSTR message) +{ + NULLCHECK(message); + + MeasurePlugin* measure = (MeasurePlugin*)rm; + + // Ignore Debug messages from plugins unless in debug mode. + if (level != (int)Logger::Level::Debug || GetRainmeter().GetDebug()) + { + GetLogger().LogSection((Logger::Level)level, measure, message); + } +} + +void RmLogF(void* rm, int level, LPCWSTR format, ...) +{ + NULLCHECK(format); + + MeasurePlugin* measure = (MeasurePlugin*)rm; + + // Ignore Debug messages from plugins unless in debug mode. + if (level != (int)Logger::Level::Debug || GetRainmeter().GetDebug()) + { + va_list args; + va_start(args, format); + GetLogger().LogSectionVF((Logger::Level)level, measure, format, args); + va_end(args); + } +} + +// Deprecated! +LPCWSTR ReadConfigString(LPCWSTR section, LPCWSTR option, LPCWSTR defValue) +{ + NULLCHECK(section); + NULLCHECK(option); + NULLCHECK(defValue); + + ConfigParser* parser = GetRainmeter().GetCurrentParser(); + if (parser) + { + return parser->ReadString(section, option, defValue, false).c_str(); + } + + return defValue; +} + +// Deprecated! +LPCWSTR PluginBridge(LPCWSTR command, LPCWSTR data) +{ + if (command == nullptr || *command == L'\0') + { + return L"noop"; + } + + NULLCHECK(data); + + if (_wcsicmp(command, L"GetConfig") == 0) + { + MeterWindow* meterWindow = GetRainmeter().GetMeterWindowByINI(data); + if (meterWindow) + { + g_Buffer = L"\""; + g_Buffer += meterWindow->GetFolderPath(); + g_Buffer += L"\""; + return g_Buffer.c_str(); + } + + return L""; + } + else if (_wcsicmp(command, L"GetWindow") == 0) + { + std::vector subStrings = CommandHandler::ParseString(data); + + if (subStrings.size() >= 1) + { + const std::wstring& config = subStrings[0]; + + MeterWindow* meterWindow = GetRainmeter().GetMeterWindow(config); + if (meterWindow) + { + WCHAR buf1[64]; + _snwprintf_s(buf1, _TRUNCATE, L"%lu", PtrToUlong(meterWindow->GetWindow())); + g_Buffer = buf1; + return g_Buffer.c_str(); + } + } + + return L"error"; + } + else if (_wcsicmp(command, L"GetVariable") == 0) + { + std::vector subStrings = CommandHandler::ParseString(data); + + if (subStrings.size() >= 2) + { + const std::wstring& config = subStrings[0]; + + MeterWindow* meterWindow = GetRainmeter().GetMeterWindow(config); + if (meterWindow) + { + const std::wstring& variable = subStrings[1]; + + const std::wstring* value = meterWindow->GetParser().GetVariable(variable); + if (value) + { + return (*value).c_str(); + } + } + } + + return L""; + } + else if (_wcsicmp(command, L"SetVariable") == 0) + { + std::vector subStrings = CommandHandler::ParseString(data); + + if (subStrings.size() == 3) + { + MeterWindow* meterWindow = GetRainmeter().GetMeterWindow(subStrings[0]); + if (meterWindow) + { + meterWindow->SetVariable(subStrings[1], subStrings[2]); + return L"success"; + } + } + + return L"error"; + } + + return L"noop"; +} diff --git a/Library/Exports_Common.h b/Library/Exports_Common.h new file mode 100644 index 00000000..8c1f2da8 --- /dev/null +++ b/Library/Exports_Common.h @@ -0,0 +1,3 @@ +#pragma once + +#define EXPORT extern "C" _declspec(dllexport) diff --git a/Library/Exports_Group.cpp b/Library/Exports_Group.cpp new file mode 100644 index 00000000..da0884ad --- /dev/null +++ b/Library/Exports_Group.cpp @@ -0,0 +1,32 @@ +#include "StdAfx.h" +#include +#include "Group.h" +#include "HandleManager.h" +#include "Exports_Common.h" + +EXPORT bool Group_BelongsToGroup (bool* result, int32_t handle, LPWSTR str) +{ + Group* group = (Group*)handle_get_resource (handle); + + if (group != nullptr) + { + *result = group->BelongsToGroup (str); + return true; + } + + return false; +} + +EXPORT bool Group_Destroy (int32_t handle) +{ + Group* group = (Group*)handle_get_resource (handle); + + if (group != nullptr) + { + handle_free (handle); + delete group; + return true; + } + + return false; +} \ No newline at end of file diff --git a/Library/Exports_Meter.cpp b/Library/Exports_Meter.cpp new file mode 100644 index 00000000..b5178cd6 --- /dev/null +++ b/Library/Exports_Meter.cpp @@ -0,0 +1,21 @@ +#include "StdAfx.h" +#include +#include "Meter.h" +#include "HandleManager.h" +#include "Exports_Common.h" + + + +EXPORT bool Meter_Destroy (int handle) +{ + Meter* meter = (Meter*)handle_get_resource (handle); + + if (meter != nullptr) + { + handle_free (handle); + delete meter; + return true; + } + + return false; +} \ No newline at end of file diff --git a/Library/Exports_MeterString.cpp b/Library/Exports_MeterString.cpp new file mode 100644 index 00000000..4e8baca4 --- /dev/null +++ b/Library/Exports_MeterString.cpp @@ -0,0 +1,35 @@ +#include "StdAfx.h" +#include +#include "MeterString.h" +#include "HandleManager.h" +#include "Exports_Common.h" + +EXPORT bool MeterString_Init (int* handle_result, int meterCanvasHandle, LPCWSTR name) +{ + MeterWindow* w = (MeterWindow*) handle_get_resource (meterCanvasHandle); + + if (w != nullptr) + { + MeterString* result = new MeterString (w, name); + *handle_result = handle_allocate (result); + + return true; + } + + return false; +} + +EXPORT bool MeterString_Destroy (int handle) +{ + MeterString* ms = (MeterString*) handle_get_resource (handle); + + if (ms != nullptr) + { + handle_free (handle); + delete ms; + return true; + } + + return false; +} + diff --git a/Library/Exports_Rainmeter.cpp b/Library/Exports_Rainmeter.cpp new file mode 100644 index 00000000..4ca632bf --- /dev/null +++ b/Library/Exports_Rainmeter.cpp @@ -0,0 +1,27 @@ +#include "Rainmeter.h" +#include "HandleManager.h" + +/* +** Initializes Rainmeter. +** +*/ +bool Rainmeter_Initialize() +{ + int res = GetRainmeter().Initialize(nullptr, nullptr); + + // Success? + if (res == 0) + return &GetRainmeter(); + + return nullptr; +} + +/* +** Finalizes Rainmeter. +** +*/ +void Rainmeter_Finalize(void* ptr) +{ + Rainmeter* rainmeter = (Rainmeter*)ptr; + rainmeter->Finalize(); +} diff --git a/Library/Exports_Section.cpp b/Library/Exports_Section.cpp new file mode 100644 index 00000000..50cad1c8 --- /dev/null +++ b/Library/Exports_Section.cpp @@ -0,0 +1,131 @@ +#include "HandleManager.h" +#include "Exports_Common.h" +#include "Section.h" + +EXPORT bool Section_GetName (LPCWCHAR* result, int32_t handle) +{ + Section* section = (Section*) handle_get_resource (handle); + + if (section != nullptr) + { + *result = section->GetName(); + return true; + } + + return false; +} + +EXPORT bool Section_GetOriginalName (LPCWCHAR* result, int32_t handle) +{ + Section* section = (Section*) handle_get_resource (handle); + + if (section != nullptr) + { + *result = section->GetOriginalName().c_str(); + return true; + } + + return false; +} + +EXPORT bool Section_HasDynamicVariables (bool* result, int32_t handle) +{ + Section* section = (Section*) handle_get_resource (handle); + + if (section != nullptr) + { + *result = section->HasDynamicVariables(); + return true; + } + + return false; +} + +EXPORT bool Section_SetDynamicVariables (int32_t handle, bool value) +{ + Section* section = (Section*) handle_get_resource (handle); + + if (section != nullptr) + { + section->SetDynamicVariables(value); + return true; + } + + return false; +} + +EXPORT bool Section_ResetUpdateCounter (int32_t handle) +{ + Section* section = (Section*) handle_get_resource (handle); + + if (section != nullptr) + { + section->ResetUpdateCounter(); + return true; + } + + return false; +} + +EXPORT bool Section_GetUpdateCounter (int* result, int32_t handle) +{ + Section* section = (Section*) handle_get_resource (handle); + + if (section != nullptr) + { + *result = section->GetUpdateCounter(); + return true; + } + + return false; +} + + +EXPORT bool Section_GetUpdateDivider (int* result, int32_t handle) +{ + Section* section = (Section*) handle_get_resource (handle); + + if (section != nullptr) + { + *result = section->GetUpdateDivider(); + return true; + } + + return false; +} + +EXPORT bool Section_GetOnUpdateAction (LPCWCHAR* result, int32_t handle) +{ + Section* section = (Section*) handle_get_resource (handle); + + if (section != nullptr) + { + *result = section->GetOnUpdateAction().c_str(); + return true; + } + + return false; +} + +EXPORT bool Section_DoUpdateAction (int32_t handle) +{ + Section* section = (Section*) handle_get_resource (handle); + if (section != nullptr) + { + section->DoUpdateAction (); + return true; + } + return false; +} + +EXPORT bool Section_Destroy (int32_t handle) +{ + Section* section = (Section*) handle_get_resource (handle); + if (section != nullptr) + { + handle_free (handle); + delete section; + return true; + } + return false; +} \ No newline at end of file diff --git a/Library/HandleManager.cpp b/Library/HandleManager.cpp new file mode 100644 index 00000000..4daa867e --- /dev/null +++ b/Library/HandleManager.cpp @@ -0,0 +1,28 @@ +#include +#include +#include "HandleManager.h" + +std::map handles; + +int32_t handle_allocate (void* resource) +{ + static int32_t handle = 1; + + handles.insert (std::make_pair(handle, resource)); + return handle++; +} + +void* handle_get_resource (int32_t handle) +{ + if (handles.count (handle) != 0) + return handles.at (handle); + + return nullptr; +} + +void handle_free (int32_t handle) +{ + handles.erase (handle); +} + +void \ No newline at end of file diff --git a/Library/HandleManager.h b/Library/HandleManager.h new file mode 100644 index 00000000..16eca2d3 --- /dev/null +++ b/Library/HandleManager.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +int32_t handle_allocate (void* resource); +void* handle_get_resource (int32_t handle); +void handle_free (int32_t handle); \ No newline at end of file diff --git a/Library/IfActions.cpp b/Library/IfActions.cpp index 178274ee..320168e7 100644 --- a/Library/IfActions.cpp +++ b/Library/IfActions.cpp @@ -1,367 +1,367 @@ -/* - Copyright (C) 2013 Brian Ferguson - - 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 "Measure.h" -#include "IfActions.h" -#include "Rainmeter.h" -#include "../Common/MathParser.h" -#include "pcre-8.10/config.h" -#include "pcre-8.10/pcre.h" - -IfActions::IfActions() : - m_AboveValue(0.0f), - m_BelowValue(0.0f), - m_EqualValue(0), - m_AboveAction(), - m_BelowAction(), - m_EqualAction(), - m_AboveCommitted(false), - m_BelowCommitted(false), - m_EqualCommitted(false), - m_Conditions(), - m_ConditionMode(false), - m_Matches(), - m_MatchMode(false) -{ -} - -IfActions::~IfActions() -{ -} - -void IfActions::ReadOptions(ConfigParser& parser, const WCHAR* section) -{ - m_AboveAction = parser.ReadString(section, L"IfAboveAction", L"", false); - m_AboveValue = parser.ReadFloat(section, L"IfAboveValue", 0.0f); - - m_BelowAction = parser.ReadString(section, L"IfBelowAction", L"", false); - m_BelowValue = parser.ReadFloat(section, L"IfBelowValue", 0.0f); - - m_EqualAction = parser.ReadString(section, L"IfEqualAction", L"", false); - m_EqualValue = (int64_t)parser.ReadFloat(section, L"IfEqualValue", 0.0f); -} - -void IfActions::ReadConditionOptions(ConfigParser& parser, const WCHAR* section) -{ - // IfCondition options - m_ConditionMode = parser.ReadBool(section, L"IfConditionMode", false); - - std::wstring condition = parser.ReadString(section, L"IfCondition", L""); - if (!condition.empty()) - { - std::wstring tAction = parser.ReadString(section, L"IfTrueAction", L"", false); - std::wstring fAction = parser.ReadString(section, L"IfFalseAction", L"", false); - if (!tAction.empty() || !fAction.empty()) - { - size_t i = 1; - do - { - if (m_Conditions.size() > (i - 1)) - { - m_Conditions[i - 1].Set(condition, tAction, fAction); - } - else - { - m_Conditions.emplace_back(condition, tAction, fAction); - } - - // Check for IfCondition2/IfTrueAction2/IfFalseAction2 ... etc. - const std::wstring num = std::to_wstring(++i); - - std::wstring key = L"IfCondition" + num; - condition = parser.ReadString(section, key.c_str(), L""); - if (condition.empty()) break; - - key = L"IfTrueAction" + num; - tAction = parser.ReadString(section, key.c_str(), L"", false); - key = L"IfFalseAction" + num; - fAction = parser.ReadString(section, key.c_str(), L"", false); - } - while (!tAction.empty() || !fAction.empty()); - } - else - { - m_Conditions.clear(); - } - } - else - { - m_Conditions.clear(); - } - - // IfMatch options - m_MatchMode = parser.ReadBool(section, L"IfMatchMode", false); - - std::wstring match = parser.ReadString(section, L"IfMatch", L""); - if (!match.empty()) - { - std::wstring tAction = parser.ReadString(section, L"IfMatchAction", L"", false); - std::wstring fAction = parser.ReadString(section, L"IfNotMatchAction", L"", false); - if (!tAction.empty() || !fAction.empty()) - { - size_t i = 1; - do - { - if (m_Matches.size() > (i - 1)) - { - m_Matches[i - 1].Set(match, tAction, fAction); - } - else - { - m_Matches.emplace_back(match, tAction, fAction); - } - - // Check for IfMatch2/IfMatchAction2/IfNotMatchAction2 ... etc. - const std::wstring num = std::to_wstring(++i); - - std::wstring key = L"IfMatch" + num; - match = parser.ReadString(section, key.c_str(), L""); - if (match.empty()) break; - - key = L"IfMatchAction" + num; - tAction = parser.ReadString(section, key.c_str(), L"", false); - key = L"IfNotMatchAction" + num; - fAction = parser.ReadString(section, key.c_str(), L"", false); - } while (!tAction.empty() || !fAction.empty()); - } - else - { - m_Matches.clear(); - } - } - else - { - m_Matches.clear(); - } -} - -void IfActions::DoIfActions(Measure& measure, double value) -{ - // IfEqual - if (!m_EqualAction.empty()) - { - if ((int64_t)value == m_EqualValue) - { - if (!m_EqualCommitted) - { - m_EqualCommitted = true; // To avoid infinite loop from !Update - Rainmeter::GetInstance().ExecuteCommand(m_EqualAction.c_str(), measure.GetMeterWindow()); - } - } - else - { - m_EqualCommitted = false; - } - } - - // IfAbove - if (!m_AboveAction.empty()) - { - if (value > m_AboveValue) - { - if (!m_AboveCommitted) - { - m_AboveCommitted = true; // To avoid infinite loop from !Update - Rainmeter::GetInstance().ExecuteCommand(m_AboveAction.c_str(), measure.GetMeterWindow()); - } - } - else - { - m_AboveCommitted = false; - } - } - - // IfBelow - if (!m_BelowAction.empty()) - { - if (value < m_BelowValue) - { - if (!m_BelowCommitted) - { - m_BelowCommitted = true; // To avoid infinite loop from !Update - Rainmeter::GetInstance().ExecuteCommand(m_BelowAction.c_str(), measure.GetMeterWindow()); - } - } - else - { - m_BelowCommitted = false; - } - } - - // IfCondition - int i = 0; - for (auto& item : m_Conditions) - { - ++i; - if (!item.value.empty() && (!item.tAction.empty() || !item.fAction.empty())) - { - double result = 0.0f; - const WCHAR* errMsg = MathParser::Parse( - item.value.c_str(), &result, measure.GetCurrentMeasureValue, &measure); - if (errMsg != nullptr) - { - if (!item.parseError) - { - if (i == 1) - { - LogErrorF(&measure, L"%s: IfCondition=%s", errMsg, item.value.c_str()); - } - else - { - LogErrorF(&measure, L"%s: IfCondition%i=%s", errMsg, i, item.value.c_str()); - } - item.parseError = true; - } - } - else - { - item.parseError = false; - - if (result == 1.0f) // "True" - { - item.fCommitted = false; - - if (m_ConditionMode || !item.tCommitted) - { - item.tCommitted = true; - Rainmeter::GetInstance().ExecuteCommand(item.tAction.c_str(), measure.GetMeterWindow()); - } - } - else if (result == 0.0f) // "False" - { - item.tCommitted = false; - - if (m_ConditionMode || !item.fCommitted) - { - item.fCommitted = true; - Rainmeter::GetInstance().ExecuteCommand(item.fAction.c_str(), measure.GetMeterWindow()); - } - } - } - } - } - - // IfMatch - i = 0; - for (auto& item : m_Matches) - { - ++i; - if (!item.value.empty() && (!item.tAction.empty() || !item.fAction.empty())) - { - const char* error; - int errorOffset; - - pcre* re = pcre_compile( - StringUtil::NarrowUTF8(item.value).c_str(), - PCRE_UTF8, - &error, - &errorOffset, - nullptr); - - if (!re) - { - if (!item.parseError) - { - if (i == 1) - { - LogErrorF(&measure, L"Error: \"%S\" in IfMatch=%s", error, item.value.c_str()); - } - else - { - LogErrorF(&measure, L"Error: \"%S\" in IfMatch%i=%s", error, i, item.value.c_str()); - } - - item.parseError = true; - } - } - else - { - item.parseError = false; - - std::string utf8str = StringUtil::NarrowUTF8(measure.GetStringValue()); - int ovector[300]; - - int rc = pcre_exec( - re, - nullptr, - utf8str.c_str(), - (int)utf8str.length(), - 0, - 0, - ovector, - (int)_countof(ovector)); - - if (rc > 0) // Match - { - item.fCommitted = false; - - if (m_MatchMode || !item.tCommitted) - { - item.tCommitted = true; - Rainmeter::GetInstance().ExecuteCommand(item.tAction.c_str(), measure.GetMeterWindow()); - } - } - else // Not Match - { - item.tCommitted = false; - - if (m_MatchMode || !item.fCommitted) - { - item.fCommitted = true; - Rainmeter::GetInstance().ExecuteCommand(item.fAction.c_str(), measure.GetMeterWindow()); - } - } - } - - // Release memory used for the compiled pattern - pcre_free(re); - } - } -} - -void IfActions::SetState(double& value) -{ - // Set IfAction committed state to false if condition is not met with value = 0 - if (m_EqualValue != (int64_t)value) - { - m_EqualCommitted = false; - } - - if (m_AboveValue <= value) - { - m_AboveCommitted = false; - } - - if (m_BelowValue >= value) - { - m_BelowCommitted = false; - } - - for (auto& item : m_Conditions) - { - item.tCommitted = false; - item.fCommitted = false; - } - - for (auto& item : m_Matches) - { - item.tCommitted = false; - item.fCommitted = false; - } -} +/* + Copyright (C) 2013 Brian Ferguson + + 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 "Measure.h" +#include "IfActions.h" +#include "Rainmeter.h" +#include "../Common/MathParser.h" +#include "pcre-8.10/config.h" +#include "pcre-8.10/pcre.h" + +IfActions::IfActions() : + m_AboveValue(0.0f), + m_BelowValue(0.0f), + m_EqualValue(0), + m_AboveAction(), + m_BelowAction(), + m_EqualAction(), + m_AboveCommitted(false), + m_BelowCommitted(false), + m_EqualCommitted(false), + m_Conditions(), + m_ConditionMode(false), + m_Matches(), + m_MatchMode(false) +{ +} + +IfActions::~IfActions() +{ +} + +void IfActions::ReadOptions(ConfigParser& parser, const WCHAR* section) +{ + m_AboveAction = parser.ReadString(section, L"IfAboveAction", L"", false); + m_AboveValue = parser.ReadFloat(section, L"IfAboveValue", 0.0f); + + m_BelowAction = parser.ReadString(section, L"IfBelowAction", L"", false); + m_BelowValue = parser.ReadFloat(section, L"IfBelowValue", 0.0f); + + m_EqualAction = parser.ReadString(section, L"IfEqualAction", L"", false); + m_EqualValue = (int64_t)parser.ReadFloat(section, L"IfEqualValue", 0.0f); +} + +void IfActions::ReadConditionOptions(ConfigParser& parser, const WCHAR* section) +{ + // IfCondition options + m_ConditionMode = parser.ReadBool(section, L"IfConditionMode", false); + + std::wstring condition = parser.ReadString(section, L"IfCondition", L""); + if (!condition.empty()) + { + std::wstring tAction = parser.ReadString(section, L"IfTrueAction", L"", false); + std::wstring fAction = parser.ReadString(section, L"IfFalseAction", L"", false); + if (!tAction.empty() || !fAction.empty()) + { + size_t i = 1; + do + { + if (m_Conditions.size() > (i - 1)) + { + m_Conditions[i - 1].Set(condition, tAction, fAction); + } + else + { + m_Conditions.emplace_back(condition, tAction, fAction); + } + + // Check for IfCondition2/IfTrueAction2/IfFalseAction2 ... etc. + const std::wstring num = std::to_wstring(++i); + + std::wstring key = L"IfCondition" + num; + condition = parser.ReadString(section, key.c_str(), L""); + if (condition.empty()) break; + + key = L"IfTrueAction" + num; + tAction = parser.ReadString(section, key.c_str(), L"", false); + key = L"IfFalseAction" + num; + fAction = parser.ReadString(section, key.c_str(), L"", false); + } + while (!tAction.empty() || !fAction.empty()); + } + else + { + m_Conditions.clear(); + } + } + else + { + m_Conditions.clear(); + } + + // IfMatch options + m_MatchMode = parser.ReadBool(section, L"IfMatchMode", false); + + std::wstring match = parser.ReadString(section, L"IfMatch", L""); + if (!match.empty()) + { + std::wstring tAction = parser.ReadString(section, L"IfMatchAction", L"", false); + std::wstring fAction = parser.ReadString(section, L"IfNotMatchAction", L"", false); + if (!tAction.empty() || !fAction.empty()) + { + size_t i = 1; + do + { + if (m_Matches.size() > (i - 1)) + { + m_Matches[i - 1].Set(match, tAction, fAction); + } + else + { + m_Matches.emplace_back(match, tAction, fAction); + } + + // Check for IfMatch2/IfMatchAction2/IfNotMatchAction2 ... etc. + const std::wstring num = std::to_wstring(++i); + + std::wstring key = L"IfMatch" + num; + match = parser.ReadString(section, key.c_str(), L""); + if (match.empty()) break; + + key = L"IfMatchAction" + num; + tAction = parser.ReadString(section, key.c_str(), L"", false); + key = L"IfNotMatchAction" + num; + fAction = parser.ReadString(section, key.c_str(), L"", false); + } while (!tAction.empty() || !fAction.empty()); + } + else + { + m_Matches.clear(); + } + } + else + { + m_Matches.clear(); + } +} + +void IfActions::DoIfActions(Measure& measure, double value) +{ + // IfEqual + if (!m_EqualAction.empty()) + { + if ((int64_t)value == m_EqualValue) + { + if (!m_EqualCommitted) + { + m_EqualCommitted = true; // To avoid infinite loop from !Update + GetRainmeter().ExecuteCommand(m_EqualAction.c_str(), measure.GetMeterWindow()); + } + } + else + { + m_EqualCommitted = false; + } + } + + // IfAbove + if (!m_AboveAction.empty()) + { + if (value > m_AboveValue) + { + if (!m_AboveCommitted) + { + m_AboveCommitted = true; // To avoid infinite loop from !Update + GetRainmeter().ExecuteCommand(m_AboveAction.c_str(), measure.GetMeterWindow()); + } + } + else + { + m_AboveCommitted = false; + } + } + + // IfBelow + if (!m_BelowAction.empty()) + { + if (value < m_BelowValue) + { + if (!m_BelowCommitted) + { + m_BelowCommitted = true; // To avoid infinite loop from !Update + GetRainmeter().ExecuteCommand(m_BelowAction.c_str(), measure.GetMeterWindow()); + } + } + else + { + m_BelowCommitted = false; + } + } + + // IfCondition + int i = 0; + for (auto& item : m_Conditions) + { + ++i; + if (!item.value.empty() && (!item.tAction.empty() || !item.fAction.empty())) + { + double result = 0.0f; + const WCHAR* errMsg = MathParser::Parse( + item.value.c_str(), &result, measure.GetCurrentMeasureValue, &measure); + if (errMsg != nullptr) + { + if (!item.parseError) + { + if (i == 1) + { + LogErrorF(&measure, L"%s: IfCondition=%s", errMsg, item.value.c_str()); + } + else + { + LogErrorF(&measure, L"%s: IfCondition%i=%s", errMsg, i, item.value.c_str()); + } + item.parseError = true; + } + } + else + { + item.parseError = false; + + if (result == 1.0f) // "True" + { + item.fCommitted = false; + + if (m_ConditionMode || !item.tCommitted) + { + item.tCommitted = true; + GetRainmeter().ExecuteCommand(item.tAction.c_str(), measure.GetMeterWindow()); + } + } + else if (result == 0.0f) // "False" + { + item.tCommitted = false; + + if (m_ConditionMode || !item.fCommitted) + { + item.fCommitted = true; + GetRainmeter().ExecuteCommand(item.fAction.c_str(), measure.GetMeterWindow()); + } + } + } + } + } + + // IfMatch + i = 0; + for (auto& item : m_Matches) + { + ++i; + if (!item.value.empty() && (!item.tAction.empty() || !item.fAction.empty())) + { + const char* error; + int errorOffset; + + pcre* re = pcre_compile( + StringUtil::NarrowUTF8(item.value).c_str(), + PCRE_UTF8, + &error, + &errorOffset, + nullptr); + + if (!re) + { + if (!item.parseError) + { + if (i == 1) + { + LogErrorF(&measure, L"Error: \"%S\" in IfMatch=%s", error, item.value.c_str()); + } + else + { + LogErrorF(&measure, L"Error: \"%S\" in IfMatch%i=%s", error, i, item.value.c_str()); + } + + item.parseError = true; + } + } + else + { + item.parseError = false; + + std::string utf8str = StringUtil::NarrowUTF8(measure.GetStringValue()); + int ovector[300]; + + int rc = pcre_exec( + re, + nullptr, + utf8str.c_str(), + (int)utf8str.length(), + 0, + 0, + ovector, + (int)_countof(ovector)); + + if (rc > 0) // Match + { + item.fCommitted = false; + + if (m_MatchMode || !item.tCommitted) + { + item.tCommitted = true; + GetRainmeter().ExecuteCommand(item.tAction.c_str(), measure.GetMeterWindow()); + } + } + else // Not Match + { + item.tCommitted = false; + + if (m_MatchMode || !item.fCommitted) + { + item.fCommitted = true; + GetRainmeter().ExecuteCommand(item.fAction.c_str(), measure.GetMeterWindow()); + } + } + } + + // Release memory used for the compiled pattern + pcre_free(re); + } + } +} + +void IfActions::SetState(double& value) +{ + // Set IfAction committed state to false if condition is not met with value = 0 + if (m_EqualValue != (int64_t)value) + { + m_EqualCommitted = false; + } + + if (m_AboveValue <= value) + { + m_AboveCommitted = false; + } + + if (m_BelowValue >= value) + { + m_BelowCommitted = false; + } + + for (auto& item : m_Conditions) + { + item.tCommitted = false; + item.fCommitted = false; + } + + for (auto& item : m_Matches) + { + item.tCommitted = false; + item.fCommitted = false; + } +} diff --git a/Library/Litestep.cpp b/Library/Litestep.cpp index 488335a3..6e83c537 100644 --- a/Library/Litestep.cpp +++ b/Library/Litestep.cpp @@ -1,90 +1,90 @@ -/* - Copyright (C) 2002 Kimmo Pekkola + few lsapi developers - - 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 "Litestep.h" -#include "Rainmeter.h" -#include "DialogAbout.h" -#include "System.h" - -UINT GetUniqueID() -{ - static UINT id = 0; - return id++; -} - -WCHAR* GetString(UINT id) -{ - LPWSTR pData; - int len = LoadString(Rainmeter::GetInstance().GetResourceInstance(), id, (LPWSTR)&pData, 0); - return len ? pData : L""; -} - -std::wstring GetFormattedString(UINT id, ...) -{ - LPWSTR pBuffer = nullptr; - va_list args = nullptr; - va_start(args, id); - - DWORD len = FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, - GetString(id), - 0, - 0, - (LPWSTR)&pBuffer, - 0, - &args); - - va_end(args); - - std::wstring tmpSz(len ? pBuffer : L"", len); - if (pBuffer) LocalFree(pBuffer); - return tmpSz; -} - -HICON GetIcon(UINT id, bool large) -{ - HINSTANCE hExe = GetModuleHandle(nullptr); - HINSTANCE hComctl = GetModuleHandle(L"Comctl32"); - if (hComctl) - { - // Try LoadIconMetric for better quality with high DPI - auto loadIconMetric = (decltype(LoadIconMetric)*)GetProcAddress(hComctl, "LoadIconMetric"); - if (loadIconMetric) - { - HICON icon; - HRESULT hr = loadIconMetric(hExe, MAKEINTRESOURCE(id), large ? LIM_LARGE : LIM_SMALL, &icon); - if (SUCCEEDED(hr)) - { - return icon; - } - } - } - - return (HICON)LoadImage( - hExe, - MAKEINTRESOURCE(id), - IMAGE_ICON, - GetSystemMetrics(large ? SM_CXICON : SM_CXSMICON), - GetSystemMetrics(large ? SM_CYICON : SM_CYSMICON), - LR_SHARED); -} - -void RmNullCRTInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved) -{ - // Do nothing. -} +/* + Copyright (C) 2002 Kimmo Pekkola + few lsapi developers + + 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 "Litestep.h" +#include "Rainmeter.h" +#include "DialogAbout.h" +#include "System.h" + +UINT GetUniqueID() +{ + static UINT id = 0; + return id++; +} + +WCHAR* GetString(UINT id) +{ + LPWSTR pData; + int len = LoadString(GetRainmeter().GetResourceInstance(), id, (LPWSTR)&pData, 0); + return len ? pData : L""; +} + +std::wstring GetFormattedString(UINT id, ...) +{ + LPWSTR pBuffer = nullptr; + va_list args = nullptr; + va_start(args, id); + + DWORD len = FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, + GetString(id), + 0, + 0, + (LPWSTR)&pBuffer, + 0, + &args); + + va_end(args); + + std::wstring tmpSz(len ? pBuffer : L"", len); + if (pBuffer) LocalFree(pBuffer); + return tmpSz; +} + +HICON GetIcon(UINT id, bool large) +{ + HINSTANCE hExe = GetModuleHandle(nullptr); + HINSTANCE hComctl = GetModuleHandle(L"Comctl32"); + if (hComctl) + { + // Try LoadIconMetric for better quality with high DPI + auto loadIconMetric = (decltype(LoadIconMetric)*)GetProcAddress(hComctl, "LoadIconMetric"); + if (loadIconMetric) + { + HICON icon; + HRESULT hr = loadIconMetric(hExe, MAKEINTRESOURCE(id), large ? LIM_LARGE : LIM_SMALL, &icon); + if (SUCCEEDED(hr)) + { + return icon; + } + } + } + + return (HICON)LoadImage( + hExe, + MAKEINTRESOURCE(id), + IMAGE_ICON, + GetSystemMetrics(large ? SM_CXICON : SM_CXSMICON), + GetSystemMetrics(large ? SM_CYICON : SM_CYSMICON), + LR_SHARED); +} + +void RmNullCRTInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved) +{ + // Do nothing. +} diff --git a/Library/Logger.cpp b/Library/Logger.cpp index 700a57eb..4f077ed4 100644 --- a/Library/Logger.cpp +++ b/Library/Logger.cpp @@ -1,276 +1,276 @@ -/* - Copyright (C) 2013 Birunthan Mohanathas - - 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 "Logger.h" -#include "DialogAbout.h" -#include "Litestep.h" -#include "Rainmeter.h" -#include "Section.h" -#include "MeterWindow.h" -#include "System.h" -#include "resource.h" - -namespace { - -const size_t MAX_LOG_ENTIRES = 20; - -} // namespace - -Logger::Logger() : - m_LogToFile(false) -{ - System::InitializeCriticalSection(&m_CsLog); - System::InitializeCriticalSection(&m_CsLogDelay); -} - -Logger::~Logger() -{ - DeleteCriticalSection(&m_CsLog); - DeleteCriticalSection(&m_CsLogDelay); -} - -Logger& Logger::GetInstance() -{ - static Logger s_Logger; - return s_Logger; -} - -void Logger::StartLogFile() -{ - const WCHAR* filePath = m_LogFilePath.c_str(); - if (_waccess(filePath, 0) == -1) - { - // Create empty log file. - HANDLE file = CreateFile(filePath, GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); - if (file != INVALID_HANDLE_VALUE) - { - CloseHandle(file); - } - else - { - const std::wstring text = GetFormattedString(ID_STR_LOGFILECREATEFAIL, filePath); - Rainmeter::GetInstance().ShowMessage(nullptr, text.c_str(), MB_OK | MB_ICONERROR); - SetLogToFile(false); - return; - } - } - - SetLogToFile(true); -} - -void Logger::StopLogFile() -{ - SetLogToFile(false); -} - -void Logger::DeleteLogFile() -{ - const WCHAR* filePath = m_LogFilePath.c_str(); - if (_waccess(filePath, 0) != -1) - { - const std::wstring text = GetFormattedString(ID_STR_LOGFILEDELETE, filePath); - const int res = Rainmeter::GetInstance().ShowMessage(nullptr, text.c_str(), MB_YESNO | MB_ICONQUESTION); - if (res == IDYES) - { - SetLogToFile(false); - System::RemoveFile(m_LogFilePath); - } - } -} - -void Logger::SetLogToFile(bool logToFile) -{ - m_LogToFile = logToFile; - WritePrivateProfileString( - L"Rainmeter", L"Logging", logToFile ? L"1" : L"0", Rainmeter::GetInstance().GetIniFile().c_str()); -} - -void Logger::LogInternal(Level level, ULONGLONG timestamp, const WCHAR* source, const WCHAR* msg) -{ - WCHAR timestampSz[128]; - size_t len = _snwprintf_s( - timestampSz, - _TRUNCATE, - L"%02llu:%02llu:%02llu.%03llu", - timestamp / (1000 * 60 * 60), - (timestamp / (1000 * 60)) % 60, - (timestamp / 1000) % 60, - timestamp % 1000); - - // Store up to MAX_LOG_ENTIRES entries. - Entry entry = {level, std::wstring(timestampSz, len), source, msg}; - m_Entries.push_back(entry); - if (m_Entries.size() > MAX_LOG_ENTIRES) - { - m_Entries.pop_front(); - } - - DialogAbout::AddLogItem(level, timestampSz, source, msg); - WriteToLogFile(entry); -} - -void Logger::WriteToLogFile(Entry& entry) -{ -#ifndef _DEBUG - if (!m_LogToFile) return; -#endif - - const WCHAR* levelSz = - (entry.level == Level::Error) ? L"ERRO" : - (entry.level == Level::Warning) ? L"WARN" : - (entry.level == Level::Notice) ? L"NOTE" : - L"DBUG"; - - std::wstring message = levelSz; - message += L" ("; - message.append(entry.timestamp); - message += L") "; - message += entry.source; - message += L": "; - message += entry.message; - message += L'\n'; - -#ifdef _DEBUG - _RPTW0(_CRT_WARN, message.c_str()); - if (!m_LogToFile) return; -#endif - - const WCHAR* filePath = m_LogFilePath.c_str(); - if (_waccess(filePath, 0) == -1) - { - // The file has been deleted manually. - StopLogFile(); - } - else - { - FILE* file = _wfopen(filePath, L"a+, ccs=UTF-8"); - if (file) - { - fputws(message.c_str(), file); - fclose(file); - } - } -} - -void Logger::Log(Level level, const WCHAR* source, const WCHAR* msg) -{ - struct DelayedEntry - { - Level level; - ULONGLONG elapsed; - std::wstring message; - }; - static std::list s_DelayedEntries; - - static ULONGLONG s_StartTime = System::GetTickCount64(); - ULONGLONG elapsed = System::GetTickCount64() - s_StartTime; - - if (TryEnterCriticalSection(&m_CsLog)) - { - // Log queued messages first. - EnterCriticalSection(&m_CsLogDelay); - - while (!s_DelayedEntries.empty()) - { - DelayedEntry& entry = s_DelayedEntries.front(); - LogInternal(entry.level, entry.elapsed, source, entry.message.c_str()); - - s_DelayedEntries.erase(s_DelayedEntries.begin()); - } - - LeaveCriticalSection(&m_CsLogDelay); - - // Log the actual message. - LogInternal(level, elapsed, source, msg); - - LeaveCriticalSection(&m_CsLog); - } - else - { - // Queue message. - EnterCriticalSection(&m_CsLogDelay); - - DelayedEntry entry = {level, elapsed, msg}; - s_DelayedEntries.push_back(entry); - - LeaveCriticalSection(&m_CsLogDelay); - } -} - -void Logger::LogVF(Level level, const WCHAR* source, const WCHAR* format, va_list args) -{ - WCHAR* buffer = new WCHAR[1024]; - - _invalid_parameter_handler oldHandler = _set_invalid_parameter_handler(RmNullCRTInvalidParameterHandler); - _CrtSetReportMode(_CRT_ASSERT, 0); - - errno = 0; - _vsnwprintf_s(buffer, 1024, _TRUNCATE, format, args); - if (errno != 0) - { - level = Level::Error; - _snwprintf_s(buffer, 1024, _TRUNCATE, L"Internal error: %s", format); - } - - _set_invalid_parameter_handler(oldHandler); - - Log(level, source, buffer); - delete [] buffer; -} - -std::wstring GetSectionSourceString(Section* section) -{ - std::wstring source; - if (section) - { - MeterWindow* meterWindow = section->GetMeterWindow(); - if (meterWindow) - { - source = meterWindow->GetSkinPath(); - source += L" - "; - } - - source += L'['; - source += section->GetOriginalName(); - source += L']'; - } - return source; -} - -void Logger::LogSection(Logger::Level level, Section* section, const WCHAR* message) -{ - const std::wstring source = GetSectionSourceString(section); - GetLogger().Log(level, source.c_str(), message); -} - -void Logger::LogSectionVF(Logger::Level level, Section* section, const WCHAR* format, va_list args) -{ - const std::wstring source = GetSectionSourceString(section); - GetLogger().LogVF(level, source.c_str(), format, args); -} - -void Logger::LogMeterWindowVF(Logger::Level level, MeterWindow* meterWindow, const WCHAR* format, va_list args) -{ - std::wstring source; - if (meterWindow) - { - source = meterWindow->GetSkinPath(); - } - GetLogger().LogVF(level, source.c_str(), format, args); -} +/* + Copyright (C) 2013 Birunthan Mohanathas + + 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 "Logger.h" +#include "DialogAbout.h" +#include "Litestep.h" +#include "Rainmeter.h" +#include "Section.h" +#include "MeterWindow.h" +#include "System.h" +#include "resource.h" + +namespace { + +const size_t MAX_LOG_ENTIRES = 20; + +} // namespace + +Logger::Logger() : + m_LogToFile(false) +{ + System::InitializeCriticalSection(&m_CsLog); + System::InitializeCriticalSection(&m_CsLogDelay); +} + +Logger::~Logger() +{ + DeleteCriticalSection(&m_CsLog); + DeleteCriticalSection(&m_CsLogDelay); +} + +Logger& Logger::GetInstance() +{ + static Logger s_Logger; + return s_Logger; +} + +void Logger::StartLogFile() +{ + const WCHAR* filePath = m_LogFilePath.c_str(); + if (_waccess(filePath, 0) == -1) + { + // Create empty log file. + HANDLE file = CreateFile(filePath, GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file != INVALID_HANDLE_VALUE) + { + CloseHandle(file); + } + else + { + const std::wstring text = GetFormattedString(ID_STR_LOGFILECREATEFAIL, filePath); + GetRainmeter().ShowMessage(nullptr, text.c_str(), MB_OK | MB_ICONERROR); + SetLogToFile(false); + return; + } + } + + SetLogToFile(true); +} + +void Logger::StopLogFile() +{ + SetLogToFile(false); +} + +void Logger::DeleteLogFile() +{ + const WCHAR* filePath = m_LogFilePath.c_str(); + if (_waccess(filePath, 0) != -1) + { + const std::wstring text = GetFormattedString(ID_STR_LOGFILEDELETE, filePath); + const int res = GetRainmeter().ShowMessage(nullptr, text.c_str(), MB_YESNO | MB_ICONQUESTION); + if (res == IDYES) + { + SetLogToFile(false); + System::RemoveFile(m_LogFilePath); + } + } +} + +void Logger::SetLogToFile(bool logToFile) +{ + m_LogToFile = logToFile; + WritePrivateProfileString( + L"Rainmeter", L"Logging", logToFile ? L"1" : L"0", GetRainmeter().GetIniFile().c_str()); +} + +void Logger::LogInternal(Level level, ULONGLONG timestamp, const WCHAR* source, const WCHAR* msg) +{ + WCHAR timestampSz[128]; + size_t len = _snwprintf_s( + timestampSz, + _TRUNCATE, + L"%02llu:%02llu:%02llu.%03llu", + timestamp / (1000 * 60 * 60), + (timestamp / (1000 * 60)) % 60, + (timestamp / 1000) % 60, + timestamp % 1000); + + // Store up to MAX_LOG_ENTIRES entries. + Entry entry = {level, std::wstring(timestampSz, len), source, msg}; + m_Entries.push_back(entry); + if (m_Entries.size() > MAX_LOG_ENTIRES) + { + m_Entries.pop_front(); + } + + DialogAbout::AddLogItem(level, timestampSz, source, msg); + WriteToLogFile(entry); +} + +void Logger::WriteToLogFile(Entry& entry) +{ +#ifndef _DEBUG + if (!m_LogToFile) return; +#endif + + const WCHAR* levelSz = + (entry.level == Level::Error) ? L"ERRO" : + (entry.level == Level::Warning) ? L"WARN" : + (entry.level == Level::Notice) ? L"NOTE" : + L"DBUG"; + + std::wstring message = levelSz; + message += L" ("; + message.append(entry.timestamp); + message += L") "; + message += entry.source; + message += L": "; + message += entry.message; + message += L'\n'; + +#ifdef _DEBUG + _RPTW0(_CRT_WARN, message.c_str()); + if (!m_LogToFile) return; +#endif + + const WCHAR* filePath = m_LogFilePath.c_str(); + if (_waccess(filePath, 0) == -1) + { + // The file has been deleted manually. + StopLogFile(); + } + else + { + FILE* file = _wfopen(filePath, L"a+, ccs=UTF-8"); + if (file) + { + fputws(message.c_str(), file); + fclose(file); + } + } +} + +void Logger::Log(Level level, const WCHAR* source, const WCHAR* msg) +{ + struct DelayedEntry + { + Level level; + ULONGLONG elapsed; + std::wstring message; + }; + static std::list s_DelayedEntries; + + static ULONGLONG s_StartTime = System::GetTickCount64(); + ULONGLONG elapsed = System::GetTickCount64() - s_StartTime; + + if (TryEnterCriticalSection(&m_CsLog)) + { + // Log queued messages first. + EnterCriticalSection(&m_CsLogDelay); + + while (!s_DelayedEntries.empty()) + { + DelayedEntry& entry = s_DelayedEntries.front(); + LogInternal(entry.level, entry.elapsed, source, entry.message.c_str()); + + s_DelayedEntries.erase(s_DelayedEntries.begin()); + } + + LeaveCriticalSection(&m_CsLogDelay); + + // Log the actual message. + LogInternal(level, elapsed, source, msg); + + LeaveCriticalSection(&m_CsLog); + } + else + { + // Queue message. + EnterCriticalSection(&m_CsLogDelay); + + DelayedEntry entry = {level, elapsed, msg}; + s_DelayedEntries.push_back(entry); + + LeaveCriticalSection(&m_CsLogDelay); + } +} + +void Logger::LogVF(Level level, const WCHAR* source, const WCHAR* format, va_list args) +{ + WCHAR* buffer = new WCHAR[1024]; + + _invalid_parameter_handler oldHandler = _set_invalid_parameter_handler(RmNullCRTInvalidParameterHandler); + _CrtSetReportMode(_CRT_ASSERT, 0); + + errno = 0; + _vsnwprintf_s(buffer, 1024, _TRUNCATE, format, args); + if (errno != 0) + { + level = Level::Error; + _snwprintf_s(buffer, 1024, _TRUNCATE, L"Internal error: %s", format); + } + + _set_invalid_parameter_handler(oldHandler); + + Log(level, source, buffer); + delete [] buffer; +} + +std::wstring GetSectionSourceString(Section* section) +{ + std::wstring source; + if (section) + { + MeterWindow* meterWindow = section->GetMeterWindow(); + if (meterWindow) + { + source = meterWindow->GetSkinPath(); + source += L" - "; + } + + source += L'['; + source += section->GetOriginalName(); + source += L']'; + } + return source; +} + +void Logger::LogSection(Logger::Level level, Section* section, const WCHAR* message) +{ + const std::wstring source = GetSectionSourceString(section); + GetLogger().Log(level, source.c_str(), message); +} + +void Logger::LogSectionVF(Logger::Level level, Section* section, const WCHAR* format, va_list args) +{ + const std::wstring source = GetSectionSourceString(section); + GetLogger().LogVF(level, source.c_str(), format, args); +} + +void Logger::LogMeterWindowVF(Logger::Level level, MeterWindow* meterWindow, const WCHAR* format, va_list args) +{ + std::wstring source; + if (meterWindow) + { + source = meterWindow->GetSkinPath(); + } + GetLogger().LogVF(level, source.c_str(), format, args); +} diff --git a/Library/Measure.cpp b/Library/Measure.cpp index 773c0efc..fb615314 100644 --- a/Library/Measure.cpp +++ b/Library/Measure.cpp @@ -1,838 +1,838 @@ -/* - Copyright (C) 2000 Kimmo Pekkola - - 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 "Measure.h" -#include "MeasureCPU.h" -#include "MeasureMemory.h" -#include "MeasurePhysicalMemory.h" -#include "MeasureVirtualMemory.h" -#include "MeasureNetIn.h" -#include "MeasureNetOut.h" -#include "MeasureNetTotal.h" -#include "MeasureDiskSpace.h" -#include "MeasureUptime.h" -#include "MeasurePlugin.h" -#include "MeasureRegistry.h" -#include "MeasureString.h" -#include "MeasureTime.h" -#include "MeasureCalc.h" -#include "MeasureScript.h" -#include "Rainmeter.h" -#include "Error.h" -#include "Litestep.h" -#include "pcre-8.10/config.h" -#include "pcre-8.10/pcre.h" - -#define OVECCOUNT 300 // Should be a multiple of 3 - -enum AUTOSCALE_INDEX -{ - AUTOSCALE_INDEX_1024 = 0, - AUTOSCALE_INDEX_1000 = 1 -}; - -static const double g_TblScale[2][4] = { - { - 1024.0 * 1024.0 * 1024.0 * 1024.0, - 1024.0 * 1024.0 * 1024.0, - 1024.0 * 1024.0, - 1024.0 - }, - { - 1000.0 * 1000.0 * 1000.0 * 1000.0, - 1000.0 * 1000.0 * 1000.0, - 1000.0 * 1000.0, - 1000.0 - } -}; - -const int MEDIAN_SIZE = 3; - -/* -** The constructor -** -*/ -Measure::Measure(MeterWindow* meterWindow, const WCHAR* name) : Section(meterWindow, name), - m_Invert(false), - m_LogMaxValue(false), - m_MinValue(), - m_MaxValue(1.0), - m_Value(), - m_RegExpSubstitute(false), - m_MedianPos(), - m_AveragePos(), - m_AverageSize(), - m_Disabled(false), - m_Paused(false), - m_Initialized(false), - m_OldValue(), - m_ValueAssigned(false) -{ -} - -/* -** The destructor -** -*/ -Measure::~Measure() -{ - delete m_OldValue; -} - -/* -** Initializes the measure. -** -*/ -void Measure::Initialize() -{ - m_Initialized = true; -} - -/* -** Read the common options specified in the ini file. The inherited classes must -** call this base implementation if they overwrite this method. -** -*/ -void Measure::ReadOptions(ConfigParser& parser, const WCHAR* section) -{ - bool oldOnChangeActionEmpty = m_OnChangeAction.empty(); - - Section::ReadOptions(parser, section); - - // Clear substitutes to prevent from being added more than once. - if (!m_Substitute.empty()) - { - m_Substitute.clear(); - } - - m_Invert = parser.ReadBool(section, L"InvertMeasure", false); - - m_Disabled = parser.ReadBool(section, L"Disabled", false); - m_Paused = parser.ReadBool(section, L"Paused", false); - - m_MinValue = parser.ReadFloat(section, L"MinValue", m_MinValue); - m_MaxValue = parser.ReadFloat(section, L"MaxValue", m_MaxValue); - - m_IfActions.ReadOptions(parser, section); - - // The first time around, we read the conditions here. Subsequent rereads will be done in - // Update() if needed. - if (!m_Initialized) - { - m_IfActions.ReadConditionOptions(parser, section); - } - - m_OnChangeAction = parser.ReadString(section, L"OnChangeAction", L"", false); - - m_AverageSize = parser.ReadUInt(section, L"AverageSize", 0); - - m_RegExpSubstitute = parser.ReadBool(section, L"RegExpSubstitute", false); - std::wstring subs = parser.ReadString(section, L"Substitute", L""); - if (!subs.empty()) - { - if ((subs[0] != L'"' || subs[subs.length() - 1] != L'\'') && - (subs[0] != L'\'' || subs[subs.length() - 1] != L'"')) - { - // Add quotes since they are removed by the GetProfileString - subs.insert(0, 1, L'"'); - subs += L'"'; - } - if (!ParseSubstitute(subs)) - { - LogErrorF(this, L"Measure: Invalid Substitute=%s", subs.c_str()); - } - } - - if (m_Initialized && - oldOnChangeActionEmpty && !m_OnChangeAction.empty()) - { - DoChangeAction(false); - } -} - -void Measure::Disable() -{ - m_Disabled = true; - - // Change the option as well to avoid reset in ReadOptions(). - m_MeterWindow->GetParser().SetValue(m_Name, L"Disabled", L"1"); -} - -void Measure::Enable() -{ - m_Disabled = false; - - // Change the option as well to avoid reset in ReadOptions(). - m_MeterWindow->GetParser().SetValue(m_Name, L"Disabled", L"0"); -} - -void Measure::Pause() -{ - m_Paused = true; - - // Change the option as well to avoid reset in ReadOptions(). - m_MeterWindow->GetParser().SetValue(m_Name, L"Paused", L"1"); -} - -void Measure::Unpause() -{ - m_Paused = false; - - // Change the option as well to avoid reset in ReadOptions(). - m_MeterWindow->GetParser().SetValue(m_Name, L"Paused", L"0"); -} - -/* -** Substitues text using a straight find and replace method -*/ -bool Measure::MakePlainSubstitute(std::wstring& str, size_t index) -{ - size_t start = 0, pos; - - do - { - pos = str.find(m_Substitute[index], start); - if (pos != std::wstring::npos) - { - str.replace(pos, m_Substitute[index].length(), m_Substitute[index + 1]); - start = pos + m_Substitute[index + 1].length(); - } - } - while (pos != std::wstring::npos); - - return true; -} - -/* -** Substitutes part of the text -*/ -const WCHAR* Measure::CheckSubstitute(const WCHAR* buffer) -{ - static std::wstring str; - - if (m_Substitute.empty()) - { - return buffer; - } - - if (!m_RegExpSubstitute) - { - str = buffer; - - for (size_t i = 0, isize = m_Substitute.size(); i < isize; i += 2) - { - if (!m_Substitute[i].empty()) - { - MakePlainSubstitute(str, i); - } - else if (str.empty()) - { - // Empty result and empty substitute -> use second - str = m_Substitute[i + 1]; - } - } - } - else - { - std::string utf8str = StringUtil::NarrowUTF8(buffer); - int ovector[300]; - - for (size_t i = 0, isize = m_Substitute.size(); i < isize; i += 2) - { - const char* error; - int errorOffset; - int offset = 0; - pcre* re = pcre_compile( - StringUtil::NarrowUTF8(m_Substitute[i]).c_str(), - PCRE_UTF8, - &error, - &errorOffset, - nullptr); // Use default character tables. - if (!re) - { - MakePlainSubstitute(str, i); - LogNoticeF(this, L"Substitute: %S", error); - } - else - { - do - { - const int rc = pcre_exec( - re, - nullptr, // No extra data - we didn't study the pattern - utf8str.c_str(), // The subject string - (int)utf8str.length(), // The length of the subject - offset, - 0, - ovector, - (int)_countof(ovector)); - if (rc <= 0) - { - break; - } - - std::string result = StringUtil::NarrowUTF8(m_Substitute[i + 1]); - - if (rc > 1) - { - for (int j = rc - 1 ; j >= 0 ; --j) - { - size_t newStart = ovector[2 * j]; - size_t inLength = ovector[2 * j + 1] - ovector[2 * j]; - - char tmpName[64]; - size_t cutLength = _snprintf_s(tmpName, _TRUNCATE, "\\%i", j);; - size_t start = 0, pos; - do - { - pos = result.find(tmpName, start, cutLength); - if (pos != std::string::npos) - { - result.replace(pos, cutLength, utf8str, newStart, inLength); - start = pos + inLength; - } - } - while (pos != std::string::npos); - } - } - - const int start = ovector[0]; - const int length = ovector[1] - ovector[0]; - utf8str.replace(start, length, result); - offset = start + (int)result.length(); - } - while (true); - - pcre_free(re); - } - } - - str = StringUtil::WidenUTF8(utf8str); - } - - return str.c_str(); -} - -/* -** Reads the buffer for "Name":"Value"-pairs separated with comma and -** fills the map with the parsed data. -*/ -bool Measure::ParseSubstitute(std::wstring buffer) -{ - if (buffer.empty()) return true; - - do - { - std::wstring word1 = ExtractWord(buffer); - std::wstring sep = ExtractWord(buffer); - if (sep.size() != 1 || sep[0] != L':') return false; - std::wstring word2 = ExtractWord(buffer); - - if (wcscmp(word1.c_str(), word2.c_str()) != 0) - { - if (m_RegExpSubstitute && word1.empty()) - { - word1 = L"^$"; - } - - m_Substitute.push_back(word1); - m_Substitute.push_back(word2); - } - - std::wstring sep2 = ExtractWord(buffer); - if (!sep2.empty() && (sep2.size() != 1 || sep2[0] != L',')) return false; - } - while (!buffer.empty()); - - return true; -} - -/* -** Returns the first word from the buffer. The word can be inside quotes. -** If not, the separators are ' ', '\t', ',' and ':'. Whitespaces are removed -** and buffer _will_ be modified. -*/ -std::wstring Measure::ExtractWord(std::wstring& buffer) -{ - std::wstring::size_type end, len = buffer.size(); - std::wstring ret; - - if (len == 0) return ret; - - // Remove whitespaces - end = 0; - while (end < len && (buffer[end] == L' ' || buffer[end] == L'\t' || buffer[end] == L'\n')) ++end; - if (end == len) - { - // End of line reached - end = std::wstring::npos; - } - else - { - buffer.erase(0, end); - len = buffer.size(); - - if (buffer[0] == L'"' || buffer[0] == L'\'') - { - WCHAR quote = buffer[0]; - - end = 1; // Skip the '"' - // Quotes around the word - while (end < len && (buffer[end] != quote)) ++end; - if (end == len) end = std::wstring::npos; - - if (end != std::wstring::npos) - { - ret.assign(buffer, 1, end - 1); - ++end; - } - else - { - // End of string reached - discard result - } - } - else - { - end = 0; - while (end < len && (buffer[end] != L',' && buffer[end] != L':' && buffer[end] != L' ' && buffer[end] != L'\t')) ++end; - if (end == len) end = std::wstring::npos; - - if (end == std::wstring::npos) - { - // End of line reached - ret = buffer; - } - else - { - ret.assign(buffer, 0, ++end); // The separator is also returned! - } - } - } - - buffer.erase(0, end); - - return ret; -} - -bool Measure::Update(bool rereadOptions) -{ - if (rereadOptions) - { - ReadOptions(m_MeterWindow->GetParser()); - } - - // Don't do anything if paused - if (m_Paused) return false; - - if (!m_Disabled) - { - // Only update the counter if the divider - if (!UpdateCounter()) return false; - - // Call derived method to update value - UpdateValue(); - - if (m_AverageSize > 0) - { - size_t averageValuesSize = m_AverageValues.size(); - - if (m_AverageSize != averageValuesSize) - { - m_AverageValues.resize(m_AverageSize, m_Value); - averageValuesSize = m_AverageValues.size(); - if (m_AveragePos >= averageValuesSize) m_AveragePos = 0; - } - m_AverageValues[m_AveragePos] = m_Value; - - ++m_AveragePos; - m_AveragePos %= averageValuesSize; - - // Calculate the average value - double value = 0; - for (size_t i = 0; i < averageValuesSize; ++i) - { - value += m_AverageValues[i]; - } - m_Value = value / (double)averageValuesSize; - } - - // If we're logging the maximum value of the measure, check if - // the new value is greater than the old one, and update if necessary. - if (m_LogMaxValue) - { - if (m_MedianValues.empty()) - { - m_MedianValues.resize(MEDIAN_SIZE, 0); - } - - m_MedianValues[m_MedianPos] = m_Value; - ++m_MedianPos; - m_MedianPos %= MEDIAN_SIZE; - - auto medianArray = m_MedianValues; - std::sort(&medianArray.data()[0], &medianArray.data()[MEDIAN_SIZE]); // Workaround for "Debug" build mode - - double medianValue = medianArray[MEDIAN_SIZE / 2]; - m_MaxValue = max(m_MaxValue, medianValue); - m_MinValue = min(m_MinValue, medianValue); - } - - m_ValueAssigned = true; - - // For the conditional options to work with the current measure value when using - // [MeasureName], we need to read the options after m_Value has been changed. - if (rereadOptions) - { - m_IfActions.ReadConditionOptions(m_MeterWindow->GetParser(), GetName()); - } - - if (m_MeterWindow) - { - m_IfActions.DoIfActions(*this, m_Value); - } - - return true; - } - else - { - // Disabled measures have 0 as value - m_Value = 0.0; - - m_IfActions.SetState(m_Value); - - return false; - } -} - -/* -** Returns the value of the measure. -** -*/ -double Measure::GetValue() -{ - // Invert if so requested - if (m_Invert) - { - return m_MaxValue - m_Value + m_MinValue; - } - - return m_Value; -} - -/* -** Returns the relative value of the measure (0.0 - 1.0). -** -*/ -double Measure::GetRelativeValue() -{ - double range = GetValueRange(); - - if (range != 0.0) - { - double value = GetValue(); - - value = min(m_MaxValue, value); - value = max(m_MinValue, value); - - value -= m_MinValue; - - return value / range; - } - - return 1.0; -} - -/* -** Returns the value range. -** -*/ -double Measure::GetValueRange() -{ - return m_MaxValue - m_MinValue; -} - -/* -** Base implementation. Derivied classes can provide an alternative implementation if they have a -** string value that is not based on m_Value. -** -*/ -const WCHAR* Measure::GetStringValue() -{ - return nullptr; -} - -/* -** Returns the unformatted string value if the measure has one or a formatted value otherwise. -** -*/ -const WCHAR* Measure::GetStringOrFormattedValue(AUTOSCALE autoScale, double scale, int decimals, bool percentual) -{ - const WCHAR* stringValue = GetStringValue(); - return stringValue ? stringValue : GetFormattedValue(autoScale, scale, decimals, percentual); -} - -/* -** This method returns the value as text string. The actual value is -** get with GetValue() so we don't have to worry about m_Invert. -** -** autoScale If true, scale the value automatically to some sensible range. -** scale The scale to use if autoScale is false. -** decimals Number of decimals used in the value. If -1, get rid of ".00000" for dynamic variables. -** percentual Return the value as % from the maximum value. -*/ -const WCHAR* Measure::GetFormattedValue(AUTOSCALE autoScale, double scale, int decimals, bool percentual) -{ - static WCHAR buffer[128]; - WCHAR format[32]; - - if (percentual) - { - double val = 100.0 * GetRelativeValue(); - - _snwprintf_s(format, _TRUNCATE, L"%%.%if", decimals); - _snwprintf_s(buffer, _TRUNCATE, format, val); - } - else if (autoScale != AUTOSCALE_OFF) - { - GetScaledValue(autoScale, decimals, GetValue(), buffer, _countof(buffer)); - } - else - { - double val = GetValue() / scale; - - if (decimals == -1) - { - int len = _snwprintf_s(buffer, _TRUNCATE, L"%.5f", val); - RemoveTrailingZero(buffer, len); - } - else - { - _snwprintf_s(format, _TRUNCATE, L"%%.%if", decimals); - _snwprintf_s(buffer, _TRUNCATE, format, val); - } - } - - return CheckSubstitute(buffer); -} - -void Measure::GetScaledValue(AUTOSCALE autoScale, int decimals, double theValue, WCHAR* buffer, size_t sizeInWords) -{ - WCHAR format[32]; - double value = 0; - - if (decimals == 0) - { - wcsncpy_s(format, L"%.0f", _TRUNCATE); - } - else - { - _snwprintf_s(format, _TRUNCATE, L"%%.%if", decimals); - } - - const double* tblScale = - g_TblScale[(autoScale == AUTOSCALE_1000 || autoScale == AUTOSCALE_1000K) ? AUTOSCALE_INDEX_1000 : AUTOSCALE_INDEX_1024]; - - if (theValue >= tblScale[0]) - { - value = theValue / tblScale[0]; - wcsncat_s(format, L" T", _TRUNCATE); - } - else if (theValue >= tblScale[1]) - { - value = theValue / tblScale[1]; - wcsncat_s(format, L" G", _TRUNCATE); - } - else if (theValue >= tblScale[2]) - { - value = theValue / tblScale[2]; - wcsncat_s(format, L" M", _TRUNCATE); - } - else if (autoScale == AUTOSCALE_1024K || autoScale == AUTOSCALE_1000K || theValue >= tblScale[3]) - { - value = theValue / tblScale[3]; - wcsncat_s(format, L" k", _TRUNCATE); - } - else - { - value = theValue; - wcsncat_s(format, L" ", _TRUNCATE); - } - _snwprintf_s(buffer, sizeInWords, _TRUNCATE, format, value); -} - -void Measure::RemoveTrailingZero(WCHAR* str, int strLen) -{ - --strLen; - while (strLen >= 0) - { - if (str[strLen] == L'0') - { - str[strLen] = L'\0'; - --strLen; - } - else - { - if (str[strLen] == L'.') - { - str[strLen] = L'\0'; - } - break; - } - } -} - -/* -** Executes OnChangeAction if action is set. -** If execute parameter is set to false, only updates old value with current value. -** -*/ -void Measure::DoChangeAction(bool execute) -{ - if (!m_OnChangeAction.empty() && m_ValueAssigned) - { - double newValue = GetValue(); - const WCHAR* newStringValue = GetStringValue(); - if (!newStringValue) - { - newStringValue = L""; - } - - if (!m_OldValue) - { - m_OldValue = new MeasureValueSet(newValue, newStringValue); - } - else if (execute) - { - if (m_OldValue->IsChanged(newValue, newStringValue)) - { - Rainmeter::GetInstance().ExecuteCommand(m_OnChangeAction.c_str(), m_MeterWindow); - } - } - else - { - m_OldValue->Set(newValue, newStringValue); - } - } -} - -/* -** Creates the given measure. This is the factory method for the measures. -** If new measures are implemented this method needs to be updated. -** -*/ -Measure* Measure::Create(const WCHAR* measure, MeterWindow* meterWindow, const WCHAR* name) -{ - // Comparison is caseinsensitive - - if (_wcsicmp(L"CPU", measure) == 0) - { - return new MeasureCPU(meterWindow, name); - } - else if (_wcsicmp(L"Memory", measure) == 0) - { - return new MeasureMemory(meterWindow, name); - } - else if (_wcsicmp(L"NetIn", measure) == 0) - { - return new MeasureNetIn(meterWindow, name); - } - else if (_wcsicmp(L"NetOut", measure) == 0) - { - return new MeasureNetOut(meterWindow, name); - } - else if (_wcsicmp(L"NetTotal", measure) == 0) - { - return new MeasureNetTotal(meterWindow, name); - } - else if (_wcsicmp(L"PhysicalMemory", measure) == 0) - { - return new MeasurePhysicalMemory(meterWindow, name); - } - else if (_wcsicmp(L"SwapMemory", measure) == 0) - { - return new MeasureVirtualMemory(meterWindow, name); - } - else if (_wcsicmp(L"FreeDiskSpace", measure) == 0) - { - return new MeasureDiskSpace(meterWindow, name); - } - else if (_wcsicmp(L"Uptime", measure) == 0) - { - return new MeasureUptime(meterWindow, name); - } - else if (_wcsicmp(L"Time", measure) == 0) - { - return new MeasureTime(meterWindow, name); - } - else if (_wcsicmp(L"Plugin", measure) == 0) - { - return new MeasurePlugin(meterWindow, name); - } - else if (_wcsicmp(L"Registry", measure) == 0) - { - return new MeasureRegistry(meterWindow, name); - } - else if (_wcsicmp(L"Calc", measure) == 0) - { - return new MeasureCalc(meterWindow, name); - } - else if (_wcsicmp(L"Script", measure) == 0) - { - return new MeasureScript(meterWindow, name); - } - else if (_wcsicmp(L"String", measure) == 0) - { - return new MeasureString(meterWindow, name); - } - - LogErrorF(meterWindow, L"Measure=%s is not valid in [%s]", measure, name); - - return nullptr; -} - -/* -** Executes a custom bang. -** -*/ -void Measure::Command(const std::wstring& command) -{ - LogWarningF(this, L"!CommandMeasure: Not supported"); -} - -/* -** Returns the number value of a measure, used by IfCondition's. -** -*/ -bool Measure::GetCurrentMeasureValue(const WCHAR* str, int len, double* value, void* context) -{ - auto measure = (Measure*)context; - const std::vector& measures = measure->m_MeterWindow->GetMeasures(); - - for (const auto& iter : measures) - { - if (iter->GetOriginalName().length() == len && - _wcsnicmp(str, iter->GetName(), len) == 0) - { - *value = iter->GetValue(); - return true; - } - } - - return false; -} +/* + Copyright (C) 2000 Kimmo Pekkola + + 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 "Measure.h" +#include "MeasureCPU.h" +#include "MeasureMemory.h" +#include "MeasurePhysicalMemory.h" +#include "MeasureVirtualMemory.h" +#include "MeasureNetIn.h" +#include "MeasureNetOut.h" +#include "MeasureNetTotal.h" +#include "MeasureDiskSpace.h" +#include "MeasureUptime.h" +#include "MeasurePlugin.h" +#include "MeasureRegistry.h" +#include "MeasureString.h" +#include "MeasureTime.h" +#include "MeasureCalc.h" +#include "MeasureScript.h" +#include "Rainmeter.h" +#include "Error.h" +#include "Litestep.h" +#include "pcre-8.10/config.h" +#include "pcre-8.10/pcre.h" + +#define OVECCOUNT 300 // Should be a multiple of 3 + +enum AUTOSCALE_INDEX +{ + AUTOSCALE_INDEX_1024 = 0, + AUTOSCALE_INDEX_1000 = 1 +}; + +static const double g_TblScale[2][4] = { + { + 1024.0 * 1024.0 * 1024.0 * 1024.0, + 1024.0 * 1024.0 * 1024.0, + 1024.0 * 1024.0, + 1024.0 + }, + { + 1000.0 * 1000.0 * 1000.0 * 1000.0, + 1000.0 * 1000.0 * 1000.0, + 1000.0 * 1000.0, + 1000.0 + } +}; + +const int MEDIAN_SIZE = 3; + +/* +** The constructor +** +*/ +Measure::Measure(MeterWindow* meterWindow, const WCHAR* name) : Section(meterWindow, name), + m_Invert(false), + m_LogMaxValue(false), + m_MinValue(), + m_MaxValue(1.0), + m_Value(), + m_RegExpSubstitute(false), + m_MedianPos(), + m_AveragePos(), + m_AverageSize(), + m_Disabled(false), + m_Paused(false), + m_Initialized(false), + m_OldValue(), + m_ValueAssigned(false) +{ +} + +/* +** The destructor +** +*/ +Measure::~Measure() +{ + delete m_OldValue; +} + +/* +** Initializes the measure. +** +*/ +void Measure::Initialize() +{ + m_Initialized = true; +} + +/* +** Read the common options specified in the ini file. The inherited classes must +** call this base implementation if they overwrite this method. +** +*/ +void Measure::ReadOptions(ConfigParser& parser, const WCHAR* section) +{ + bool oldOnChangeActionEmpty = m_OnChangeAction.empty(); + + Section::ReadOptions(parser, section); + + // Clear substitutes to prevent from being added more than once. + if (!m_Substitute.empty()) + { + m_Substitute.clear(); + } + + m_Invert = parser.ReadBool(section, L"InvertMeasure", false); + + m_Disabled = parser.ReadBool(section, L"Disabled", false); + m_Paused = parser.ReadBool(section, L"Paused", false); + + m_MinValue = parser.ReadFloat(section, L"MinValue", m_MinValue); + m_MaxValue = parser.ReadFloat(section, L"MaxValue", m_MaxValue); + + m_IfActions.ReadOptions(parser, section); + + // The first time around, we read the conditions here. Subsequent rereads will be done in + // Update() if needed. + if (!m_Initialized) + { + m_IfActions.ReadConditionOptions(parser, section); + } + + m_OnChangeAction = parser.ReadString(section, L"OnChangeAction", L"", false); + + m_AverageSize = parser.ReadUInt(section, L"AverageSize", 0); + + m_RegExpSubstitute = parser.ReadBool(section, L"RegExpSubstitute", false); + std::wstring subs = parser.ReadString(section, L"Substitute", L""); + if (!subs.empty()) + { + if ((subs[0] != L'"' || subs[subs.length() - 1] != L'\'') && + (subs[0] != L'\'' || subs[subs.length() - 1] != L'"')) + { + // Add quotes since they are removed by the GetProfileString + subs.insert(0, 1, L'"'); + subs += L'"'; + } + if (!ParseSubstitute(subs)) + { + LogErrorF(this, L"Measure: Invalid Substitute=%s", subs.c_str()); + } + } + + if (m_Initialized && + oldOnChangeActionEmpty && !m_OnChangeAction.empty()) + { + DoChangeAction(false); + } +} + +void Measure::Disable() +{ + m_Disabled = true; + + // Change the option as well to avoid reset in ReadOptions(). + m_MeterWindow->GetParser().SetValue(m_Name, L"Disabled", L"1"); +} + +void Measure::Enable() +{ + m_Disabled = false; + + // Change the option as well to avoid reset in ReadOptions(). + m_MeterWindow->GetParser().SetValue(m_Name, L"Disabled", L"0"); +} + +void Measure::Pause() +{ + m_Paused = true; + + // Change the option as well to avoid reset in ReadOptions(). + m_MeterWindow->GetParser().SetValue(m_Name, L"Paused", L"1"); +} + +void Measure::Unpause() +{ + m_Paused = false; + + // Change the option as well to avoid reset in ReadOptions(). + m_MeterWindow->GetParser().SetValue(m_Name, L"Paused", L"0"); +} + +/* +** Substitues text using a straight find and replace method +*/ +bool Measure::MakePlainSubstitute(std::wstring& str, size_t index) +{ + size_t start = 0, pos; + + do + { + pos = str.find(m_Substitute[index], start); + if (pos != std::wstring::npos) + { + str.replace(pos, m_Substitute[index].length(), m_Substitute[index + 1]); + start = pos + m_Substitute[index + 1].length(); + } + } + while (pos != std::wstring::npos); + + return true; +} + +/* +** Substitutes part of the text +*/ +const WCHAR* Measure::CheckSubstitute(const WCHAR* buffer) +{ + static std::wstring str; + + if (m_Substitute.empty()) + { + return buffer; + } + + if (!m_RegExpSubstitute) + { + str = buffer; + + for (size_t i = 0, isize = m_Substitute.size(); i < isize; i += 2) + { + if (!m_Substitute[i].empty()) + { + MakePlainSubstitute(str, i); + } + else if (str.empty()) + { + // Empty result and empty substitute -> use second + str = m_Substitute[i + 1]; + } + } + } + else + { + std::string utf8str = StringUtil::NarrowUTF8(buffer); + int ovector[300]; + + for (size_t i = 0, isize = m_Substitute.size(); i < isize; i += 2) + { + const char* error; + int errorOffset; + int offset = 0; + pcre* re = pcre_compile( + StringUtil::NarrowUTF8(m_Substitute[i]).c_str(), + PCRE_UTF8, + &error, + &errorOffset, + nullptr); // Use default character tables. + if (!re) + { + MakePlainSubstitute(str, i); + LogNoticeF(this, L"Substitute: %S", error); + } + else + { + do + { + const int rc = pcre_exec( + re, + nullptr, // No extra data - we didn't study the pattern + utf8str.c_str(), // The subject string + (int)utf8str.length(), // The length of the subject + offset, + 0, + ovector, + (int)_countof(ovector)); + if (rc <= 0) + { + break; + } + + std::string result = StringUtil::NarrowUTF8(m_Substitute[i + 1]); + + if (rc > 1) + { + for (int j = rc - 1 ; j >= 0 ; --j) + { + size_t newStart = ovector[2 * j]; + size_t inLength = ovector[2 * j + 1] - ovector[2 * j]; + + char tmpName[64]; + size_t cutLength = _snprintf_s(tmpName, _TRUNCATE, "\\%i", j);; + size_t start = 0, pos; + do + { + pos = result.find(tmpName, start, cutLength); + if (pos != std::string::npos) + { + result.replace(pos, cutLength, utf8str, newStart, inLength); + start = pos + inLength; + } + } + while (pos != std::string::npos); + } + } + + const int start = ovector[0]; + const int length = ovector[1] - ovector[0]; + utf8str.replace(start, length, result); + offset = start + (int)result.length(); + } + while (true); + + pcre_free(re); + } + } + + str = StringUtil::WidenUTF8(utf8str); + } + + return str.c_str(); +} + +/* +** Reads the buffer for "Name":"Value"-pairs separated with comma and +** fills the map with the parsed data. +*/ +bool Measure::ParseSubstitute(std::wstring buffer) +{ + if (buffer.empty()) return true; + + do + { + std::wstring word1 = ExtractWord(buffer); + std::wstring sep = ExtractWord(buffer); + if (sep.size() != 1 || sep[0] != L':') return false; + std::wstring word2 = ExtractWord(buffer); + + if (wcscmp(word1.c_str(), word2.c_str()) != 0) + { + if (m_RegExpSubstitute && word1.empty()) + { + word1 = L"^$"; + } + + m_Substitute.push_back(word1); + m_Substitute.push_back(word2); + } + + std::wstring sep2 = ExtractWord(buffer); + if (!sep2.empty() && (sep2.size() != 1 || sep2[0] != L',')) return false; + } + while (!buffer.empty()); + + return true; +} + +/* +** Returns the first word from the buffer. The word can be inside quotes. +** If not, the separators are ' ', '\t', ',' and ':'. Whitespaces are removed +** and buffer _will_ be modified. +*/ +std::wstring Measure::ExtractWord(std::wstring& buffer) +{ + std::wstring::size_type end, len = buffer.size(); + std::wstring ret; + + if (len == 0) return ret; + + // Remove whitespaces + end = 0; + while (end < len && (buffer[end] == L' ' || buffer[end] == L'\t' || buffer[end] == L'\n')) ++end; + if (end == len) + { + // End of line reached + end = std::wstring::npos; + } + else + { + buffer.erase(0, end); + len = buffer.size(); + + if (buffer[0] == L'"' || buffer[0] == L'\'') + { + WCHAR quote = buffer[0]; + + end = 1; // Skip the '"' + // Quotes around the word + while (end < len && (buffer[end] != quote)) ++end; + if (end == len) end = std::wstring::npos; + + if (end != std::wstring::npos) + { + ret.assign(buffer, 1, end - 1); + ++end; + } + else + { + // End of string reached - discard result + } + } + else + { + end = 0; + while (end < len && (buffer[end] != L',' && buffer[end] != L':' && buffer[end] != L' ' && buffer[end] != L'\t')) ++end; + if (end == len) end = std::wstring::npos; + + if (end == std::wstring::npos) + { + // End of line reached + ret = buffer; + } + else + { + ret.assign(buffer, 0, ++end); // The separator is also returned! + } + } + } + + buffer.erase(0, end); + + return ret; +} + +bool Measure::Update(bool rereadOptions) +{ + if (rereadOptions) + { + ReadOptions(m_MeterWindow->GetParser()); + } + + // Don't do anything if paused + if (m_Paused) return false; + + if (!m_Disabled) + { + // Only update the counter if the divider + if (!UpdateCounter()) return false; + + // Call derived method to update value + UpdateValue(); + + if (m_AverageSize > 0) + { + size_t averageValuesSize = m_AverageValues.size(); + + if (m_AverageSize != averageValuesSize) + { + m_AverageValues.resize(m_AverageSize, m_Value); + averageValuesSize = m_AverageValues.size(); + if (m_AveragePos >= averageValuesSize) m_AveragePos = 0; + } + m_AverageValues[m_AveragePos] = m_Value; + + ++m_AveragePos; + m_AveragePos %= averageValuesSize; + + // Calculate the average value + double value = 0; + for (size_t i = 0; i < averageValuesSize; ++i) + { + value += m_AverageValues[i]; + } + m_Value = value / (double)averageValuesSize; + } + + // If we're logging the maximum value of the measure, check if + // the new value is greater than the old one, and update if necessary. + if (m_LogMaxValue) + { + if (m_MedianValues.empty()) + { + m_MedianValues.resize(MEDIAN_SIZE, 0); + } + + m_MedianValues[m_MedianPos] = m_Value; + ++m_MedianPos; + m_MedianPos %= MEDIAN_SIZE; + + auto medianArray = m_MedianValues; + std::sort(&medianArray.data()[0], &medianArray.data()[MEDIAN_SIZE]); // Workaround for "Debug" build mode + + double medianValue = medianArray[MEDIAN_SIZE / 2]; + m_MaxValue = max(m_MaxValue, medianValue); + m_MinValue = min(m_MinValue, medianValue); + } + + m_ValueAssigned = true; + + // For the conditional options to work with the current measure value when using + // [MeasureName], we need to read the options after m_Value has been changed. + if (rereadOptions) + { + m_IfActions.ReadConditionOptions(m_MeterWindow->GetParser(), GetName()); + } + + if (m_MeterWindow) + { + m_IfActions.DoIfActions(*this, m_Value); + } + + return true; + } + else + { + // Disabled measures have 0 as value + m_Value = 0.0; + + m_IfActions.SetState(m_Value); + + return false; + } +} + +/* +** Returns the value of the measure. +** +*/ +double Measure::GetValue() +{ + // Invert if so requested + if (m_Invert) + { + return m_MaxValue - m_Value + m_MinValue; + } + + return m_Value; +} + +/* +** Returns the relative value of the measure (0.0 - 1.0). +** +*/ +double Measure::GetRelativeValue() +{ + double range = GetValueRange(); + + if (range != 0.0) + { + double value = GetValue(); + + value = min(m_MaxValue, value); + value = max(m_MinValue, value); + + value -= m_MinValue; + + return value / range; + } + + return 1.0; +} + +/* +** Returns the value range. +** +*/ +double Measure::GetValueRange() +{ + return m_MaxValue - m_MinValue; +} + +/* +** Base implementation. Derivied classes can provide an alternative implementation if they have a +** string value that is not based on m_Value. +** +*/ +const WCHAR* Measure::GetStringValue() +{ + return nullptr; +} + +/* +** Returns the unformatted string value if the measure has one or a formatted value otherwise. +** +*/ +const WCHAR* Measure::GetStringOrFormattedValue(AUTOSCALE autoScale, double scale, int decimals, bool percentual) +{ + const WCHAR* stringValue = GetStringValue(); + return stringValue ? stringValue : GetFormattedValue(autoScale, scale, decimals, percentual); +} + +/* +** This method returns the value as text string. The actual value is +** get with GetValue() so we don't have to worry about m_Invert. +** +** autoScale If true, scale the value automatically to some sensible range. +** scale The scale to use if autoScale is false. +** decimals Number of decimals used in the value. If -1, get rid of ".00000" for dynamic variables. +** percentual Return the value as % from the maximum value. +*/ +const WCHAR* Measure::GetFormattedValue(AUTOSCALE autoScale, double scale, int decimals, bool percentual) +{ + static WCHAR buffer[128]; + WCHAR format[32]; + + if (percentual) + { + double val = 100.0 * GetRelativeValue(); + + _snwprintf_s(format, _TRUNCATE, L"%%.%if", decimals); + _snwprintf_s(buffer, _TRUNCATE, format, val); + } + else if (autoScale != AUTOSCALE_OFF) + { + GetScaledValue(autoScale, decimals, GetValue(), buffer, _countof(buffer)); + } + else + { + double val = GetValue() / scale; + + if (decimals == -1) + { + int len = _snwprintf_s(buffer, _TRUNCATE, L"%.5f", val); + RemoveTrailingZero(buffer, len); + } + else + { + _snwprintf_s(format, _TRUNCATE, L"%%.%if", decimals); + _snwprintf_s(buffer, _TRUNCATE, format, val); + } + } + + return CheckSubstitute(buffer); +} + +void Measure::GetScaledValue(AUTOSCALE autoScale, int decimals, double theValue, WCHAR* buffer, size_t sizeInWords) +{ + WCHAR format[32]; + double value = 0; + + if (decimals == 0) + { + wcsncpy_s(format, L"%.0f", _TRUNCATE); + } + else + { + _snwprintf_s(format, _TRUNCATE, L"%%.%if", decimals); + } + + const double* tblScale = + g_TblScale[(autoScale == AUTOSCALE_1000 || autoScale == AUTOSCALE_1000K) ? AUTOSCALE_INDEX_1000 : AUTOSCALE_INDEX_1024]; + + if (theValue >= tblScale[0]) + { + value = theValue / tblScale[0]; + wcsncat_s(format, L" T", _TRUNCATE); + } + else if (theValue >= tblScale[1]) + { + value = theValue / tblScale[1]; + wcsncat_s(format, L" G", _TRUNCATE); + } + else if (theValue >= tblScale[2]) + { + value = theValue / tblScale[2]; + wcsncat_s(format, L" M", _TRUNCATE); + } + else if (autoScale == AUTOSCALE_1024K || autoScale == AUTOSCALE_1000K || theValue >= tblScale[3]) + { + value = theValue / tblScale[3]; + wcsncat_s(format, L" k", _TRUNCATE); + } + else + { + value = theValue; + wcsncat_s(format, L" ", _TRUNCATE); + } + _snwprintf_s(buffer, sizeInWords, _TRUNCATE, format, value); +} + +void Measure::RemoveTrailingZero(WCHAR* str, int strLen) +{ + --strLen; + while (strLen >= 0) + { + if (str[strLen] == L'0') + { + str[strLen] = L'\0'; + --strLen; + } + else + { + if (str[strLen] == L'.') + { + str[strLen] = L'\0'; + } + break; + } + } +} + +/* +** Executes OnChangeAction if action is set. +** If execute parameter is set to false, only updates old value with current value. +** +*/ +void Measure::DoChangeAction(bool execute) +{ + if (!m_OnChangeAction.empty() && m_ValueAssigned) + { + double newValue = GetValue(); + const WCHAR* newStringValue = GetStringValue(); + if (!newStringValue) + { + newStringValue = L""; + } + + if (!m_OldValue) + { + m_OldValue = new MeasureValueSet(newValue, newStringValue); + } + else if (execute) + { + if (m_OldValue->IsChanged(newValue, newStringValue)) + { + GetRainmeter().ExecuteCommand(m_OnChangeAction.c_str(), m_MeterWindow); + } + } + else + { + m_OldValue->Set(newValue, newStringValue); + } + } +} + +/* +** Creates the given measure. This is the factory method for the measures. +** If new measures are implemented this method needs to be updated. +** +*/ +Measure* Measure::Create(const WCHAR* measure, MeterWindow* meterWindow, const WCHAR* name) +{ + // Comparison is caseinsensitive + + if (_wcsicmp(L"CPU", measure) == 0) + { + return new MeasureCPU(meterWindow, name); + } + else if (_wcsicmp(L"Memory", measure) == 0) + { + return new MeasureMemory(meterWindow, name); + } + else if (_wcsicmp(L"NetIn", measure) == 0) + { + return new MeasureNetIn(meterWindow, name); + } + else if (_wcsicmp(L"NetOut", measure) == 0) + { + return new MeasureNetOut(meterWindow, name); + } + else if (_wcsicmp(L"NetTotal", measure) == 0) + { + return new MeasureNetTotal(meterWindow, name); + } + else if (_wcsicmp(L"PhysicalMemory", measure) == 0) + { + return new MeasurePhysicalMemory(meterWindow, name); + } + else if (_wcsicmp(L"SwapMemory", measure) == 0) + { + return new MeasureVirtualMemory(meterWindow, name); + } + else if (_wcsicmp(L"FreeDiskSpace", measure) == 0) + { + return new MeasureDiskSpace(meterWindow, name); + } + else if (_wcsicmp(L"Uptime", measure) == 0) + { + return new MeasureUptime(meterWindow, name); + } + else if (_wcsicmp(L"Time", measure) == 0) + { + return new MeasureTime(meterWindow, name); + } + else if (_wcsicmp(L"Plugin", measure) == 0) + { + return new MeasurePlugin(meterWindow, name); + } + else if (_wcsicmp(L"Registry", measure) == 0) + { + return new MeasureRegistry(meterWindow, name); + } + else if (_wcsicmp(L"Calc", measure) == 0) + { + return new MeasureCalc(meterWindow, name); + } + else if (_wcsicmp(L"Script", measure) == 0) + { + return new MeasureScript(meterWindow, name); + } + else if (_wcsicmp(L"String", measure) == 0) + { + return new MeasureString(meterWindow, name); + } + + LogErrorF(meterWindow, L"Measure=%s is not valid in [%s]", measure, name); + + return nullptr; +} + +/* +** Executes a custom bang. +** +*/ +void Measure::Command(const std::wstring& command) +{ + LogWarningF(this, L"!CommandMeasure: Not supported"); +} + +/* +** Returns the number value of a measure, used by IfCondition's. +** +*/ +bool Measure::GetCurrentMeasureValue(const WCHAR* str, int len, double* value, void* context) +{ + auto measure = (Measure*)context; + const std::vector& measures = measure->m_MeterWindow->GetMeasures(); + + for (const auto& iter : measures) + { + if (iter->GetOriginalName().length() == len && + _wcsnicmp(str, iter->GetName(), len) == 0) + { + *value = iter->GetValue(); + return true; + } + } + + return false; +} diff --git a/Library/MeasureNet.cpp b/Library/MeasureNet.cpp index 902b1e4f..a6fad3ba 100644 --- a/Library/MeasureNet.cpp +++ b/Library/MeasureNet.cpp @@ -1,736 +1,736 @@ -/* - Copyright (C) 2001 Kimmo Pekkola - - 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 "MeasureNet.h" -#include "Rainmeter.h" -#include "System.h" - -BYTE* MeasureNet::c_Table = nullptr; -UINT MeasureNet::c_NumOfTables = 0; -std::vector MeasureNet::c_StatValues; -std::vector MeasureNet::c_OldStatValues; - -decltype(GetIfTable2)* MeasureNet::c_GetIfTable2 = nullptr; -decltype(FreeMibTable)* MeasureNet::c_FreeMibTable = nullptr; - -/* -** The constructor. This is the base class for the net-meters. -** -*/ -MeasureNet::MeasureNet(MeterWindow* meterWindow, const WCHAR* name, NET type) : Measure(meterWindow, name), - m_Net(type), - m_Interface(), - m_Octets(), - m_FirstTime(true), - m_Cumulative(false) -{ -} - -/* -** The destructor -** -*/ -MeasureNet::~MeasureNet() -{ -} - -/* -** Reads the tables for all net interfaces -** -*/ -void MeasureNet::UpdateIFTable() -{ - bool logging = false; - - if (c_GetIfTable2) - { - if (c_Table) - { - c_FreeMibTable(c_Table); - c_Table = nullptr; - } - - if (c_GetIfTable2((MIB_IF_TABLE2**)&c_Table) == NO_ERROR) - { - MIB_IF_TABLE2* ifTable = (MIB_IF_TABLE2*)c_Table; - - if (c_NumOfTables != ifTable->NumEntries) - { - c_NumOfTables = ifTable->NumEntries; - logging = true; - } - - if (Rainmeter::GetInstance().GetDebug() && logging) - { - LogDebug(L"------------------------------"); - LogDebugF(L"* NETWORK-INTERFACE: Count=%i", c_NumOfTables); - - for (size_t i = 0; i < c_NumOfTables; ++i) - { - const WCHAR* type = L"Other"; - switch (ifTable->Table[i].Type) - { - case IF_TYPE_ETHERNET_CSMACD: - type = L"Ethernet"; - break; - case IF_TYPE_PPP: - type = L"PPP"; - break; - case IF_TYPE_SOFTWARE_LOOPBACK: - type = L"Loopback"; - break; - case IF_TYPE_IEEE80211: - type = L"IEEE802.11"; - break; - case IF_TYPE_TUNNEL: - type = L"Tunnel"; - break; - case IF_TYPE_IEEE1394: - type = L"IEEE1394"; - break; - } - - LogDebugF(L"%i: %s", (int)i + 1, ifTable->Table[i].Description); - LogDebugF(L" Alias: %s", ifTable->Table[i].Alias); - LogDebugF(L" Type=%s(%i), Hardware=%s, Filter=%s", - type, ifTable->Table[i].Type, - (ifTable->Table[i].InterfaceAndOperStatusFlags.HardwareInterface == 1) ? L"Yes" : L"No", - (ifTable->Table[i].InterfaceAndOperStatusFlags.FilterInterface == 1) ? L"Yes" : L"No"); - } - LogDebug(L"------------------------------"); - } - } - else - { - // Something's wrong. Unable to get the table. - c_Table = nullptr; - c_NumOfTables = 0; - } - } - else - { - if (c_Table == nullptr) - { - // Gotta reserve few bytes for the tables - DWORD value = 0; - if (GetNumberOfInterfaces(&value) == NO_ERROR) - { - if (c_NumOfTables != value) - { - c_NumOfTables = value; - logging = true; - } - - if (c_NumOfTables > 0) - { - DWORD size = sizeof(MIB_IFTABLE) + sizeof(MIB_IFROW) * c_NumOfTables; - c_Table = new BYTE[size]; - } - } - } - - if (c_Table) - { - DWORD ret, size = 0; - - MIB_IFTABLE* ifTable = (MIB_IFTABLE*)c_Table; - - if ((ret = GetIfTable(ifTable, &size, FALSE)) == ERROR_INSUFFICIENT_BUFFER) - { - delete [] c_Table; - c_Table = new BYTE[size]; - - ifTable = (MIB_IFTABLE*)c_Table; - - ret = GetIfTable(ifTable, &size, FALSE); - } - - if (ret == NO_ERROR) - { - if (c_NumOfTables != ifTable->dwNumEntries) - { - c_NumOfTables = ifTable->dwNumEntries; - logging = true; - } - - if (Rainmeter::GetInstance().GetDebug() && logging) - { - LogDebug(L"------------------------------"); - LogDebugF(L"* NETWORK-INTERFACE: Count=%i", c_NumOfTables); - - for (size_t i = 0; i < c_NumOfTables; ++i) - { - const WCHAR* type = L""; - switch (ifTable->table[i].dwType) - { - case IF_TYPE_ETHERNET_CSMACD: - type = L"Ethernet"; - break; - case IF_TYPE_PPP: - type = L"PPP"; - break; - case IF_TYPE_SOFTWARE_LOOPBACK: - type = L"Loopback"; - break; - case IF_TYPE_IEEE80211: - type = L"IEEE802.11"; - break; - case IF_TYPE_TUNNEL: - type = L"Tunnel"; - break; - case IF_TYPE_IEEE1394: - type = L"IEEE1394"; - break; - default: - type = L"Other"; - break; - } - - LogDebugF(L"%i: %.*S", (int)i + 1, ifTable->table[i].dwDescrLen, (char*)ifTable->table[i].bDescr); - LogDebugF(L" Type=%s(%i)", type, ifTable->table[i].dwType); - } - LogDebug(L"------------------------------"); - } - } - else - { - // Something's wrong. Unable to get the table. - delete [] c_Table; - c_Table = nullptr; - c_NumOfTables = 0; - } - } - } -} - -/* -** Reads the amount of octets. This is the same for in, out and total. -** the net-parameter informs which inherited class called this method. -** -*/ -ULONG64 MeasureNet::GetNetOctets(NET net) -{ - ULONG64 value = 0; - - if (c_GetIfTable2) - { - MIB_IF_ROW2* table = (MIB_IF_ROW2*)((MIB_IF_TABLE2*)c_Table)->Table; - - if (m_Interface == 0) - { - // Get all interfaces - for (UINT i = 0; i < c_NumOfTables; ++i) - { - // Ignore the loopback and filter interfaces - if (table[i].Type == IF_TYPE_SOFTWARE_LOOPBACK || - table[i].InterfaceAndOperStatusFlags.FilterInterface == 1) continue; - - switch (net) - { - case NET_IN: - value += table[i].InOctets; - break; - - case NET_OUT: - value += table[i].OutOctets; - break; - - case NET_TOTAL: - value += table[i].InOctets; - value += table[i].OutOctets; - break; - } - } - } - else - { - // Get the selected interface - if (m_Interface <= c_NumOfTables) - { - switch (net) - { - case NET_IN: - value += table[m_Interface - 1].InOctets; - break; - - case NET_OUT: - value += table[m_Interface - 1].OutOctets; - break; - - case NET_TOTAL: - value += table[m_Interface - 1].InOctets; - value += table[m_Interface - 1].OutOctets; - break; - } - } - } - } - else - { - MIB_IFROW* table = (MIB_IFROW*)((MIB_IFTABLE*)c_Table)->table; - - if (m_Interface == 0) - { - // Get all interfaces - for (UINT i = 0; i < c_NumOfTables; ++i) - { - // Ignore the loopback - if (table[i].dwType == IF_TYPE_SOFTWARE_LOOPBACK) continue; - - switch (net) - { - case NET_IN: - value += table[i].dwInOctets; - break; - - case NET_OUT: - value += table[i].dwOutOctets; - break; - - case NET_TOTAL: - value += table[i].dwInOctets; - value += table[i].dwOutOctets; - break; - } - } - } - else - { - // Get the selected interface - if (m_Interface <= c_NumOfTables) - { - switch (net) - { - case NET_IN: - value += table[m_Interface - 1].dwInOctets; - break; - - case NET_OUT: - value += table[m_Interface - 1].dwOutOctets; - break; - - case NET_TOTAL: - value += table[m_Interface - 1].dwInOctets; - value += table[m_Interface - 1].dwOutOctets; - break; - } - } - } - } - - return value; -} - -/* -** Returns the stats value of the interface -** -*/ -ULONG64 MeasureNet::GetNetStatsValue(NET net) -{ - ULONG64 value = 0; - size_t statsSize = c_StatValues.size() / 2; - - if (m_Interface == 0) - { - // Get all interfaces - for (size_t i = 0; i < statsSize; ++i) - { - // Ignore the loopback and filter interfaces - if (c_NumOfTables == statsSize) - { - if (c_GetIfTable2) - { - if (((MIB_IF_TABLE2*)c_Table)->Table[i].Type == IF_TYPE_SOFTWARE_LOOPBACK || - ((MIB_IF_TABLE2*)c_Table)->Table[i].InterfaceAndOperStatusFlags.FilterInterface == 1) continue; - } - else - { - if (((MIB_IFTABLE*)c_Table)->table[i].dwType == IF_TYPE_SOFTWARE_LOOPBACK) continue; - } - } - - switch (net) - { - case NET_IN: - value += c_StatValues[i * 2 + 0]; - break; - - case NET_OUT: - value += c_StatValues[i * 2 + 1]; - break; - - case NET_TOTAL: - value += c_StatValues[i * 2 + 0]; - value += c_StatValues[i * 2 + 1]; - break; - } - } - } - else - { - // Get the selected interface - if (m_Interface <= statsSize) - { - switch (net) - { - case NET_IN: - value += c_StatValues[(m_Interface - 1) * 2 + 0]; - break; - - case NET_OUT: - value += c_StatValues[(m_Interface - 1) * 2 + 1]; - break; - - case NET_TOTAL: - value += c_StatValues[(m_Interface - 1) * 2 + 0]; - value += c_StatValues[(m_Interface - 1) * 2 + 1]; - break; - } - } - } - - return value; -} - -/* -** Updates the current value. -** -*/ -void MeasureNet::UpdateValue() -{ - if (c_Table == nullptr) return; - - if (m_Cumulative) - { - m_Value = (double)(__int64)GetNetStatsValue(m_Net); - } - else - { - ULONG64 value = 0; - - if (!m_FirstTime) - { - value = GetNetOctets(m_Net); - if (value > m_Octets) - { - ULONG64 tmpValue = value; - value -= m_Octets; - m_Octets = tmpValue; - } - else - { - m_Octets = value; - value = 0; - } - } - else - { - m_Octets = GetNetOctets(m_Net); - m_FirstTime = false; - } - - m_Value = (double)(__int64)value; - } -} - -/* -** Read the options specified in the ini file. -** -*/ -void MeasureNet::ReadOptions(ConfigParser& parser, const WCHAR* section) -{ - Measure::ReadOptions(parser, section); - - double value; - const WCHAR* netName = nullptr; - - if (m_Net == NET_IN) - { - netName = L"NetInSpeed"; - value = Rainmeter::GetInstance().GetGlobalOptions().netInSpeed; - } - else if (m_Net == NET_OUT) - { - netName = L"NetOutSpeed"; - value = Rainmeter::GetInstance().GetGlobalOptions().netOutSpeed; - } - else // if (m_Net == NET_TOTAL) - { - netName = L"NetTotalSpeed"; - value = Rainmeter::GetInstance().GetGlobalOptions().netInSpeed + Rainmeter::GetInstance().GetGlobalOptions().netOutSpeed; - } - - double maxValue = parser.ReadFloat(section, L"MaxValue", -1); - if (maxValue == -1) - { - maxValue = parser.ReadFloat(section, netName, -1); - if (maxValue == -1) - { - maxValue = value; - } - } - - m_Interface = parser.ReadInt(section, L"Interface", 0); - - m_Cumulative = parser.ReadBool(section, L"Cumulative", false); - if (m_Cumulative) - { - Rainmeter::GetInstance().SetNetworkStatisticsTimer(); - } - - if (maxValue == 0.0) - { - if (!m_LogMaxValue) - { - m_MaxValue = 1.0; - m_LogMaxValue = true; - m_MedianValues.clear(); - } - } - else - { - m_MaxValue = maxValue / 8; - m_LogMaxValue = false; - } -} - -/* -** Updates the statistics. -** -*/ -void MeasureNet::UpdateStats() -{ - if (c_Table) - { - size_t statsSize = c_NumOfTables * 2; - - // Fill the vectors - if (c_StatValues.size() < statsSize) - { - c_StatValues.resize(statsSize, 0); - } - - if (c_OldStatValues.size() < statsSize) - { - c_OldStatValues.resize(statsSize, 0); - } - - for (UINT i = 0; i < c_NumOfTables; ++i) - { - ULONG64 in, out; - - if (c_GetIfTable2) - { - in = ((MIB_IF_TABLE2*)c_Table)->Table[i].InOctets; - out = ((MIB_IF_TABLE2*)c_Table)->Table[i].OutOctets; - } - else - { - in = ((MIB_IFTABLE*)c_Table)->table[i].dwInOctets; - out = ((MIB_IFTABLE*)c_Table)->table[i].dwOutOctets; - } - - if (c_OldStatValues[i * 2 + 0] != 0) - { - if (in > c_OldStatValues[i * 2 + 0]) - { - c_StatValues[i * 2 + 0] += in - c_OldStatValues[i * 2 + 0]; - } - } - - if (c_OldStatValues[i * 2 + 1] != 0) - { - if (out > c_OldStatValues[i * 2 + 1]) - { - c_StatValues[i * 2 + 1] += out - c_OldStatValues[i * 2 + 1]; - } - } - - c_OldStatValues[i * 2 + 0] = in; - c_OldStatValues[i * 2 + 1] = out; - } - } -} - -/* -** Resets the statistics. -** -*/ -void MeasureNet::ResetStats() -{ - c_StatValues.clear(); -} - -/* -** Reads statistics. -** -*/ -void MeasureNet::ReadStats(const std::wstring& iniFile, std::wstring& statsDate) -{ - WCHAR buffer[48]; - - ConfigParser parser; - parser.Initialize(iniFile, nullptr, L"Statistics"); - - const std::wstring& date = parser.ReadString(L"Statistics", L"Since", L"", false); - if (!date.empty()) - { - statsDate = date; - } - - uint32_t count = parser.ReadUInt(L"Statistics", L"Count", 0); - if (parser.GetLastDefaultUsed()) - { - count = parser.ReadUInt(L"Statistics", L"NetStatsCount", 0); - } - - c_StatValues.clear(); - c_StatValues.reserve(count * 2); - - for (uint32_t i = 1; i <= count; ++i) - { - ULARGE_INTEGER value; - - _snwprintf_s(buffer, _TRUNCATE, L"In%u", i); - value.QuadPart = parser.ReadUInt64(L"Statistics", buffer, 0); - if (parser.GetLastDefaultUsed()) - { - _snwprintf_s(buffer, _TRUNCATE, L"NetStatsInHigh%u", i); - value.HighPart = parser.ReadUInt(L"Statistics", buffer, 0); - - _snwprintf_s(buffer, _TRUNCATE, L"NetStatsInLow%u", i); - value.LowPart = parser.ReadUInt(L"Statistics", buffer, 0); - } - c_StatValues.push_back(value.QuadPart); - - _snwprintf_s(buffer, _TRUNCATE, L"Out%u", i); - value.QuadPart = parser.ReadUInt64(L"Statistics", buffer, 0); - if (parser.GetLastDefaultUsed()) - { - _snwprintf_s(buffer, _TRUNCATE, L"NetStatsOutHigh%u", i); - value.HighPart = parser.ReadUInt(L"Statistics", buffer, 0); - - _snwprintf_s(buffer, _TRUNCATE, L"NetStatsOutLow%u", i); - value.LowPart = parser.ReadUInt(L"Statistics", buffer, 0); - } - c_StatValues.push_back(value.QuadPart); - } -} - -/* -** Writes statistics. -** -*/ -void MeasureNet::WriteStats(const WCHAR* iniFile, const std::wstring& statsDate) -{ - WCHAR buffer[48]; - int len; - - uint32_t count = (uint32_t)c_StatValues.size() / 2; - - // Reserve sufficient buffer for statistics - std::wstring data; - data.reserve(48 * (2 + count)); - - // Add date - data = L"Since="; - data += statsDate; - data += L'\0'; - - auto appendStatsValue = [&]() - { - data.append(buffer, len); - data += L'\0'; - }; - - // Add stats count - len = _snwprintf_s(buffer, _TRUNCATE, L"Count=%u", count); - appendStatsValue(); - - // Add stats - for (uint32_t i = 0; i < count; ++i) - { - if (c_StatValues[i * 2] > 0) - { - len = _snwprintf_s(buffer, _TRUNCATE, L"In%u=%llu", i + 1, c_StatValues[i * 2]); - appendStatsValue(); - } - - if (c_StatValues[i * 2 + 1] > 0) - { - len = _snwprintf_s(buffer, _TRUNCATE, L"Out%u=%llu", i + 1, c_StatValues[i * 2 + 1]); - appendStatsValue(); - } - } - - // Write statistics - WritePrivateProfileSection(L"Statistics", data.c_str(), iniFile); -} - -/* -** Prepares in order to use the new APIs which are available on Vista or newer. -** -*/ -void MeasureNet::InitializeStatic() -{ - if (IsWindowsVistaOrGreater()) - { - HMODULE IpHlpApiLibrary = GetModuleHandle(L"IpHlpApi.dll"); - if (IpHlpApiLibrary) - { - c_GetIfTable2 = (decltype(c_GetIfTable2))GetProcAddress(IpHlpApiLibrary, "GetIfTable2"); - c_FreeMibTable = (decltype(c_FreeMibTable))GetProcAddress(IpHlpApiLibrary, "FreeMibTable"); - } - - if (!c_GetIfTable2 || !c_FreeMibTable) - { - c_GetIfTable2 = nullptr; - c_FreeMibTable = nullptr; - } - } - - if (Rainmeter::GetInstance().GetDebug()) - { - UpdateIFTable(); - } -} - -/* -** Frees the resources. -** -*/ -void MeasureNet::FinalizeStatic() -{ - if (c_GetIfTable2) - { - if (c_Table) - { - c_FreeMibTable(c_Table); - } - - c_GetIfTable2 = nullptr; - c_FreeMibTable = nullptr; - } - else - { - delete [] c_Table; - } - c_Table = nullptr; - c_NumOfTables = 0; -} +/* + Copyright (C) 2001 Kimmo Pekkola + + 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 "MeasureNet.h" +#include "Rainmeter.h" +#include "System.h" + +BYTE* MeasureNet::c_Table = nullptr; +UINT MeasureNet::c_NumOfTables = 0; +std::vector MeasureNet::c_StatValues; +std::vector MeasureNet::c_OldStatValues; + +decltype(GetIfTable2)* MeasureNet::c_GetIfTable2 = nullptr; +decltype(FreeMibTable)* MeasureNet::c_FreeMibTable = nullptr; + +/* +** The constructor. This is the base class for the net-meters. +** +*/ +MeasureNet::MeasureNet(MeterWindow* meterWindow, const WCHAR* name, NET type) : Measure(meterWindow, name), + m_Net(type), + m_Interface(), + m_Octets(), + m_FirstTime(true), + m_Cumulative(false) +{ +} + +/* +** The destructor +** +*/ +MeasureNet::~MeasureNet() +{ +} + +/* +** Reads the tables for all net interfaces +** +*/ +void MeasureNet::UpdateIFTable() +{ + bool logging = false; + + if (c_GetIfTable2) + { + if (c_Table) + { + c_FreeMibTable(c_Table); + c_Table = nullptr; + } + + if (c_GetIfTable2((MIB_IF_TABLE2**)&c_Table) == NO_ERROR) + { + MIB_IF_TABLE2* ifTable = (MIB_IF_TABLE2*)c_Table; + + if (c_NumOfTables != ifTable->NumEntries) + { + c_NumOfTables = ifTable->NumEntries; + logging = true; + } + + if (GetRainmeter().GetDebug() && logging) + { + LogDebug(L"------------------------------"); + LogDebugF(L"* NETWORK-INTERFACE: Count=%i", c_NumOfTables); + + for (size_t i = 0; i < c_NumOfTables; ++i) + { + const WCHAR* type = L"Other"; + switch (ifTable->Table[i].Type) + { + case IF_TYPE_ETHERNET_CSMACD: + type = L"Ethernet"; + break; + case IF_TYPE_PPP: + type = L"PPP"; + break; + case IF_TYPE_SOFTWARE_LOOPBACK: + type = L"Loopback"; + break; + case IF_TYPE_IEEE80211: + type = L"IEEE802.11"; + break; + case IF_TYPE_TUNNEL: + type = L"Tunnel"; + break; + case IF_TYPE_IEEE1394: + type = L"IEEE1394"; + break; + } + + LogDebugF(L"%i: %s", (int)i + 1, ifTable->Table[i].Description); + LogDebugF(L" Alias: %s", ifTable->Table[i].Alias); + LogDebugF(L" Type=%s(%i), Hardware=%s, Filter=%s", + type, ifTable->Table[i].Type, + (ifTable->Table[i].InterfaceAndOperStatusFlags.HardwareInterface == 1) ? L"Yes" : L"No", + (ifTable->Table[i].InterfaceAndOperStatusFlags.FilterInterface == 1) ? L"Yes" : L"No"); + } + LogDebug(L"------------------------------"); + } + } + else + { + // Something's wrong. Unable to get the table. + c_Table = nullptr; + c_NumOfTables = 0; + } + } + else + { + if (c_Table == nullptr) + { + // Gotta reserve few bytes for the tables + DWORD value = 0; + if (GetNumberOfInterfaces(&value) == NO_ERROR) + { + if (c_NumOfTables != value) + { + c_NumOfTables = value; + logging = true; + } + + if (c_NumOfTables > 0) + { + DWORD size = sizeof(MIB_IFTABLE) + sizeof(MIB_IFROW) * c_NumOfTables; + c_Table = new BYTE[size]; + } + } + } + + if (c_Table) + { + DWORD ret, size = 0; + + MIB_IFTABLE* ifTable = (MIB_IFTABLE*)c_Table; + + if ((ret = GetIfTable(ifTable, &size, FALSE)) == ERROR_INSUFFICIENT_BUFFER) + { + delete [] c_Table; + c_Table = new BYTE[size]; + + ifTable = (MIB_IFTABLE*)c_Table; + + ret = GetIfTable(ifTable, &size, FALSE); + } + + if (ret == NO_ERROR) + { + if (c_NumOfTables != ifTable->dwNumEntries) + { + c_NumOfTables = ifTable->dwNumEntries; + logging = true; + } + + if (GetRainmeter().GetDebug() && logging) + { + LogDebug(L"------------------------------"); + LogDebugF(L"* NETWORK-INTERFACE: Count=%i", c_NumOfTables); + + for (size_t i = 0; i < c_NumOfTables; ++i) + { + const WCHAR* type = L""; + switch (ifTable->table[i].dwType) + { + case IF_TYPE_ETHERNET_CSMACD: + type = L"Ethernet"; + break; + case IF_TYPE_PPP: + type = L"PPP"; + break; + case IF_TYPE_SOFTWARE_LOOPBACK: + type = L"Loopback"; + break; + case IF_TYPE_IEEE80211: + type = L"IEEE802.11"; + break; + case IF_TYPE_TUNNEL: + type = L"Tunnel"; + break; + case IF_TYPE_IEEE1394: + type = L"IEEE1394"; + break; + default: + type = L"Other"; + break; + } + + LogDebugF(L"%i: %.*S", (int)i + 1, ifTable->table[i].dwDescrLen, (char*)ifTable->table[i].bDescr); + LogDebugF(L" Type=%s(%i)", type, ifTable->table[i].dwType); + } + LogDebug(L"------------------------------"); + } + } + else + { + // Something's wrong. Unable to get the table. + delete [] c_Table; + c_Table = nullptr; + c_NumOfTables = 0; + } + } + } +} + +/* +** Reads the amount of octets. This is the same for in, out and total. +** the net-parameter informs which inherited class called this method. +** +*/ +ULONG64 MeasureNet::GetNetOctets(NET net) +{ + ULONG64 value = 0; + + if (c_GetIfTable2) + { + MIB_IF_ROW2* table = (MIB_IF_ROW2*)((MIB_IF_TABLE2*)c_Table)->Table; + + if (m_Interface == 0) + { + // Get all interfaces + for (UINT i = 0; i < c_NumOfTables; ++i) + { + // Ignore the loopback and filter interfaces + if (table[i].Type == IF_TYPE_SOFTWARE_LOOPBACK || + table[i].InterfaceAndOperStatusFlags.FilterInterface == 1) continue; + + switch (net) + { + case NET_IN: + value += table[i].InOctets; + break; + + case NET_OUT: + value += table[i].OutOctets; + break; + + case NET_TOTAL: + value += table[i].InOctets; + value += table[i].OutOctets; + break; + } + } + } + else + { + // Get the selected interface + if (m_Interface <= c_NumOfTables) + { + switch (net) + { + case NET_IN: + value += table[m_Interface - 1].InOctets; + break; + + case NET_OUT: + value += table[m_Interface - 1].OutOctets; + break; + + case NET_TOTAL: + value += table[m_Interface - 1].InOctets; + value += table[m_Interface - 1].OutOctets; + break; + } + } + } + } + else + { + MIB_IFROW* table = (MIB_IFROW*)((MIB_IFTABLE*)c_Table)->table; + + if (m_Interface == 0) + { + // Get all interfaces + for (UINT i = 0; i < c_NumOfTables; ++i) + { + // Ignore the loopback + if (table[i].dwType == IF_TYPE_SOFTWARE_LOOPBACK) continue; + + switch (net) + { + case NET_IN: + value += table[i].dwInOctets; + break; + + case NET_OUT: + value += table[i].dwOutOctets; + break; + + case NET_TOTAL: + value += table[i].dwInOctets; + value += table[i].dwOutOctets; + break; + } + } + } + else + { + // Get the selected interface + if (m_Interface <= c_NumOfTables) + { + switch (net) + { + case NET_IN: + value += table[m_Interface - 1].dwInOctets; + break; + + case NET_OUT: + value += table[m_Interface - 1].dwOutOctets; + break; + + case NET_TOTAL: + value += table[m_Interface - 1].dwInOctets; + value += table[m_Interface - 1].dwOutOctets; + break; + } + } + } + } + + return value; +} + +/* +** Returns the stats value of the interface +** +*/ +ULONG64 MeasureNet::GetNetStatsValue(NET net) +{ + ULONG64 value = 0; + size_t statsSize = c_StatValues.size() / 2; + + if (m_Interface == 0) + { + // Get all interfaces + for (size_t i = 0; i < statsSize; ++i) + { + // Ignore the loopback and filter interfaces + if (c_NumOfTables == statsSize) + { + if (c_GetIfTable2) + { + if (((MIB_IF_TABLE2*)c_Table)->Table[i].Type == IF_TYPE_SOFTWARE_LOOPBACK || + ((MIB_IF_TABLE2*)c_Table)->Table[i].InterfaceAndOperStatusFlags.FilterInterface == 1) continue; + } + else + { + if (((MIB_IFTABLE*)c_Table)->table[i].dwType == IF_TYPE_SOFTWARE_LOOPBACK) continue; + } + } + + switch (net) + { + case NET_IN: + value += c_StatValues[i * 2 + 0]; + break; + + case NET_OUT: + value += c_StatValues[i * 2 + 1]; + break; + + case NET_TOTAL: + value += c_StatValues[i * 2 + 0]; + value += c_StatValues[i * 2 + 1]; + break; + } + } + } + else + { + // Get the selected interface + if (m_Interface <= statsSize) + { + switch (net) + { + case NET_IN: + value += c_StatValues[(m_Interface - 1) * 2 + 0]; + break; + + case NET_OUT: + value += c_StatValues[(m_Interface - 1) * 2 + 1]; + break; + + case NET_TOTAL: + value += c_StatValues[(m_Interface - 1) * 2 + 0]; + value += c_StatValues[(m_Interface - 1) * 2 + 1]; + break; + } + } + } + + return value; +} + +/* +** Updates the current value. +** +*/ +void MeasureNet::UpdateValue() +{ + if (c_Table == nullptr) return; + + if (m_Cumulative) + { + m_Value = (double)(__int64)GetNetStatsValue(m_Net); + } + else + { + ULONG64 value = 0; + + if (!m_FirstTime) + { + value = GetNetOctets(m_Net); + if (value > m_Octets) + { + ULONG64 tmpValue = value; + value -= m_Octets; + m_Octets = tmpValue; + } + else + { + m_Octets = value; + value = 0; + } + } + else + { + m_Octets = GetNetOctets(m_Net); + m_FirstTime = false; + } + + m_Value = (double)(__int64)value; + } +} + +/* +** Read the options specified in the ini file. +** +*/ +void MeasureNet::ReadOptions(ConfigParser& parser, const WCHAR* section) +{ + Measure::ReadOptions(parser, section); + + double value; + const WCHAR* netName = nullptr; + + if (m_Net == NET_IN) + { + netName = L"NetInSpeed"; + value = GetRainmeter().GetGlobalOptions().netInSpeed; + } + else if (m_Net == NET_OUT) + { + netName = L"NetOutSpeed"; + value = GetRainmeter().GetGlobalOptions().netOutSpeed; + } + else // if (m_Net == NET_TOTAL) + { + netName = L"NetTotalSpeed"; + value = GetRainmeter().GetGlobalOptions().netInSpeed + GetRainmeter().GetGlobalOptions().netOutSpeed; + } + + double maxValue = parser.ReadFloat(section, L"MaxValue", -1); + if (maxValue == -1) + { + maxValue = parser.ReadFloat(section, netName, -1); + if (maxValue == -1) + { + maxValue = value; + } + } + + m_Interface = parser.ReadInt(section, L"Interface", 0); + + m_Cumulative = parser.ReadBool(section, L"Cumulative", false); + if (m_Cumulative) + { + GetRainmeter().SetNetworkStatisticsTimer(); + } + + if (maxValue == 0.0) + { + if (!m_LogMaxValue) + { + m_MaxValue = 1.0; + m_LogMaxValue = true; + m_MedianValues.clear(); + } + } + else + { + m_MaxValue = maxValue / 8; + m_LogMaxValue = false; + } +} + +/* +** Updates the statistics. +** +*/ +void MeasureNet::UpdateStats() +{ + if (c_Table) + { + size_t statsSize = c_NumOfTables * 2; + + // Fill the vectors + if (c_StatValues.size() < statsSize) + { + c_StatValues.resize(statsSize, 0); + } + + if (c_OldStatValues.size() < statsSize) + { + c_OldStatValues.resize(statsSize, 0); + } + + for (UINT i = 0; i < c_NumOfTables; ++i) + { + ULONG64 in, out; + + if (c_GetIfTable2) + { + in = ((MIB_IF_TABLE2*)c_Table)->Table[i].InOctets; + out = ((MIB_IF_TABLE2*)c_Table)->Table[i].OutOctets; + } + else + { + in = ((MIB_IFTABLE*)c_Table)->table[i].dwInOctets; + out = ((MIB_IFTABLE*)c_Table)->table[i].dwOutOctets; + } + + if (c_OldStatValues[i * 2 + 0] != 0) + { + if (in > c_OldStatValues[i * 2 + 0]) + { + c_StatValues[i * 2 + 0] += in - c_OldStatValues[i * 2 + 0]; + } + } + + if (c_OldStatValues[i * 2 + 1] != 0) + { + if (out > c_OldStatValues[i * 2 + 1]) + { + c_StatValues[i * 2 + 1] += out - c_OldStatValues[i * 2 + 1]; + } + } + + c_OldStatValues[i * 2 + 0] = in; + c_OldStatValues[i * 2 + 1] = out; + } + } +} + +/* +** Resets the statistics. +** +*/ +void MeasureNet::ResetStats() +{ + c_StatValues.clear(); +} + +/* +** Reads statistics. +** +*/ +void MeasureNet::ReadStats(const std::wstring& iniFile, std::wstring& statsDate) +{ + WCHAR buffer[48]; + + ConfigParser parser; + parser.Initialize(iniFile, nullptr, L"Statistics"); + + const std::wstring& date = parser.ReadString(L"Statistics", L"Since", L"", false); + if (!date.empty()) + { + statsDate = date; + } + + uint32_t count = parser.ReadUInt(L"Statistics", L"Count", 0); + if (parser.GetLastDefaultUsed()) + { + count = parser.ReadUInt(L"Statistics", L"NetStatsCount", 0); + } + + c_StatValues.clear(); + c_StatValues.reserve(count * 2); + + for (uint32_t i = 1; i <= count; ++i) + { + ULARGE_INTEGER value; + + _snwprintf_s(buffer, _TRUNCATE, L"In%u", i); + value.QuadPart = parser.ReadUInt64(L"Statistics", buffer, 0); + if (parser.GetLastDefaultUsed()) + { + _snwprintf_s(buffer, _TRUNCATE, L"NetStatsInHigh%u", i); + value.HighPart = parser.ReadUInt(L"Statistics", buffer, 0); + + _snwprintf_s(buffer, _TRUNCATE, L"NetStatsInLow%u", i); + value.LowPart = parser.ReadUInt(L"Statistics", buffer, 0); + } + c_StatValues.push_back(value.QuadPart); + + _snwprintf_s(buffer, _TRUNCATE, L"Out%u", i); + value.QuadPart = parser.ReadUInt64(L"Statistics", buffer, 0); + if (parser.GetLastDefaultUsed()) + { + _snwprintf_s(buffer, _TRUNCATE, L"NetStatsOutHigh%u", i); + value.HighPart = parser.ReadUInt(L"Statistics", buffer, 0); + + _snwprintf_s(buffer, _TRUNCATE, L"NetStatsOutLow%u", i); + value.LowPart = parser.ReadUInt(L"Statistics", buffer, 0); + } + c_StatValues.push_back(value.QuadPart); + } +} + +/* +** Writes statistics. +** +*/ +void MeasureNet::WriteStats(const WCHAR* iniFile, const std::wstring& statsDate) +{ + WCHAR buffer[48]; + int len; + + uint32_t count = (uint32_t)c_StatValues.size() / 2; + + // Reserve sufficient buffer for statistics + std::wstring data; + data.reserve(48 * (2 + count)); + + // Add date + data = L"Since="; + data += statsDate; + data += L'\0'; + + auto appendStatsValue = [&]() + { + data.append(buffer, len); + data += L'\0'; + }; + + // Add stats count + len = _snwprintf_s(buffer, _TRUNCATE, L"Count=%u", count); + appendStatsValue(); + + // Add stats + for (uint32_t i = 0; i < count; ++i) + { + if (c_StatValues[i * 2] > 0) + { + len = _snwprintf_s(buffer, _TRUNCATE, L"In%u=%llu", i + 1, c_StatValues[i * 2]); + appendStatsValue(); + } + + if (c_StatValues[i * 2 + 1] > 0) + { + len = _snwprintf_s(buffer, _TRUNCATE, L"Out%u=%llu", i + 1, c_StatValues[i * 2 + 1]); + appendStatsValue(); + } + } + + // Write statistics + WritePrivateProfileSection(L"Statistics", data.c_str(), iniFile); +} + +/* +** Prepares in order to use the new APIs which are available on Vista or newer. +** +*/ +void MeasureNet::InitializeStatic() +{ + if (IsWindowsVistaOrGreater()) + { + HMODULE IpHlpApiLibrary = GetModuleHandle(L"IpHlpApi.dll"); + if (IpHlpApiLibrary) + { + c_GetIfTable2 = (decltype(c_GetIfTable2))GetProcAddress(IpHlpApiLibrary, "GetIfTable2"); + c_FreeMibTable = (decltype(c_FreeMibTable))GetProcAddress(IpHlpApiLibrary, "FreeMibTable"); + } + + if (!c_GetIfTable2 || !c_FreeMibTable) + { + c_GetIfTable2 = nullptr; + c_FreeMibTable = nullptr; + } + } + + if (GetRainmeter().GetDebug()) + { + UpdateIFTable(); + } +} + +/* +** Frees the resources. +** +*/ +void MeasureNet::FinalizeStatic() +{ + if (c_GetIfTable2) + { + if (c_Table) + { + c_FreeMibTable(c_Table); + } + + c_GetIfTable2 = nullptr; + c_FreeMibTable = nullptr; + } + else + { + delete [] c_Table; + } + c_Table = nullptr; + c_NumOfTables = 0; +} diff --git a/Library/MeasurePlugin.cpp b/Library/MeasurePlugin.cpp index 949b04bd..584780b7 100644 --- a/Library/MeasurePlugin.cpp +++ b/Library/MeasurePlugin.cpp @@ -1,258 +1,258 @@ -/* - Copyright (C) 2001 Kimmo Pekkola - - 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 "MeasurePlugin.h" -#include "Rainmeter.h" -#include "Export.h" -#include "System.h" -#include "Error.h" - -/* -** The constructor -** -*/ -MeasurePlugin::MeasurePlugin(MeterWindow* meterWindow, const WCHAR* name) : Measure(meterWindow, name), - m_Plugin(), - m_ReloadFunc(), - m_ID(), - m_Update2(false), - m_PluginData(), - m_UpdateFunc(), - m_GetStringFunc(), - m_ExecuteBangFunc() -{ -} - -/* -** The destructor -** -*/ -MeasurePlugin::~MeasurePlugin() -{ - if (m_Plugin) - { - FARPROC finalizeFunc = GetProcAddress(m_Plugin, "Finalize"); - if (finalizeFunc) - { - if (IsNewApi()) - { - ((NEWFINALIZE)finalizeFunc)(m_PluginData); - } - else - { - ((FINALIZE)finalizeFunc)(m_Plugin, m_ID); - } - } - - FreeLibrary(m_Plugin); - } -} - -/* -** Gets the current value from the plugin -** -*/ -void MeasurePlugin::UpdateValue() -{ - if (m_UpdateFunc) - { - if (IsNewApi()) - { - m_Value = ((NEWUPDATE)m_UpdateFunc)(m_PluginData); - } - else - { - if (m_Update2) - { - m_Value = ((UPDATE2)m_UpdateFunc)(m_ID); - } - else - { - m_Value = ((UPDATE)m_UpdateFunc)(m_ID); - } - } - - // Reset to default - System::ResetWorkingDirectory(); - } -} - -/* -** Reads the options and loads the plugin -** -*/ -void MeasurePlugin::ReadOptions(ConfigParser& parser, const WCHAR* section) -{ - static UINT id = 0; - - Measure::ReadOptions(parser, section); - - if (m_Initialized) - { - if (IsNewApi()) - { - ((NEWRELOAD)m_ReloadFunc)(m_PluginData, this, &m_MaxValue); - } - - // DynamicVariables doesn't work with old plugins - return; - } - - const std::wstring& plugin = parser.ReadString(section, L"Plugin", L""); - size_t pos = plugin.find_last_of(L"\\/"); - std::wstring pluginName; - if (pos != std::wstring::npos) - { - pluginName.assign(plugin, pos, plugin.length() - pos); - } - else - { - pluginName = plugin; - } - - // First try from program path - std::wstring pluginFile = Rainmeter::GetInstance().GetPluginPath(); - pluginFile += pluginName; - m_Plugin = System::RmLoadLibrary(pluginFile.c_str()); - if (!m_Plugin) - { - if (Rainmeter::GetInstance().HasUserPluginPath()) - { - // Try from settings path - pluginFile = Rainmeter::GetInstance().GetUserPluginPath(); - pluginFile += pluginName; - m_Plugin = System::RmLoadLibrary(pluginFile.c_str()); - } - if (!m_Plugin) - { - LogErrorF( - this, L"Plugin: Unable to load \"%s\" (error %ld)", - pluginName.c_str(), GetLastError()); - return; - } - } - - FARPROC initializeFunc = GetProcAddress(m_Plugin, "Initialize"); - m_ReloadFunc = GetProcAddress(m_Plugin, "Reload"); - m_UpdateFunc = GetProcAddress(m_Plugin, "Update"); - m_GetStringFunc = GetProcAddress(m_Plugin, "GetString"); - m_ExecuteBangFunc = GetProcAddress(m_Plugin, "ExecuteBang"); - - // Remove current directory from DLL search path - SetDllDirectory(L""); - - double maxValue = 0.0; - - if (IsNewApi()) - { - m_PluginData = (void*)id; - - if (initializeFunc) - { - ((NEWINITIALIZE)initializeFunc)(&m_PluginData, this); - } - - ((NEWRELOAD)m_ReloadFunc)(m_PluginData, this, &maxValue); - } - else - { - m_ID = id; - - if (!m_UpdateFunc) - { - m_UpdateFunc = GetProcAddress(m_Plugin, "Update2"); - m_Update2 = true; - } - - if (initializeFunc) - { - maxValue = ((INITIALIZE)initializeFunc)(m_Plugin, m_MeterWindow->GetFilePath().c_str(), section, m_ID); - } - } - - const std::wstring& szMaxValue = parser.ReadString(section, L"MaxValue", L""); - if (szMaxValue.empty()) - { - if (maxValue == 0.0) - { - m_MaxValue = 1.0; - m_LogMaxValue = true; - m_MedianValues.clear(); - } - else - { - m_MaxValue = maxValue; - m_LogMaxValue = false; - } - } - - // Reset to default - SetDllDirectory(L""); - System::ResetWorkingDirectory(); - - ++id; -} - -/* -** Gets the string value from the plugin. -** -*/ -const WCHAR* MeasurePlugin::GetStringValue() -{ - if (m_GetStringFunc) - { - const WCHAR* ret; - if (IsNewApi()) - { - ret = ((NEWGETSTRING)m_GetStringFunc)(m_PluginData); - } - else - { - ret = ((GETSTRING)m_GetStringFunc)(m_ID, 0); - } - - if (ret) return CheckSubstitute(ret); - } - - return nullptr; -} - -/* -** Sends a bang to the plugin -** -*/ -void MeasurePlugin::Command(const std::wstring& command) -{ - if (m_ExecuteBangFunc) - { - const WCHAR* str = command.c_str(); - if (IsNewApi()) - { - ((NEWEXECUTEBANG)m_ExecuteBangFunc)(m_PluginData, str); - } - else - { - ((EXECUTEBANG)m_ExecuteBangFunc)(str, m_ID); - } - } - else - { - Measure::Command(command); - } -} +/* + Copyright (C) 2001 Kimmo Pekkola + + 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 "MeasurePlugin.h" +#include "Rainmeter.h" +#include "Export.h" +#include "System.h" +#include "Error.h" + +/* +** The constructor +** +*/ +MeasurePlugin::MeasurePlugin(MeterWindow* meterWindow, const WCHAR* name) : Measure(meterWindow, name), + m_Plugin(), + m_ReloadFunc(), + m_ID(), + m_Update2(false), + m_PluginData(), + m_UpdateFunc(), + m_GetStringFunc(), + m_ExecuteBangFunc() +{ +} + +/* +** The destructor +** +*/ +MeasurePlugin::~MeasurePlugin() +{ + if (m_Plugin) + { + FARPROC finalizeFunc = GetProcAddress(m_Plugin, "Finalize"); + if (finalizeFunc) + { + if (IsNewApi()) + { + ((NEWFINALIZE)finalizeFunc)(m_PluginData); + } + else + { + ((FINALIZE)finalizeFunc)(m_Plugin, m_ID); + } + } + + FreeLibrary(m_Plugin); + } +} + +/* +** Gets the current value from the plugin +** +*/ +void MeasurePlugin::UpdateValue() +{ + if (m_UpdateFunc) + { + if (IsNewApi()) + { + m_Value = ((NEWUPDATE)m_UpdateFunc)(m_PluginData); + } + else + { + if (m_Update2) + { + m_Value = ((UPDATE2)m_UpdateFunc)(m_ID); + } + else + { + m_Value = ((UPDATE)m_UpdateFunc)(m_ID); + } + } + + // Reset to default + System::ResetWorkingDirectory(); + } +} + +/* +** Reads the options and loads the plugin +** +*/ +void MeasurePlugin::ReadOptions(ConfigParser& parser, const WCHAR* section) +{ + static UINT id = 0; + + Measure::ReadOptions(parser, section); + + if (m_Initialized) + { + if (IsNewApi()) + { + ((NEWRELOAD)m_ReloadFunc)(m_PluginData, this, &m_MaxValue); + } + + // DynamicVariables doesn't work with old plugins + return; + } + + const std::wstring& plugin = parser.ReadString(section, L"Plugin", L""); + size_t pos = plugin.find_last_of(L"\\/"); + std::wstring pluginName; + if (pos != std::wstring::npos) + { + pluginName.assign(plugin, pos, plugin.length() - pos); + } + else + { + pluginName = plugin; + } + + // First try from program path + std::wstring pluginFile = GetRainmeter().GetPluginPath(); + pluginFile += pluginName; + m_Plugin = System::RmLoadLibrary(pluginFile.c_str()); + if (!m_Plugin) + { + if (GetRainmeter().HasUserPluginPath()) + { + // Try from settings path + pluginFile = GetRainmeter().GetUserPluginPath(); + pluginFile += pluginName; + m_Plugin = System::RmLoadLibrary(pluginFile.c_str()); + } + if (!m_Plugin) + { + LogErrorF( + this, L"Plugin: Unable to load \"%s\" (error %ld)", + pluginName.c_str(), GetLastError()); + return; + } + } + + FARPROC initializeFunc = GetProcAddress(m_Plugin, "Initialize"); + m_ReloadFunc = GetProcAddress(m_Plugin, "Reload"); + m_UpdateFunc = GetProcAddress(m_Plugin, "Update"); + m_GetStringFunc = GetProcAddress(m_Plugin, "GetString"); + m_ExecuteBangFunc = GetProcAddress(m_Plugin, "ExecuteBang"); + + // Remove current directory from DLL search path + SetDllDirectory(L""); + + double maxValue = 0.0; + + if (IsNewApi()) + { + m_PluginData = (void*)id; + + if (initializeFunc) + { + ((NEWINITIALIZE)initializeFunc)(&m_PluginData, this); + } + + ((NEWRELOAD)m_ReloadFunc)(m_PluginData, this, &maxValue); + } + else + { + m_ID = id; + + if (!m_UpdateFunc) + { + m_UpdateFunc = GetProcAddress(m_Plugin, "Update2"); + m_Update2 = true; + } + + if (initializeFunc) + { + maxValue = ((INITIALIZE)initializeFunc)(m_Plugin, m_MeterWindow->GetFilePath().c_str(), section, m_ID); + } + } + + const std::wstring& szMaxValue = parser.ReadString(section, L"MaxValue", L""); + if (szMaxValue.empty()) + { + if (maxValue == 0.0) + { + m_MaxValue = 1.0; + m_LogMaxValue = true; + m_MedianValues.clear(); + } + else + { + m_MaxValue = maxValue; + m_LogMaxValue = false; + } + } + + // Reset to default + SetDllDirectory(L""); + System::ResetWorkingDirectory(); + + ++id; +} + +/* +** Gets the string value from the plugin. +** +*/ +const WCHAR* MeasurePlugin::GetStringValue() +{ + if (m_GetStringFunc) + { + const WCHAR* ret; + if (IsNewApi()) + { + ret = ((NEWGETSTRING)m_GetStringFunc)(m_PluginData); + } + else + { + ret = ((GETSTRING)m_GetStringFunc)(m_ID, 0); + } + + if (ret) return CheckSubstitute(ret); + } + + return nullptr; +} + +/* +** Sends a bang to the plugin +** +*/ +void MeasurePlugin::Command(const std::wstring& command) +{ + if (m_ExecuteBangFunc) + { + const WCHAR* str = command.c_str(); + if (IsNewApi()) + { + ((NEWEXECUTEBANG)m_ExecuteBangFunc)(m_PluginData, str); + } + else + { + ((EXECUTEBANG)m_ExecuteBangFunc)(str, m_ID); + } + } + else + { + Measure::Command(command); + } +} diff --git a/Library/Meter.cpp b/Library/Meter.cpp index 5c847a3b..b1d99acb 100644 --- a/Library/Meter.cpp +++ b/Library/Meter.cpp @@ -1,760 +1,760 @@ -/* - Copyright (C) 2001 Kimmo Pekkola - - 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 "Error.h" -#include "Meter.h" -#include "MeterBitmap.h" -#include "MeterBar.h" -#include "MeterHistogram.h" -#include "MeterString.h" -#include "MeterImage.h" -#include "MeterLine.h" -#include "MeterRoundLine.h" -#include "MeterRotator.h" -#include "MeterButton.h" -#include "Measure.h" -#include "Rainmeter.h" -#include "../Common/Gfx/Canvas.h" - -using namespace Gdiplus; - -/* -** The constructor -** -*/ -Meter::Meter(MeterWindow* meterWindow, const WCHAR* name) : Section(meterWindow, name), - m_X(), - m_Y(), - m_W(0), - m_H(0), - m_Hidden(false), - m_WDefined(false), - m_HDefined(false), - m_RelativeMeter(), - m_Transformation(), - m_ToolTipWidth(), - m_ToolTipType(false), - m_ToolTipHidden(meterWindow->GetMeterToolTipHidden()), - m_ToolTipHandle(), - m_Mouse(meterWindow, this), - m_HasMouseAction(false), - m_MouseOver(false), - m_RelativeX(POSITION_ABSOLUTE), - m_RelativeY(POSITION_ABSOLUTE), - m_SolidBevel(BEVELTYPE_NONE), - m_SolidAngle(), - m_Padding(), - m_AntiAlias(false), - m_Initialized(false) -{ -} - -/* -** The destructor -** -*/ -Meter::~Meter() -{ - delete m_Transformation; - - if (m_ToolTipHandle != nullptr) - { - DestroyWindow(m_ToolTipHandle); - } -} - -/* -** Initializes the meter. Usually this method is overwritten by the inherited -** classes, which load bitmaps and such things during initialization. -** -*/ -void Meter::Initialize() -{ - m_Initialized = true; -} - -/* -** Returns the X-position of the meter. -** -*/ -int Meter::GetX(bool abs) -{ - if (m_RelativeX != POSITION_ABSOLUTE && m_RelativeMeter) - { - if (m_RelativeX == POSITION_RELATIVE_TL) - { - return m_RelativeMeter->GetX(true) + m_X; - } - else - { - return m_RelativeMeter->GetX(true) + m_RelativeMeter->GetW() + m_X; - } - } - return m_X; -} - -/* -** Returns the Y-position of the meter. -** -*/ -int Meter::GetY(bool abs) -{ - if (m_RelativeY != POSITION_ABSOLUTE && m_RelativeMeter) - { - if (m_RelativeY == POSITION_RELATIVE_TL) - { - return m_RelativeMeter->GetY(true) + m_Y; - } - else - { - return m_RelativeMeter->GetY(true) + m_RelativeMeter->GetH() + m_Y; - } - } - return m_Y; -} - -void Meter::SetX(int x) -{ - m_X = x; - m_RelativeX = POSITION_ABSOLUTE; - - // Change the option as well to avoid reset in ReadOptions(). - WCHAR buffer[32]; - _itow_s(x, buffer, 10); - m_MeterWindow->GetParser().SetValue(m_Name, L"X", buffer); -} - -void Meter::SetY(int y) -{ - m_Y = y; - m_RelativeY = POSITION_ABSOLUTE; - - // Change the option as well to avoid reset in ReadOptions(). - WCHAR buffer[32]; - _itow_s(y, buffer, 10); - m_MeterWindow->GetParser().SetValue(m_Name, L"Y", buffer); -} - -/* -** Returns a RECT containing the dimensions of the meter within the MeterWindow -** -*/ -RECT Meter::GetMeterRect() -{ - RECT meterRect; - - meterRect.left = GetX(); - meterRect.top = GetY(); - meterRect.right = meterRect.left + m_W; - meterRect.bottom = meterRect.top + m_H; - - return meterRect; -} - -/* -** Returns a Rect containing the adjusted meter location with "Padding" option -** -*/ -Gdiplus::Rect Meter::GetMeterRectPadding() -{ - Gdiplus::Rect meterRect; - - meterRect.X = GetX() + m_Padding.X; - meterRect.Y = GetY() + m_Padding.Y; - meterRect.Width = m_W - m_Padding.X - m_Padding.Width; - meterRect.Height = m_H - m_Padding.Y - m_Padding.Height; - - return meterRect; -} - -/* -** Checks if the given point is inside the meter. -** This function doesn't check Hidden state, so check it before calling this function if needed. -** -*/ -bool Meter::HitTest(int x, int y) -{ - int p; - return (x >= (p = GetX()) && x < p + m_W && y >= (p = GetY()) && y < p + m_H); -} - -/* -** Shows the meter and tooltip. -** -*/ -void Meter::Show() -{ - m_Hidden = false; - - // Change the option as well to avoid reset in ReadOptions(). - m_MeterWindow->GetParser().SetValue(m_Name, L"Hidden", L"0"); - - if (m_ToolTipHandle != nullptr) - { - if (!m_ToolTipHidden) - { - SendMessage(m_ToolTipHandle, TTM_ACTIVATE, TRUE, 0); - } - } -} - -/* -** Hides the meter and tooltip. -** -*/ -void Meter::Hide() -{ - m_Hidden = true; - - // Change the option as well to avoid reset in ReadOptions(). - m_MeterWindow->GetParser().SetValue(m_Name, L"Hidden", L"1"); - - if (m_ToolTipHandle != nullptr) - { - SendMessage(m_ToolTipHandle, TTM_ACTIVATE, FALSE, 0); - } -} - -/* -** Read the common options specified in the ini file. The inherited classes must -** call this base implementation if they overwrite this method. -** -*/ -void Meter::ReadOptions(ConfigParser& parser, const WCHAR* section) -{ - // The MeterStyle defines a template where the values are read if the meter doesn't have it itself - const std::wstring& style = parser.ReadString(section, L"MeterStyle", L""); - if (!style.empty()) - { - parser.SetStyleTemplate(style); - } - - Section::ReadOptions(parser, section); - - BindMeasures(parser, section); - - int oldX = m_X; - std::wstring& x = (std::wstring&)parser.ReadString(section, L"X", L"0"); - if (!x.empty()) - { - WCHAR lastChar = x[x.size() - 1]; - if (lastChar == L'r') - { - m_RelativeX = POSITION_RELATIVE_TL; - x.pop_back(); - } - else if (lastChar == L'R') - { - m_RelativeX = POSITION_RELATIVE_BR; - x.pop_back(); - } - else - { - m_RelativeX = POSITION_ABSOLUTE; - } - - m_X = parser.ParseInt(x.c_str(), 0); - } - else - { - m_X = 0; - m_RelativeX = POSITION_ABSOLUTE; - } - - int oldY = m_Y; - std::wstring& y = (std::wstring&)parser.ReadString(section, L"Y", L"0"); - if (!y.empty()) - { - WCHAR lastChar = y[y.size() - 1]; - if (lastChar == L'r') - { - m_RelativeY = POSITION_RELATIVE_TL; - y.pop_back(); - } - else if (lastChar == L'R') - { - m_RelativeY = POSITION_RELATIVE_BR; - y.pop_back(); - } - else - { - m_RelativeY = POSITION_ABSOLUTE; - } - - m_Y = parser.ParseInt(y.c_str(), 0); - } - else - { - m_Y = 0; - m_RelativeY = POSITION_ABSOLUTE; - } - - static const Gdiplus::Rect defPadding; - m_Padding = parser.ReadRect(section, L"Padding", defPadding); - - const int oldW = m_W; - const bool oldWDefined = m_WDefined; - const int widthPadding = GetWidthPadding(); - - const int w = parser.ReadInt(section, L"W", m_W); - m_WDefined = parser.GetLastValueDefined(); - - if (IsFixedSize(true)) m_W = w; - if (oldW != (m_W - widthPadding)) m_W += widthPadding; - if (!m_WDefined && oldWDefined && IsFixedSize()) - { - m_W = 0; - } - - const int oldH = m_H; - const bool oldHDefined = m_HDefined; - const int heightPadding = GetHeightPadding(); - - const int h = parser.ReadInt(section, L"H", m_H); - m_HDefined = parser.GetLastValueDefined(); - - if (IsFixedSize(true)) m_H = h; - if (oldH != (m_H - heightPadding)) m_H += heightPadding; - if (!m_HDefined && oldHDefined && IsFixedSize()) - { - m_H = 0; - } - - bool oldHidden = m_Hidden; - m_Hidden = parser.ReadBool(section, L"Hidden", false); - - if (oldX != m_X || oldY != m_Y || oldHidden != m_Hidden) - { - m_MeterWindow->SetResizeWindowMode(RESIZEMODE_CHECK); // Need to recalculate the window size - } - - m_SolidBevel = (BEVELTYPE)parser.ReadInt(section, L"BevelType", BEVELTYPE_NONE); - - m_SolidColor = parser.ReadColor(section, L"SolidColor", Color::MakeARGB(0, 0, 0, 0)); - m_SolidColor2 = parser.ReadColor(section, L"SolidColor2", m_SolidColor.GetValue()); - m_SolidAngle = (Gdiplus::REAL)parser.ReadFloat(section, L"GradientAngle", 0.0); - - m_Mouse.ReadOptions(parser, section); - m_HasMouseAction = m_Mouse.HasButtonAction() || m_Mouse.HasScrollAction(); - - m_ToolTipText = parser.ReadString(section, L"ToolTipText", L""); - m_ToolTipTitle = parser.ReadString(section, L"ToolTipTitle", L""); - m_ToolTipIcon = parser.ReadString(section, L"ToolTipIcon", L""); - m_ToolTipWidth = parser.ReadInt(section, L"ToolTipWidth", 1000); - m_ToolTipType = parser.ReadBool(section, L"ToolTipType", false); - m_ToolTipHidden = parser.ReadBool(section, L"ToolTipHidden", m_MeterWindow->GetMeterToolTipHidden()); - - m_AntiAlias = parser.ReadBool(section, L"AntiAlias", false); - - std::vector matrix = parser.ReadFloats(section, L"TransformationMatrix"); - if (matrix.size() == 6) - { - if (m_Transformation) - { - m_Transformation->SetElements(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]); - } - else - { - m_Transformation = new Matrix(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]); - } - } - else if (!matrix.empty()) - { - delete m_Transformation; - m_Transformation = nullptr; - - LogErrorF(this, L"Meter: Incorrect number of values in TransformationMatrix=%s", parser.ReadString(section, L"TransformationMatrix", L"").c_str()); - } -} - -/* -** Binds this meter to the given measure. The same measure can be bound to -** several meters but one meter and only be bound to one measure. -** -*/ -void Meter::BindMeasures(ConfigParser& parser, const WCHAR* section) -{ - BindPrimaryMeasure(parser, section, false); -} - -/* -** Creates the given meter. This is the factory method for the meters. -** If new meters are implemented this method needs to be updated. -** -*/ -Meter* Meter::Create(const WCHAR* meter, MeterWindow* meterWindow, const WCHAR* name) -{ - if (_wcsicmp(L"STRING", meter) == 0) - { - return new MeterString(meterWindow, name); - } - else if (_wcsicmp(L"IMAGE", meter) == 0) - { - return new MeterImage(meterWindow, name); - } - else if (_wcsicmp(L"HISTOGRAM", meter) == 0) - { - return new MeterHistogram(meterWindow, name); - } - else if (_wcsicmp(L"BAR", meter) == 0) - { - return new MeterBar(meterWindow, name); - } - else if (_wcsicmp(L"BITMAP", meter) == 0) - { - return new MeterBitmap(meterWindow, name); - } - else if (_wcsicmp(L"LINE", meter) == 0) - { - return new MeterLine(meterWindow, name); - } - else if (_wcsicmp(L"ROUNDLINE", meter) == 0) - { - return new MeterRoundLine(meterWindow, name); - } - else if (_wcsicmp(L"ROTATOR", meter) == 0) - { - return new MeterRotator(meterWindow, name); - } - else if (_wcsicmp(L"BUTTON", meter) == 0) - { - return new MeterButton(meterWindow, name); - } - - LogErrorF(meterWindow, L"Meter=%s is not valid in [%s]", meter, name); - - return nullptr; -} - -/* -** Updates the value(s) from the measures. Derived classes should -** only update if this returns true; -*/ -bool Meter::Update() -{ - // Only update the meter's value when the divider is equal to the counter - return UpdateCounter(); -} - -/* -** Reads and binds the primary MeasureName. This must always be called in overridden -** BindMeasures() implementations. -** -*/ -bool Meter::BindPrimaryMeasure(ConfigParser& parser, const WCHAR* section, bool optional) -{ - m_Measures.clear(); - - const std::wstring& measureName = parser.ReadString(section, L"MeasureName", L""); - - Measure* measure = parser.GetMeasure(measureName); - if (measure) - { - m_Measures.push_back(measure); - return true; - } - else if (!optional) - { - LogErrorF(this, L"MeasureName=%s is not valid", measureName.c_str()); - } - - return false; -} - -/* -** Reads and binds secondary measures (MeasureName2 - MeasureNameN). -** -*/ -void Meter::BindSecondaryMeasures(ConfigParser& parser, const WCHAR* section) -{ - if (!m_Measures.empty()) - { - WCHAR tmpName[64]; - - int i = 2; - do - { - _snwprintf_s(tmpName, _TRUNCATE, L"MeasureName%i", i); - const std::wstring& measureName = parser.ReadString(section, tmpName, L""); - Measure* measure = parser.GetMeasure(measureName); - if (measure) - { - m_Measures.push_back(measure); - } - else - { - if (!measureName.empty()) - { - LogErrorF(this, L"MeasureName%i=%s is not valid", i, measureName.c_str()); - } - - break; - } - ++i; - } - while (true); - } -} - -/* -** Replaces %1, %2, ... with the corresponding measure value. -** -*/ -bool Meter::ReplaceMeasures(std::wstring& str, AUTOSCALE autoScale, double scale, int decimals, bool percentual) -{ - bool replaced = false; - - if (str.find(L'%') != std::wstring::npos) - { - WCHAR buffer[64]; - - for (size_t i = m_Measures.size(); i > 0; --i) - { - size_t len = _snwprintf_s(buffer, _TRUNCATE, L"%%%i", (int)i); - size_t start = 0, pos; - - const WCHAR* measureValue = m_Measures[i - 1]->GetStringOrFormattedValue( - autoScale, scale, decimals, percentual); - const size_t measureValueLen = wcslen(measureValue); - - do - { - pos = str.find(buffer, start, len); - if (pos != std::wstring::npos) - { - str.replace(pos, len, measureValue, measureValueLen); - start = pos + measureValueLen; - replaced = true; - } - } - while (pos != std::wstring::npos); - } - } - - return replaced; -} - -/* -** Does the initial construction of the ToolTip for the meter -*/ -void Meter::CreateToolTip(MeterWindow* meterWindow) -{ - HWND hMeterWindow = m_MeterWindow->GetWindow(); - HINSTANCE hInstance = Rainmeter::GetInstance().GetModuleInstance(); - DWORD style = WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP; - - if (m_ToolTipType) - { - style |= TTS_BALLOON; - } - - HWND hwndTT = CreateWindowEx(WS_EX_TOPMOST, - TOOLTIPS_CLASS, - nullptr, - style, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - hMeterWindow, - nullptr, - hInstance, - nullptr); - - if (hwndTT) - { - SetWindowPos(hwndTT, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); - - TOOLINFO ti = {sizeof(TOOLINFO), TTF_SUBCLASS, hMeterWindow, 0, GetMeterRect(), hInstance}; - - SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM)&ti); - - m_ToolTipHandle = hwndTT; - UpdateToolTip(); - } -} - -/* -** Updates the ToolTip to match new values -*/ -void Meter::UpdateToolTip() -{ - HWND hwndTT = m_ToolTipHandle; - - TOOLINFO ti = {sizeof(TOOLINFO)}; - ti.hwnd = m_MeterWindow->GetWindow(); - - SendMessage(hwndTT, TTM_GETTOOLINFO, 0, (LPARAM)&ti); - - std::wstring text = m_ToolTipText; - ReplaceMeasures(text); - ti.lpszText = (LPTSTR)text.c_str(); - ti.rect = GetMeterRect(); - - SendMessage(hwndTT, TTM_SETTOOLINFO, 0, (LPARAM)&ti); - SendMessage(hwndTT, TTM_SETMAXTIPWIDTH, 0, m_ToolTipWidth); - - if (!m_ToolTipTitle.empty()) - { - HICON hIcon = nullptr; - bool destroy = false; - - if (!m_ToolTipIcon.empty()) - { - const WCHAR* tipIcon = m_ToolTipIcon.c_str(); - if (_wcsicmp(tipIcon, L"INFO") == 0) - { - hIcon = (HICON)TTI_INFO; - } - else if (_wcsicmp(tipIcon, L"WARNING") == 0) - { - hIcon = (HICON)TTI_WARNING; - } - else if (_wcsicmp(tipIcon, L"ERROR") == 0) - { - hIcon = (HICON)TTI_ERROR; - } - else if (_wcsicmp(tipIcon, L"QUESTION") == 0) - { - hIcon = LoadIcon(nullptr, IDI_QUESTION); - } - else if (_wcsicmp(tipIcon, L"SHIELD") == 0) - { - hIcon = LoadIcon(nullptr, IDI_SHIELD); - } - else - { - std::wstring iconPath = m_ToolTipIcon; - m_MeterWindow->MakePathAbsolute(iconPath); - hIcon = (HICON)LoadImage(nullptr, iconPath.c_str(), IMAGE_ICON, 0, 0, LR_LOADFROMFILE); - destroy = true; - } - } - - text = m_ToolTipTitle; - ReplaceMeasures(text); - SendMessage(hwndTT, TTM_SETTITLE, (WPARAM)hIcon, (LPARAM)text.c_str()); - - if (destroy) - { - DestroyIcon(hIcon); - } - } - - if (m_ToolTipHidden) - { - SendMessage(hwndTT, TTM_ACTIVATE, FALSE, 0); - } - else - { - SendMessage(hwndTT, TTM_ACTIVATE, !IsHidden(), 0); - } -} - -/* -** Draws the solid background & bevel if such are defined -*/ -bool Meter::Draw(Gfx::Canvas& canvas) -{ - if (IsHidden()) return false; - - canvas.SetAntiAliasing(m_AntiAlias); - - if (m_SolidColor.GetA() != 0 || m_SolidColor2.GetA() != 0) - { - int x = GetX(); - int y = GetY(); - - Rect r(x, y, m_W, m_H); - - if (m_SolidColor.GetValue() == m_SolidColor2.GetValue()) - { - SolidBrush solid(m_SolidColor); - canvas.FillRectangle(r, solid); - } - else - { - Gdiplus::Graphics& graphics = canvas.BeginGdiplusContext(); - - if (!m_AntiAlias) - { - // Fix the tiling issue in some GradientAngle values - graphics.SetPixelOffsetMode(PixelOffsetModeHalf); - } - - LinearGradientBrush gradient(r, m_SolidColor, m_SolidColor2, m_SolidAngle, TRUE); - graphics.FillRectangle(&gradient, r); - - if (!m_AntiAlias) - { - graphics.SetPixelOffsetMode(PixelOffsetModeDefault); - } - - canvas.EndGdiplusContext(); - } - } - - if (m_SolidBevel != BEVELTYPE_NONE) - { - Gdiplus::Graphics& graphics = canvas.BeginGdiplusContext(); - - int x = GetX(); - int y = GetY(); - - Color lightColor(255, 255, 255, 255); - Color darkColor(255, 0, 0, 0); - - if (m_SolidBevel == BEVELTYPE_DOWN) - { - lightColor.SetValue(Color::MakeARGB(255, 0, 0, 0)); - darkColor.SetValue(Color::MakeARGB(255, 255, 255, 255)); - } - - Pen light(lightColor); - Pen dark(darkColor); - - // The bevel is drawn outside the meter - Rect rect(x - 2, y - 2, m_W + 4, m_H + 4); - DrawBevel(graphics, rect, light, dark); - - canvas.EndGdiplusContext(); - } - - return true; -} - -/* -** Draws a bevel inside the given area -*/ -void Meter::DrawBevel(Graphics& graphics, const Rect& rect, const Pen& light, const Pen& dark) -{ - int l = rect.GetLeft(); - int r = rect.GetRight() - 1; - int t = rect.GetTop(); - int b = rect.GetBottom() - 1; - - graphics.DrawLine(&light, l, t, l, b); - graphics.DrawLine(&light, l, t, r, t); - graphics.DrawLine(&light, l + 1, t + 1, l + 1, b - 1); - graphics.DrawLine(&light, l + 1, t + 1, r - 1, t + 1); - graphics.DrawLine(&dark, l, b, r, b); - graphics.DrawLine(&dark, r, t, r, b); - graphics.DrawLine(&dark, l + 1, b - 1, r - 1, b - 1); - graphics.DrawLine(&dark, r - 1, t + 1, r - 1, b - 1); -} +/* + Copyright (C) 2001 Kimmo Pekkola + + 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 "Error.h" +#include "Meter.h" +#include "MeterBitmap.h" +#include "MeterBar.h" +#include "MeterHistogram.h" +#include "MeterString.h" +#include "MeterImage.h" +#include "MeterLine.h" +#include "MeterRoundLine.h" +#include "MeterRotator.h" +#include "MeterButton.h" +#include "Measure.h" +#include "Rainmeter.h" +#include "../Common/Gfx/Canvas.h" + +using namespace Gdiplus; + +/* +** The constructor +** +*/ +Meter::Meter(MeterWindow* meterWindow, const WCHAR* name) : Section(meterWindow, name), + m_X(), + m_Y(), + m_W(0), + m_H(0), + m_Hidden(false), + m_WDefined(false), + m_HDefined(false), + m_RelativeMeter(), + m_Transformation(), + m_ToolTipWidth(), + m_ToolTipType(false), + m_ToolTipHidden(meterWindow->GetMeterToolTipHidden()), + m_ToolTipHandle(), + m_Mouse(meterWindow, this), + m_HasMouseAction(false), + m_MouseOver(false), + m_RelativeX(POSITION_ABSOLUTE), + m_RelativeY(POSITION_ABSOLUTE), + m_SolidBevel(BEVELTYPE_NONE), + m_SolidAngle(), + m_Padding(), + m_AntiAlias(false), + m_Initialized(false) +{ +} + +/* +** The destructor +** +*/ +Meter::~Meter() +{ + delete m_Transformation; + + if (m_ToolTipHandle != nullptr) + { + DestroyWindow(m_ToolTipHandle); + } +} + +/* +** Initializes the meter. Usually this method is overwritten by the inherited +** classes, which load bitmaps and such things during initialization. +** +*/ +void Meter::Initialize() +{ + m_Initialized = true; +} + +/* +** Returns the X-position of the meter. +** +*/ +int Meter::GetX(bool abs) +{ + if (m_RelativeX != POSITION_ABSOLUTE && m_RelativeMeter) + { + if (m_RelativeX == POSITION_RELATIVE_TL) + { + return m_RelativeMeter->GetX(true) + m_X; + } + else + { + return m_RelativeMeter->GetX(true) + m_RelativeMeter->GetW() + m_X; + } + } + return m_X; +} + +/* +** Returns the Y-position of the meter. +** +*/ +int Meter::GetY(bool abs) +{ + if (m_RelativeY != POSITION_ABSOLUTE && m_RelativeMeter) + { + if (m_RelativeY == POSITION_RELATIVE_TL) + { + return m_RelativeMeter->GetY(true) + m_Y; + } + else + { + return m_RelativeMeter->GetY(true) + m_RelativeMeter->GetH() + m_Y; + } + } + return m_Y; +} + +void Meter::SetX(int x) +{ + m_X = x; + m_RelativeX = POSITION_ABSOLUTE; + + // Change the option as well to avoid reset in ReadOptions(). + WCHAR buffer[32]; + _itow_s(x, buffer, 10); + m_MeterWindow->GetParser().SetValue(m_Name, L"X", buffer); +} + +void Meter::SetY(int y) +{ + m_Y = y; + m_RelativeY = POSITION_ABSOLUTE; + + // Change the option as well to avoid reset in ReadOptions(). + WCHAR buffer[32]; + _itow_s(y, buffer, 10); + m_MeterWindow->GetParser().SetValue(m_Name, L"Y", buffer); +} + +/* +** Returns a RECT containing the dimensions of the meter within the MeterWindow +** +*/ +RECT Meter::GetMeterRect() +{ + RECT meterRect; + + meterRect.left = GetX(); + meterRect.top = GetY(); + meterRect.right = meterRect.left + m_W; + meterRect.bottom = meterRect.top + m_H; + + return meterRect; +} + +/* +** Returns a Rect containing the adjusted meter location with "Padding" option +** +*/ +Gdiplus::Rect Meter::GetMeterRectPadding() +{ + Gdiplus::Rect meterRect; + + meterRect.X = GetX() + m_Padding.X; + meterRect.Y = GetY() + m_Padding.Y; + meterRect.Width = m_W - m_Padding.X - m_Padding.Width; + meterRect.Height = m_H - m_Padding.Y - m_Padding.Height; + + return meterRect; +} + +/* +** Checks if the given point is inside the meter. +** This function doesn't check Hidden state, so check it before calling this function if needed. +** +*/ +bool Meter::HitTest(int x, int y) +{ + int p; + return (x >= (p = GetX()) && x < p + m_W && y >= (p = GetY()) && y < p + m_H); +} + +/* +** Shows the meter and tooltip. +** +*/ +void Meter::Show() +{ + m_Hidden = false; + + // Change the option as well to avoid reset in ReadOptions(). + m_MeterWindow->GetParser().SetValue(m_Name, L"Hidden", L"0"); + + if (m_ToolTipHandle != nullptr) + { + if (!m_ToolTipHidden) + { + SendMessage(m_ToolTipHandle, TTM_ACTIVATE, TRUE, 0); + } + } +} + +/* +** Hides the meter and tooltip. +** +*/ +void Meter::Hide() +{ + m_Hidden = true; + + // Change the option as well to avoid reset in ReadOptions(). + m_MeterWindow->GetParser().SetValue(m_Name, L"Hidden", L"1"); + + if (m_ToolTipHandle != nullptr) + { + SendMessage(m_ToolTipHandle, TTM_ACTIVATE, FALSE, 0); + } +} + +/* +** Read the common options specified in the ini file. The inherited classes must +** call this base implementation if they overwrite this method. +** +*/ +void Meter::ReadOptions(ConfigParser& parser, const WCHAR* section) +{ + // The MeterStyle defines a template where the values are read if the meter doesn't have it itself + const std::wstring& style = parser.ReadString(section, L"MeterStyle", L""); + if (!style.empty()) + { + parser.SetStyleTemplate(style); + } + + Section::ReadOptions(parser, section); + + BindMeasures(parser, section); + + int oldX = m_X; + std::wstring& x = (std::wstring&)parser.ReadString(section, L"X", L"0"); + if (!x.empty()) + { + WCHAR lastChar = x[x.size() - 1]; + if (lastChar == L'r') + { + m_RelativeX = POSITION_RELATIVE_TL; + x.pop_back(); + } + else if (lastChar == L'R') + { + m_RelativeX = POSITION_RELATIVE_BR; + x.pop_back(); + } + else + { + m_RelativeX = POSITION_ABSOLUTE; + } + + m_X = parser.ParseInt(x.c_str(), 0); + } + else + { + m_X = 0; + m_RelativeX = POSITION_ABSOLUTE; + } + + int oldY = m_Y; + std::wstring& y = (std::wstring&)parser.ReadString(section, L"Y", L"0"); + if (!y.empty()) + { + WCHAR lastChar = y[y.size() - 1]; + if (lastChar == L'r') + { + m_RelativeY = POSITION_RELATIVE_TL; + y.pop_back(); + } + else if (lastChar == L'R') + { + m_RelativeY = POSITION_RELATIVE_BR; + y.pop_back(); + } + else + { + m_RelativeY = POSITION_ABSOLUTE; + } + + m_Y = parser.ParseInt(y.c_str(), 0); + } + else + { + m_Y = 0; + m_RelativeY = POSITION_ABSOLUTE; + } + + static const Gdiplus::Rect defPadding; + m_Padding = parser.ReadRect(section, L"Padding", defPadding); + + const int oldW = m_W; + const bool oldWDefined = m_WDefined; + const int widthPadding = GetWidthPadding(); + + const int w = parser.ReadInt(section, L"W", m_W); + m_WDefined = parser.GetLastValueDefined(); + + if (IsFixedSize(true)) m_W = w; + if (oldW != (m_W - widthPadding)) m_W += widthPadding; + if (!m_WDefined && oldWDefined && IsFixedSize()) + { + m_W = 0; + } + + const int oldH = m_H; + const bool oldHDefined = m_HDefined; + const int heightPadding = GetHeightPadding(); + + const int h = parser.ReadInt(section, L"H", m_H); + m_HDefined = parser.GetLastValueDefined(); + + if (IsFixedSize(true)) m_H = h; + if (oldH != (m_H - heightPadding)) m_H += heightPadding; + if (!m_HDefined && oldHDefined && IsFixedSize()) + { + m_H = 0; + } + + bool oldHidden = m_Hidden; + m_Hidden = parser.ReadBool(section, L"Hidden", false); + + if (oldX != m_X || oldY != m_Y || oldHidden != m_Hidden) + { + m_MeterWindow->SetResizeWindowMode(RESIZEMODE_CHECK); // Need to recalculate the window size + } + + m_SolidBevel = (BEVELTYPE)parser.ReadInt(section, L"BevelType", BEVELTYPE_NONE); + + m_SolidColor = parser.ReadColor(section, L"SolidColor", Color::MakeARGB(0, 0, 0, 0)); + m_SolidColor2 = parser.ReadColor(section, L"SolidColor2", m_SolidColor.GetValue()); + m_SolidAngle = (Gdiplus::REAL)parser.ReadFloat(section, L"GradientAngle", 0.0); + + m_Mouse.ReadOptions(parser, section); + m_HasMouseAction = m_Mouse.HasButtonAction() || m_Mouse.HasScrollAction(); + + m_ToolTipText = parser.ReadString(section, L"ToolTipText", L""); + m_ToolTipTitle = parser.ReadString(section, L"ToolTipTitle", L""); + m_ToolTipIcon = parser.ReadString(section, L"ToolTipIcon", L""); + m_ToolTipWidth = parser.ReadInt(section, L"ToolTipWidth", 1000); + m_ToolTipType = parser.ReadBool(section, L"ToolTipType", false); + m_ToolTipHidden = parser.ReadBool(section, L"ToolTipHidden", m_MeterWindow->GetMeterToolTipHidden()); + + m_AntiAlias = parser.ReadBool(section, L"AntiAlias", false); + + std::vector matrix = parser.ReadFloats(section, L"TransformationMatrix"); + if (matrix.size() == 6) + { + if (m_Transformation) + { + m_Transformation->SetElements(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]); + } + else + { + m_Transformation = new Matrix(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]); + } + } + else if (!matrix.empty()) + { + delete m_Transformation; + m_Transformation = nullptr; + + LogErrorF(this, L"Meter: Incorrect number of values in TransformationMatrix=%s", parser.ReadString(section, L"TransformationMatrix", L"").c_str()); + } +} + +/* +** Binds this meter to the given measure. The same measure can be bound to +** several meters but one meter and only be bound to one measure. +** +*/ +void Meter::BindMeasures(ConfigParser& parser, const WCHAR* section) +{ + BindPrimaryMeasure(parser, section, false); +} + +/* +** Creates the given meter. This is the factory method for the meters. +** If new meters are implemented this method needs to be updated. +** +*/ +Meter* Meter::Create(const WCHAR* meter, MeterWindow* meterWindow, const WCHAR* name) +{ + if (_wcsicmp(L"STRING", meter) == 0) + { + return new MeterString(meterWindow, name); + } + else if (_wcsicmp(L"IMAGE", meter) == 0) + { + return new MeterImage(meterWindow, name); + } + else if (_wcsicmp(L"HISTOGRAM", meter) == 0) + { + return new MeterHistogram(meterWindow, name); + } + else if (_wcsicmp(L"BAR", meter) == 0) + { + return new MeterBar(meterWindow, name); + } + else if (_wcsicmp(L"BITMAP", meter) == 0) + { + return new MeterBitmap(meterWindow, name); + } + else if (_wcsicmp(L"LINE", meter) == 0) + { + return new MeterLine(meterWindow, name); + } + else if (_wcsicmp(L"ROUNDLINE", meter) == 0) + { + return new MeterRoundLine(meterWindow, name); + } + else if (_wcsicmp(L"ROTATOR", meter) == 0) + { + return new MeterRotator(meterWindow, name); + } + else if (_wcsicmp(L"BUTTON", meter) == 0) + { + return new MeterButton(meterWindow, name); + } + + LogErrorF(meterWindow, L"Meter=%s is not valid in [%s]", meter, name); + + return nullptr; +} + +/* +** Updates the value(s) from the measures. Derived classes should +** only update if this returns true; +*/ +bool Meter::Update() +{ + // Only update the meter's value when the divider is equal to the counter + return UpdateCounter(); +} + +/* +** Reads and binds the primary MeasureName. This must always be called in overridden +** BindMeasures() implementations. +** +*/ +bool Meter::BindPrimaryMeasure(ConfigParser& parser, const WCHAR* section, bool optional) +{ + m_Measures.clear(); + + const std::wstring& measureName = parser.ReadString(section, L"MeasureName", L""); + + Measure* measure = parser.GetMeasure(measureName); + if (measure) + { + m_Measures.push_back(measure); + return true; + } + else if (!optional) + { + LogErrorF(this, L"MeasureName=%s is not valid", measureName.c_str()); + } + + return false; +} + +/* +** Reads and binds secondary measures (MeasureName2 - MeasureNameN). +** +*/ +void Meter::BindSecondaryMeasures(ConfigParser& parser, const WCHAR* section) +{ + if (!m_Measures.empty()) + { + WCHAR tmpName[64]; + + int i = 2; + do + { + _snwprintf_s(tmpName, _TRUNCATE, L"MeasureName%i", i); + const std::wstring& measureName = parser.ReadString(section, tmpName, L""); + Measure* measure = parser.GetMeasure(measureName); + if (measure) + { + m_Measures.push_back(measure); + } + else + { + if (!measureName.empty()) + { + LogErrorF(this, L"MeasureName%i=%s is not valid", i, measureName.c_str()); + } + + break; + } + ++i; + } + while (true); + } +} + +/* +** Replaces %1, %2, ... with the corresponding measure value. +** +*/ +bool Meter::ReplaceMeasures(std::wstring& str, AUTOSCALE autoScale, double scale, int decimals, bool percentual) +{ + bool replaced = false; + + if (str.find(L'%') != std::wstring::npos) + { + WCHAR buffer[64]; + + for (size_t i = m_Measures.size(); i > 0; --i) + { + size_t len = _snwprintf_s(buffer, _TRUNCATE, L"%%%i", (int)i); + size_t start = 0, pos; + + const WCHAR* measureValue = m_Measures[i - 1]->GetStringOrFormattedValue( + autoScale, scale, decimals, percentual); + const size_t measureValueLen = wcslen(measureValue); + + do + { + pos = str.find(buffer, start, len); + if (pos != std::wstring::npos) + { + str.replace(pos, len, measureValue, measureValueLen); + start = pos + measureValueLen; + replaced = true; + } + } + while (pos != std::wstring::npos); + } + } + + return replaced; +} + +/* +** Does the initial construction of the ToolTip for the meter +*/ +void Meter::CreateToolTip(MeterWindow* meterWindow) +{ + HWND hMeterWindow = m_MeterWindow->GetWindow(); + HINSTANCE hInstance = GetRainmeter().GetModuleInstance(); + DWORD style = WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP; + + if (m_ToolTipType) + { + style |= TTS_BALLOON; + } + + HWND hwndTT = CreateWindowEx(WS_EX_TOPMOST, + TOOLTIPS_CLASS, + nullptr, + style, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + hMeterWindow, + nullptr, + hInstance, + nullptr); + + if (hwndTT) + { + SetWindowPos(hwndTT, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + + TOOLINFO ti = {sizeof(TOOLINFO), TTF_SUBCLASS, hMeterWindow, 0, GetMeterRect(), hInstance}; + + SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM)&ti); + + m_ToolTipHandle = hwndTT; + UpdateToolTip(); + } +} + +/* +** Updates the ToolTip to match new values +*/ +void Meter::UpdateToolTip() +{ + HWND hwndTT = m_ToolTipHandle; + + TOOLINFO ti = {sizeof(TOOLINFO)}; + ti.hwnd = m_MeterWindow->GetWindow(); + + SendMessage(hwndTT, TTM_GETTOOLINFO, 0, (LPARAM)&ti); + + std::wstring text = m_ToolTipText; + ReplaceMeasures(text); + ti.lpszText = (LPTSTR)text.c_str(); + ti.rect = GetMeterRect(); + + SendMessage(hwndTT, TTM_SETTOOLINFO, 0, (LPARAM)&ti); + SendMessage(hwndTT, TTM_SETMAXTIPWIDTH, 0, m_ToolTipWidth); + + if (!m_ToolTipTitle.empty()) + { + HICON hIcon = nullptr; + bool destroy = false; + + if (!m_ToolTipIcon.empty()) + { + const WCHAR* tipIcon = m_ToolTipIcon.c_str(); + if (_wcsicmp(tipIcon, L"INFO") == 0) + { + hIcon = (HICON)TTI_INFO; + } + else if (_wcsicmp(tipIcon, L"WARNING") == 0) + { + hIcon = (HICON)TTI_WARNING; + } + else if (_wcsicmp(tipIcon, L"ERROR") == 0) + { + hIcon = (HICON)TTI_ERROR; + } + else if (_wcsicmp(tipIcon, L"QUESTION") == 0) + { + hIcon = LoadIcon(nullptr, IDI_QUESTION); + } + else if (_wcsicmp(tipIcon, L"SHIELD") == 0) + { + hIcon = LoadIcon(nullptr, IDI_SHIELD); + } + else + { + std::wstring iconPath = m_ToolTipIcon; + m_MeterWindow->MakePathAbsolute(iconPath); + hIcon = (HICON)LoadImage(nullptr, iconPath.c_str(), IMAGE_ICON, 0, 0, LR_LOADFROMFILE); + destroy = true; + } + } + + text = m_ToolTipTitle; + ReplaceMeasures(text); + SendMessage(hwndTT, TTM_SETTITLE, (WPARAM)hIcon, (LPARAM)text.c_str()); + + if (destroy) + { + DestroyIcon(hIcon); + } + } + + if (m_ToolTipHidden) + { + SendMessage(hwndTT, TTM_ACTIVATE, FALSE, 0); + } + else + { + SendMessage(hwndTT, TTM_ACTIVATE, !IsHidden(), 0); + } +} + +/* +** Draws the solid background & bevel if such are defined +*/ +bool Meter::Draw(Gfx::Canvas& canvas) +{ + if (IsHidden()) return false; + + canvas.SetAntiAliasing(m_AntiAlias); + + if (m_SolidColor.GetA() != 0 || m_SolidColor2.GetA() != 0) + { + int x = GetX(); + int y = GetY(); + + Rect r(x, y, m_W, m_H); + + if (m_SolidColor.GetValue() == m_SolidColor2.GetValue()) + { + SolidBrush solid(m_SolidColor); + canvas.FillRectangle(r, solid); + } + else + { + Gdiplus::Graphics& graphics = canvas.BeginGdiplusContext(); + + if (!m_AntiAlias) + { + // Fix the tiling issue in some GradientAngle values + graphics.SetPixelOffsetMode(PixelOffsetModeHalf); + } + + LinearGradientBrush gradient(r, m_SolidColor, m_SolidColor2, m_SolidAngle, TRUE); + graphics.FillRectangle(&gradient, r); + + if (!m_AntiAlias) + { + graphics.SetPixelOffsetMode(PixelOffsetModeDefault); + } + + canvas.EndGdiplusContext(); + } + } + + if (m_SolidBevel != BEVELTYPE_NONE) + { + Gdiplus::Graphics& graphics = canvas.BeginGdiplusContext(); + + int x = GetX(); + int y = GetY(); + + Color lightColor(255, 255, 255, 255); + Color darkColor(255, 0, 0, 0); + + if (m_SolidBevel == BEVELTYPE_DOWN) + { + lightColor.SetValue(Color::MakeARGB(255, 0, 0, 0)); + darkColor.SetValue(Color::MakeARGB(255, 255, 255, 255)); + } + + Pen light(lightColor); + Pen dark(darkColor); + + // The bevel is drawn outside the meter + Rect rect(x - 2, y - 2, m_W + 4, m_H + 4); + DrawBevel(graphics, rect, light, dark); + + canvas.EndGdiplusContext(); + } + + return true; +} + +/* +** Draws a bevel inside the given area +*/ +void Meter::DrawBevel(Graphics& graphics, const Rect& rect, const Pen& light, const Pen& dark) +{ + int l = rect.GetLeft(); + int r = rect.GetRight() - 1; + int t = rect.GetTop(); + int b = rect.GetBottom() - 1; + + graphics.DrawLine(&light, l, t, l, b); + graphics.DrawLine(&light, l, t, r, t); + graphics.DrawLine(&light, l + 1, t + 1, l + 1, b - 1); + graphics.DrawLine(&light, l + 1, t + 1, r - 1, t + 1); + graphics.DrawLine(&dark, l, b, r, b); + graphics.DrawLine(&dark, r, t, r, b); + graphics.DrawLine(&dark, l + 1, b - 1, r - 1, b - 1); + graphics.DrawLine(&dark, r - 1, t + 1, r - 1, b - 1); +} diff --git a/Library/MeterButton.cpp b/Library/MeterButton.cpp index 76568c2d..190f1297 100644 --- a/Library/MeterButton.cpp +++ b/Library/MeterButton.cpp @@ -1,330 +1,330 @@ -/* - Copyright (C) 2005 Kimmo Pekkola - - 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 "MeterButton.h" -#include "Measure.h" -#include "Rainmeter.h" -#include "Error.h" -#include "../Common/Gfx/Canvas.h" - -using namespace Gdiplus; - -enum BUTTON_STATE -{ - BUTTON_STATE_NORMAL, - BUTTON_STATE_DOWN, - BUTTON_STATE_HOVER -}; - -/* -** The constructor -** -*/ -MeterButton::MeterButton(MeterWindow* meterWindow, const WCHAR* name) : Meter(meterWindow, name), - m_Image(L"ButtonImage", nullptr, true, meterWindow), - m_NeedsReload(false), - m_Bitmaps(), - m_State(BUTTON_STATE_NORMAL), - m_Clicked(false), - m_Focus(false) -{ -} - -/* -** The destructor -** -*/ -MeterButton::~MeterButton() -{ - for (int i = 0; i < BUTTON_FRAMES; ++i) - { - delete m_Bitmaps[i]; - } -} - -/* -** Load the image and get the dimensions of the meter from it. -** -*/ -void MeterButton::Initialize() -{ - Meter::Initialize(); - - for (int i = 0; i < BUTTON_FRAMES; ++i) - { - delete m_Bitmaps[i]; - m_Bitmaps[i] = nullptr; - } - - // Load the bitmaps if defined - if (!m_ImageName.empty()) - { - m_Image.LoadImage(m_ImageName, m_NeedsReload); - - if (m_Image.IsLoaded()) - { - Bitmap* bitmap = m_Image.GetImage(); - - int bitmapW = bitmap->GetWidth(); - int bitmapH = bitmap->GetHeight(); - - m_W = bitmapW; - m_H = bitmapH; - - if (m_H > m_W) - { - m_H /= BUTTON_FRAMES; - } - else - { - m_W /= BUTTON_FRAMES; - } - - // Separate the frames - for (int i = 0; i < BUTTON_FRAMES; ++i) - { - Bitmap bitmapPart(m_W, m_H, PixelFormat32bppPARGB); - Graphics graphics(&bitmapPart); - Rect r(0, 0, m_W, m_H); - - if (bitmapH > bitmapW) - { - graphics.DrawImage(bitmap, r, 0, m_H * i, m_W, m_H, UnitPixel); - } - else - { - graphics.DrawImage(bitmap, r, m_W * i, 0, m_W, m_H, UnitPixel); - } - m_Bitmaps[i] = new CachedBitmap(&bitmapPart, &graphics); - } - - m_W += GetWidthPadding(); - m_H += GetHeightPadding(); - } - } - else if (m_Image.IsLoaded()) - { - m_Image.DisposeImage(); - } -} - -/* -** Read the options specified in the ini file. -** -*/ -void MeterButton::ReadOptions(ConfigParser& parser, const WCHAR* section) -{ - // Store the current values so we know if the image needs to be updated - std::wstring oldImageName = m_ImageName; - int oldW = m_W; - int oldH = m_H; - - Meter::ReadOptions(parser, section); - - m_ImageName = parser.ReadString(section, L"ButtonImage", L""); - if (!m_ImageName.empty()) - { - // Read tinting options - m_Image.ReadOptions(parser, section); - } - else - { - m_Image.ClearOptionFlags(); - } - - m_Command = parser.ReadString(section, L"ButtonCommand", L"", false); - - if (m_Initialized) - { - m_NeedsReload = (wcscmp(oldImageName.c_str(), m_ImageName.c_str()) != 0); - - if (m_NeedsReload || - m_Image.IsOptionsChanged()) - { - Initialize(); // Reload the image - } - else - { - // Reset to old dimensions - m_W = oldW; - m_H = oldH; - } - } -} - -/* -** Updates the value(s) from the measures. -** -*/ -bool MeterButton::Update() -{ - return Meter::Update(); -} - -/* -** Draws the meter on the double buffer -** -*/ -bool MeterButton::Draw(Gfx::Canvas& canvas) -{ - if (!Meter::Draw(canvas)) return false; - - if (m_Bitmaps[m_State] == nullptr) return false; // Unable to continue - - Gdiplus::Graphics& graphics = canvas.BeginGdiplusContext(); - - Gdiplus::Rect meterRect = GetMeterRectPadding(); - - // Blit the image - graphics.DrawCachedBitmap(m_Bitmaps[m_State], meterRect.X, meterRect.Y); - - canvas.EndGdiplusContext(); - - return true; -} - -/* -** Overridden method. The meters need not to be bound on anything -** -*/ -void MeterButton::BindMeasures(ConfigParser& parser, const WCHAR* section) -{ - BindPrimaryMeasure(parser, section, true); -} - -/* -** Checks if the given point is inside the button. -** -*/ -bool MeterButton::HitTest2(int px, int py) -{ - int x = GetX(); - int y = GetY(); - - if (m_MouseOver && - px >= x && px < x + m_W && - py >= y && py < y + m_H) - { - if (m_SolidColor.GetA() != 0 || m_SolidColor2.GetA() != 0) - { - return true; - } - - // Check transparent pixels - if (m_Image.IsLoaded()) - { - Rect meterRect = GetMeterRectPadding(); - int ix = meterRect.Width * m_State; - px = px - meterRect.X + ix; - py = py - meterRect.Y; - if (px >= ix && px < ix + meterRect.Width && - py >= 0 && py < meterRect.Height) - { - Color color; - Status status = m_Image.GetImage()->GetPixel(px, py, &color); - if (status != Ok || color.GetA() != 0) - { - return true; - } - } - } - else - { - return true; - } - } - return false; -} - -bool MeterButton::MouseUp(POINT pos, bool execute) -{ - if (m_State == BUTTON_STATE_DOWN) - { - if (execute && m_Clicked && m_Focus && HitTest2(pos.x, pos.y)) - { - Rainmeter::GetInstance().ExecuteCommand(m_Command.c_str(), m_MeterWindow); - } - m_State = BUTTON_STATE_NORMAL; - m_Clicked = false; - return true; - } - m_Clicked = false; - - return false; -} - -bool MeterButton::MouseDown(POINT pos) -{ - if (m_Focus && HitTest2(pos.x, pos.y)) - { - m_State = BUTTON_STATE_DOWN; - m_Clicked = true; - return true; - } - return false; -} - -bool MeterButton::MouseMove(POINT pos) -{ - if (m_Clicked) - { - if (HitTest2(pos.x, pos.y)) - { - if (m_State == BUTTON_STATE_NORMAL) - { - m_State = BUTTON_STATE_DOWN; - return true; - } - } - else - { - // If the left button is not down anymore the clicked state needs to be set false - if (!IsLButtonDown()) - { - m_Clicked = false; - } - - if (m_State == BUTTON_STATE_DOWN) - { - m_State = BUTTON_STATE_NORMAL; - return true; - } - } - } - else - { - if (HitTest2(pos.x, pos.y)) - { - if (m_State == BUTTON_STATE_NORMAL) - { - m_State = BUTTON_STATE_HOVER; - return true; - } - } - else - { - if (m_State == BUTTON_STATE_HOVER) - { - m_State = BUTTON_STATE_NORMAL; - return true; - } - } - } - return false; -} +/* + Copyright (C) 2005 Kimmo Pekkola + + 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 "MeterButton.h" +#include "Measure.h" +#include "Rainmeter.h" +#include "Error.h" +#include "../Common/Gfx/Canvas.h" + +using namespace Gdiplus; + +enum BUTTON_STATE +{ + BUTTON_STATE_NORMAL, + BUTTON_STATE_DOWN, + BUTTON_STATE_HOVER +}; + +/* +** The constructor +** +*/ +MeterButton::MeterButton(MeterWindow* meterWindow, const WCHAR* name) : Meter(meterWindow, name), + m_Image(L"ButtonImage", nullptr, true, meterWindow), + m_NeedsReload(false), + m_Bitmaps(), + m_State(BUTTON_STATE_NORMAL), + m_Clicked(false), + m_Focus(false) +{ +} + +/* +** The destructor +** +*/ +MeterButton::~MeterButton() +{ + for (int i = 0; i < BUTTON_FRAMES; ++i) + { + delete m_Bitmaps[i]; + } +} + +/* +** Load the image and get the dimensions of the meter from it. +** +*/ +void MeterButton::Initialize() +{ + Meter::Initialize(); + + for (int i = 0; i < BUTTON_FRAMES; ++i) + { + delete m_Bitmaps[i]; + m_Bitmaps[i] = nullptr; + } + + // Load the bitmaps if defined + if (!m_ImageName.empty()) + { + m_Image.LoadImage(m_ImageName, m_NeedsReload); + + if (m_Image.IsLoaded()) + { + Bitmap* bitmap = m_Image.GetImage(); + + int bitmapW = bitmap->GetWidth(); + int bitmapH = bitmap->GetHeight(); + + m_W = bitmapW; + m_H = bitmapH; + + if (m_H > m_W) + { + m_H /= BUTTON_FRAMES; + } + else + { + m_W /= BUTTON_FRAMES; + } + + // Separate the frames + for (int i = 0; i < BUTTON_FRAMES; ++i) + { + Bitmap bitmapPart(m_W, m_H, PixelFormat32bppPARGB); + Graphics graphics(&bitmapPart); + Rect r(0, 0, m_W, m_H); + + if (bitmapH > bitmapW) + { + graphics.DrawImage(bitmap, r, 0, m_H * i, m_W, m_H, UnitPixel); + } + else + { + graphics.DrawImage(bitmap, r, m_W * i, 0, m_W, m_H, UnitPixel); + } + m_Bitmaps[i] = new CachedBitmap(&bitmapPart, &graphics); + } + + m_W += GetWidthPadding(); + m_H += GetHeightPadding(); + } + } + else if (m_Image.IsLoaded()) + { + m_Image.DisposeImage(); + } +} + +/* +** Read the options specified in the ini file. +** +*/ +void MeterButton::ReadOptions(ConfigParser& parser, const WCHAR* section) +{ + // Store the current values so we know if the image needs to be updated + std::wstring oldImageName = m_ImageName; + int oldW = m_W; + int oldH = m_H; + + Meter::ReadOptions(parser, section); + + m_ImageName = parser.ReadString(section, L"ButtonImage", L""); + if (!m_ImageName.empty()) + { + // Read tinting options + m_Image.ReadOptions(parser, section); + } + else + { + m_Image.ClearOptionFlags(); + } + + m_Command = parser.ReadString(section, L"ButtonCommand", L"", false); + + if (m_Initialized) + { + m_NeedsReload = (wcscmp(oldImageName.c_str(), m_ImageName.c_str()) != 0); + + if (m_NeedsReload || + m_Image.IsOptionsChanged()) + { + Initialize(); // Reload the image + } + else + { + // Reset to old dimensions + m_W = oldW; + m_H = oldH; + } + } +} + +/* +** Updates the value(s) from the measures. +** +*/ +bool MeterButton::Update() +{ + return Meter::Update(); +} + +/* +** Draws the meter on the double buffer +** +*/ +bool MeterButton::Draw(Gfx::Canvas& canvas) +{ + if (!Meter::Draw(canvas)) return false; + + if (m_Bitmaps[m_State] == nullptr) return false; // Unable to continue + + Gdiplus::Graphics& graphics = canvas.BeginGdiplusContext(); + + Gdiplus::Rect meterRect = GetMeterRectPadding(); + + // Blit the image + graphics.DrawCachedBitmap(m_Bitmaps[m_State], meterRect.X, meterRect.Y); + + canvas.EndGdiplusContext(); + + return true; +} + +/* +** Overridden method. The meters need not to be bound on anything +** +*/ +void MeterButton::BindMeasures(ConfigParser& parser, const WCHAR* section) +{ + BindPrimaryMeasure(parser, section, true); +} + +/* +** Checks if the given point is inside the button. +** +*/ +bool MeterButton::HitTest2(int px, int py) +{ + int x = GetX(); + int y = GetY(); + + if (m_MouseOver && + px >= x && px < x + m_W && + py >= y && py < y + m_H) + { + if (m_SolidColor.GetA() != 0 || m_SolidColor2.GetA() != 0) + { + return true; + } + + // Check transparent pixels + if (m_Image.IsLoaded()) + { + Rect meterRect = GetMeterRectPadding(); + int ix = meterRect.Width * m_State; + px = px - meterRect.X + ix; + py = py - meterRect.Y; + if (px >= ix && px < ix + meterRect.Width && + py >= 0 && py < meterRect.Height) + { + Color color; + Status status = m_Image.GetImage()->GetPixel(px, py, &color); + if (status != Ok || color.GetA() != 0) + { + return true; + } + } + } + else + { + return true; + } + } + return false; +} + +bool MeterButton::MouseUp(POINT pos, bool execute) +{ + if (m_State == BUTTON_STATE_DOWN) + { + if (execute && m_Clicked && m_Focus && HitTest2(pos.x, pos.y)) + { + GetRainmeter().ExecuteCommand(m_Command.c_str(), m_MeterWindow); + } + m_State = BUTTON_STATE_NORMAL; + m_Clicked = false; + return true; + } + m_Clicked = false; + + return false; +} + +bool MeterButton::MouseDown(POINT pos) +{ + if (m_Focus && HitTest2(pos.x, pos.y)) + { + m_State = BUTTON_STATE_DOWN; + m_Clicked = true; + return true; + } + return false; +} + +bool MeterButton::MouseMove(POINT pos) +{ + if (m_Clicked) + { + if (HitTest2(pos.x, pos.y)) + { + if (m_State == BUTTON_STATE_NORMAL) + { + m_State = BUTTON_STATE_DOWN; + return true; + } + } + else + { + // If the left button is not down anymore the clicked state needs to be set false + if (!IsLButtonDown()) + { + m_Clicked = false; + } + + if (m_State == BUTTON_STATE_DOWN) + { + m_State = BUTTON_STATE_NORMAL; + return true; + } + } + } + else + { + if (HitTest2(pos.x, pos.y)) + { + if (m_State == BUTTON_STATE_NORMAL) + { + m_State = BUTTON_STATE_HOVER; + return true; + } + } + else + { + if (m_State == BUTTON_STATE_HOVER) + { + m_State = BUTTON_STATE_NORMAL; + return true; + } + } + } + return false; +} diff --git a/Library/MeterString.cpp b/Library/MeterString.cpp index 72b88440..f07848f5 100644 --- a/Library/MeterString.cpp +++ b/Library/MeterString.cpp @@ -1,682 +1,682 @@ -/* - Copyright (C) 2001 Kimmo Pekkola - - 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 "MeterString.h" -#include "Rainmeter.h" -#include "Measure.h" -#include "Error.h" -#include "../Common/Gfx/Canvas.h" - -using namespace Gdiplus; - -#define PI (3.14159265f) -#define CONVERT_TO_DEGREES(X) ((X) * (180.0f / PI)) - -void StringToUpper(std::wstring& str) -{ - WCHAR* srcAndDest = &str[0]; - int strAndDestLen = (int)str.length(); - LCMapString(LOCALE_USER_DEFAULT, LCMAP_UPPERCASE, srcAndDest, strAndDestLen, srcAndDest, strAndDestLen); -} - -void StringToLower(std::wstring& str) -{ - WCHAR* srcAndDest = &str[0]; - int strAndDestLen = (int)str.length(); - LCMapString(LOCALE_USER_DEFAULT, LCMAP_LOWERCASE, srcAndDest, strAndDestLen, srcAndDest, strAndDestLen); -} - -void StringToProper(std::wstring& str) -{ - if (!str.empty()) - { - WCHAR* srcAndDest = &str[0]; - LCMapString(LOCALE_USER_DEFAULT, LCMAP_UPPERCASE, srcAndDest, 1, srcAndDest, 1); - - for (size_t i = 1; i < str.length(); ++i) - { - srcAndDest = &str[i]; - LCMapString(LOCALE_USER_DEFAULT, (iswspace(str[i - 1]) > 0) ? LCMAP_UPPERCASE : LCMAP_LOWERCASE, srcAndDest, 1, srcAndDest, 1); - } - } -} - -/* -** The constructor -** -*/ -MeterString::MeterString(MeterWindow* meterWindow, const WCHAR* name) : Meter(meterWindow, name), - m_Color(Color::White), - m_EffectColor(Color::Black), - m_AutoScale(AUTOSCALE_OFF), - m_Style(NORMAL), - m_Effect(EFFECT_NONE), - m_Case(TEXTCASE_NONE), - m_FontSize(10), - m_Scale(1.0), - m_NoDecimals(true), - m_Percentual(true), - m_ClipType(CLIP_OFF), - m_NeedsClipping(false), - m_ClipStringW(-1), - m_ClipStringH(-1), - m_TextFormat(meterWindow->GetCanvas().CreateTextFormat()), - m_NumOfDecimals(-1), - m_Angle() -{ -} - -/* -** The destructor -** -*/ -MeterString::~MeterString() -{ - delete m_TextFormat; - m_TextFormat = nullptr; -} - -/* -** Returns the X-coordinate of the meter -** -*/ -int MeterString::GetX(bool abs) -{ - int x = Meter::GetX(); - - if (!abs) - { - switch (m_TextFormat->GetHorizontalAlignment()) - { - case Gfx::HorizontalAlignment::Center: - x -= m_W / 2; - break; - - case Gfx::HorizontalAlignment::Right: - x -= m_W; - break; - } - } - - return x; -} - -/* -** Returns the Y-coordinate of the meter -** -*/ -int MeterString::GetY(bool abs) -{ - int y = Meter::GetY(); - - if (!abs) - { - switch (m_TextFormat->GetVerticalAlignment()) - { - case Gfx::VerticalAlignment::Center: - y -= m_H / 2; - break; - - case Gfx::VerticalAlignment::Bottom: - y -= m_H; - break; - } - } - - return y; -} - -/* -** Create the font that is used to draw the text. -** -*/ -void MeterString::Initialize() -{ - Meter::Initialize(); - - m_TextFormat->SetProperties( - m_FontFace.c_str(), - m_FontSize, - (m_Style & BOLD) != 0, - (m_Style & ITALIC) != 0, - m_MeterWindow->GetFontCollection()); -} - -/* -** Read the options specified in the ini file. -** -*/ -void MeterString::ReadOptions(ConfigParser& parser, const WCHAR* section) -{ - // Store the current font values so we know if the font needs to be updated - std::wstring oldFontFace = m_FontFace; - int oldFontSize = m_FontSize; - TEXTSTYLE oldStyle = m_Style; - - Meter::ReadOptions(parser, section); - - m_Color = parser.ReadColor(section, L"FontColor", Color::Black); - m_EffectColor = parser.ReadColor(section, L"FontEffectColor", Color::Black); - - m_Prefix = parser.ReadString(section, L"Prefix", L""); - m_Postfix = parser.ReadString(section, L"Postfix", L""); - m_Text = parser.ReadString(section, L"Text", L""); - - m_Percentual = parser.ReadBool(section, L"Percentual", false); - - int clipping = parser.ReadInt(section, L"ClipString", 0); - switch (clipping) - { - case 2: - m_ClipType = CLIP_AUTO; - - m_ClipStringW = parser.ReadInt(section, L"ClipStringW", -1); - m_ClipStringH = parser.ReadInt(section, L"ClipStringH", -1); - break; - - case 1: - m_ClipType = CLIP_ON; - break; - - case 0: - m_ClipType = CLIP_OFF; - break; - - default: - LogErrorF(this, L"ClipString=%s is not valid", clipping); - } - - m_FontFace = parser.ReadString(section, L"FontFace", L"Arial"); - if (m_FontFace.empty()) - { - m_FontFace = L"Arial"; - } - - m_FontSize = parser.ReadInt(section, L"FontSize", 10); - if (m_FontSize < 0) - { - m_FontSize = 10; - } - - m_NumOfDecimals = parser.ReadInt(section, L"NumOfDecimals", -1); - - m_Angle = (Gdiplus::REAL)parser.ReadFloat(section, L"Angle", 0.0); - - const std::wstring& autoscale = parser.ReadString(section, L"AutoScale", L"0"); - int autoscaleValue = _wtoi(autoscale.c_str()); - if (autoscaleValue == 0) - { - m_AutoScale = AUTOSCALE_OFF; - } - else - { - if (autoscale.find_last_of(L"kK") == std::wstring::npos) - { - m_AutoScale = (autoscaleValue == 2) ? AUTOSCALE_1000 : AUTOSCALE_1024; - } - else - { - m_AutoScale = (autoscaleValue == 2) ? AUTOSCALE_1000K : AUTOSCALE_1024K; - } - } - - const std::wstring& scale = parser.ReadString(section, L"Scale", L"1"); - m_NoDecimals = (scale.find(L'.') == std::wstring::npos); - m_Scale = parser.ParseDouble(scale.c_str(), 1); - - const WCHAR* hAlign = parser.ReadString(section, L"StringAlign", L"LEFT").c_str(); - const WCHAR* vAlign = nullptr; - if (_wcsnicmp(hAlign, L"LEFT", 4) == 0) - { - m_TextFormat->SetHorizontalAlignment(Gfx::HorizontalAlignment::Left); - vAlign = hAlign + 4; - } - else if (_wcsnicmp(hAlign, L"RIGHT", 5) == 0) - { - m_TextFormat->SetHorizontalAlignment(Gfx::HorizontalAlignment::Right); - vAlign = hAlign + 5; - } - else if (_wcsnicmp(hAlign, L"CENTER", 6) == 0) - { - m_TextFormat->SetHorizontalAlignment(Gfx::HorizontalAlignment::Center); - vAlign = hAlign + 6; - } - - if (!vAlign || _wcsicmp(vAlign, L"TOP") == 0) - { - m_TextFormat->SetVerticalAlignment(Gfx::VerticalAlignment::Top); - } - else if (_wcsicmp(vAlign, L"BOTTOM") == 0) - { - m_TextFormat->SetVerticalAlignment(Gfx::VerticalAlignment::Bottom); - } - else if (_wcsicmp(vAlign, L"CENTER") == 0) - { - m_TextFormat->SetVerticalAlignment(Gfx::VerticalAlignment::Center); - } - - const WCHAR* stringCase = parser.ReadString(section, L"StringCase", L"NONE").c_str(); - if (_wcsicmp(stringCase, L"NONE") == 0) - { - m_Case = TEXTCASE_NONE; - } - else if (_wcsicmp(stringCase, L"UPPER") == 0) - { - m_Case = TEXTCASE_UPPER; - } - else if (_wcsicmp(stringCase, L"LOWER") == 0) - { - m_Case = TEXTCASE_LOWER; - } - else if (_wcsicmp(stringCase, L"PROPER") == 0) - { - m_Case = TEXTCASE_PROPER; - } - else - { - LogErrorF(this, L"StringCase=%s is not valid", stringCase); - } - - const WCHAR* style = parser.ReadString(section, L"StringStyle", L"NORMAL").c_str(); - if (_wcsicmp(style, L"NORMAL") == 0) - { - m_Style = NORMAL; - } - else if (_wcsicmp(style, L"BOLD") == 0) - { - m_Style = BOLD; - } - else if (_wcsicmp(style, L"ITALIC") == 0) - { - m_Style = ITALIC; - } - else if (_wcsicmp(style, L"BOLDITALIC") == 0) - { - m_Style = BOLDITALIC; - } - else - { - LogErrorF(this, L"StringStyle=%s is not valid", style); - } - - const WCHAR* effect = parser.ReadString(section, L"StringEffect", L"NONE").c_str(); - if (_wcsicmp(effect, L"NONE") == 0) - { - m_Effect = EFFECT_NONE; - } - else if (_wcsicmp(effect, L"SHADOW") == 0) - { - m_Effect = EFFECT_SHADOW; - } - else if (_wcsicmp(effect, L"BORDER") == 0) - { - m_Effect = EFFECT_BORDER; - } - else - { - LogErrorF(this, L"StringEffect=%s is not valid", effect); - } - - if (m_Initialized && - (wcscmp(oldFontFace.c_str(), m_FontFace.c_str()) != 0 || - oldFontSize != m_FontSize || - oldStyle != m_Style)) - { - Initialize(); // Recreate the font - } -} - -/* -** Updates the value(s) from the measures. -** -*/ -bool MeterString::Update() -{ - if (Meter::Update()) - { - int decimals = (m_NumOfDecimals != -1) ? m_NumOfDecimals : (m_NoDecimals && (m_Percentual || m_AutoScale == AUTOSCALE_OFF)) ? 0 : 1; - - // Create the text - m_String = m_Prefix; - if (!m_Measures.empty()) - { - if (m_Text.empty()) - { - m_String += m_Measures[0]->GetStringOrFormattedValue( - m_AutoScale, m_Scale, decimals, m_Percentual); - } - else - { - std::wstring tmpText = m_Text; - ReplaceMeasures(tmpText, m_AutoScale, m_Scale, decimals, m_Percentual); - m_String += tmpText; - } - } - else - { - m_String += m_Text; - } - if (!m_Postfix.empty()) m_String += m_Postfix; - - switch (m_Case) - { - case TEXTCASE_UPPER: - StringToUpper(m_String); - break; - case TEXTCASE_LOWER: - StringToLower(m_String); - break; - case TEXTCASE_PROPER: - StringToProper(m_String); - break; - } - - for (size_t i = 0; i < m_String.length(); ++i) - { - if (m_String[i] == L'\u00A0' || // No-Break Space - m_String[i] == L'\u205F') // Medium Mathematical Space - { - // Ugly hack to make D2D render trailing spaces followed by a non-breaking space - // correctly. By default, D2D ignores all trailing whitespace. Both GDI+ and D2D, - // however, acknowledge the presense of the zero-width space (and give it a width - // of 0px), so we append the zero-width space after each non-breaking space. - ++i; - m_String.insert(i, 1, L'\u200B'); - } - else if (m_String[i] == L'\r') - { - // GDI+ seems to ignore carriage returns, so strip it entirely to make it behave - // similarly with D2D as well. - m_String.erase(i, 1); - --i; - } - } - - if (!m_WDefined || !m_HDefined) - { - // Calculate the text size - RectF rect; - if (DrawString(m_MeterWindow->GetCanvas(), &rect)) - { - m_W = (int)rect.Width + GetWidthPadding(); - m_H = (int)rect.Height + GetHeightPadding(); - } - else - { - m_W = 1; - m_H = 1; - } - } - - return true; - } - return false; -} - -/* -** Draws the meter on the double buffer -** -*/ -bool MeterString::Draw(Gfx::Canvas& canvas) -{ - if (!Meter::Draw(canvas)) return false; - - return DrawString(canvas, nullptr); -} - -/* -** Draws the string or calculates it's size -** -*/ -bool MeterString::DrawString(Gfx::Canvas& canvas, RectF* rect) -{ - if (!m_TextFormat->IsInitialized()) return false; - - LPCWSTR string = m_String.c_str(); - UINT stringLen = (UINT)m_String.length(); - - canvas.SetTextAntiAliasing(m_AntiAlias); - - m_TextFormat->SetTrimming( - m_ClipType == CLIP_ON || - (m_ClipType == CLIP_AUTO && (m_NeedsClipping || (m_WDefined && m_HDefined)))); - - Gdiplus::Rect meterRect = GetMeterRectPadding(); - - if (rect) - { - rect->X = (REAL)meterRect.X; - rect->Y = (REAL)meterRect.Y; - if (canvas.MeasureTextW(string, stringLen, *m_TextFormat, *rect) && - m_ClipType == CLIP_AUTO) - { - // Set initial clipping - m_NeedsClipping = false; - - REAL w, h; - bool updateSize = true; - - if (m_WDefined) - { - w = (REAL)meterRect.Width; - h = rect->Height; - m_NeedsClipping = true; - } - else if (m_HDefined) - { - if (m_ClipStringW == -1) - { - // Text does not fit in defined height, clip it - if (rect->Height > (REAL)meterRect.Height) - { - m_NeedsClipping = true; - } - - rect->Height = (REAL)meterRect.Height; - updateSize = false; - - } - else - { - if (rect->Width > (REAL)m_ClipStringW) - { - w = (REAL)m_ClipStringW; - m_NeedsClipping = true; - } - else - { - w = rect->Width; - } - - h = (REAL)meterRect.Height; - } - } - else - { - if (m_ClipStringW == -1) - { - // Clip text if already larger than ClipStringH - if (m_ClipStringH != -1 && rect->Height > (REAL)m_ClipStringH) - { - m_NeedsClipping = true; - rect->Height = (REAL)m_ClipStringH; - } - - updateSize = false; - } - else - { - if (rect->Width > (REAL)m_ClipStringW) - { - w = (REAL)m_ClipStringW; - m_NeedsClipping = true; - } - else - { - w = rect->Width; - } - - h = rect->Height; - } - } - - if (updateSize) - { - UINT lines = 0; - RectF layout((REAL)meterRect.X, (REAL)meterRect.Y, w, h); - if (canvas.MeasureTextLinesW(string, stringLen, *m_TextFormat, layout, lines) && - lines != 0) - { - rect->Width = w; - rect->Height = layout.Height; - - if (m_HDefined || (m_ClipStringH != -1 && rect->Height > (REAL)m_ClipStringH)) - { - rect->Height = m_HDefined ? (REAL)meterRect.Height : (REAL)m_ClipStringH; - } - } - } - } - } - else - { - RectF rcDest((REAL)meterRect.X, (REAL)meterRect.Y, (REAL)meterRect.Width, (REAL)meterRect.Height); - m_Rect = rcDest; - - if (m_Angle != 0.0f) - { - const float baseX = (float)Meter::GetX(); - canvas.RotateTransform(CONVERT_TO_DEGREES(m_Angle), baseX, (REAL)meterRect.Y, -baseX, -(REAL)meterRect.Y); - } - - if (m_Effect != EFFECT_NONE) - { - SolidBrush solidBrush(m_EffectColor); - RectF rcEffect(rcDest); - - if (m_Effect == EFFECT_SHADOW) - { - rcEffect.Offset(1, 1); - canvas.DrawTextW(string, (UINT)stringLen, *m_TextFormat, rcEffect, solidBrush); - } - else //if (m_Effect == EFFECT_BORDER) - { - rcEffect.Offset(0, 1); - canvas.DrawTextW(string, (UINT)stringLen, *m_TextFormat, rcEffect, solidBrush); - rcEffect.Offset(1, -1); - canvas.DrawTextW(string, (UINT)stringLen, *m_TextFormat, rcEffect, solidBrush); - rcEffect.Offset(-1, -1); - canvas.DrawTextW(string, (UINT)stringLen, *m_TextFormat, rcEffect, solidBrush); - rcEffect.Offset(-1, 1); - canvas.DrawTextW(string, (UINT)stringLen, *m_TextFormat, rcEffect, solidBrush); - } - } - - SolidBrush solidBrush(m_Color); - canvas.DrawTextW(string, (UINT)stringLen, *m_TextFormat, rcDest, solidBrush); - - if (m_Angle != 0.0f) - { - canvas.ResetTransform(); - } - } - - return true; -} - -/* -** Overridden method. The string meters need not to be bound on anything -** -*/ -void MeterString::BindMeasures(ConfigParser& parser, const WCHAR* section) -{ - if (BindPrimaryMeasure(parser, section, true)) - { - BindSecondaryMeasures(parser, section); - } -} - -/* -** Static helper to log all installed font families. -** -*/ -void MeterString::EnumerateInstalledFontFamilies() -{ - INT fontCount; - InstalledFontCollection fontCollection; - - if (Ok == fontCollection.GetLastStatus()) - { - fontCount = fontCollection.GetFamilyCount(); - if (fontCount > 0) - { - INT fontFound; - - FontFamily* fontFamilies = new FontFamily[fontCount]; - - if (Ok == fontCollection.GetFamilies(fontCount, fontFamilies, &fontFound)) - { - std::wstring fonts; - for (INT i = 0; i < fontCount; ++i) - { - WCHAR familyName[LF_FACESIZE]; - if (Ok == fontFamilies[i].GetFamilyName(familyName)) - { - fonts += familyName; - } - else - { - fonts += L"***"; - } - fonts += L", "; - } - LogWarning(fonts.c_str()); - } - else - { - LogError(L"Font enumeration: GetFamilies failed"); - } - - delete [] fontFamilies; - } - else - { - LogWarning(L"No installed fonts"); - } - } - else - { - LogError(L"Font enumeration: InstalledFontCollection failed"); - } -} - -void MeterString::InitializeStatic() -{ - if (Rainmeter::GetInstance().GetDebug()) - { - LogDebug(L"------------------------------"); - LogDebug(L"* Font families:"); - EnumerateInstalledFontFamilies(); - LogDebug(L"------------------------------"); - } -} - -void MeterString::FinalizeStatic() -{ -} +/* + Copyright (C) 2001 Kimmo Pekkola + + 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 "MeterString.h" +#include "Rainmeter.h" +#include "Measure.h" +#include "Error.h" +#include "../Common/Gfx/Canvas.h" + +using namespace Gdiplus; + +#define PI (3.14159265f) +#define CONVERT_TO_DEGREES(X) ((X) * (180.0f / PI)) + +void StringToUpper(std::wstring& str) +{ + WCHAR* srcAndDest = &str[0]; + int strAndDestLen = (int)str.length(); + LCMapString(LOCALE_USER_DEFAULT, LCMAP_UPPERCASE, srcAndDest, strAndDestLen, srcAndDest, strAndDestLen); +} + +void StringToLower(std::wstring& str) +{ + WCHAR* srcAndDest = &str[0]; + int strAndDestLen = (int)str.length(); + LCMapString(LOCALE_USER_DEFAULT, LCMAP_LOWERCASE, srcAndDest, strAndDestLen, srcAndDest, strAndDestLen); +} + +void StringToProper(std::wstring& str) +{ + if (!str.empty()) + { + WCHAR* srcAndDest = &str[0]; + LCMapString(LOCALE_USER_DEFAULT, LCMAP_UPPERCASE, srcAndDest, 1, srcAndDest, 1); + + for (size_t i = 1; i < str.length(); ++i) + { + srcAndDest = &str[i]; + LCMapString(LOCALE_USER_DEFAULT, (iswspace(str[i - 1]) > 0) ? LCMAP_UPPERCASE : LCMAP_LOWERCASE, srcAndDest, 1, srcAndDest, 1); + } + } +} + +/* +** The constructor +** +*/ +MeterString::MeterString(MeterWindow* meterWindow, const WCHAR* name) : Meter(meterWindow, name), + m_Color(Color::White), + m_EffectColor(Color::Black), + m_AutoScale(AUTOSCALE_OFF), + m_Style(NORMAL), + m_Effect(EFFECT_NONE), + m_Case(TEXTCASE_NONE), + m_FontSize(10), + m_Scale(1.0), + m_NoDecimals(true), + m_Percentual(true), + m_ClipType(CLIP_OFF), + m_NeedsClipping(false), + m_ClipStringW(-1), + m_ClipStringH(-1), + m_TextFormat(meterWindow->GetCanvas().CreateTextFormat()), + m_NumOfDecimals(-1), + m_Angle() +{ +} + +/* +** The destructor +** +*/ +MeterString::~MeterString() +{ + delete m_TextFormat; + m_TextFormat = nullptr; +} + +/* +** Returns the X-coordinate of the meter +** +*/ +int MeterString::GetX(bool abs) +{ + int x = Meter::GetX(); + + if (!abs) + { + switch (m_TextFormat->GetHorizontalAlignment()) + { + case Gfx::HorizontalAlignment::Center: + x -= m_W / 2; + break; + + case Gfx::HorizontalAlignment::Right: + x -= m_W; + break; + } + } + + return x; +} + +/* +** Returns the Y-coordinate of the meter +** +*/ +int MeterString::GetY(bool abs) +{ + int y = Meter::GetY(); + + if (!abs) + { + switch (m_TextFormat->GetVerticalAlignment()) + { + case Gfx::VerticalAlignment::Center: + y -= m_H / 2; + break; + + case Gfx::VerticalAlignment::Bottom: + y -= m_H; + break; + } + } + + return y; +} + +/* +** Create the font that is used to draw the text. +** +*/ +void MeterString::Initialize() +{ + Meter::Initialize(); + + m_TextFormat->SetProperties( + m_FontFace.c_str(), + m_FontSize, + (m_Style & BOLD) != 0, + (m_Style & ITALIC) != 0, + m_MeterWindow->GetFontCollection()); +} + +/* +** Read the options specified in the ini file. +** +*/ +void MeterString::ReadOptions(ConfigParser& parser, const WCHAR* section) +{ + // Store the current font values so we know if the font needs to be updated + std::wstring oldFontFace = m_FontFace; + int oldFontSize = m_FontSize; + TEXTSTYLE oldStyle = m_Style; + + Meter::ReadOptions(parser, section); + + m_Color = parser.ReadColor(section, L"FontColor", Color::Black); + m_EffectColor = parser.ReadColor(section, L"FontEffectColor", Color::Black); + + m_Prefix = parser.ReadString(section, L"Prefix", L""); + m_Postfix = parser.ReadString(section, L"Postfix", L""); + m_Text = parser.ReadString(section, L"Text", L""); + + m_Percentual = parser.ReadBool(section, L"Percentual", false); + + int clipping = parser.ReadInt(section, L"ClipString", 0); + switch (clipping) + { + case 2: + m_ClipType = CLIP_AUTO; + + m_ClipStringW = parser.ReadInt(section, L"ClipStringW", -1); + m_ClipStringH = parser.ReadInt(section, L"ClipStringH", -1); + break; + + case 1: + m_ClipType = CLIP_ON; + break; + + case 0: + m_ClipType = CLIP_OFF; + break; + + default: + LogErrorF(this, L"ClipString=%s is not valid", clipping); + } + + m_FontFace = parser.ReadString(section, L"FontFace", L"Arial"); + if (m_FontFace.empty()) + { + m_FontFace = L"Arial"; + } + + m_FontSize = parser.ReadInt(section, L"FontSize", 10); + if (m_FontSize < 0) + { + m_FontSize = 10; + } + + m_NumOfDecimals = parser.ReadInt(section, L"NumOfDecimals", -1); + + m_Angle = (Gdiplus::REAL)parser.ReadFloat(section, L"Angle", 0.0); + + const std::wstring& autoscale = parser.ReadString(section, L"AutoScale", L"0"); + int autoscaleValue = _wtoi(autoscale.c_str()); + if (autoscaleValue == 0) + { + m_AutoScale = AUTOSCALE_OFF; + } + else + { + if (autoscale.find_last_of(L"kK") == std::wstring::npos) + { + m_AutoScale = (autoscaleValue == 2) ? AUTOSCALE_1000 : AUTOSCALE_1024; + } + else + { + m_AutoScale = (autoscaleValue == 2) ? AUTOSCALE_1000K : AUTOSCALE_1024K; + } + } + + const std::wstring& scale = parser.ReadString(section, L"Scale", L"1"); + m_NoDecimals = (scale.find(L'.') == std::wstring::npos); + m_Scale = parser.ParseDouble(scale.c_str(), 1); + + const WCHAR* hAlign = parser.ReadString(section, L"StringAlign", L"LEFT").c_str(); + const WCHAR* vAlign = nullptr; + if (_wcsnicmp(hAlign, L"LEFT", 4) == 0) + { + m_TextFormat->SetHorizontalAlignment(Gfx::HorizontalAlignment::Left); + vAlign = hAlign + 4; + } + else if (_wcsnicmp(hAlign, L"RIGHT", 5) == 0) + { + m_TextFormat->SetHorizontalAlignment(Gfx::HorizontalAlignment::Right); + vAlign = hAlign + 5; + } + else if (_wcsnicmp(hAlign, L"CENTER", 6) == 0) + { + m_TextFormat->SetHorizontalAlignment(Gfx::HorizontalAlignment::Center); + vAlign = hAlign + 6; + } + + if (!vAlign || _wcsicmp(vAlign, L"TOP") == 0) + { + m_TextFormat->SetVerticalAlignment(Gfx::VerticalAlignment::Top); + } + else if (_wcsicmp(vAlign, L"BOTTOM") == 0) + { + m_TextFormat->SetVerticalAlignment(Gfx::VerticalAlignment::Bottom); + } + else if (_wcsicmp(vAlign, L"CENTER") == 0) + { + m_TextFormat->SetVerticalAlignment(Gfx::VerticalAlignment::Center); + } + + const WCHAR* stringCase = parser.ReadString(section, L"StringCase", L"NONE").c_str(); + if (_wcsicmp(stringCase, L"NONE") == 0) + { + m_Case = TEXTCASE_NONE; + } + else if (_wcsicmp(stringCase, L"UPPER") == 0) + { + m_Case = TEXTCASE_UPPER; + } + else if (_wcsicmp(stringCase, L"LOWER") == 0) + { + m_Case = TEXTCASE_LOWER; + } + else if (_wcsicmp(stringCase, L"PROPER") == 0) + { + m_Case = TEXTCASE_PROPER; + } + else + { + LogErrorF(this, L"StringCase=%s is not valid", stringCase); + } + + const WCHAR* style = parser.ReadString(section, L"StringStyle", L"NORMAL").c_str(); + if (_wcsicmp(style, L"NORMAL") == 0) + { + m_Style = NORMAL; + } + else if (_wcsicmp(style, L"BOLD") == 0) + { + m_Style = BOLD; + } + else if (_wcsicmp(style, L"ITALIC") == 0) + { + m_Style = ITALIC; + } + else if (_wcsicmp(style, L"BOLDITALIC") == 0) + { + m_Style = BOLDITALIC; + } + else + { + LogErrorF(this, L"StringStyle=%s is not valid", style); + } + + const WCHAR* effect = parser.ReadString(section, L"StringEffect", L"NONE").c_str(); + if (_wcsicmp(effect, L"NONE") == 0) + { + m_Effect = EFFECT_NONE; + } + else if (_wcsicmp(effect, L"SHADOW") == 0) + { + m_Effect = EFFECT_SHADOW; + } + else if (_wcsicmp(effect, L"BORDER") == 0) + { + m_Effect = EFFECT_BORDER; + } + else + { + LogErrorF(this, L"StringEffect=%s is not valid", effect); + } + + if (m_Initialized && + (wcscmp(oldFontFace.c_str(), m_FontFace.c_str()) != 0 || + oldFontSize != m_FontSize || + oldStyle != m_Style)) + { + Initialize(); // Recreate the font + } +} + +/* +** Updates the value(s) from the measures. +** +*/ +bool MeterString::Update() +{ + if (Meter::Update()) + { + int decimals = (m_NumOfDecimals != -1) ? m_NumOfDecimals : (m_NoDecimals && (m_Percentual || m_AutoScale == AUTOSCALE_OFF)) ? 0 : 1; + + // Create the text + m_String = m_Prefix; + if (!m_Measures.empty()) + { + if (m_Text.empty()) + { + m_String += m_Measures[0]->GetStringOrFormattedValue( + m_AutoScale, m_Scale, decimals, m_Percentual); + } + else + { + std::wstring tmpText = m_Text; + ReplaceMeasures(tmpText, m_AutoScale, m_Scale, decimals, m_Percentual); + m_String += tmpText; + } + } + else + { + m_String += m_Text; + } + if (!m_Postfix.empty()) m_String += m_Postfix; + + switch (m_Case) + { + case TEXTCASE_UPPER: + StringToUpper(m_String); + break; + case TEXTCASE_LOWER: + StringToLower(m_String); + break; + case TEXTCASE_PROPER: + StringToProper(m_String); + break; + } + + for (size_t i = 0; i < m_String.length(); ++i) + { + if (m_String[i] == L'\u00A0' || // No-Break Space + m_String[i] == L'\u205F') // Medium Mathematical Space + { + // Ugly hack to make D2D render trailing spaces followed by a non-breaking space + // correctly. By default, D2D ignores all trailing whitespace. Both GDI+ and D2D, + // however, acknowledge the presense of the zero-width space (and give it a width + // of 0px), so we append the zero-width space after each non-breaking space. + ++i; + m_String.insert(i, 1, L'\u200B'); + } + else if (m_String[i] == L'\r') + { + // GDI+ seems to ignore carriage returns, so strip it entirely to make it behave + // similarly with D2D as well. + m_String.erase(i, 1); + --i; + } + } + + if (!m_WDefined || !m_HDefined) + { + // Calculate the text size + RectF rect; + if (DrawString(m_MeterWindow->GetCanvas(), &rect)) + { + m_W = (int)rect.Width + GetWidthPadding(); + m_H = (int)rect.Height + GetHeightPadding(); + } + else + { + m_W = 1; + m_H = 1; + } + } + + return true; + } + return false; +} + +/* +** Draws the meter on the double buffer +** +*/ +bool MeterString::Draw(Gfx::Canvas& canvas) +{ + if (!Meter::Draw(canvas)) return false; + + return DrawString(canvas, nullptr); +} + +/* +** Draws the string or calculates it's size +** +*/ +bool MeterString::DrawString(Gfx::Canvas& canvas, RectF* rect) +{ + if (!m_TextFormat->IsInitialized()) return false; + + LPCWSTR string = m_String.c_str(); + UINT stringLen = (UINT)m_String.length(); + + canvas.SetTextAntiAliasing(m_AntiAlias); + + m_TextFormat->SetTrimming( + m_ClipType == CLIP_ON || + (m_ClipType == CLIP_AUTO && (m_NeedsClipping || (m_WDefined && m_HDefined)))); + + Gdiplus::Rect meterRect = GetMeterRectPadding(); + + if (rect) + { + rect->X = (REAL)meterRect.X; + rect->Y = (REAL)meterRect.Y; + if (canvas.MeasureTextW(string, stringLen, *m_TextFormat, *rect) && + m_ClipType == CLIP_AUTO) + { + // Set initial clipping + m_NeedsClipping = false; + + REAL w, h; + bool updateSize = true; + + if (m_WDefined) + { + w = (REAL)meterRect.Width; + h = rect->Height; + m_NeedsClipping = true; + } + else if (m_HDefined) + { + if (m_ClipStringW == -1) + { + // Text does not fit in defined height, clip it + if (rect->Height > (REAL)meterRect.Height) + { + m_NeedsClipping = true; + } + + rect->Height = (REAL)meterRect.Height; + updateSize = false; + + } + else + { + if (rect->Width > (REAL)m_ClipStringW) + { + w = (REAL)m_ClipStringW; + m_NeedsClipping = true; + } + else + { + w = rect->Width; + } + + h = (REAL)meterRect.Height; + } + } + else + { + if (m_ClipStringW == -1) + { + // Clip text if already larger than ClipStringH + if (m_ClipStringH != -1 && rect->Height > (REAL)m_ClipStringH) + { + m_NeedsClipping = true; + rect->Height = (REAL)m_ClipStringH; + } + + updateSize = false; + } + else + { + if (rect->Width > (REAL)m_ClipStringW) + { + w = (REAL)m_ClipStringW; + m_NeedsClipping = true; + } + else + { + w = rect->Width; + } + + h = rect->Height; + } + } + + if (updateSize) + { + UINT lines = 0; + RectF layout((REAL)meterRect.X, (REAL)meterRect.Y, w, h); + if (canvas.MeasureTextLinesW(string, stringLen, *m_TextFormat, layout, lines) && + lines != 0) + { + rect->Width = w; + rect->Height = layout.Height; + + if (m_HDefined || (m_ClipStringH != -1 && rect->Height > (REAL)m_ClipStringH)) + { + rect->Height = m_HDefined ? (REAL)meterRect.Height : (REAL)m_ClipStringH; + } + } + } + } + } + else + { + RectF rcDest((REAL)meterRect.X, (REAL)meterRect.Y, (REAL)meterRect.Width, (REAL)meterRect.Height); + m_Rect = rcDest; + + if (m_Angle != 0.0f) + { + const float baseX = (float)Meter::GetX(); + canvas.RotateTransform(CONVERT_TO_DEGREES(m_Angle), baseX, (REAL)meterRect.Y, -baseX, -(REAL)meterRect.Y); + } + + if (m_Effect != EFFECT_NONE) + { + SolidBrush solidBrush(m_EffectColor); + RectF rcEffect(rcDest); + + if (m_Effect == EFFECT_SHADOW) + { + rcEffect.Offset(1, 1); + canvas.DrawTextW(string, (UINT)stringLen, *m_TextFormat, rcEffect, solidBrush); + } + else //if (m_Effect == EFFECT_BORDER) + { + rcEffect.Offset(0, 1); + canvas.DrawTextW(string, (UINT)stringLen, *m_TextFormat, rcEffect, solidBrush); + rcEffect.Offset(1, -1); + canvas.DrawTextW(string, (UINT)stringLen, *m_TextFormat, rcEffect, solidBrush); + rcEffect.Offset(-1, -1); + canvas.DrawTextW(string, (UINT)stringLen, *m_TextFormat, rcEffect, solidBrush); + rcEffect.Offset(-1, 1); + canvas.DrawTextW(string, (UINT)stringLen, *m_TextFormat, rcEffect, solidBrush); + } + } + + SolidBrush solidBrush(m_Color); + canvas.DrawTextW(string, (UINT)stringLen, *m_TextFormat, rcDest, solidBrush); + + if (m_Angle != 0.0f) + { + canvas.ResetTransform(); + } + } + + return true; +} + +/* +** Overridden method. The string meters need not to be bound on anything +** +*/ +void MeterString::BindMeasures(ConfigParser& parser, const WCHAR* section) +{ + if (BindPrimaryMeasure(parser, section, true)) + { + BindSecondaryMeasures(parser, section); + } +} + +/* +** Static helper to log all installed font families. +** +*/ +void MeterString::EnumerateInstalledFontFamilies() +{ + INT fontCount; + InstalledFontCollection fontCollection; + + if (Ok == fontCollection.GetLastStatus()) + { + fontCount = fontCollection.GetFamilyCount(); + if (fontCount > 0) + { + INT fontFound; + + FontFamily* fontFamilies = new FontFamily[fontCount]; + + if (Ok == fontCollection.GetFamilies(fontCount, fontFamilies, &fontFound)) + { + std::wstring fonts; + for (INT i = 0; i < fontCount; ++i) + { + WCHAR familyName[LF_FACESIZE]; + if (Ok == fontFamilies[i].GetFamilyName(familyName)) + { + fonts += familyName; + } + else + { + fonts += L"***"; + } + fonts += L", "; + } + LogWarning(fonts.c_str()); + } + else + { + LogError(L"Font enumeration: GetFamilies failed"); + } + + delete [] fontFamilies; + } + else + { + LogWarning(L"No installed fonts"); + } + } + else + { + LogError(L"Font enumeration: InstalledFontCollection failed"); + } +} + +void MeterString::InitializeStatic() +{ + if (GetRainmeter().GetDebug()) + { + LogDebug(L"------------------------------"); + LogDebug(L"* Font families:"); + EnumerateInstalledFontFamilies(); + LogDebug(L"------------------------------"); + } +} + +void MeterString::FinalizeStatic() +{ +} diff --git a/Library/MeterWindow.cpp b/Library/MeterWindow.cpp index 096109c7..2168af44 100644 --- a/Library/MeterWindow.cpp +++ b/Library/MeterWindow.cpp @@ -1,4922 +1,4922 @@ -/* - Copyright (C) 2001 Kimmo Pekkola - - 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 "MeterWindow.h" -#include "Rainmeter.h" -#include "TrayWindow.h" -#include "System.h" -#include "Error.h" -#include "Meter.h" -#include "Measure.h" -#include "DialogAbout.h" -#include "DialogManage.h" -#include "resource.h" -#include "Litestep.h" -#include "MeasureCalc.h" -#include "MeasureNet.h" -#include "MeasurePlugin.h" -#include "MeterButton.h" -#include "MeterString.h" -#include "TintedImage.h" -#include "MeasureScript.h" -#include "../Version.h" -#include "../Common/PathUtil.h" -#include "../Common/Gfx/Canvas.h" - -using namespace Gdiplus; - -#define SNAPDISTANCE 10 - -#define ZPOS_FLAGS (SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOSENDCHANGING) - -enum TIMER -{ - TIMER_METER = 1, - TIMER_MOUSE = 2, - TIMER_FADE = 3, - TIMER_TRANSITION = 4, - TIMER_DEACTIVATE = 5 -}; -enum INTERVAL -{ - INTERVAL_METER = 1000, - INTERVAL_MOUSE = 500, - INTERVAL_FADE = 10, - INTERVAL_TRANSITION = 100 -}; - -int MeterWindow::c_InstanceCount = 0; - -HINSTANCE MeterWindow::c_DwmInstance = nullptr; -decltype(DwmEnableBlurBehindWindow)* MeterWindow::c_DwmEnableBlurBehindWindow = nullptr; -decltype(DwmGetColorizationColor)* MeterWindow::c_DwmGetColorizationColor = nullptr; -decltype(DwmSetWindowAttribute)* MeterWindow::c_DwmSetWindowAttribute = nullptr; -decltype(DwmIsCompositionEnabled)* MeterWindow::c_DwmIsCompositionEnabled = nullptr; - -/* -** Constructor -** -*/ -MeterWindow::MeterWindow(const std::wstring& folderPath, const std::wstring& file) : m_FolderPath(folderPath), m_FileName(file), - m_Canvas(), - m_Background(), - m_BackgroundSize(), - m_Window(), - m_Mouse(this), - m_MouseOver(false), - m_MouseInputRegistered(false), - m_HasMouseScrollAction(false), - m_BackgroundMargins(), - m_DragMargins(), - m_WindowX(1, L'0'), - m_WindowY(1, L'0'), - m_WindowXScreen(1), - m_WindowYScreen(1), - m_WindowXScreenDefined(false), - m_WindowYScreenDefined(false), - m_WindowXFromRight(false), - m_WindowYFromBottom(false), - m_WindowXPercentage(false), - m_WindowYPercentage(false), - m_WindowW(), - m_WindowH(), - m_ScreenX(), - m_ScreenY(), - m_AnchorXFromRight(false), - m_AnchorYFromBottom(false), - m_AnchorXPercentage(false), - m_AnchorYPercentage(false), - m_AnchorScreenX(), - m_AnchorScreenY(), - m_WindowDraggable(true), - m_WindowUpdate(INTERVAL_METER), - m_TransitionUpdate(INTERVAL_TRANSITION), - m_ActiveTransition(false), - m_HasNetMeasures(false), - m_HasButtons(false), - m_WindowHide(HIDEMODE_NONE), - m_WindowStartHidden(false), - m_SavePosition(false), // Must be false - m_SnapEdges(true), - m_AlphaValue(255), - m_FadeDuration(250), - m_WindowZPosition(ZPOSITION_NORMAL), - m_DynamicWindowSize(false), - m_ClickThrough(false), - m_KeepOnScreen(true), - m_AutoSelectScreen(false), - m_UseD2D(true), - m_Dragging(false), - m_Dragged(false), - m_BackgroundMode(BGMODE_IMAGE), - m_SolidAngle(), - m_SolidBevel(BEVELTYPE_NONE), - m_Blur(false), - m_BlurMode(BLURMODE_NONE), - m_BlurRegion(), - m_FadeStartTime(), - m_FadeStartValue(), - m_FadeEndValue(), - m_TransparencyValue(), - m_State(STATE_INITIALIZING), - m_Hidden(false), - m_ResizeWindow(RESIZEMODE_NONE), - m_UpdateCounter(), - m_MouseMoveCounter(), - m_FontCollection(), - m_ToolTipHidden(false) -{ - if (!c_DwmInstance && IsWindowsVistaOrGreater() && - (c_DwmInstance = System::RmLoadLibrary(L"dwmapi.dll")) != nullptr) - { - c_DwmEnableBlurBehindWindow = - (decltype(c_DwmEnableBlurBehindWindow))GetProcAddress(c_DwmInstance, "DwmEnableBlurBehindWindow"); - c_DwmGetColorizationColor = - (decltype(c_DwmGetColorizationColor))GetProcAddress(c_DwmInstance, "DwmGetColorizationColor"); - c_DwmSetWindowAttribute = - (decltype(c_DwmSetWindowAttribute))GetProcAddress(c_DwmInstance, "DwmSetWindowAttribute"); - c_DwmIsCompositionEnabled = - (decltype(c_DwmIsCompositionEnabled))GetProcAddress(c_DwmInstance, "DwmIsCompositionEnabled"); - } - - if (c_InstanceCount == 0) - { - WNDCLASSEX wc = {sizeof(WNDCLASSEX)}; - wc.style = CS_NOCLOSE | CS_DBLCLKS; - wc.lpfnWndProc = InitialWndProc; - wc.hInstance = Rainmeter::GetInstance().GetModuleInstance(); - wc.hCursor = nullptr; // The cursor should be controlled by using SetCursor() when needed. - wc.lpszClassName = METERWINDOW_CLASS_NAME; - RegisterClassEx(&wc); - } - - ++c_InstanceCount; -} - -/* -** Destructor -** -*/ -MeterWindow::~MeterWindow() -{ - m_State = STATE_CLOSING; - - if (!m_OnCloseAction.empty()) - { - Rainmeter::GetInstance().ExecuteCommand(m_OnCloseAction.c_str(), this); - } - - Dispose(false); - - --c_InstanceCount; - - if (c_InstanceCount == 0) - { - UnregisterClass(METERWINDOW_CLASS_NAME, Rainmeter::GetInstance().GetModuleInstance()); - - if (c_DwmInstance) - { - FreeLibrary(c_DwmInstance); - c_DwmInstance = nullptr; - - c_DwmEnableBlurBehindWindow = nullptr; - c_DwmGetColorizationColor = nullptr; - c_DwmSetWindowAttribute = nullptr; - c_DwmIsCompositionEnabled = nullptr; - } - } -} - -/* -** Kills timers/hooks and disposes buffers -** -*/ -void MeterWindow::Dispose(bool refresh) -{ - // Kill the timer/hook - KillTimer(m_Window, TIMER_METER); - KillTimer(m_Window, TIMER_MOUSE); - KillTimer(m_Window, TIMER_FADE); - KillTimer(m_Window, TIMER_TRANSITION); - - m_FadeStartTime = 0; - - UnregisterMouseInput(); - m_HasMouseScrollAction = false; - - m_ActiveTransition = false; - - m_MouseOver = false; - SetMouseLeaveEvent(true); - - // Destroy the meters - for (auto j = m_Meters.begin(); j != m_Meters.end(); ++j) - { - delete (*j); - } - m_Meters.clear(); - - // Destroy the measures - for (auto i = m_Measures.begin(); i != m_Measures.end(); ++i) - { - delete (*i); - } - m_Measures.clear(); - - delete m_Background; - m_Background = nullptr; - - m_BackgroundSize.cx = m_BackgroundSize.cy = 0; - m_BackgroundName.clear(); - - if (m_BlurRegion) - { - DeleteObject(m_BlurRegion); - m_BlurRegion = nullptr; - } - - if (m_FontCollection) - { - delete m_FontCollection; - m_FontCollection = nullptr; - } - - if (!refresh) - { - if (m_Window) - { - DestroyWindow(m_Window); - m_Window = nullptr; - } - } - - delete m_Canvas; - m_Canvas = nullptr; -} - -/* -** Initializes the window, creates the class and the window. -** -*/ -void MeterWindow::Initialize() -{ - m_Window = CreateWindowEx( - WS_EX_TOOLWINDOW | WS_EX_LAYERED, - METERWINDOW_CLASS_NAME, - nullptr, - WS_POPUP, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - nullptr, - nullptr, - Rainmeter::GetInstance().GetModuleInstance(), - this); - - setlocale(LC_NUMERIC, "C"); - - // Mark the window to ignore the Aero peek - IgnoreAeroPeek(); - - Refresh(true, true); - if (!m_WindowStartHidden) - { - if (m_WindowHide == HIDEMODE_FADEOUT) - { - FadeWindow(0, 255); - } - else - { - FadeWindow(0, m_AlphaValue); - } - } -} - -/* -** Excludes this window from the Aero Peek. -** -*/ -void MeterWindow::IgnoreAeroPeek() -{ - if (c_DwmSetWindowAttribute) - { - BOOL bValue = TRUE; - c_DwmSetWindowAttribute(m_Window, DWMWA_EXCLUDED_FROM_PEEK, &bValue, sizeof(bValue)); - } -} - -/* -** Registers to receive WM_INPUT for the mouse events. -** -*/ -void MeterWindow::RegisterMouseInput() -{ - if (!m_MouseInputRegistered && m_HasMouseScrollAction) - { - RAWINPUTDEVICE rid; - rid.usUsagePage = 0x01; - rid.usUsage = 0x02; // HID mouse - rid.dwFlags = RIDEV_INPUTSINK; - rid.hwndTarget = m_Window; - if (RegisterRawInputDevices(&rid, 1, sizeof(rid))) - { - m_MouseInputRegistered = true; - } - } -} - -void MeterWindow::UnregisterMouseInput() -{ - if (m_MouseInputRegistered) - { - RAWINPUTDEVICE rid; - rid.usUsagePage = 0x01; - rid.usUsage = 0x02; // HID mouse - rid.dwFlags = RIDEV_REMOVE; - rid.hwndTarget = m_Window; - RegisterRawInputDevices(&rid, 1, sizeof(rid)); - m_MouseInputRegistered = false; - } -} - -void MeterWindow::AddWindowExStyle(LONG_PTR flag) -{ - LONG_PTR style = GetWindowLongPtr(m_Window, GWL_EXSTYLE); - if ((style & flag) == 0) - { - SetWindowLongPtr(m_Window, GWL_EXSTYLE, style | flag); - } -} - -void MeterWindow::RemoveWindowExStyle(LONG_PTR flag) -{ - LONG_PTR style = GetWindowLongPtr(m_Window, GWL_EXSTYLE); - if ((style & flag) != 0) - { - SetWindowLongPtr(m_Window, GWL_EXSTYLE, style & ~flag); - } -} - -/* -** Unloads the skin with delay to avoid crash (and for fade to complete). -** -*/ -void MeterWindow::Deactivate() -{ - if (m_State == STATE_CLOSING) return; - m_State = STATE_CLOSING; - - Rainmeter::GetInstance().RemoveMeterWindow(this); - Rainmeter::GetInstance().AddUnmanagedMeterWindow(this); - - HideFade(); - SetTimer(m_Window, TIMER_DEACTIVATE, m_FadeDuration + 50, nullptr); -} - -/* -** Rebuilds the skin. -** -*/ -void MeterWindow::Refresh(bool init, bool all) -{ - if (m_State == STATE_CLOSING) return; - m_State = STATE_REFRESHING; - - Rainmeter::GetInstance().SetCurrentParser(&m_Parser); - - LogNoticeF(this, L"Refreshing skin"); - - SetResizeWindowMode(RESIZEMODE_RESET); - - if (!init) - { - Dispose(true); - } - - ZPOSITION oldZPos = m_WindowZPosition; - - if (!ReadSkin()) - { - Rainmeter::GetInstance().DeactivateSkin(this, -1); - return; - } - - // Remove transparent flag - RemoveWindowExStyle(WS_EX_TRANSPARENT); - - m_Hidden = m_WindowStartHidden; - m_TransparencyValue = m_AlphaValue; - - Update(true); - - if (m_BlurMode == BLURMODE_NONE) - { - HideBlur(); - } - else - { - ShowBlur(); - } - - if (m_KeepOnScreen) - { - MapCoordsToScreen(m_ScreenX, m_ScreenY, m_WindowW, m_WindowH); - } - - SetWindowPos(m_Window, nullptr, m_ScreenX, m_ScreenY, m_WindowW, m_WindowH, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSENDCHANGING); - - ScreenToWindow(); - - if (init) - { - ChangeSingleZPos(m_WindowZPosition, all); - } - else if (all || oldZPos != m_WindowZPosition) - { - ChangeZPos(m_WindowZPosition, all); - } - - // Start the timers - if (m_WindowUpdate >= 0) - { - SetTimer(m_Window, TIMER_METER, m_WindowUpdate, nullptr); - } - - SetTimer(m_Window, TIMER_MOUSE, INTERVAL_MOUSE, nullptr); - - Rainmeter::GetInstance().SetCurrentParser(nullptr); - - m_State = STATE_RUNNING; - - if (!m_OnRefreshAction.empty()) - { - Rainmeter::GetInstance().ExecuteCommand(m_OnRefreshAction.c_str(), this); - } -} - -void MeterWindow::SetMouseLeaveEvent(bool cancel) -{ - if (!cancel && (!m_MouseOver || m_ClickThrough)) return; - - // Check whether the mouse event is set - TRACKMOUSEEVENT tme = {sizeof(TRACKMOUSEEVENT)}; - tme.hwndTrack = m_Window; - tme.dwFlags = TME_QUERY; - - if (TrackMouseEvent(&tme) != 0) - { - if (cancel) - { - if (tme.dwFlags == 0) return; - } - else - { - if (m_WindowDraggable) - { - if (tme.dwFlags == (TME_LEAVE | TME_NONCLIENT)) return; - } - else - { - if (tme.dwFlags == TME_LEAVE) return; - } - } - } - - tme.cbSize = sizeof(TRACKMOUSEEVENT); - tme.hwndTrack = m_Window; - - // Cancel the mouse event set before - tme.dwFlags |= TME_CANCEL; - TrackMouseEvent(&tme); - - if (cancel) return; - - // Set the mouse event - tme.dwFlags = TME_LEAVE; - if (m_WindowDraggable) - { - tme.dwFlags |= TME_NONCLIENT; - } - TrackMouseEvent(&tme); -} - -void MeterWindow::MapCoordsToScreen(int& x, int& y, int w, int h) -{ - const size_t numOfMonitors = System::GetMonitorCount(); // intentional - const std::vector& monitors = System::GetMultiMonitorInfo().monitors; - - // Check that the window is inside the screen area - POINT pt = {x + w / 2, y + h / 2}; - for (int i = 0; i < 5; ++i) - { - switch (i) - { - case 0: - // Use initial value - break; - - case 1: - pt.x = x; - pt.y = y; - break; - - case 2: - pt.x = x + w; - pt.y = y + h; - break; - - case 3: - pt.x = x; - pt.y = y + h; - break; - - case 4: - pt.x = x + w; - pt.y = y; - break; - } - - for (auto iter = monitors.cbegin(); iter != monitors.cend(); ++iter) - { - if (!(*iter).active) continue; - - const RECT r = (*iter).screen; - if (pt.x >= r.left && pt.x < r.right && pt.y >= r.top && pt.y < r.bottom) - { - x = min(x, r.right - w); - x = max(x, r.left); - y = min(y, r.bottom - h); - y = max(y, r.top); - return; - } - } - } - - // No monitor found for the window -> Use the default work area - const RECT r = monitors[System::GetMultiMonitorInfo().primary - 1].work; - x = min(x, r.right - w); - x = max(x, r.left); - y = min(y, r.bottom - h); - y = max(y, r.top); -} - -/* -** Moves the window to a new place (on the virtual screen) -** -*/ -void MeterWindow::MoveWindow(int x, int y) -{ - SetWindowPos(m_Window, nullptr, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); - - SavePositionIfAppropriate(); -} - -/* -** Sets the window's z-position -** -*/ -void MeterWindow::ChangeZPos(ZPOSITION zPos, bool all) -{ - HWND winPos = HWND_NOTOPMOST; - m_WindowZPosition = zPos; - - switch (zPos) - { - case ZPOSITION_ONTOPMOST: - case ZPOSITION_ONTOP: - winPos = HWND_TOPMOST; - break; - - case ZPOSITION_ONBOTTOM: - if (all) - { - if (System::GetShowDesktop()) - { - // Insert after the system window temporarily to keep order - winPos = System::GetWindow(); - } - else - { - // Insert after the helper window - winPos = System::GetHelperWindow(); - } - } - else - { - winPos = HWND_BOTTOM; - } - break; - - case ZPOSITION_NORMAL: - if (all || !Rainmeter::GetInstance().IsNormalStayDesktop()) break; - case ZPOSITION_ONDESKTOP: - if (System::GetShowDesktop()) - { - winPos = System::GetHelperWindow(); - - if (all) - { - // Insert after the helper window - } - else - { - // Find the "backmost" topmost window - while (winPos = ::GetNextWindow(winPos, GW_HWNDPREV)) - { - if (GetWindowLongPtr(winPos, GWL_EXSTYLE) & WS_EX_TOPMOST) - { - // Insert after the found window - if (0 != SetWindowPos(m_Window, winPos, 0, 0, 0, 0, ZPOS_FLAGS)) - { - break; - } - } - } - return; - } - } - else - { - if (all) - { - // Insert after the helper window - winPos = System::GetHelperWindow(); - } - else - { - winPos = HWND_BOTTOM; - } - } - break; - } - - SetWindowPos(m_Window, winPos, 0, 0, 0, 0, ZPOS_FLAGS); -} - -/* -** Sets the window's z-position in proper order. -** -*/ -void MeterWindow::ChangeSingleZPos(ZPOSITION zPos, bool all) -{ - if (zPos == ZPOSITION_NORMAL && Rainmeter::GetInstance().IsNormalStayDesktop() && (!all || System::GetShowDesktop())) - { - m_WindowZPosition = zPos; - - // Set window on top of all other ZPOSITION_ONDESKTOP, ZPOSITION_BOTTOM, and ZPOSITION_NORMAL windows - SetWindowPos(m_Window, System::GetBackmostTopWindow(), 0, 0, 0, 0, ZPOS_FLAGS); - - // Bring window on top of other application windows - BringWindowToTop(m_Window); - } - else - { - ChangeZPos(zPos, all); - } -} - -/* -** Runs the bang command with the given arguments. -** Correct number of arguments must be passed (or use Rainmeter::ExecuteBang). -*/ -void MeterWindow::DoBang(Bang bang, const std::vector& args) -{ - switch (bang) - { - case Bang::Refresh: - // Refresh needs to be delayed since it crashes if done during Update() - PostMessage(m_Window, WM_METERWINDOW_DELAYED_REFRESH, (WPARAM)nullptr, (LPARAM)nullptr); - break; - - case Bang::Redraw: - Redraw(); - break; - - case Bang::Update: - KillTimer(m_Window, TIMER_METER); // Kill timer temporarily - Update(false); - if (m_WindowUpdate >= 0) - { - SetTimer(m_Window, TIMER_METER, m_WindowUpdate, nullptr); - } - break; - - case Bang::ShowBlur: - ShowBlur(); - break; - - case Bang::HideBlur: - HideBlur(); - break; - - case Bang::ToggleBlur: - DoBang(IsBlur() ? Bang::HideBlur : Bang::ShowBlur, args); - break; - - case Bang::AddBlur: - ResizeBlur(args[0], RGN_OR); - if (IsBlur()) ShowBlur(); - break; - - case Bang::RemoveBlur: - ResizeBlur(args[0], RGN_DIFF); - if (IsBlur()) ShowBlur(); - break; - - case Bang::ToggleMeter: - ToggleMeter(args[0]); - break; - - case Bang::ShowMeter: - ShowMeter(args[0]); - break; - - case Bang::HideMeter: - HideMeter(args[0]); - break; - - case Bang::UpdateMeter: - UpdateMeter(args[0]); - break; - - case Bang::ToggleMeterGroup: - ToggleMeter(args[0], true); - break; - - case Bang::ShowMeterGroup: - ShowMeter(args[0], true); - break; - - case Bang::HideMeterGroup: - HideMeter(args[0], true); - break; - - case Bang::UpdateMeterGroup: - UpdateMeter(args[0], true); - break; - - case Bang::ToggleMeasure: - ToggleMeasure(args[0]); - break; - - case Bang::EnableMeasure: - EnableMeasure(args[0]); - break; - - case Bang::DisableMeasure: - DisableMeasure(args[0]); - break; - - case Bang::PauseMeasure: - PauseMeasure(args[0]); - break; - - case Bang::UnpauseMeasure: - UnpauseMeasure(args[0]); - break; - - case Bang::TogglePauseMeasure: - TogglePauseMeasure(args[0]); - break; - - case Bang::UpdateMeasure: - UpdateMeasure(args[0]); - DialogAbout::UpdateMeasures(this); - break; - - case Bang::DisableMeasureGroup: - DisableMeasure(args[0], true); - break; - - case Bang::ToggleMeasureGroup: - ToggleMeasure(args[0], true); - break; - - case Bang::EnableMeasureGroup: - EnableMeasure(args[0], true); - break; - - case Bang::PauseMeasureGroup: - PauseMeasure(args[0], true); - break; - - case Bang::UnpauseMeasureGroup: - UnpauseMeasure(args[0], true); - break; - - case Bang::TogglePauseMeasureGroup: - TogglePauseMeasure(args[0], true); - break; - - case Bang::UpdateMeasureGroup: - UpdateMeasure(args[0], true); - DialogAbout::UpdateMeasures(this); - break; - - case Bang::Show: - m_Hidden = false; - ShowWindow(m_Window, SW_SHOWNOACTIVATE); - UpdateWindowTransparency((m_WindowHide == HIDEMODE_FADEOUT) ? 255 : m_AlphaValue); - break; - - case Bang::Hide: - m_Hidden = true; - ShowWindow(m_Window, SW_HIDE); - break; - - case Bang::Toggle: - DoBang(m_Hidden ? Bang::Show : Bang::Hide, args); - break; - - case Bang::ShowFade: - ShowFade(); - break; - - case Bang::HideFade: - HideFade(); - break; - - case Bang::ToggleFade: - DoBang(m_Hidden ? Bang::ShowFade : Bang::HideFade, args); - break; - - case Bang::Move: - { - int x = m_Parser.ParseInt(args[0].c_str(), 0); - int y = m_Parser.ParseInt(args[1].c_str(), 0); - MoveWindow(x, y); - } - break; - - case Bang::ZPos: - SetWindowZPosition((ZPOSITION)m_Parser.ParseInt(args[0].c_str(), 0)); - break; - - case Bang::ClickThrough: - { - int f = m_Parser.ParseInt(args[0].c_str(), 0); - SetClickThrough((f == -1) ? !m_ClickThrough : f != 0); - } - break; - - case Bang::Draggable: - { - int f = m_Parser.ParseInt(args[0].c_str(), 0); - SetWindowDraggable((f == -1) ? !m_WindowDraggable : f != 0); - } - break; - - case Bang::SnapEdges: - { - int f = m_Parser.ParseInt(args[0].c_str(), 0); - SetSnapEdges((f == -1) ? !m_SnapEdges : f != 0); - } - break; - - case Bang::KeepOnScreen: - { - int f = m_Parser.ParseInt(args[0].c_str(), 0); - SetKeepOnScreen((f == -1) ? !m_KeepOnScreen : f != 0); - } - break; - - case Bang::SetTransparency: - { - const std::wstring& arg = args[0]; - m_AlphaValue = ConfigParser::ParseInt(arg.c_str(), 255); - m_AlphaValue = max(m_AlphaValue, 0); - m_AlphaValue = min(m_AlphaValue, 255); - UpdateWindowTransparency(m_AlphaValue); - } - break; - - case Bang::MoveMeter: - { - int x = m_Parser.ParseInt(args[0].c_str(), 0); - int y = m_Parser.ParseInt(args[1].c_str(), 0); - MoveMeter(args[2], x, y); - } - break; - - case Bang::CommandMeasure: - { - const std::wstring& measure = args[0]; - Measure* m = GetMeasure(measure); - if (m) - { - m->Command(args[1]); - } - else - { - LogWarningF(this, L"!CommandMeasure: [%s] not found", measure.c_str()); - } - } - break; - - case Bang::PluginBang: - { - std::wstring arg = args[0]; - std::wstring::size_type pos; - while ((pos = arg.find(L'"')) != std::wstring::npos) - { - arg.erase(pos, 1); - } - - std::wstring measure; - pos = arg.find(L' '); - if (pos != std::wstring::npos) - { - measure.assign(arg, 0, pos); - ++pos; - } - else - { - measure = arg; - } - arg.erase(0, pos); - - if (!measure.empty()) - { - Measure* m = GetMeasure(measure); - if (m) - { - m->Command(arg); - return; - } - - LogWarningF(this, L"!PluginBang: [%s] not found", measure.c_str()); - } - else - { - LogErrorF(this, L"!PluginBang: Invalid parameters"); - } - } - break; - - case Bang::SetVariable: - SetVariable(args[0], args[1]); - break; - - case Bang::SetOption: - SetOption(args[0], args[1], args[2], false); - break; - - case Bang::SetOptionGroup: - SetOption(args[0], args[1], args[2], true); - break; - - case Bang::SkinCustomMenu: - Rainmeter::GetInstance().ShowSkinCustomContextMenu(System::GetCursorPosition(), this); - break; - } -} - -/* -** Enables blurring of the window background (using Aero) -** -*/ -void MeterWindow::ShowBlur() -{ - if (c_DwmGetColorizationColor && c_DwmIsCompositionEnabled && c_DwmEnableBlurBehindWindow) - { - SetBlur(true); - - // Check that Aero and transparency is enabled - DWORD color; - BOOL opaque, enabled; - if (c_DwmGetColorizationColor(&color, &opaque) != S_OK) - { - opaque = TRUE; - } - if (c_DwmIsCompositionEnabled(&enabled) != S_OK) - { - enabled = FALSE; - } - if (opaque || !enabled) return; - - if (m_BlurMode == BLURMODE_FULL) - { - if (m_BlurRegion) DeleteObject(m_BlurRegion); - m_BlurRegion = CreateRectRgn(0, 0, GetW(), GetH()); - } - - BlurBehindWindow(TRUE); - } -} - -/* -** Disables Aero blur -** -*/ -void MeterWindow::HideBlur() -{ - if (c_DwmEnableBlurBehindWindow) - { - SetBlur(false); - - BlurBehindWindow(FALSE); - } -} - -/* -** Adds to or removes from blur region -** -*/ -void MeterWindow::ResizeBlur(const std::wstring& arg, int mode) -{ - if (IsWindowsVistaOrGreater()) - { - WCHAR* parseSz = _wcsdup(arg.c_str()); - int type, x, y, w = 0, h = 0; - - WCHAR* token = wcstok(parseSz, L","); - if (token) - { - while (token[0] == L' ') ++token; - type = m_Parser.ParseInt(token, 0); - - token = wcstok(nullptr, L","); - if (token) - { - while (token[0] == L' ') ++token; - x = m_Parser.ParseInt(token, 0); - - token = wcstok(nullptr, L","); - if (token) - { - while (token[0] == L' ') ++token; - y = m_Parser.ParseInt(token, 0); - - token = wcstok(nullptr, L","); - if (token) - { - while (token[0] == L' ') ++token; - w = m_Parser.ParseInt(token, 0); - - token = wcstok(nullptr, L","); - if (token) - { - while (token[0] == L' ') ++token; - h = m_Parser.ParseInt(token, 0); - } - } - } - } - } - - if (w && h) - { - HRGN tempRegion; - - switch (type) - { - case 1: - tempRegion = CreateRectRgn(x, y, w, h); - break; - - case 2: - token = wcstok(nullptr, L","); - if (token) - { - while (token[0] == L' ') ++token; - int r = m_Parser.ParseInt(token, 0); - tempRegion = CreateRoundRectRgn(x, y, w, h, r, r); - } - break; - - case 3: - tempRegion = CreateEllipticRgn(x, y, w, h); - break; - - default: // Unknown type - free(parseSz); - return; - } - - CombineRgn(m_BlurRegion, m_BlurRegion, tempRegion, mode); - DeleteObject(tempRegion); - } - free(parseSz); - } -} - -/* -** Helper function that compares the given name to section's name. -** -*/ -bool CompareName(const Section* section, const WCHAR* name, bool group) -{ - return (group) ? section->BelongsToGroup(name) : (_wcsicmp(section->GetName(), name) == 0); -} - -/* -** Shows the given meter -** -*/ -void MeterWindow::ShowMeter(const std::wstring& name, bool group) -{ - const WCHAR* meter = name.c_str(); - - std::vector::const_iterator j = m_Meters.begin(); - for ( ; j != m_Meters.end(); ++j) - { - if (CompareName((*j), meter, group)) - { - (*j)->Show(); - SetResizeWindowMode(RESIZEMODE_CHECK); // Need to recalculate the window size - if (!group) return; - } - } - - if (!group) LogErrorF(this, L"!ShowMeter: [%s] not found", meter); -} - -/* -** Hides the given meter -** -*/ -void MeterWindow::HideMeter(const std::wstring& name, bool group) -{ - const WCHAR* meter = name.c_str(); - - std::vector::const_iterator j = m_Meters.begin(); - for ( ; j != m_Meters.end(); ++j) - { - if (CompareName((*j), meter, group)) - { - (*j)->Hide(); - SetResizeWindowMode(RESIZEMODE_CHECK); // Need to recalculate the window size - if (!group) return; - } - } - - if (!group) LogErrorF(this, L"!HideMeter: [%s] not found", meter); -} - -/* -** Toggles the given meter -** -*/ -void MeterWindow::ToggleMeter(const std::wstring& name, bool group) -{ - const WCHAR* meter = name.c_str(); - - std::vector::const_iterator j = m_Meters.begin(); - for ( ; j != m_Meters.end(); ++j) - { - if (CompareName((*j), meter, group)) - { - if ((*j)->IsHidden()) - { - (*j)->Show(); - } - else - { - (*j)->Hide(); - } - SetResizeWindowMode(RESIZEMODE_CHECK); // Need to recalculate the window size - if (!group) return; - } - } - - if (!group) LogErrorF(this, L"!ToggleMeter: [%s] not found", meter); -} - -/* -** Moves the given meter -** -*/ -void MeterWindow::MoveMeter(const std::wstring& name, int x, int y) -{ - const WCHAR* meter = name.c_str(); - - std::vector::const_iterator j = m_Meters.begin(); - for ( ; j != m_Meters.end(); ++j) - { - if (CompareName((*j), meter, false)) - { - (*j)->SetX(x); - (*j)->SetY(y); - SetResizeWindowMode(RESIZEMODE_CHECK); // Need to recalculate the window size - return; - } - } - - LogErrorF(this, L"!MoveMeter: [%s] not found", meter); -} - -/* -** Updates the given meter -** -*/ -void MeterWindow::UpdateMeter(const std::wstring& name, bool group) -{ - const WCHAR* meter = name.c_str(); - bool all = false; - - if (!group && meter[0] == L'*' && meter[1] == L'\0') // Allow [!UpdateMeter *] - { - all = true; - group = true; - } - - bool bActiveTransition = false; - bool bContinue = true; - for (auto j = m_Meters.cbegin(); j != m_Meters.cend(); ++j) - { - if (all || (bContinue && CompareName((*j), meter, group))) - { - if (UpdateMeter((*j), bActiveTransition, true)) - { - (*j)->DoUpdateAction(); - } - - SetResizeWindowMode(RESIZEMODE_CHECK); // Need to recalculate the window size - if (!group) - { - bContinue = false; - if (bActiveTransition) break; - } - } - else - { - // Check for transitions - if (!bActiveTransition && (*j)->HasActiveTransition()) - { - bActiveTransition = true; - if (!group && !bContinue) break; - } - } - } - - // Post-updates - PostUpdate(bActiveTransition); - - if (!group && bContinue) LogErrorF(this, L"!UpdateMeter: [%s] not found", meter); -} - -/* -** Enables the given measure -** -*/ -void MeterWindow::EnableMeasure(const std::wstring& name, bool group) -{ - const WCHAR* measure = name.c_str(); - - std::vector::const_iterator i = m_Measures.begin(); - for ( ; i != m_Measures.end(); ++i) - { - if (CompareName((*i), measure, group)) - { - (*i)->Enable(); - if (!group) return; - } - } - - if (!group) LogErrorF(this, L"!EnableMeasure: [%s] not found", measure); -} - -/* -** Disables the given measure -** -*/ -void MeterWindow::DisableMeasure(const std::wstring& name, bool group) -{ - const WCHAR* measure = name.c_str(); - - std::vector::const_iterator i = m_Measures.begin(); - for ( ; i != m_Measures.end(); ++i) - { - if (CompareName((*i), measure, group)) - { - (*i)->Disable(); - if (!group) return; - } - } - - if (!group) LogErrorF(this, L"!DisableMeasure: [%s] not found", measure); -} - -/* -** Toggles the given measure -** -*/ -void MeterWindow::ToggleMeasure(const std::wstring& name, bool group) -{ - const WCHAR* measure = name.c_str(); - - std::vector::const_iterator i = m_Measures.begin(); - for ( ; i != m_Measures.end(); ++i) - { - if (CompareName((*i), measure, group)) - { - if ((*i)->IsDisabled()) - { - (*i)->Enable(); - } - else - { - (*i)->Disable(); - } - if (!group) return; - } - } - - if (!group) LogErrorF(this, L"!ToggleMeasure: [%s] not found", measure); -} - -/* -** Pauses the given measure -** -*/ -void MeterWindow::PauseMeasure(const std::wstring& name, bool group) -{ - const WCHAR* measure = name.c_str(); - - std::vector::const_iterator i = m_Measures.begin(); - for ( ; i != m_Measures.end(); ++i) - { - if (CompareName((*i), measure, group)) - { - (*i)->Pause(); - if (!group) return; - } - } - - if (!group) LogErrorF(this, L"!PauseMeasure: [%s] not found", measure); -} - -/* -** Unpauses the given measure -** -*/ -void MeterWindow::UnpauseMeasure(const std::wstring& name, bool group) -{ - const WCHAR* measure = name.c_str(); - - std::vector::const_iterator i = m_Measures.begin(); - for ( ; i != m_Measures.end(); ++i) - { - if (CompareName((*i), measure, group)) - { - (*i)->Unpause(); - if (!group) return; - } - } - - if (!group) LogErrorF(this, L"!UnpauseMeasure: [%s] not found", measure); -} - -/* -** Toggles the pause state of the given measure -** -*/ -void MeterWindow::TogglePauseMeasure(const std::wstring& name, bool group) -{ - const WCHAR* measure = name.c_str(); - - std::vector::const_iterator i = m_Measures.begin(); - for ( ; i != m_Measures.end(); ++i) - { - if (CompareName((*i), measure, group)) - { - if ((*i)->IsPaused()) - { - (*i)->Unpause(); - } - else - { - (*i)->Pause(); - } - if (!group) return; - } - } - - if (!group) LogErrorF(this, L"!TogglePauseMeasure: [%s] not found", measure); -} - -/* -** Updates the given measure -** -*/ -void MeterWindow::UpdateMeasure(const std::wstring& name, bool group) -{ - const WCHAR* measure = name.c_str(); - bool all = false; - - if (!group && measure[0] == L'*' && measure[1] == L'\0') // Allow [!UpdateMeasure *] - { - all = true; - group = true; - } - - bool bNetStats = m_HasNetMeasures; - for (auto i = m_Measures.cbegin(); i != m_Measures.cend(); ++i) - { - if (all || CompareName((*i), measure, group)) - { - if (bNetStats && (*i)->GetTypeID() == TypeID()) - { - MeasureNet::UpdateIFTable(); - MeasureNet::UpdateStats(); - bNetStats = false; - } - - if (UpdateMeasure((*i), true)) - { - (*i)->DoUpdateAction(); - (*i)->DoChangeAction(); - } - - if (!group) return; - } - } - - if (!group) LogErrorF(this, L"!UpdateMeasure: [%s] not found", measure); -} - -/* -** Sets variable to given value. -** -*/ -void MeterWindow::SetVariable(const std::wstring& variable, const std::wstring& value) -{ - double result; - if (m_Parser.ParseFormula(value, &result)) - { - WCHAR buffer[256]; - int len = _snwprintf_s(buffer, _TRUNCATE, L"%.5f", result); - Measure::RemoveTrailingZero(buffer, len); - - const std::wstring& resultString = buffer; - - m_Parser.SetVariable(variable, resultString); - } - else - { - m_Parser.SetVariable(variable, value); - } -} - -/* -** Changes the property of a meter or measure. -** -*/ -void MeterWindow::SetOption(const std::wstring& section, const std::wstring& option, const std::wstring& value, bool group) -{ - auto setValue = [&](Section* section, const std::wstring& option, const std::wstring& value) - { - // Force DynamicVariables temporarily (until next ReadOptions()). - section->SetDynamicVariables(true); - - if (value.empty()) - { - m_Parser.DeleteValue(section->GetOriginalName(), option); - } - else - { - m_Parser.SetValue(section->GetOriginalName(), option, value); - } - }; - - if (group) - { - for (auto j = m_Meters.begin(); j != m_Meters.end(); ++j) - { - if ((*j)->BelongsToGroup(section)) - { - setValue(*j, option, value); - } - } - - for (auto i = m_Measures.begin(); i != m_Measures.end(); ++i) - { - if ((*i)->BelongsToGroup(section)) - { - setValue(*i, option, value); - } - } - } - else - { - Meter* meter = GetMeter(section); - if (meter) - { - setValue(meter, option, value); - return; - } - - Measure* measure = GetMeasure(section); - if (measure) - { - setValue(measure, option, value); - return; - } - - // ContextTitle and ContextAction in [Rainmeter] are dynamic - if (_wcsicmp(section.c_str(), L"Rainmeter") == 0 && - _wcsnicmp(option.c_str(), L"Context", 7) == 0) - { - if (value.empty()) - { - m_Parser.DeleteValue(section, option); - } - else - { - m_Parser.SetValue(section, option, value); - } - } - - // Is it a style? - } -} - -/* -** Calculates the screen cordinates from the WindowX/Y options -** -*/ -void MeterWindow::WindowToScreen() -{ - std::wstring::size_type index, index2; - int pixel = 0; - float num; - int screenx, screeny, screenh, screenw; - - const int numOfMonitors = (int)System::GetMonitorCount(); - const MultiMonitorInfo& monitorsInfo = System::GetMultiMonitorInfo(); - const std::vector& monitors = monitorsInfo.monitors; - - // Clear position flags - m_WindowXScreen = m_WindowYScreen = monitorsInfo.primary; // Default to primary screen - m_WindowXScreenDefined = m_WindowYScreenDefined = false; - m_WindowXFromRight = m_WindowYFromBottom = false; // Default to from left/top - m_WindowXPercentage = m_WindowYPercentage = false; // Default to pixels - m_AnchorXFromRight = m_AnchorYFromBottom = false; - m_AnchorXPercentage = m_AnchorYPercentage = false; - - // --- Calculate AnchorScreenX --- - - index = m_AnchorX.find_first_not_of(L"0123456789."); - num = (float)_wtof(m_AnchorX.substr(0,index).c_str()); - index = m_AnchorX.find_last_of(L'%'); - if (index != std::wstring::npos) m_AnchorXPercentage = true; - index = m_AnchorX.find_last_of(L'R'); - if (index != std::wstring::npos) m_AnchorXFromRight = true; - if (m_AnchorXPercentage) //is a percentage - { - pixel = (int)(m_WindowW * num / 100.0f); - } - else - { - pixel = (int)num; - } - if (m_AnchorXFromRight) //measure from right - { - pixel = m_WindowW - pixel; - } - else - { - //pixel = pixel; - } - m_AnchorScreenX = pixel; - - // --- Calculate AnchorScreenY --- - - index = m_AnchorY.find_first_not_of(L"0123456789."); - num = (float)_wtof(m_AnchorY.substr(0,index).c_str()); - index = m_AnchorY.find_last_of(L'%'); - if (index != std::wstring::npos) m_AnchorYPercentage = true; - index = m_AnchorY.find_last_of(L'R'); - if (index != std::wstring::npos) m_AnchorYFromBottom = true; - if (m_AnchorYPercentage) //is a percentage - { - pixel = (int)(m_WindowH * num / 100.0f); - } - else - { - pixel = (int)num; - } - if (m_AnchorYFromBottom) //measure from bottom - { - pixel = m_WindowH - pixel; - } - else - { - //pixel = pixel; - } - m_AnchorScreenY = pixel; - - // --- Calculate ScreenX --- - - index = m_WindowX.find_first_not_of(L"-0123456789."); - num = (float)_wtof(m_WindowX.substr(0,index).c_str()); - index = m_WindowX.find_last_of(L'%'); - index2 = m_WindowX.find_last_of(L'#'); // for ignoring the non-replaced variables such as "#WORKAREAX@n#" - if (index != std::wstring::npos && (index2 == std::wstring::npos || index2 < index)) - { - m_WindowXPercentage = true; - } - index = m_WindowX.find_last_of(L'R'); - if (index != std::wstring::npos && (index2 == std::wstring::npos || index2 < index)) - { - m_WindowXFromRight = true; - } - index = m_WindowX.find_last_of(L'@'); - if (index != std::wstring::npos && (index2 == std::wstring::npos || index2 < index)) - { - index = index + 1; - index2 = m_WindowX.find_first_not_of(L"0123456789", index); - - std::wstring screenStr = m_WindowX.substr(index, (index2 != std::wstring::npos) ? index2 - index : std::wstring::npos); - if (!screenStr.empty()) - { - int screenIndex = _wtoi(screenStr.c_str()); - if (screenIndex >= 0 && (screenIndex == 0 || screenIndex <= numOfMonitors && monitors[screenIndex - 1].active)) - { - m_WindowXScreen = screenIndex; - m_WindowXScreenDefined = true; - m_WindowYScreen = m_WindowXScreen; //Default to X and Y on same screen if not overridden on WindowY - m_WindowYScreenDefined = true; - } - } - } - if (m_WindowXScreen == 0) - { - screenx = monitorsInfo.vsL; - screenw = monitorsInfo.vsW; - } - else - { - screenx = monitors[m_WindowXScreen - 1].screen.left; - screenw = monitors[m_WindowXScreen - 1].screen.right - monitors[m_WindowXScreen - 1].screen.left; - } - if (m_WindowXPercentage) //is a percentage - { - pixel = (int)(screenw * num / 100.0f); - } - else - { - pixel = (int)num; - } - if (m_WindowXFromRight) //measure from right - { - pixel = screenx + (screenw - pixel); - } - else - { - pixel = screenx + pixel; - } - m_ScreenX = pixel - m_AnchorScreenX; - - // --- Calculate ScreenY --- - - index = m_WindowY.find_first_not_of(L"-0123456789."); - num = (float)_wtof(m_WindowY.substr(0,index).c_str()); - index = m_WindowY.find_last_of(L'%'); - index2 = m_WindowX.find_last_of(L'#'); // for ignoring the non-replaced variables such as "#WORKAREAY@n#" - if (index != std::wstring::npos && (index2 == std::wstring::npos || index2 < index)) - { - m_WindowYPercentage = true; - } - index = m_WindowY.find_last_of(L'B'); - if (index != std::wstring::npos && (index2 == std::wstring::npos || index2 < index)) - { - m_WindowYFromBottom = true; - } - index = m_WindowY.find_last_of(L'@'); - if (index != std::wstring::npos && (index2 == std::wstring::npos || index2 < index)) - { - index = index + 1; - index2 = m_WindowY.find_first_not_of(L"0123456789", index); - - std::wstring screenStr = m_WindowY.substr(index, (index2 != std::wstring::npos) ? index2 - index : std::wstring::npos); - if (!screenStr.empty()) - { - int screenIndex = _wtoi(screenStr.c_str()); - if (screenIndex >= 0 && (screenIndex == 0 || screenIndex <= numOfMonitors && monitors[screenIndex - 1].active)) - { - m_WindowYScreen = screenIndex; - m_WindowYScreenDefined = true; - } - } - } - if (m_WindowYScreen == 0) - { - screeny = monitorsInfo.vsT; - screenh = monitorsInfo.vsH; - } - else - { - screeny = monitors[m_WindowYScreen - 1].screen.top; - screenh = monitors[m_WindowYScreen - 1].screen.bottom - monitors[m_WindowYScreen - 1].screen.top; - } - if (m_WindowYPercentage) //is a percentage - { - pixel = (int)(screenh * num / 100.0f); - } - else - { - pixel = (int)num; - } - if (m_WindowYFromBottom) //measure from right - { - pixel = screeny + (screenh - pixel); - } - else - { - pixel = screeny + pixel; - } - m_ScreenY = pixel - m_AnchorScreenY; -} - -/* ScreenToWindow -** -** Calculates the WindowX/Y cordinates from the ScreenX/Y -** -*/ -void MeterWindow::ScreenToWindow() -{ - WCHAR buffer[256]; - int pixel = 0; - float num; - int screenx, screeny, screenh, screenw; - - const size_t numOfMonitors = System::GetMonitorCount(); - const MultiMonitorInfo& monitorsInfo = System::GetMultiMonitorInfo(); - const std::vector& monitors = monitorsInfo.monitors; - - // Correct to auto-selected screen - if (m_AutoSelectScreen) - { - RECT rect = {m_ScreenX, m_ScreenY, m_ScreenX + m_WindowW, m_ScreenY + m_WindowH}; - HMONITOR hMonitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST); - - if (hMonitor != nullptr) - { - int screenIndex = 1; - for (auto iter = monitors.cbegin(); iter != monitors.cend(); ++iter, ++screenIndex) - { - if ((*iter).active && (*iter).handle == hMonitor) - { - bool reset = (!m_WindowXScreenDefined || !m_WindowYScreenDefined || - m_WindowXScreen != screenIndex || m_WindowYScreen != screenIndex); - - m_WindowXScreen = m_WindowYScreen = screenIndex; - m_WindowXScreenDefined = m_WindowYScreenDefined = true; - - if (reset) - { - m_Parser.ResetMonitorVariables(this); // Set present monitor variables - } - break; - } - } - } - } - - // --- Calculate WindowX --- - - if (m_WindowXScreen == 0) - { - screenx = monitorsInfo.vsL; - screenw = monitorsInfo.vsW; - } - else - { - screenx = monitors[m_WindowXScreen - 1].screen.left; - screenw = monitors[m_WindowXScreen - 1].screen.right - monitors[m_WindowXScreen - 1].screen.left; - } - if (m_WindowXFromRight) - { - pixel = (screenx + screenw) - m_ScreenX; - pixel -= m_AnchorScreenX; - } - else - { - pixel = m_ScreenX - screenx; - pixel += m_AnchorScreenX; - } - if (m_WindowXPercentage) - { - num = 100.0f * (float)pixel / (float)screenw; - _snwprintf_s(buffer, _TRUNCATE, L"%.5f%%", num); - } - else - { - _itow_s(pixel, buffer, 10); - } - if (m_WindowXFromRight) - { - wcscat_s(buffer, L"R"); - } - if (m_WindowXScreenDefined) - { - _snwprintf_s(buffer, _TRUNCATE, L"%s@%i", buffer, m_WindowXScreen); - } - m_WindowX = buffer; - - // --- Calculate WindowY --- - - if (m_WindowYScreen == 0) - { - screeny = monitorsInfo.vsT; - screenh = monitorsInfo.vsH; - } - else - { - screeny = monitors[m_WindowYScreen - 1].screen.top; - screenh = monitors[m_WindowYScreen - 1].screen.bottom - monitors[m_WindowYScreen - 1].screen.top; - } - if (m_WindowYFromBottom) - { - pixel = (screeny + screenh) - m_ScreenY; - pixel -= m_AnchorScreenY; - } - else - { - pixel = m_ScreenY - screeny; - pixel += m_AnchorScreenY; - } - if (m_WindowYPercentage) - { - num = 100.0f * (float)pixel / (float)screenh; - _snwprintf_s(buffer, _TRUNCATE, L"%.5f%%", num); - } - else - { - _itow_s(pixel, buffer, 10); - } - if (m_WindowYFromBottom) - { - wcscat_s(buffer, L"B"); - } - if (m_WindowYScreenDefined) - { - _snwprintf_s(buffer, _TRUNCATE, L"%s@%i", buffer, m_WindowYScreen); - } - m_WindowY = buffer; -} - -/* -** Reads the skin options from Rainmeter.ini -** -*/ -void MeterWindow::ReadOptions() -{ - WCHAR buffer[32]; - - const WCHAR* section = m_FolderPath.c_str(); - ConfigParser parser; - parser.Initialize(Rainmeter::GetInstance().GetIniFile(), nullptr, section); - - INT writeFlags = 0; - auto addWriteFlag = [&](INT flag) - { - if (parser.GetLastDefaultUsed()) - { - writeFlags |= flag; - } - }; - - // Check if the window position should be read as a formula - double value; - m_WindowX = parser.ReadString(section, L"WindowX", L"0"); - addWriteFlag(OPTION_POSITION); - if (parser.ParseFormula(m_WindowX, &value)) - { - _itow_s((int)value, buffer, 10); - m_WindowX = buffer; - } - m_WindowY = parser.ReadString(section, L"WindowY", L"0"); - addWriteFlag(OPTION_POSITION); - if (parser.ParseFormula(m_WindowY, &value)) - { - _itow_s((int)value, buffer, 10); - m_WindowY = buffer; - } - - m_AnchorX = parser.ReadString(section, L"AnchorX", L"0"); - m_AnchorY = parser.ReadString(section, L"AnchorY", L"0"); - - int zPos = parser.ReadInt(section, L"AlwaysOnTop", ZPOSITION_NORMAL); - addWriteFlag(OPTION_ALWAYSONTOP); - m_WindowZPosition = (zPos >= ZPOSITION_ONDESKTOP && zPos <= ZPOSITION_ONTOPMOST) ? (ZPOSITION)zPos : ZPOSITION_NORMAL; - - int hideMode = parser.ReadInt(section, L"HideOnMouseOver", HIDEMODE_NONE); - m_WindowHide = (hideMode >= HIDEMODE_NONE && hideMode <= HIDEMODE_FADEOUT) ? (HIDEMODE)hideMode : HIDEMODE_NONE; - - m_WindowDraggable = parser.ReadBool(section, L"Draggable", true); - addWriteFlag(OPTION_DRAGGABLE); - - m_SnapEdges = parser.ReadBool(section, L"SnapEdges", true); - addWriteFlag(OPTION_SNAPEDGES); - - m_ClickThrough = parser.ReadBool(section, L"ClickThrough", false); - addWriteFlag(OPTION_CLICKTHROUGH); - - m_KeepOnScreen = parser.ReadBool(section, L"KeepOnScreen", true); - addWriteFlag(OPTION_KEEPONSCREEN); - - m_UseD2D = parser.ReadBool(section, L"UseD2D", true); - m_SavePosition = parser.ReadBool(section, L"SavePosition", true); - m_WindowStartHidden = parser.ReadBool(section, L"StartHidden", false); - m_AutoSelectScreen = parser.ReadBool(section, L"AutoSelectScreen", false); - - m_AlphaValue = parser.ReadInt(section, L"AlphaValue", 255); - m_AlphaValue = max(m_AlphaValue, 0); - m_AlphaValue = min(m_AlphaValue, 255); - - m_FadeDuration = parser.ReadInt(section, L"FadeDuration", 250); - - m_SkinGroup = parser.ReadString(section, L"Group", L""); - - if (writeFlags != 0) - { - WriteOptions(writeFlags); - } - - // Set WindowXScreen/WindowYScreen temporarily - WindowToScreen(); -} - -/* -** Writes the specified options to Rainmeter.ini -** -*/ -void MeterWindow::WriteOptions(INT setting) -{ - const WCHAR* iniFile = Rainmeter::GetInstance().GetIniFile().c_str(); - - if (*iniFile) - { - WCHAR buffer[32]; - const WCHAR* section = m_FolderPath.c_str(); - - if (setting != OPTION_ALL) - { - DialogManage::UpdateSkins(this); - } - - if (setting & OPTION_POSITION) - { - ScreenToWindow(); - - // If position needs to be save, do so. - if (m_SavePosition) - { - WritePrivateProfileString(section, L"WindowX", m_WindowX.c_str(), iniFile); - WritePrivateProfileString(section, L"WindowY", m_WindowY.c_str(), iniFile); - } - - if (setting == OPTION_POSITION) return; - } - - if (setting & OPTION_ALPHAVALUE) - { - _itow_s(m_AlphaValue, buffer, 10); - WritePrivateProfileString(section, L"AlphaValue", buffer, iniFile); - } - - if (setting & OPTION_FADEDURATION) - { - _itow_s(m_FadeDuration, buffer, 10); - WritePrivateProfileString(section, L"FadeDuration", buffer, iniFile); - } - - if (setting & OPTION_CLICKTHROUGH) - { - WritePrivateProfileString(section, L"ClickThrough", m_ClickThrough ? L"1" : L"0", iniFile); - } - - if (setting & OPTION_DRAGGABLE) - { - WritePrivateProfileString(section, L"Draggable", m_WindowDraggable ? L"1" : L"0", iniFile); - } - - if (setting & OPTION_HIDEONMOUSEOVER) - { - _itow_s(m_WindowHide, buffer, 10); - WritePrivateProfileString(section, L"HideOnMouseOver", buffer, iniFile); - } - - if (setting & OPTION_SAVEPOSITION) - { - WritePrivateProfileString(section, L"SavePosition", m_SavePosition ? L"1" : L"0", iniFile); - } - - if (setting & OPTION_SNAPEDGES) - { - WritePrivateProfileString(section, L"SnapEdges", m_SnapEdges ? L"1" : L"0", iniFile); - } - - if (setting & OPTION_KEEPONSCREEN) - { - WritePrivateProfileString(section, L"KeepOnScreen", m_KeepOnScreen ? L"1" : L"0", iniFile); - } - - if (setting & OPTION_AUTOSELECTSCREEN) - { - WritePrivateProfileString(section, L"AutoSelectScreen", m_AutoSelectScreen ? L"1" : L"0", iniFile); - } - - if (setting & OPTION_ALWAYSONTOP) - { - _itow_s(m_WindowZPosition, buffer, 10); - WritePrivateProfileString(section, L"AlwaysOnTop", buffer, iniFile); - } - - if (setting & OPTION_USED2D) - { - WritePrivateProfileString(section, L"UseD2D", m_UseD2D ? L"1" : L"0", iniFile); - } - } -} - -/* -** Reads the skin file and creates the meters and measures. -** -*/ -bool MeterWindow::ReadSkin() -{ - WCHAR buffer[128]; - - std::wstring iniFile = GetFilePath(); - - // Verify whether the file exists - if (_waccess(iniFile.c_str(), 0) == -1) - { - std::wstring message = GetFormattedString(ID_STR_UNABLETOREFRESHSKIN, m_FolderPath.c_str(), m_FileName.c_str()); - Rainmeter::GetInstance().ShowMessage(m_Window, message.c_str(), MB_OK | MB_ICONEXCLAMATION); - return false; - } - - std::wstring resourcePath = GetResourcesPath(); - bool hasResourcesFolder = (_waccess(resourcePath.c_str(), 0) == 0); - - // Read options from Rainmeter.ini. - ReadOptions(); - - m_Parser.Initialize(iniFile, this, nullptr, &resourcePath); - - m_Canvas = Gfx::Canvas::Create( - m_UseD2D && Rainmeter::GetInstance().GetUseD2D() ? Gfx::Renderer::PreferD2D : Gfx::Renderer::GDIP); - m_Canvas->SetAccurateText(m_Parser.ReadBool(L"Rainmeter", L"AccurateText", false)); - - // Gotta have some kind of buffer during initialization - CreateDoubleBuffer(1, 1); - - // Check the version - UINT appVersion = m_Parser.ReadUInt(L"Rainmeter", L"AppVersion", 0); - if (appVersion > RAINMETER_VERSION) - { - if (appVersion % 1000 != 0) - { - _snwprintf_s(buffer, _TRUNCATE, L"%u.%u.%u", appVersion / 1000000, (appVersion / 1000) % 1000, appVersion % 1000); - } - else - { - _snwprintf_s(buffer, _TRUNCATE, L"%u.%u", appVersion / 1000000, (appVersion / 1000) % 1000); - } - - std::wstring text = GetFormattedString(ID_STR_NEWVERSIONREQUIRED, m_FolderPath.c_str(), m_FileName.c_str(), buffer); - Rainmeter::GetInstance().ShowMessage(m_Window, text.c_str(), MB_OK | MB_ICONEXCLAMATION); - return false; - } - - // Initialize window variables - SetWindowPositionVariables(m_ScreenX, m_ScreenY); - SetWindowSizeVariables(0, 0); - - // Global settings - const std::wstring& group = m_Parser.ReadString(L"Rainmeter", L"Group", L""); - if (!group.empty()) - { - m_SkinGroup += L'|'; - m_SkinGroup += group; - } - InitializeGroup(m_SkinGroup); - - static const RECT defMargins = {0}; - m_BackgroundMargins = m_Parser.ReadRECT(L"Rainmeter", L"BackgroundMargins", defMargins); - m_DragMargins = m_Parser.ReadRECT(L"Rainmeter", L"DragMargins", defMargins); - - m_BackgroundMode = (BGMODE)m_Parser.ReadInt(L"Rainmeter", L"BackgroundMode", BGMODE_IMAGE); - m_SolidBevel = (BEVELTYPE)m_Parser.ReadInt(L"Rainmeter", L"BevelType", BEVELTYPE_NONE); - - m_SolidColor = m_Parser.ReadColor(L"Rainmeter", L"SolidColor", Color::Gray); - m_SolidColor2 = m_Parser.ReadColor(L"Rainmeter", L"SolidColor2", m_SolidColor.GetValue()); - m_SolidAngle = (Gdiplus::REAL)m_Parser.ReadFloat(L"Rainmeter", L"GradientAngle", 0.0); - - m_DynamicWindowSize = m_Parser.ReadBool(L"Rainmeter", L"DynamicWindowSize", false); - - if (m_BackgroundMode == BGMODE_IMAGE || m_BackgroundMode == BGMODE_SCALED_IMAGE || m_BackgroundMode == BGMODE_TILED_IMAGE) - { - m_BackgroundName = m_Parser.ReadString(L"Rainmeter", L"Background", L""); - if (!m_BackgroundName.empty()) - { - MakePathAbsolute(m_BackgroundName); - } - else - { - m_BackgroundMode = BGMODE_COPY; - } - } - - m_Mouse.ReadOptions(m_Parser, L"Rainmeter"); - - m_OnRefreshAction = m_Parser.ReadString(L"Rainmeter", L"OnRefreshAction", L"", false); - m_OnCloseAction = m_Parser.ReadString(L"Rainmeter", L"OnCloseAction", L"", false); - m_OnFocusAction = m_Parser.ReadString(L"Rainmeter", L"OnFocusAction", L"", false); - m_OnUnfocusAction = m_Parser.ReadString(L"Rainmeter", L"OnUnfocusAction", L"", false); - m_OnUpdateAction = m_Parser.ReadString(L"Rainmeter", L"OnUpdateAction", L"", false); - m_OnWakeAction = m_Parser.ReadString(L"Rainmeter", L"OnWakeAction", L"", false); - - m_WindowUpdate = m_Parser.ReadInt(L"Rainmeter", L"Update", INTERVAL_METER); - m_TransitionUpdate = m_Parser.ReadInt(L"Rainmeter", L"TransitionUpdate", INTERVAL_TRANSITION); - m_ToolTipHidden = m_Parser.ReadBool(L"Rainmeter", L"ToolTipHidden", false); - - if (IsWindowsVistaOrGreater()) - { - if (m_Parser.ReadBool(L"Rainmeter", L"Blur", false)) - { - const WCHAR* blurRegion = m_Parser.ReadString(L"Rainmeter", L"BlurRegion", L"", false).c_str(); - - if (*blurRegion) - { - m_BlurMode = BLURMODE_REGION; - m_BlurRegion = CreateRectRgn(0, 0, 0, 0); // Create empty region - int i = 1; - - do - { - ResizeBlur(blurRegion, RGN_OR); - - // Check for BlurRegion2, BlurRegion3, etc. - _snwprintf_s(buffer, _TRUNCATE, L"BlurRegion%i", ++i); - blurRegion = m_Parser.ReadString(L"Rainmeter", buffer, L"").c_str(); - } - while (*blurRegion); - } - else - { - m_BlurMode = BLURMODE_FULL; - } - } - else - { - m_BlurMode = BLURMODE_NONE; - } - } - - // Load fonts in Resources folder - if (hasResourcesFolder) - { - WIN32_FIND_DATA fd; - resourcePath += L"Fonts\\*"; - - HANDLE find = FindFirstFileEx( - resourcePath.c_str(), - (IsWindows7OrGreater()) ? FindExInfoBasic : FindExInfoStandard, - &fd, - FindExSearchNameMatch, - nullptr, - 0); - - if (find != INVALID_HANDLE_VALUE) - { - m_FontCollection = m_Canvas->CreateFontCollection(); - - do - { - if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) - { - std::wstring file(resourcePath, 0, resourcePath.length() - 1); - file += fd.cFileName; - if (!m_FontCollection->AddFile(file.c_str())) - { - LogErrorF(this, L"Unable to load font: %s", file.c_str()); - } - } - } - while (FindNextFile(find, &fd)); - - FindClose(find); - } - } - - // Load local fonts - const WCHAR* localFont = m_Parser.ReadString(L"Rainmeter", L"LocalFont", L"").c_str(); - if (*localFont) - { - if (!m_FontCollection) - { - m_FontCollection = m_Canvas->CreateFontCollection(); - } - - int i = 1; - do - { - // Try program folder first - std::wstring szFontFile = Rainmeter::GetInstance().GetPath() + L"Fonts\\"; - szFontFile += localFont; - if (!m_FontCollection->AddFile(szFontFile.c_str())) - { - szFontFile = localFont; - MakePathAbsolute(szFontFile); - if (!m_FontCollection->AddFile(szFontFile.c_str())) - { - LogErrorF(this, L"Unable to load font: %s", localFont); - } - } - - // Check for LocalFont2, LocalFont3, etc. - _snwprintf_s(buffer, _TRUNCATE, L"LocalFont%i", ++i); - localFont = m_Parser.ReadString(L"Rainmeter", buffer, L"").c_str(); - } - while (*localFont); - } - - // Create all meters and measures. The meters and measures are not initialized in this loop - // to avoid errors caused by referencing nonexistent [sections] in the options. - m_HasNetMeasures = false; - m_HasButtons = false; - Meter* prevMeter = nullptr; - for (auto iter = m_Parser.GetSections().cbegin(); iter != m_Parser.GetSections().cend(); ++iter) - { - const WCHAR* section = (*iter).c_str(); - - if (_wcsicmp(L"Rainmeter", section) != 0 && - _wcsicmp(L"Variables", section) != 0 && - _wcsicmp(L"Metadata", section) != 0) - { - const std::wstring& measureName = m_Parser.ReadString(section, L"Measure", L"", false); - if (!measureName.empty()) - { - Measure* measure = Measure::Create(measureName.c_str(), this, section); - if (measure) - { - m_Measures.push_back(measure); - m_Parser.AddMeasure(measure); - - if (measure->GetTypeID() == TypeID()) - { - m_HasNetMeasures = true; - } - } - - continue; - } - - const std::wstring& meterName = m_Parser.ReadString(section, L"Meter", L"", false); - if (!meterName.empty()) - { - // It's a meter - Meter* meter = Meter::Create(meterName.c_str(), this, section); - if (meter) - { - m_Meters.push_back(meter); - meter->SetRelativeMeter(prevMeter); - - if (meter->GetTypeID() == TypeID()) - { - m_HasButtons = true; - } - - prevMeter = meter; - } - - continue; - } - } - } - - if (m_Meters.empty()) - { - std::wstring text = GetFormattedString(ID_STR_NOMETERSINSKIN, m_FolderPath.c_str(), m_FileName.c_str()); - Rainmeter::GetInstance().ShowMessage(m_Window, text.c_str(), MB_OK | MB_ICONEXCLAMATION); - return false; - } - - // Read measure options. This is done before the meters to ensure that e.g. Substitute is used - // when the meters get the value of the measure. The measures cannot be initialized yet as som - // measures (e.g. Script) except that the meters are ready when calling Initialize(). - for (auto iter = m_Measures.cbegin(); iter != m_Measures.cend(); ++iter) - { - Measure* measure = *iter; - measure->ReadOptions(m_Parser); - } - - // Initialize meters. - for (auto iter = m_Meters.cbegin(); iter != m_Meters.cend(); ++iter) - { - Meter* meter = *iter; - meter->ReadOptions(m_Parser); - meter->Initialize(); - - if (!meter->GetToolTipText().empty()) - { - meter->CreateToolTip(this); - } - } - - // Initialize measures. - for (auto iter = m_Measures.cbegin(); iter != m_Measures.cend(); ++iter) - { - Measure* measure = *iter; - measure->Initialize(); - } - - // Set window size (and CURRENTCONFIGWIDTH/HEIGHT) temporarily - for (auto iter = m_Meters.cbegin(); iter != m_Meters.cend(); ++iter) - { - bool bActiveTransition = true; // Do not track the change of ActiveTransition - UpdateMeter(*iter, bActiveTransition, true); - } - ResizeWindow(true); - - return true; -} - -/* -** Changes the size of the window and re-adjusts the background -*/ -bool MeterWindow::ResizeWindow(bool reset) -{ - int w = m_BackgroundMargins.left; - int h = m_BackgroundMargins.top; - - // Get the largest meter point - std::vector::const_iterator j = m_Meters.begin(); - for ( ; j != m_Meters.end(); ++j) - { - int mr = (*j)->GetX() + (*j)->GetW(); - w = max(w, mr); - int mb = (*j)->GetY() + (*j)->GetH(); - h = max(h, mb); - } - - w += m_BackgroundMargins.right; - h += m_BackgroundMargins.bottom; - - w = max(w, m_BackgroundSize.cx); - h = max(h, m_BackgroundSize.cy); - - if (!reset && m_WindowW == w && m_WindowH == h) - { - WindowToScreen(); - return false; // The window is already correct size - } - - // Reset size (this is calculated below) - - delete m_Background; - m_Background = nullptr; - - if ((m_BackgroundMode == BGMODE_IMAGE || m_BackgroundMode == BGMODE_SCALED_IMAGE || m_BackgroundMode == BGMODE_TILED_IMAGE) && !m_BackgroundName.empty()) - { - // Load the background - TintedImage* tintedBackground = new TintedImage(L"Background"); - tintedBackground->ReadOptions(m_Parser, L"Rainmeter"); - tintedBackground->LoadImage(m_BackgroundName, true); - - if (!tintedBackground->IsLoaded()) - { - m_BackgroundSize.cx = 0; - m_BackgroundSize.cy = 0; - - m_WindowW = 0; - m_WindowH = 0; - } - else - { - Bitmap* tempBackground = tintedBackground->GetImage(); - - // Calculate the window dimensions - m_BackgroundSize.cx = tempBackground->GetWidth(); - m_BackgroundSize.cy = tempBackground->GetHeight(); - - if (m_BackgroundMode == BGMODE_IMAGE) - { - w = m_BackgroundSize.cx; - h = m_BackgroundSize.cy; - } - else - { - w = max(w, m_BackgroundSize.cx); - h = max(h, m_BackgroundSize.cy); - } - - Bitmap* background = new Bitmap(w, h, PixelFormat32bppPARGB); - Graphics graphics(background); - - if (m_BackgroundMode == BGMODE_IMAGE) - { - Rect r(0, 0, w, h); - graphics.DrawImage(tempBackground, r, 0, 0, w, h, UnitPixel); - } - else - { - // Scale the background to fill the whole window - if (m_BackgroundMode == BGMODE_SCALED_IMAGE) - { - const RECT m = m_BackgroundMargins; - - if (m.top > 0) - { - if (m.left > 0) - { - // Top-Left - Rect r(0, 0, m.left, m.top); - graphics.DrawImage(tempBackground, r, 0, 0, m.left, m.top, UnitPixel); - } - - // Top - Rect r(m.left, 0, w - m.left - m.right, m.top); - graphics.DrawImage(tempBackground, r, m.left, 0, m_BackgroundSize.cx - m.left - m.right, m.top, UnitPixel); - - if (m.right > 0) - { - // Top-Right - Rect r(w - m.right, 0, m.right, m.top); - graphics.DrawImage(tempBackground, r, m_BackgroundSize.cx - m.right, 0, m.right, m.top, UnitPixel); - } - } - - if (m.left > 0) - { - // Left - Rect r(0, m.top, m.left, h - m.top - m.bottom); - graphics.DrawImage(tempBackground, r, 0, m.top, m.left, m_BackgroundSize.cy - m.top - m.bottom, UnitPixel); - } - - // Center - Rect r(m.left, m.top, w - m.left - m.right, h - m.top - m.bottom); - graphics.DrawImage(tempBackground, r, m.left, m.top, m_BackgroundSize.cx - m.left - m.right, m_BackgroundSize.cy - m.top - m.bottom, UnitPixel); - - if (m.right > 0) - { - // Right - Rect r(w - m.right, m.top, m.right, h - m.top - m.bottom); - graphics.DrawImage(tempBackground, r, m_BackgroundSize.cx - m.right, m.top, m.right, m_BackgroundSize.cy - m.top - m.bottom, UnitPixel); - } - - if (m.bottom > 0) - { - if (m.left > 0) - { - // Bottom-Left - Rect r(0, h - m.bottom, m.left, m.bottom); - graphics.DrawImage(tempBackground, r, 0, m_BackgroundSize.cy - m.bottom, m.left, m.bottom, UnitPixel); - } - - // Bottom - Rect r(m.left, h - m.bottom, w - m.left - m.right, m.bottom); - graphics.DrawImage(tempBackground, r, m.left, m_BackgroundSize.cy - m.bottom, m_BackgroundSize.cx - m.left - m.right, m.bottom, UnitPixel); - - if (m.right > 0) - { - // Bottom-Right - Rect r(w - m.right, h - m.bottom, m.right, m.bottom); - graphics.DrawImage(tempBackground, r, m_BackgroundSize.cx - m.right, m_BackgroundSize.cy - m.bottom, m.right, m.bottom, UnitPixel); - } - } - } - else - { - ImageAttributes imgAttr; - imgAttr.SetWrapMode(WrapModeTile); - - Rect r(0, 0, w, h); - graphics.DrawImage(tempBackground, r, 0, 0, w, h, UnitPixel, &imgAttr); - } - } - - m_Background = background; - - // Get the size form the background bitmap - m_WindowW = m_Background->GetWidth(); - m_WindowH = m_Background->GetHeight(); - - WindowToScreen(); - } - - delete tintedBackground; - } - else - { - m_WindowW = w; - m_WindowH = h; - WindowToScreen(); - } - - SetWindowSizeVariables(m_WindowW, m_WindowH); - - return true; -} - -/* -** Creates the back buffer bitmap. -** -*/ -void MeterWindow::CreateDoubleBuffer(int cx, int cy) -{ - m_Canvas->Resize(cx, cy); -} - -/* -** Redraws the meters and paints the window -** -*/ -void MeterWindow::Redraw() -{ - if (m_ResizeWindow) - { - ResizeWindow(m_ResizeWindow == RESIZEMODE_RESET); - SetResizeWindowMode(RESIZEMODE_NONE); - } - - // Create or clear the doublebuffer - { - int cx = m_WindowW; - int cy = m_WindowH; - - if (cx == 0 || cy == 0) - { - // Set dummy size to avoid invalid state - cx = 1; - cy = 1; - } - - if (cx != m_Canvas->GetW() || cy != m_Canvas->GetH()) - { - CreateDoubleBuffer(cx, cy); - } - } - - if (!m_Canvas->BeginDraw()) - { - return; - } - - m_Canvas->Clear(); - - if (m_WindowW != 0 && m_WindowH != 0) - { - if (m_Background) - { - const Rect dst(0, 0, m_WindowW, m_WindowH); - const Rect src(0, 0, m_Background->GetWidth(), m_Background->GetHeight()); - m_Canvas->DrawBitmap(m_Background, dst, src); - } - else if (m_BackgroundMode == BGMODE_SOLID) - { - // Draw the solid color background - Rect r(0, 0, m_WindowW, m_WindowH); - - if (m_SolidColor.GetA() != 0 || m_SolidColor2.GetA() != 0) - { - if (m_SolidColor.GetValue() == m_SolidColor2.GetValue()) - { - m_Canvas->Clear(m_SolidColor); - } - else - { - Gdiplus::Graphics& graphics = m_Canvas->BeginGdiplusContext(); - LinearGradientBrush gradient(r, m_SolidColor, m_SolidColor2, m_SolidAngle, TRUE); - graphics.FillRectangle(&gradient, r); - m_Canvas->EndGdiplusContext(); - } - } - - if (m_SolidBevel != BEVELTYPE_NONE) - { - Color lightColor(255, 255, 255, 255); - Color darkColor(255, 0, 0, 0); - - if (m_SolidBevel == BEVELTYPE_DOWN) - { - lightColor.SetValue(Color::MakeARGB(255, 0, 0, 0)); - darkColor.SetValue(Color::MakeARGB(255, 255, 255, 255)); - } - - Pen light(lightColor); - Pen dark(darkColor); - - Gdiplus::Graphics& graphics = m_Canvas->BeginGdiplusContext(); - Meter::DrawBevel(graphics, r, light, dark); - m_Canvas->EndGdiplusContext(); - } - } - - // Draw the meters - std::vector::const_iterator j = m_Meters.begin(); - for ( ; j != m_Meters.end(); ++j) - { - const Matrix* matrix = (*j)->GetTransformationMatrix(); - if (matrix && !matrix->IsIdentity()) - { - m_Canvas->SetTransform(*matrix); - (*j)->Draw(*m_Canvas); - m_Canvas->ResetTransform(); - } - else - { - (*j)->Draw(*m_Canvas); - } - } - } - - UpdateWindow(m_TransparencyValue, true); - - m_Canvas->EndDraw(); -} - -/* -** Updates the transition state -** -*/ -void MeterWindow::PostUpdate(bool bActiveTransition) -{ - // Start/stop the transition timer if necessary - if (bActiveTransition && !m_ActiveTransition) - { - SetTimer(m_Window, TIMER_TRANSITION, m_TransitionUpdate, nullptr); - m_ActiveTransition = true; - } - else if (m_ActiveTransition && !bActiveTransition) - { - KillTimer(m_Window, TIMER_TRANSITION); - m_ActiveTransition = false; - } -} - -/* -** Updates the given measure -** -*/ -bool MeterWindow::UpdateMeasure(Measure* measure, bool force) -{ - bool bUpdate = false; - - if (force) - { - measure->ResetUpdateCounter(); - } - - int updateDivider = measure->GetUpdateDivider(); - if (updateDivider >= 0 || force) - { - const bool rereadOptions = - measure->HasDynamicVariables() && (measure->GetUpdateCounter() + 1) >= updateDivider; - bUpdate = measure->Update(rereadOptions); - } - - return bUpdate; -} - -/* -** Updates the given meter -** -*/ -bool MeterWindow::UpdateMeter(Meter* meter, bool& bActiveTransition, bool force) -{ - bool bUpdate = false; - - if (force) - { - meter->ResetUpdateCounter(); - } - - int updateDivider = meter->GetUpdateDivider(); - if (updateDivider >= 0 || force) - { - if (meter->HasDynamicVariables() && - (meter->GetUpdateCounter() + 1) >= updateDivider) - { - meter->ReadOptions(m_Parser); - } - - bUpdate = meter->Update(); - } - - // Update tooltips - if (!meter->HasToolTip()) - { - if (!meter->GetToolTipText().empty()) - { - meter->CreateToolTip(this); - } - } - else - { - meter->UpdateToolTip(); - } - - // Check for transitions - if (!bActiveTransition && meter->HasActiveTransition()) - { - bActiveTransition = true; - } - - return bUpdate; -} - -/* -** Updates all the measures and redraws the meters -** -*/ -void MeterWindow::Update(bool refresh) -{ - ++m_UpdateCounter; - - if (!m_Measures.empty()) - { - // Pre-updates - if (m_HasNetMeasures) - { - MeasureNet::UpdateIFTable(); - MeasureNet::UpdateStats(); - } - - // Update all measures - std::vector::const_iterator i = m_Measures.begin(); - for ( ; i != m_Measures.end(); ++i) - { - if (UpdateMeasure((*i), refresh)) - { - (*i)->DoUpdateAction(); - (*i)->DoChangeAction(); - } - } - } - - DialogAbout::UpdateMeasures(this); - - // Update all meters - bool bActiveTransition = false; - bool bUpdate = false; - std::vector::const_iterator j = m_Meters.begin(); - for ( ; j != m_Meters.end(); ++j) - { - if (UpdateMeter((*j), bActiveTransition, refresh)) - { - bUpdate = true; - - (*j)->DoUpdateAction(); - } - } - - // Redraw all meters - if (bUpdate || m_ResizeWindow || refresh) - { - if (m_DynamicWindowSize) - { - // Resize the window - SetResizeWindowMode(RESIZEMODE_CHECK); - } - - // If our option is to disable when in an RDP session, then check if in an RDP session. - // Only redraw if we are not in a remote session - if (Rainmeter::GetInstance().IsRedrawable()) - { - Redraw(); - } - } - - // Post-updates - PostUpdate(bActiveTransition); - - if (!m_OnUpdateAction.empty()) - { - Rainmeter::GetInstance().ExecuteCommand(m_OnUpdateAction.c_str(), this); - } -} - -/* -** Updates the window contents -** -*/ -void MeterWindow::UpdateWindow(int alpha, bool canvasBeginDrawCalled) -{ - BLENDFUNCTION blendPixelFunction = {AC_SRC_OVER, 0, alpha, AC_SRC_ALPHA}; - POINT ptWindowScreenPosition = {m_ScreenX, m_ScreenY}; - POINT ptSrc = {0, 0}; - SIZE szWindow = {m_Canvas->GetW(), m_Canvas->GetH()}; - - if (!canvasBeginDrawCalled) m_Canvas->BeginDraw(); - - HDC dcMemory = m_Canvas->GetDC(); - if (!UpdateLayeredWindow(m_Window, nullptr, &ptWindowScreenPosition, &szWindow, dcMemory, &ptSrc, 0, &blendPixelFunction, ULW_ALPHA)) - { - // Retry after resetting WS_EX_LAYERED flag. - RemoveWindowExStyle(WS_EX_LAYERED); - AddWindowExStyle(WS_EX_LAYERED); - UpdateLayeredWindow(m_Window, nullptr, &ptWindowScreenPosition, &szWindow, dcMemory, &ptSrc, 0, &blendPixelFunction, ULW_ALPHA); - } - m_Canvas->ReleaseDC(dcMemory); - - if (!canvasBeginDrawCalled) m_Canvas->EndDraw(); - - m_TransparencyValue = alpha; -} - -/* -** Updates the window transparency (using existing contents). -** -*/ -void MeterWindow::UpdateWindowTransparency(int alpha) -{ - BLENDFUNCTION blendPixelFunction = {AC_SRC_OVER, 0, alpha, AC_SRC_ALPHA}; - UpdateLayeredWindow(m_Window, nullptr, nullptr, nullptr, nullptr, nullptr, 0, &blendPixelFunction, ULW_ALPHA); - m_TransparencyValue = alpha; -} - -/* -** Handles the timers. The METERTIMER updates all the measures -** MOUSETIMER is used to hide/show the window. -** -*/ -LRESULT MeterWindow::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (wParam) - { - case TIMER_METER: - Update(false); - break; - - case TIMER_MOUSE: - if (!Rainmeter::GetInstance().IsMenuActive() && !m_Dragging) - { - ShowWindowIfAppropriate(); - - if (m_WindowZPosition == ZPOSITION_ONTOPMOST) - { - ChangeZPos(ZPOSITION_ONTOPMOST); - } - - if (m_MouseOver) - { - POINT pos = System::GetCursorPosition(); - - if (!m_ClickThrough) - { - if (WindowFromPoint(pos) == m_Window) - { - SetMouseLeaveEvent(false); - } - else - { - // Run all mouse leave actions - OnMouseLeave(m_WindowDraggable ? WM_NCMOUSELEAVE : WM_MOUSELEAVE, 0, 0); - } - } - else - { - bool keyDown = IsCtrlKeyDown() || IsShiftKeyDown() || IsAltKeyDown(); - - if (!keyDown || GetWindowFromPoint(pos) != m_Window) - { - // Run all mouse leave actions - OnMouseLeave(m_WindowDraggable ? WM_NCMOUSELEAVE : WM_MOUSELEAVE, 0, 0); - } - } - } - } - break; - - case TIMER_TRANSITION: - { - // Redraw only if there is active transition still going - bool bActiveTransition = false; - std::vector::const_iterator j = m_Meters.begin(); - for ( ; j != m_Meters.end(); ++j) - { - if ((*j)->HasActiveTransition()) - { - bActiveTransition = true; - break; - } - } - - if (bActiveTransition) - { - Redraw(); - } - else - { - // Stop the transition timer - KillTimer(m_Window, TIMER_TRANSITION); - m_ActiveTransition = false; - } - } - break; - - case TIMER_FADE: - { - ULONGLONG ticks = System::GetTickCount64(); - if (m_FadeStartTime == 0) - { - m_FadeStartTime = ticks; - } - - if (ticks - m_FadeStartTime > (ULONGLONG)m_FadeDuration) - { - KillTimer(m_Window, TIMER_FADE); - m_FadeStartTime = 0; - if (m_FadeEndValue == 0) - { - ShowWindow(m_Window, SW_HIDE); - } - else - { - UpdateWindowTransparency(m_FadeEndValue); - } - } - else - { - double value = (double)(__int64)(ticks - m_FadeStartTime); - value /= m_FadeDuration; - value *= m_FadeEndValue - m_FadeStartValue; - value += m_FadeStartValue; - value = min(value, 255); - value = max(value, 0); - - UpdateWindowTransparency((int)value); - } - } - break; - - case TIMER_DEACTIVATE: - if (m_FadeStartTime == 0) - { - KillTimer(m_Window, TIMER_DEACTIVATE); - Rainmeter::GetInstance().RemoveUnmanagedMeterWindow(this); - delete this; - } - break; - } - - return 0; -} - -void MeterWindow::FadeWindow(int from, int to) -{ - if (m_FadeDuration == 0) - { - if (to == 0) - { - ShowWindow(m_Window, SW_HIDE); - } - else - { - if (m_FadeDuration == 0) - { - UpdateWindowTransparency(to); - } - if (from == 0) - { - if (!m_Hidden) - { - ShowWindow(m_Window, SW_SHOWNOACTIVATE); - } - } - } - } - else - { - m_FadeStartValue = from; - m_FadeEndValue = to; - UpdateWindowTransparency(from); - if (from == 0) - { - if (!m_Hidden) - { - ShowWindow(m_Window, SW_SHOWNOACTIVATE); - } - } - - SetTimer(m_Window, TIMER_FADE, INTERVAL_FADE, nullptr); - } -} - -void MeterWindow::HideFade() -{ - m_Hidden = true; - if (IsWindowVisible(m_Window)) - { - FadeWindow(m_AlphaValue, 0); - } -} - -void MeterWindow::ShowFade() -{ - m_Hidden = false; - if (!IsWindowVisible(m_Window)) - { - FadeWindow(0, (m_WindowHide == HIDEMODE_FADEOUT) ? 255 : m_AlphaValue); - } -} - -/* -** Show the window if it is temporarily hidden. -** -*/ -void MeterWindow::ShowWindowIfAppropriate() -{ - bool keyDown = IsCtrlKeyDown() || IsShiftKeyDown() || IsAltKeyDown(); - - POINT pos = System::GetCursorPosition(); - POINT posScr = pos; - - MapWindowPoints(nullptr, m_Window, &pos, 1); - bool inside = HitTest(pos.x, pos.y); - - if (inside) - { - inside = (GetWindowFromPoint(posScr) == m_Window); - } - - if (m_ClickThrough) - { - if (!inside || keyDown) - { - // If Alt, shift or control is down, remove the transparent flag - RemoveWindowExStyle(WS_EX_TRANSPARENT); - } - } - - if (m_WindowHide) - { - if (!m_Hidden && !inside && !keyDown) - { - switch (m_WindowHide) - { - case HIDEMODE_HIDE: - if (m_TransparencyValue == 0 || !IsWindowVisible(m_Window)) - { - ShowWindow(m_Window, SW_SHOWNOACTIVATE); - FadeWindow(0, m_AlphaValue); - } - break; - - case HIDEMODE_FADEIN: - if (m_AlphaValue != 255 && m_TransparencyValue == 255) - { - FadeWindow(255, m_AlphaValue); - } - break; - - case HIDEMODE_FADEOUT: - if (m_AlphaValue != 255 && m_TransparencyValue == m_AlphaValue) - { - FadeWindow(m_AlphaValue, 255); - } - break; - } - } - } - else - { - if (!m_Hidden) - { - if (m_TransparencyValue == 0 || !IsWindowVisible(m_Window)) - { - ShowWindow(m_Window, SW_SHOWNOACTIVATE); - FadeWindow(0, m_AlphaValue); - } - } - } -} - -/* -** Retrieves a handle to the window that contains the specified point. -** -*/ -HWND MeterWindow::GetWindowFromPoint(POINT pos) -{ - HWND hwndPos = WindowFromPoint(pos); - - if (hwndPos == m_Window || (!m_ClickThrough && m_WindowHide != HIDEMODE_HIDE)) - { - return hwndPos; - } - - MapWindowPoints(nullptr, m_Window, &pos, 1); - - if (HitTest(pos.x, pos.y)) - { - if (hwndPos) - { - HWND hWnd = GetAncestor(hwndPos, GA_ROOT); - while (hWnd = FindWindowEx(nullptr, hWnd, METERWINDOW_CLASS_NAME, nullptr)) - { - if (hWnd == m_Window) - { - return hwndPos; - } - } - } - return m_Window; - } - - return hwndPos; -} - -/* -** Checks if the given point is inside the window. -** -*/ -bool MeterWindow::HitTest(int x, int y) -{ - return m_Canvas->IsTransparentPixel(x, y); -} - -/* -** Handles all buttons and cursor. -** -*/ -void MeterWindow::HandleButtons(POINT pos, BUTTONPROC proc, bool execute) -{ - bool redraw = false; - HCURSOR cursor = nullptr; - - std::vector::const_reverse_iterator j = m_Meters.rbegin(); - for ( ; j != m_Meters.rend(); ++j) - { - // Hidden meters are ignored - if ((*j)->IsHidden()) continue; - - MeterButton* button = nullptr; - if (m_HasButtons && (*j)->GetTypeID() == TypeID()) - { - button = (MeterButton*)(*j); - if (button) - { - switch (proc) - { - case BUTTONPROC_DOWN: - redraw |= button->MouseDown(pos); - break; - - case BUTTONPROC_UP: - redraw |= button->MouseUp(pos, execute); - break; - - case BUTTONPROC_MOVE: - default: - redraw |= button->MouseMove(pos); - break; - } - } - } - - // Get cursor if required - if (!cursor && (*j)->GetMouse().GetCursorState()) - { - if ((*j)->HasMouseAction()) - { - if ((*j)->HitTest(pos.x, pos.y)) - { - cursor = (*j)->GetMouse().GetCursor(); - } - } - else - { - // Special case for Button meter: reacts only on valid pixel in button image - if (button && button->HitTest2(pos.x, pos.y)) - { - cursor = (*j)->GetMouse().GetCursor(); - } - } - } - } - - if (redraw) - { - Redraw(); - } - - if (!cursor) - { - cursor = LoadCursor(nullptr, IDC_ARROW); - } - - SetCursor(cursor); -} - -/* -** During setting the cursor do nothing. -** -*/ -LRESULT MeterWindow::OnSetCursor(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - return 0; -} - -/* -** Enters context menu loop. -** -*/ -LRESULT MeterWindow::OnEnterMenuLoop(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - // Set cursor to default - SetCursor(LoadCursor(nullptr, IDC_ARROW)); - - return 0; -} - -/* -** When we get WM_MOUSEMOVE messages, hide the window as the mouse is over it. -** -*/ -LRESULT MeterWindow::OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - bool keyDown = IsCtrlKeyDown() || IsShiftKeyDown() || IsAltKeyDown(); - - if (!keyDown) - { - if (m_ClickThrough) - { - AddWindowExStyle(WS_EX_TRANSPARENT); - } - - if (!m_Hidden) - { - // If Alt, shift or control is down, do not hide the window - switch (m_WindowHide) - { - case HIDEMODE_HIDE: - if (m_TransparencyValue == m_AlphaValue) - { - FadeWindow(m_AlphaValue, 0); - } - break; - - case HIDEMODE_FADEIN: - if (m_AlphaValue != 255 && m_TransparencyValue == m_AlphaValue) - { - FadeWindow(m_AlphaValue, 255); - } - break; - - case HIDEMODE_FADEOUT: - if (m_AlphaValue != 255 && m_TransparencyValue == 255) - { - FadeWindow(255, m_AlphaValue); - } - break; - } - } - } - - if (!m_ClickThrough || keyDown) - { - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - if (uMsg == WM_NCMOUSEMOVE) - { - // Map to local window - MapWindowPoints(nullptr, m_Window, &pos, 1); - } - - ++m_MouseMoveCounter; - - while (DoMoveAction(pos.x, pos.y, MOUSE_LEAVE)) ; - while (DoMoveAction(pos.x, pos.y, MOUSE_OVER)) ; - - // Handle buttons - HandleButtons(pos, BUTTONPROC_MOVE); - } - - return 0; -} - -/* -** When we get WM_MOUSELEAVE messages, run all leave actions. -** -*/ -LRESULT MeterWindow::OnMouseLeave(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - POINT pos = System::GetCursorPosition(); - HWND hWnd = WindowFromPoint(pos); - if (!hWnd || (hWnd != m_Window && GetParent(hWnd) != m_Window)) // ignore tooltips - { - ++m_MouseMoveCounter; - - POINT pos = {SHRT_MIN, SHRT_MIN}; - while (DoMoveAction(pos.x, pos.y, MOUSE_LEAVE)) ; // Leave all forcibly - - // Handle buttons - HandleButtons(pos, BUTTONPROC_MOVE); - } - - return 0; -} - -/* -** When we get WM_MOUSEWHEEL messages. -** -*/ -LRESULT MeterWindow::OnMouseScrollMove(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - if (uMsg == WM_MOUSEWHEEL) // If sent through WM_INPUT, uMsg is WM_INPUT. - { - // Fix for Notepad++, which sends WM_MOUSEWHEEL to unfocused windows. - if (m_Window != GetFocus()) - { - return 0; - } - } - - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - MapWindowPoints(nullptr, m_Window, &pos, 1); - - // Handle buttons - HandleButtons(pos, BUTTONPROC_MOVE); - - const int delta = GET_WHEEL_DELTA_WPARAM(wParam); - DoAction(pos.x, pos.y, (delta < 0) ? MOUSE_MW_DOWN : MOUSE_MW_UP, false); - - return 0; -} - -/* -** When we get WM_MOUSEHWHEEL messages. -** -*/ -LRESULT MeterWindow::OnMouseHScrollMove(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - MapWindowPoints(nullptr, m_Window, &pos, 1); - - // Handle buttons - HandleButtons(pos, BUTTONPROC_MOVE); - - const int delta = GET_WHEEL_DELTA_WPARAM(wParam); - DoAction(pos.x, pos.y, (delta < 0) ? MOUSE_MW_LEFT : MOUSE_MW_RIGHT, false); - - return 0; -} - -/* -** Handle the menu commands. -** -*/ -LRESULT MeterWindow::OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (wParam) - { - case IDM_SKIN_EDITSKIN: - Rainmeter::GetInstance().EditSkinFile(m_FolderPath, m_FileName); - break; - - case IDM_SKIN_REFRESH: - Refresh(false); - break; - - case IDM_SKIN_OPENSKINSFOLDER: - Rainmeter::GetInstance().OpenSkinFolder(m_FolderPath); - break; - - case IDM_SKIN_MANAGESKIN: - DialogManage::OpenSkin(this); - break; - - case IDM_SKIN_VERYTOPMOST: - SetWindowZPosition(ZPOSITION_ONTOPMOST); - break; - - case IDM_SKIN_TOPMOST: - SetWindowZPosition(ZPOSITION_ONTOP); - break; - - case IDM_SKIN_BOTTOM: - SetWindowZPosition(ZPOSITION_ONBOTTOM); - break; - - case IDM_SKIN_NORMAL: - SetWindowZPosition(ZPOSITION_NORMAL); - break; - - case IDM_SKIN_ONDESKTOP: - SetWindowZPosition(ZPOSITION_ONDESKTOP); - break; - - case IDM_SKIN_KEEPONSCREEN: - SetKeepOnScreen(!m_KeepOnScreen); - break; - - case IDM_SKIN_USED2D: - SetUseD2D(!m_UseD2D); - break; - - case IDM_SKIN_CLICKTHROUGH: - SetClickThrough(!m_ClickThrough); - break; - - case IDM_SKIN_DRAGGABLE: - SetWindowDraggable(!m_WindowDraggable); - break; - - case IDM_SKIN_HIDEONMOUSE: - SetWindowHide((m_WindowHide == HIDEMODE_NONE) ? HIDEMODE_HIDE : HIDEMODE_NONE); - break; - - case IDM_SKIN_TRANSPARENCY_FADEIN: - SetWindowHide((m_WindowHide == HIDEMODE_NONE) ? HIDEMODE_FADEIN : HIDEMODE_NONE); - break; - - case IDM_SKIN_TRANSPARENCY_FADEOUT: - SetWindowHide((m_WindowHide == HIDEMODE_NONE) ? HIDEMODE_FADEOUT : HIDEMODE_NONE); - break; - - case IDM_SKIN_REMEMBERPOSITION: - SetSavePosition(!m_SavePosition); - break; - - case IDM_SKIN_SNAPTOEDGES: - SetSnapEdges(!m_SnapEdges); - break; - - case IDM_CLOSESKIN: - if (m_State != STATE_CLOSING) - { - Rainmeter::GetInstance().DeactivateSkin(this, -1); - } - break; - - case IDM_SKIN_FROMRIGHT: - m_WindowXFromRight = !m_WindowXFromRight; - - SavePositionIfAppropriate(); - break; - - case IDM_SKIN_FROMBOTTOM: - m_WindowYFromBottom = !m_WindowYFromBottom; - - SavePositionIfAppropriate(); - break; - - case IDM_SKIN_XPERCENTAGE: - m_WindowXPercentage = !m_WindowXPercentage; - - SavePositionIfAppropriate(); - break; - - case IDM_SKIN_YPERCENTAGE: - m_WindowYPercentage = !m_WindowYPercentage; - - SavePositionIfAppropriate(); - break; - - case IDM_SKIN_MONITOR_AUTOSELECT: - m_AutoSelectScreen = !m_AutoSelectScreen; - - WriteOptions(OPTION_POSITION | OPTION_AUTOSELECTSCREEN); - break; - - default: - if (wParam >= IDM_SKIN_TRANSPARENCY_0 && wParam <= IDM_SKIN_TRANSPARENCY_90) - { - m_AlphaValue = (int)(255.0 - (wParam - IDM_SKIN_TRANSPARENCY_0) * (230.0 / (IDM_SKIN_TRANSPARENCY_90 - IDM_SKIN_TRANSPARENCY_0))); - UpdateWindowTransparency(m_AlphaValue); - WriteOptions(OPTION_ALPHAVALUE); - } - else if (wParam == IDM_SKIN_MONITOR_PRIMARY || wParam >= ID_MONITOR_FIRST && wParam <= ID_MONITOR_LAST) - { - const int numOfMonitors = (int)System::GetMonitorCount(); - const MultiMonitorInfo& monitorsInfo = System::GetMultiMonitorInfo(); - const std::vector& monitors = monitorsInfo.monitors; - - int screenIndex; - bool screenDefined; - if (wParam == IDM_SKIN_MONITOR_PRIMARY) - { - screenIndex = monitorsInfo.primary; - screenDefined = false; - } - else - { - screenIndex = (wParam & 0x0ffff) - ID_MONITOR_FIRST; - screenDefined = true; - } - - if (screenIndex >= 0 && (screenIndex == 0 || screenIndex <= numOfMonitors && monitors[screenIndex - 1].active)) - { - m_AutoSelectScreen = false; - - m_WindowXScreen = m_WindowYScreen = screenIndex; - m_WindowXScreenDefined = m_WindowYScreenDefined = screenDefined; - - m_Parser.ResetMonitorVariables(this); // Set present monitor variables - WriteOptions(OPTION_POSITION | OPTION_AUTOSELECTSCREEN); - } - } - else if (wParam >= IDM_SKIN_CUSTOMCONTEXTMENU_FIRST && wParam <= IDM_SKIN_CUSTOMCONTEXTMENU_LAST) - { - std::wstring action; - - int position = (int)wParam - IDM_SKIN_CUSTOMCONTEXTMENU_FIRST + 1; - if (position == 1) - { - action = m_Parser.ReadString(L"Rainmeter", L"ContextAction", L"", false); - } - else - { - WCHAR buffer[128]; - - _snwprintf_s(buffer, _TRUNCATE, L"ContextAction%i", position); - action = m_Parser.ReadString(L"Rainmeter", buffer, L"", false); - } - - if (!action.empty()) - { - Rainmeter::GetInstance().ExecuteCommand(action.c_str(), this); - } - } - else - { - // Forward to tray window, which handles all the other commands - HWND tray = Rainmeter::GetInstance().GetTrayWindow()->GetWindow(); - - if (wParam == IDM_QUIT) - { - PostMessage(tray, WM_COMMAND, wParam, lParam); - } - else - { - SendMessage(tray, WM_COMMAND, wParam, lParam); - } - } - break; - } - - return 0; -} - -/* -** Helper function for setting ClickThrough -** -*/ -void MeterWindow::SetClickThrough(bool b) -{ - m_ClickThrough = b; - WriteOptions(OPTION_CLICKTHROUGH); - - if (!m_ClickThrough) - { - // Remove transparent flag - RemoveWindowExStyle(WS_EX_TRANSPARENT); - } - - if (m_MouseOver) - { - SetMouseLeaveEvent(m_ClickThrough); - } -} - -/* -** Helper function for setting KeepOnScreen -** -*/ -void MeterWindow::SetKeepOnScreen(bool b) -{ - m_KeepOnScreen = b; - WriteOptions(OPTION_KEEPONSCREEN); - - if (m_KeepOnScreen) - { - int x = m_ScreenX; - int y = m_ScreenY; - - MapCoordsToScreen(x, y, m_WindowW, m_WindowH); - - if (x != m_ScreenX || y != m_ScreenY) - { - MoveWindow(x, y); - } - } -} - -/* -** Helper function for setting UseD2D -** -*/ -void MeterWindow::SetUseD2D(bool b) -{ - m_UseD2D = b; - WriteOptions(OPTION_USED2D); - Refresh(false); -} - -/* -** Helper function for setting WindowDraggable -** -*/ -void MeterWindow::SetWindowDraggable(bool b) -{ - m_WindowDraggable = b; - WriteOptions(OPTION_DRAGGABLE); -} - -/* -** Helper function for setting SavePosition -** -*/ -void MeterWindow::SetSavePosition(bool b) -{ - m_SavePosition = b; - WriteOptions(OPTION_POSITION | OPTION_SAVEPOSITION); -} - -/* -** Helper function for setting SavePosition -** -*/ -void MeterWindow::SavePositionIfAppropriate() -{ - if (m_SavePosition) - { - WriteOptions(OPTION_POSITION); - } - else - { - ScreenToWindow(); - DialogManage::UpdateSkins(this); - } -} - -/* -** Helper function for setting SnapEdges -** -*/ -void MeterWindow::SetSnapEdges(bool b) -{ - m_SnapEdges = b; - WriteOptions(OPTION_SNAPEDGES); -} - -/* -** Helper function for setting WindowHide -** -*/ -void MeterWindow::SetWindowHide(HIDEMODE hide) -{ - m_WindowHide = hide; - UpdateWindowTransparency(m_AlphaValue); - WriteOptions(OPTION_HIDEONMOUSEOVER); -} - -/* -** Helper function for setting Position -** -*/ -void MeterWindow::SetWindowZPosition(ZPOSITION zpos) -{ - ChangeSingleZPos(zpos); - WriteOptions(OPTION_ALWAYSONTOP); -} - -/* -** Handle dragging the window -** -*/ -LRESULT MeterWindow::OnSysCommand(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - if ((wParam & 0xFFF0) != SC_MOVE) - { - return DefWindowProc(m_Window, uMsg, wParam, lParam); - } - - // --- SC_MOVE --- - - // Prepare the dragging flags - m_Dragging = true; - m_Dragged = false; - - // Run the DefWindowProc so the dragging works - LRESULT result = DefWindowProc(m_Window, uMsg, wParam, lParam); - - if (m_Dragged) - { - SavePositionIfAppropriate(); - - POINT pos = System::GetCursorPosition(); - MapWindowPoints(nullptr, m_Window, &pos, 1); - - // Handle buttons - HandleButtons(pos, BUTTONPROC_UP, false); // redraw only - } - else // not dragged - { - if ((wParam & 0x000F) == 2) // triggered by mouse - { - // Post the WM_NCLBUTTONUP message so the LeftMouseUpAction works - PostMessage(m_Window, WM_NCLBUTTONUP, (WPARAM)HTCAPTION, lParam); - } - } - - // Clear the dragging flags - m_Dragging = false; - m_Dragged = false; - - return result; -} - -/* -** Starts dragging -** -*/ -LRESULT MeterWindow::OnEnterSizeMove(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - if (m_Dragging) - { - m_Dragged = true; // Don't post the WM_NCLBUTTONUP message! - - // Set cursor to default - SetCursor(LoadCursor(nullptr, IDC_ARROW)); - } - - return 0; -} - -/* -** Ends dragging -** -*/ -LRESULT MeterWindow::OnExitSizeMove(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - return 0; -} - -/* -** This is overwritten so that the window can be dragged -** -*/ -LRESULT MeterWindow::OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - if (m_WindowDraggable && !Rainmeter::GetInstance().GetDisableDragging()) - { - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - MapWindowPoints(nullptr, m_Window, &pos, 1); - - int x1 = m_DragMargins.left; - if (x1 < 0) x1 += m_WindowW; - - int x2 = m_WindowW - m_DragMargins.right; - if (x2 > m_WindowW) x2 -= m_WindowW; - - if (pos.x >= x1 && pos.x < x2) - { - int y1 = m_DragMargins.top; - if (y1 < 0) y1 += m_WindowH; - - int y2 = m_WindowH - m_DragMargins.bottom; - if (y2 > m_WindowH) y2 -= m_WindowH; - - if (pos.y >= y1 && pos.y < y2) - { - return HTCAPTION; - } - } - } - return HTCLIENT; -} - -/* -** Called when windows position is about to change -** -*/ -LRESULT MeterWindow::OnWindowPosChanging(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - LPWINDOWPOS wp = (LPWINDOWPOS)lParam; - - if (m_State != STATE_REFRESHING) - { - if (m_WindowZPosition == ZPOSITION_NORMAL && Rainmeter::GetInstance().IsNormalStayDesktop() && System::GetShowDesktop()) - { - if (!(wp->flags & (SWP_NOOWNERZORDER | SWP_NOACTIVATE))) - { - // Set window on top of all other ZPOSITION_ONDESKTOP, ZPOSITION_BOTTOM, and ZPOSITION_NORMAL windows - wp->hwndInsertAfter = System::GetBackmostTopWindow(); - } - } - else if (m_WindowZPosition == ZPOSITION_ONDESKTOP || m_WindowZPosition == ZPOSITION_ONBOTTOM) - { - // Do not change the z-order. This keeps the window on bottom. - wp->flags |= SWP_NOZORDER; - } - } - - if ((wp->flags & SWP_NOMOVE) == 0) - { - if (m_SnapEdges && !(IsCtrlKeyDown() || IsShiftKeyDown())) - { - // only process movement (ignore anything without winpos values) - if (wp->cx != 0 && wp->cy != 0) - { - // Search display monitor that has the largest area of intersection with the window - const size_t numOfMonitors = System::GetMonitorCount(); // intentional - const std::vector& monitors = System::GetMultiMonitorInfo().monitors; - - const RECT windowRect = {wp->x, wp->y, wp->x + (m_WindowW ? m_WindowW : 1), wp->y + (m_WindowH ? m_WindowH : 1)}; - const RECT* workArea = nullptr; - - size_t maxSize = 0; - for (auto iter = monitors.cbegin(); iter != monitors.cend(); ++iter) - { - RECT r; - if ((*iter).active && IntersectRect(&r, &windowRect, &(*iter).screen)) - { - size_t size = (r.right - r.left) * (r.bottom - r.top); - if (size > maxSize) - { - workArea = &(*iter).work; - maxSize = size; - } - } - } - - // Snap to other windows - for (auto iter = Rainmeter::GetInstance().GetAllMeterWindows().cbegin(); iter != Rainmeter::GetInstance().GetAllMeterWindows().cend(); ++iter) - { - if ((*iter).second != this) - { - SnapToWindow((*iter).second, wp); - } - } - - // Snap to work area if window is on the appropriate screen - if (workArea) - { - int w = workArea->right - m_WindowW; - int h = workArea->bottom - m_WindowH; - - if ((wp->x < SNAPDISTANCE + workArea->left) && (wp->x > workArea->left - SNAPDISTANCE)) wp->x = workArea->left; - if ((wp->y < SNAPDISTANCE + workArea->top) && (wp->y > workArea->top - SNAPDISTANCE)) wp->y = workArea->top; - if ((wp->x < SNAPDISTANCE + w) && (wp->x > -SNAPDISTANCE + w)) wp->x = w; - if ((wp->y < SNAPDISTANCE + h) && (wp->y > -SNAPDISTANCE + h)) wp->y = h; - } - } - } - - if (m_KeepOnScreen) - { - MapCoordsToScreen(wp->x, wp->y, m_WindowW, m_WindowH); - } - } - - return 0; -} - -void MeterWindow::SnapToWindow(MeterWindow* window, LPWINDOWPOS wp) -{ - int x = window->m_ScreenX; - int y = window->m_ScreenY; - int w = window->m_WindowW; - int h = window->m_WindowH; - - if (wp->y < y + h && wp->y + m_WindowH > y) - { - if ((wp->x < SNAPDISTANCE + x) && (wp->x > x - SNAPDISTANCE)) wp->x = x; - if ((wp->x < SNAPDISTANCE + x + w) && (wp->x > x + w - SNAPDISTANCE)) wp->x = x + w; - - if ((wp->x + m_WindowW < SNAPDISTANCE + x) && (wp->x + m_WindowW > x - SNAPDISTANCE)) wp->x = x - m_WindowW; - if ((wp->x + m_WindowW < SNAPDISTANCE + x + w) && (wp->x + m_WindowW > x + w - SNAPDISTANCE)) wp->x = x + w - m_WindowW; - } - - if (wp->x < x + w && wp->x + m_WindowW > x) - { - if ((wp->y < SNAPDISTANCE + y) && (wp->y > y - SNAPDISTANCE)) wp->y = y; - if ((wp->y < SNAPDISTANCE + y + h) && (wp->y > y + h - SNAPDISTANCE)) wp->y = y + h; - - if ((wp->y + m_WindowH < SNAPDISTANCE + y) && (wp->y + m_WindowH > y - SNAPDISTANCE)) wp->y = y - m_WindowH; - if ((wp->y + m_WindowH < SNAPDISTANCE + y + h) && (wp->y + m_WindowH > y + h - SNAPDISTANCE)) wp->y = y + h - m_WindowH; - } -} - -/* -** Disables blur when Aero transparency is disabled -** -*/ -LRESULT MeterWindow::OnDwmColorChange(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - if (m_BlurMode != BLURMODE_NONE && IsBlur() && c_DwmGetColorizationColor && c_DwmEnableBlurBehindWindow) - { - DWORD color; - BOOL opaque; - if (c_DwmGetColorizationColor(&color, &opaque) != S_OK) - { - opaque = TRUE; - } - - BlurBehindWindow(!opaque); - } - - return 0; -} - -/* -** Disables blur when desktop composition is disabled -** -*/ -LRESULT MeterWindow::OnDwmCompositionChange(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - if (m_BlurMode != BLURMODE_NONE && IsBlur() && c_DwmIsCompositionEnabled && c_DwmEnableBlurBehindWindow) - { - BOOL enabled; - if (c_DwmIsCompositionEnabled(&enabled) != S_OK) - { - enabled = FALSE; - } - - BlurBehindWindow(enabled); - } - - return 0; -} - -/* -** Adds the blur region to the window -** -*/ -void MeterWindow::BlurBehindWindow(BOOL fEnable) -{ - if (c_DwmEnableBlurBehindWindow) - { - DWM_BLURBEHIND bb = {0}; - bb.fEnable = fEnable; - - if (fEnable) - { - // Restore blur with whatever the region was prior to disabling - bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; - bb.hRgnBlur = m_BlurRegion; - c_DwmEnableBlurBehindWindow(m_Window, &bb); - } - else - { - // Disable blur - bb.dwFlags = DWM_BB_ENABLE; - c_DwmEnableBlurBehindWindow(m_Window, &bb); - } - } -} - -/* -** During resolution changes do nothing. -** (OnDelayedMove function is used instead.) -** -*/ -LRESULT MeterWindow::OnDisplayChange(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - return 0; -} - -/* -** During setting changes do nothing. -** (OnDelayedMove function is used instead.) -** -*/ -LRESULT MeterWindow::OnSettingChange(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - return 0; -} - -/* -** Runs the action when left mouse button is down -** -*/ -LRESULT MeterWindow::OnLeftButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - if (uMsg == WM_NCLBUTTONDOWN) - { - // Transform the point to client rect - MapWindowPoints(nullptr, m_Window, &pos, 1); - } - - // Handle buttons - HandleButtons(pos, BUTTONPROC_DOWN); - - if (IsCtrlKeyDown() || // Ctrl is pressed, so only run default action - (!DoAction(pos.x, pos.y, MOUSE_LMB_DOWN, false) && m_WindowDraggable)) - { - // Cancel the mouse event beforehand - SetMouseLeaveEvent(true); - - // Run the DefWindowProc so the dragging works - return DefWindowProc(m_Window, uMsg, wParam, lParam); - } - - return 0; -} - -/* -** Runs the action when left mouse button is up -** -*/ -LRESULT MeterWindow::OnLeftButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - if (uMsg == WM_NCLBUTTONUP) - { - // Transform the point to client rect - MapWindowPoints(nullptr, m_Window, &pos, 1); - } - - // Handle buttons - HandleButtons(pos, BUTTONPROC_UP); - - DoAction(pos.x, pos.y, MOUSE_LMB_UP, false); - - return 0; -} - -/* -** Runs the action when left mouse button is double-clicked -** -*/ -LRESULT MeterWindow::OnLeftButtonDoubleClick(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - if (uMsg == WM_NCLBUTTONDBLCLK) - { - // Transform the point to client rect - MapWindowPoints(nullptr, m_Window, &pos, 1); - } - - // Handle buttons - HandleButtons(pos, BUTTONPROC_DOWN); - - if (!DoAction(pos.x, pos.y, MOUSE_LMB_DBLCLK, false)) - { - DoAction(pos.x, pos.y, MOUSE_LMB_DOWN, false); - } - - return 0; -} - -/* -** Runs the action when right mouse button is down -** -*/ -LRESULT MeterWindow::OnRightButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - if (uMsg == WM_NCRBUTTONDOWN) - { - // Transform the point to client rect - MapWindowPoints(nullptr, m_Window, &pos, 1); - } - - // Handle buttons - HandleButtons(pos, BUTTONPROC_MOVE); - - DoAction(pos.x, pos.y, MOUSE_RMB_DOWN, false); - - return 0; -} - -/* -** Runs the action when right mouse button is up -** -*/ -LRESULT MeterWindow::OnRightButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - // Handle buttons - HandleButtons(pos, BUTTONPROC_MOVE); - - if (IsCtrlKeyDown() || // Ctrl is pressed, so only run default action - !DoAction(pos.x, pos.y, MOUSE_RMB_UP, false)) - { - // Run the DefWindowProc so the context menu works - return DefWindowProc(m_Window, WM_RBUTTONUP, wParam, lParam); - } - - return 0; -} - -/* -** Runs the action when right mouse button is double-clicked -** -*/ -LRESULT MeterWindow::OnRightButtonDoubleClick(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - if (uMsg == WM_NCRBUTTONDBLCLK) - { - // Transform the point to client rect - MapWindowPoints(nullptr, m_Window, &pos, 1); - } - - // Handle buttons - HandleButtons(pos, BUTTONPROC_MOVE); - - if (!DoAction(pos.x, pos.y, MOUSE_RMB_DBLCLK, false)) - { - DoAction(pos.x, pos.y, MOUSE_RMB_DOWN, false); - } - - return 0; -} - -/* -** Runs the action when middle mouse button is down -** -*/ -LRESULT MeterWindow::OnMiddleButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - if (uMsg == WM_NCMBUTTONDOWN) - { - // Transform the point to client rect - MapWindowPoints(nullptr, m_Window, &pos, 1); - } - - // Handle buttons - HandleButtons(pos, BUTTONPROC_MOVE); - - DoAction(pos.x, pos.y, MOUSE_MMB_DOWN, false); - - return 0; -} - -/* -** Runs the action when middle mouse button is up -** -*/ -LRESULT MeterWindow::OnMiddleButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - if (uMsg == WM_NCMBUTTONUP) - { - // Transform the point to client rect - MapWindowPoints(nullptr, m_Window, &pos, 1); - } - - // Handle buttons - HandleButtons(pos, BUTTONPROC_MOVE); - - DoAction(pos.x, pos.y, MOUSE_MMB_UP, false); - - return 0; -} - -/* -** Runs the action when middle mouse button is double-clicked -** -*/ -LRESULT MeterWindow::OnMiddleButtonDoubleClick(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - if (uMsg == WM_NCMBUTTONDBLCLK) - { - // Transform the point to client rect - MapWindowPoints(nullptr, m_Window, &pos, 1); - } - - // Handle buttons - HandleButtons(pos, BUTTONPROC_MOVE); - - if (!DoAction(pos.x, pos.y, MOUSE_MMB_DBLCLK, false)) - { - DoAction(pos.x, pos.y, MOUSE_MMB_DOWN, false); - } - - return 0; -} - -/* -** Runs the action when a X mouse button is down -** -*/ -LRESULT MeterWindow::OnXButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - if (uMsg == WM_NCXBUTTONDOWN) - { - // Transform the point to client rect - MapWindowPoints(nullptr, m_Window, &pos, 1); - } - - // Handle buttons - HandleButtons(pos, BUTTONPROC_MOVE); - - if (GET_XBUTTON_WPARAM (wParam) == XBUTTON1) - { - DoAction(pos.x, pos.y, MOUSE_X1MB_DOWN, false); - } - else if (GET_XBUTTON_WPARAM (wParam) == XBUTTON2) - { - DoAction(pos.x, pos.y, MOUSE_X2MB_DOWN, false); - } - - return 0; -} - -/* -** Runs the action when a X mouse button is up -** -*/ -LRESULT MeterWindow::OnXButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - if (uMsg == WM_NCXBUTTONUP) - { - // Transform the point to client rect - MapWindowPoints(nullptr, m_Window, &pos, 1); - } - - // Handle buttons - HandleButtons(pos, BUTTONPROC_MOVE); - - if (GET_XBUTTON_WPARAM (wParam) == XBUTTON1) - { - DoAction(pos.x, pos.y, MOUSE_X1MB_UP, false); - } - else if (GET_XBUTTON_WPARAM (wParam) == XBUTTON2) - { - DoAction(pos.x, pos.y, MOUSE_X2MB_UP, false); - } - - return 0; -} - -/* -** Runs the action when a X mouse button is double-clicked -** -*/ -LRESULT MeterWindow::OnXButtonDoubleClick(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - POINT pos; - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - if (uMsg == WM_NCXBUTTONDBLCLK) - { - // Transform the point to client rect - MapWindowPoints(nullptr, m_Window, &pos, 1); - } - - // Handle buttons - HandleButtons(pos, BUTTONPROC_MOVE); - - if (GET_XBUTTON_WPARAM (wParam) == XBUTTON1 && - !DoAction(pos.x, pos.y, MOUSE_X1MB_DBLCLK, false)) - { - DoAction(pos.x, pos.y, MOUSE_X1MB_DOWN, false); - } - else if (GET_XBUTTON_WPARAM (wParam) == XBUTTON2 && - !DoAction(pos.x, pos.y, MOUSE_X2MB_DBLCLK, false)) - { - DoAction(pos.x, pos.y, MOUSE_X2MB_DOWN, false); - } - - return 0; -} - -/* -** Runs the action when the MeterWindow gets or loses focus -** -*/ -LRESULT MeterWindow::OnSetWindowFocus(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) - { - case WM_SETFOCUS: - if (!m_OnFocusAction.empty()) - { - Rainmeter::GetInstance().ExecuteCommand(m_OnFocusAction.c_str(), this); - } - break; - - case WM_KILLFOCUS: - if (!m_OnUnfocusAction.empty()) - { - Rainmeter::GetInstance().ExecuteCommand(m_OnUnfocusAction.c_str(), this); - } - break; - } - - return 0; -} - -/* -** Handles the context menu. The menu is recreated every time it is shown. -** -*/ -LRESULT MeterWindow::OnContextMenu(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - POINT pos; - RECT rect; - GetWindowRect(m_Window, &rect); - - if ((lParam & 0xFFFFFFFF) == 0xFFFFFFFF) // WM_CONTEXTMENU is generated from the keyboard (Shift+F10/VK_APPS) - { - // Set menu position to (0,0) on the window - pos.x = rect.left; - pos.y = rect.top; - } - else - { - pos.x = GET_X_LPARAM(lParam); - pos.y = GET_Y_LPARAM(lParam); - - // Transform the point to client rect - POINT posc = {pos.x - rect.left, pos.y - rect.top}; - - // Handle buttons - HandleButtons(posc, BUTTONPROC_MOVE); - - // If RMB up or RMB down or double-click cause actions, do not show the menu! - if (!IsCtrlKeyDown() && // Ctrl is pressed, so ignore any actions - (DoAction(posc.x, posc.y, MOUSE_RMB_UP, false) || DoAction(posc.x, posc.y, MOUSE_RMB_DOWN, true) || DoAction(posc.x, posc.y, MOUSE_RMB_DBLCLK, true))) - { - return 0; - } - } - - Rainmeter::GetInstance().ShowContextMenu(pos, this); - - return 0; -} - -/* -** Executes the action if such are defined. Returns true, if action was executed. -** If the test is true, the action is not executed. -** -*/ -bool MeterWindow::DoAction(int x, int y, MOUSEACTION action, bool test) -{ - std::wstring command; - - // Check if the hitpoint was over some meter - std::vector::const_reverse_iterator j = m_Meters.rbegin(); - for ( ; j != m_Meters.rend(); ++j) - { - // Hidden meters are ignored - if ((*j)->IsHidden()) continue; - - const Mouse& mouse = (*j)->GetMouse(); - if (mouse.HasActionCommand(action) && (*j)->HitTest(x, y)) - { - command = mouse.GetActionCommand(action); - break; - } - } - - if (command.empty()) - { - if (m_Mouse.HasActionCommand(action) && HitTest(x, y)) - { - command = m_Mouse.GetActionCommand(action); - } - } - - if (!command.empty()) - { - if (!test) - { - Rainmeter::GetInstance().ExecuteCommand(command.c_str(), this); - } - - return true; - } - - return false; -} - -/* -** Executes the action if such are defined. Returns true, if meter/window which should be processed still may exist. -** -*/ -bool MeterWindow::DoMoveAction(int x, int y, MOUSEACTION action) -{ - bool buttonFound = false; - - // Check if the hitpoint was over some meter - std::vector::const_reverse_iterator j = m_Meters.rbegin(); - for ( ; j != m_Meters.rend(); ++j) - { - if (!(*j)->IsHidden() && (*j)->HitTest(x, y)) - { - if (action == MOUSE_OVER) - { - if (!m_MouseOver) - { - // If the mouse is over a meter it's also over the main window - //LogDebugF(L"@Enter: %s", m_FolderPath.c_str()); - m_MouseOver = true; - SetMouseLeaveEvent(false); - RegisterMouseInput(); - - if (!m_Mouse.GetOverAction().empty()) - { - UINT currCounter = m_MouseMoveCounter; - Rainmeter::GetInstance().ExecuteCommand(m_Mouse.GetOverAction().c_str(), this); - return (currCounter == m_MouseMoveCounter); - } - } - - // Handle button - MeterButton* button = nullptr; - if (m_HasButtons && (*j)->GetTypeID() == TypeID()) - { - button = (MeterButton*)(*j); - if (button) - { - if (!buttonFound) - { - button->SetFocus(true); - buttonFound = true; - } - else - { - button->SetFocus(false); - } - } - } - - if (!(*j)->IsMouseOver()) - { - const Mouse& mouse = (*j)->GetMouse(); - if (!mouse.GetOverAction().empty() || - !mouse.GetLeaveAction().empty() || - button) - { - //LogDebugF(L"MeterEnter: %s - [%s]", m_FolderPath.c_str(), (*j)->GetName()); - (*j)->SetMouseOver(true); - - if (!mouse.GetOverAction().empty()) - { - UINT currCounter = m_MouseMoveCounter; - Rainmeter::GetInstance().ExecuteCommand(mouse.GetOverAction().c_str(), this); - return (currCounter == m_MouseMoveCounter); - } - } - } - } - } - else - { - if (action == MOUSE_LEAVE) - { - if ((*j)->IsMouseOver()) - { - // Handle button - if (m_HasButtons && (*j)->GetTypeID() == TypeID()) - { - MeterButton* button = (MeterButton*)(*j); - button->SetFocus(false); - } - - //LogDebugF(L"MeterLeave: %s - [%s]", m_FolderPath.c_str(), (*j)->GetName()); - (*j)->SetMouseOver(false); - - const Mouse& mouse = (*j)->GetMouse(); - if (!mouse.GetLeaveAction().empty()) - { - Rainmeter::GetInstance().ExecuteCommand(mouse.GetLeaveAction().c_str(), this); - return true; - } - } - } - } - } - - if (HitTest(x, y)) - { - // If no meters caused actions, do the default actions - if (action == MOUSE_OVER) - { - if (!m_MouseOver) - { - //LogDebugF(L"Enter: %s", m_FolderPath.c_str()); - m_MouseOver = true; - SetMouseLeaveEvent(false); - RegisterMouseInput(); - - if (!m_Mouse.GetOverAction().empty()) - { - UINT currCounter = m_MouseMoveCounter; - Rainmeter::GetInstance().ExecuteCommand(m_Mouse.GetOverAction().c_str(), this); - return (currCounter == m_MouseMoveCounter); - } - } - } - } - else - { - if (action == MOUSE_LEAVE) - { - // Mouse leave happens when the mouse is outside the window - if (m_MouseOver) - { - //LogDebugF(L"Leave: %s", m_FolderPath.c_str()); - m_MouseOver = false; - SetMouseLeaveEvent(true); - UnregisterMouseInput(); - - if (!m_Mouse.GetLeaveAction().empty()) - { - Rainmeter::GetInstance().ExecuteCommand(m_Mouse.GetLeaveAction().c_str(), this); - return true; - } - } - } - } - - return false; -} - -/* -** Sends mouse wheel messages to the window if the window does not have focus. -** -*/ -LRESULT MeterWindow::OnMouseInput(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - const POINT pos = System::GetCursorPosition(); - - // Only process for unfocused skin window. - if (m_Window == WindowFromPoint(pos) && m_Window != GetFocus()) - { - RAWINPUT ri; - UINT riSize = sizeof(ri); - const UINT dataSize = GetRawInputData( - (HRAWINPUT)lParam, RID_INPUT, &ri, &riSize, sizeof(RAWINPUTHEADER)); - if (dataSize != (UINT)-1 && - ri.header.dwType == RIM_TYPEMOUSE) - { - const WPARAM wheelDelta = MAKEWPARAM(0, HIWORD((short)ri.data.mouse.usButtonData)); - const LPARAM wheelPos = MAKELPARAM(pos.x, pos.y); - if (ri.data.mouse.usButtonFlags == RI_MOUSE_WHEEL) - { - OnMouseScrollMove(WM_INPUT, wheelDelta, wheelPos); - } - else if (ri.data.mouse.usButtonFlags == RI_MOUSE_HORIZONTAL_WHEEL) - { - OnMouseHScrollMove(WM_MOUSEHWHEEL, wheelDelta, wheelPos); - } - } - } - - // DefWindowProc must be called after processing WM_INPUT. - DefWindowProc(m_Window, uMsg, wParam, lParam); - return 0; -} - -/* -** Stores the new place of the window, in screen coordinates. -** -*/ -LRESULT MeterWindow::OnMove(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - // The lParam's x/y parameters are given in screen coordinates for overlapped and pop-up windows - // and in parent-client coordinates for child windows. - - // Store the new window position - m_ScreenX = GET_X_LPARAM(lParam); - m_ScreenY = GET_Y_LPARAM(lParam); - - SetWindowPositionVariables(m_ScreenX, m_ScreenY); - - if (m_Dragging) - { - ScreenToWindow(); - } - - return 0; -} - -/* -** Performs an action when returning from sleep -** -*/ -LRESULT MeterWindow::OnWake(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - if (wParam == PBT_APMRESUMEAUTOMATIC && !m_OnWakeAction.empty()) - { - Rainmeter::GetInstance().ExecuteCommand(m_OnWakeAction.c_str(), this); - } - - return 0; -} - -/* -** The main window procedure for the meter window. -** -*/ -LRESULT CALLBACK MeterWindow::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - MeterWindow* window = (MeterWindow*)GetWindowLongPtr(hWnd, GWLP_USERDATA); - - BEGIN_MESSAGEPROC - MESSAGE(OnMouseInput, WM_INPUT) - MESSAGE(OnMove, WM_MOVE) - MESSAGE(OnTimer, WM_TIMER) - MESSAGE(OnCommand, WM_COMMAND) - MESSAGE(OnSysCommand, WM_SYSCOMMAND) - MESSAGE(OnEnterSizeMove, WM_ENTERSIZEMOVE) - MESSAGE(OnExitSizeMove, WM_EXITSIZEMOVE) - MESSAGE(OnNcHitTest, WM_NCHITTEST) - MESSAGE(OnSetCursor, WM_SETCURSOR) - MESSAGE(OnEnterMenuLoop, WM_ENTERMENULOOP) - MESSAGE(OnMouseMove, WM_MOUSEMOVE) - MESSAGE(OnMouseMove, WM_NCMOUSEMOVE) - MESSAGE(OnMouseLeave, WM_MOUSELEAVE) - MESSAGE(OnMouseLeave, WM_NCMOUSELEAVE) - MESSAGE(OnMouseScrollMove, WM_MOUSEWHEEL) - MESSAGE(OnMouseHScrollMove, WM_MOUSEHWHEEL) - MESSAGE(OnContextMenu, WM_CONTEXTMENU) - MESSAGE(OnRightButtonDown, WM_NCRBUTTONDOWN) - MESSAGE(OnRightButtonDown, WM_RBUTTONDOWN) - MESSAGE(OnRightButtonUp, WM_RBUTTONUP) - MESSAGE(OnContextMenu, WM_NCRBUTTONUP) - MESSAGE(OnRightButtonDoubleClick, WM_RBUTTONDBLCLK) - MESSAGE(OnRightButtonDoubleClick, WM_NCRBUTTONDBLCLK) - MESSAGE(OnLeftButtonDown, WM_NCLBUTTONDOWN) - MESSAGE(OnLeftButtonDown, WM_LBUTTONDOWN) - MESSAGE(OnLeftButtonUp, WM_LBUTTONUP) - MESSAGE(OnLeftButtonUp, WM_NCLBUTTONUP) - MESSAGE(OnLeftButtonDoubleClick, WM_LBUTTONDBLCLK) - MESSAGE(OnLeftButtonDoubleClick, WM_NCLBUTTONDBLCLK) - MESSAGE(OnMiddleButtonDown, WM_NCMBUTTONDOWN) - MESSAGE(OnMiddleButtonDown, WM_MBUTTONDOWN) - MESSAGE(OnMiddleButtonUp, WM_MBUTTONUP) - MESSAGE(OnMiddleButtonUp, WM_NCMBUTTONUP) - MESSAGE(OnMiddleButtonDoubleClick, WM_MBUTTONDBLCLK) - MESSAGE(OnMiddleButtonDoubleClick, WM_NCMBUTTONDBLCLK) - MESSAGE(OnXButtonDown, WM_XBUTTONDOWN); - MESSAGE(OnXButtonDown, WM_NCXBUTTONDOWN); - MESSAGE(OnXButtonUp, WM_XBUTTONUP); - MESSAGE(OnXButtonUp, WM_NCXBUTTONUP); - MESSAGE(OnXButtonDoubleClick, WM_XBUTTONDBLCLK); - MESSAGE(OnXButtonDoubleClick, WM_NCXBUTTONDBLCLK); - MESSAGE(OnWindowPosChanging, WM_WINDOWPOSCHANGING) - MESSAGE(OnCopyData, WM_COPYDATA) - MESSAGE(OnDelayedRefresh, WM_METERWINDOW_DELAYED_REFRESH) - MESSAGE(OnDelayedMove, WM_METERWINDOW_DELAYED_MOVE) - MESSAGE(OnDwmColorChange, WM_DWMCOLORIZATIONCOLORCHANGED) - MESSAGE(OnDwmCompositionChange, WM_DWMCOMPOSITIONCHANGED) - MESSAGE(OnSettingChange, WM_SETTINGCHANGE) - MESSAGE(OnDisplayChange, WM_DISPLAYCHANGE) - MESSAGE(OnSetWindowFocus, WM_SETFOCUS) - MESSAGE(OnSetWindowFocus, WM_KILLFOCUS) - MESSAGE(OnWake, WM_POWERBROADCAST) - END_MESSAGEPROC -} - -/* -** The initial window procedure for the meter window. Passes control to WndProc after initial setup. -** -*/ -LRESULT CALLBACK MeterWindow::InitialWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - if (uMsg == WM_NCCREATE) - { - MeterWindow* window = (MeterWindow*)((LPCREATESTRUCT)lParam)->lpCreateParams; - SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)window); - - // Change the window procedure over to MainWndProc now that GWLP_USERDATA is set - SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)WndProc); - return TRUE; - } - - return DefWindowProc(hWnd, uMsg, wParam, lParam); -} - -/* -** Handles delayed refresh -** -*/ -LRESULT MeterWindow::OnDelayedRefresh(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - Refresh(false); - return 0; -} - -/* -** Handles delayed move. -** Do not save the position in this handler for the sake of preventing move by temporal resolution/workarea change. -** -*/ -LRESULT MeterWindow::OnDelayedMove(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - m_Parser.ResetMonitorVariables(this); - - // Move the window temporarily - ResizeWindow(false); - SetWindowPos(m_Window, nullptr, m_ScreenX, m_ScreenY, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); - - return 0; -} - -/* -** Handles bangs from the exe -** -*/ -LRESULT MeterWindow::OnCopyData(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - COPYDATASTRUCT* pCopyDataStruct = (COPYDATASTRUCT*)lParam; - - if (pCopyDataStruct && (pCopyDataStruct->dwData == 1) && (pCopyDataStruct->cbData > 0)) - { - if (Rainmeter::GetInstance().HasMeterWindow(this)) - { - const WCHAR* command = (const WCHAR*)pCopyDataStruct->lpData; - Rainmeter::GetInstance().ExecuteCommand(command, this); - } - else - { - // This meterwindow has been deactivated - LogWarning(L"Unable to bang unloaded skin"); - } - - return TRUE; - } - - return FALSE; -} - -/* -** Sets up the window position variables. -** -*/ -void MeterWindow::SetWindowPositionVariables(int x, int y) -{ - WCHAR buffer[32]; - - _itow_s(x, buffer, 10); - m_Parser.SetBuiltInVariable(L"CURRENTCONFIGX", buffer); - _itow_s(y, buffer, 10); - m_Parser.SetBuiltInVariable(L"CURRENTCONFIGY", buffer); -} - -/* -** Sets up the window size variables. -** -*/ -void MeterWindow::SetWindowSizeVariables(int w, int h) -{ - WCHAR buffer[32]; - - _itow_s(w, buffer, 10); - m_Parser.SetBuiltInVariable(L"CURRENTCONFIGWIDTH", buffer); - _itow_s(h, buffer, 10); - m_Parser.SetBuiltInVariable(L"CURRENTCONFIGHEIGHT", buffer); -} - -/* -** Converts the path to absolute by adding the skin's path to it (unless it already is absolute). -** -*/ -void MeterWindow::MakePathAbsolute(std::wstring& path) -{ - if (path.empty() || PathUtil::IsAbsolute(path)) - { - return; // It's already absolute path (or it's empty) - } - else - { - std::wstring absolute; - absolute.reserve(Rainmeter::GetInstance().GetSkinPath().size() + m_FolderPath.size() + 1 + path.size()); - absolute = Rainmeter::GetInstance().GetSkinPath(); - absolute += m_FolderPath; - absolute += L'\\'; - absolute += path; - absolute.swap(path); - } -} - -std::wstring MeterWindow::GetFilePath() -{ - std::wstring file = Rainmeter::GetInstance().GetSkinPath() + m_FolderPath; - file += L'\\'; - file += m_FileName; - return file; -} - -std::wstring MeterWindow::GetRootName() -{ - std::wstring::size_type loc; - if ((loc = m_FolderPath.find_first_of(L'\\')) != std::wstring::npos) - { - return m_FolderPath.substr(0, loc); - } - - return m_FolderPath; -} - -std::wstring MeterWindow::GetRootPath() -{ - std::wstring path = Rainmeter::GetInstance().GetSkinPath(); - - std::wstring::size_type loc; - if ((loc = m_FolderPath.find_first_of(L'\\')) != std::wstring::npos) - { - path.append(m_FolderPath, 0, loc + 1); - } - else - { - path += m_FolderPath; - path += L'\\'; - } - - return path; -} - -std::wstring MeterWindow::GetResourcesPath() -{ - std::wstring path = GetRootPath(); - path += L"@Resources\\"; - return path; -} - -std::wstring MeterWindow::GetSkinPath() -{ - std::wstring path; - if (!m_FolderPath.empty()) - { - path += m_FolderPath; - path += L"\\"; - } - - path += m_FileName; - return path; -} - -Meter* MeterWindow::GetMeter(const std::wstring& meterName) -{ - const WCHAR* name = meterName.c_str(); - std::vector::const_iterator j = m_Meters.begin(); - for ( ; j != m_Meters.end(); ++j) - { - if (_wcsicmp((*j)->GetName(), name) == 0) - { - return (*j); - } - } - return nullptr; -} +/* + Copyright (C) 2001 Kimmo Pekkola + + 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 "MeterWindow.h" +#include "Rainmeter.h" +#include "TrayWindow.h" +#include "System.h" +#include "Error.h" +#include "Meter.h" +#include "Measure.h" +#include "DialogAbout.h" +#include "DialogManage.h" +#include "resource.h" +#include "Litestep.h" +#include "MeasureCalc.h" +#include "MeasureNet.h" +#include "MeasurePlugin.h" +#include "MeterButton.h" +#include "MeterString.h" +#include "TintedImage.h" +#include "MeasureScript.h" +#include "../Version.h" +#include "../Common/PathUtil.h" +#include "../Common/Gfx/Canvas.h" + +using namespace Gdiplus; + +#define SNAPDISTANCE 10 + +#define ZPOS_FLAGS (SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOSENDCHANGING) + +enum TIMER +{ + TIMER_METER = 1, + TIMER_MOUSE = 2, + TIMER_FADE = 3, + TIMER_TRANSITION = 4, + TIMER_DEACTIVATE = 5 +}; +enum INTERVAL +{ + INTERVAL_METER = 1000, + INTERVAL_MOUSE = 500, + INTERVAL_FADE = 10, + INTERVAL_TRANSITION = 100 +}; + +int MeterWindow::c_InstanceCount = 0; + +HINSTANCE MeterWindow::c_DwmInstance = nullptr; +decltype(DwmEnableBlurBehindWindow)* MeterWindow::c_DwmEnableBlurBehindWindow = nullptr; +decltype(DwmGetColorizationColor)* MeterWindow::c_DwmGetColorizationColor = nullptr; +decltype(DwmSetWindowAttribute)* MeterWindow::c_DwmSetWindowAttribute = nullptr; +decltype(DwmIsCompositionEnabled)* MeterWindow::c_DwmIsCompositionEnabled = nullptr; + +/* +** Constructor +** +*/ +MeterWindow::MeterWindow(const std::wstring& folderPath, const std::wstring& file) : m_FolderPath(folderPath), m_FileName(file), + m_Canvas(), + m_Background(), + m_BackgroundSize(), + m_Window(), + m_Mouse(this), + m_MouseOver(false), + m_MouseInputRegistered(false), + m_HasMouseScrollAction(false), + m_BackgroundMargins(), + m_DragMargins(), + m_WindowX(1, L'0'), + m_WindowY(1, L'0'), + m_WindowXScreen(1), + m_WindowYScreen(1), + m_WindowXScreenDefined(false), + m_WindowYScreenDefined(false), + m_WindowXFromRight(false), + m_WindowYFromBottom(false), + m_WindowXPercentage(false), + m_WindowYPercentage(false), + m_WindowW(), + m_WindowH(), + m_ScreenX(), + m_ScreenY(), + m_AnchorXFromRight(false), + m_AnchorYFromBottom(false), + m_AnchorXPercentage(false), + m_AnchorYPercentage(false), + m_AnchorScreenX(), + m_AnchorScreenY(), + m_WindowDraggable(true), + m_WindowUpdate(INTERVAL_METER), + m_TransitionUpdate(INTERVAL_TRANSITION), + m_ActiveTransition(false), + m_HasNetMeasures(false), + m_HasButtons(false), + m_WindowHide(HIDEMODE_NONE), + m_WindowStartHidden(false), + m_SavePosition(false), // Must be false + m_SnapEdges(true), + m_AlphaValue(255), + m_FadeDuration(250), + m_WindowZPosition(ZPOSITION_NORMAL), + m_DynamicWindowSize(false), + m_ClickThrough(false), + m_KeepOnScreen(true), + m_AutoSelectScreen(false), + m_UseD2D(true), + m_Dragging(false), + m_Dragged(false), + m_BackgroundMode(BGMODE_IMAGE), + m_SolidAngle(), + m_SolidBevel(BEVELTYPE_NONE), + m_Blur(false), + m_BlurMode(BLURMODE_NONE), + m_BlurRegion(), + m_FadeStartTime(), + m_FadeStartValue(), + m_FadeEndValue(), + m_TransparencyValue(), + m_State(STATE_INITIALIZING), + m_Hidden(false), + m_ResizeWindow(RESIZEMODE_NONE), + m_UpdateCounter(), + m_MouseMoveCounter(), + m_FontCollection(), + m_ToolTipHidden(false) +{ + if (!c_DwmInstance && IsWindowsVistaOrGreater() && + (c_DwmInstance = System::RmLoadLibrary(L"dwmapi.dll")) != nullptr) + { + c_DwmEnableBlurBehindWindow = + (decltype(c_DwmEnableBlurBehindWindow))GetProcAddress(c_DwmInstance, "DwmEnableBlurBehindWindow"); + c_DwmGetColorizationColor = + (decltype(c_DwmGetColorizationColor))GetProcAddress(c_DwmInstance, "DwmGetColorizationColor"); + c_DwmSetWindowAttribute = + (decltype(c_DwmSetWindowAttribute))GetProcAddress(c_DwmInstance, "DwmSetWindowAttribute"); + c_DwmIsCompositionEnabled = + (decltype(c_DwmIsCompositionEnabled))GetProcAddress(c_DwmInstance, "DwmIsCompositionEnabled"); + } + + if (c_InstanceCount == 0) + { + WNDCLASSEX wc = {sizeof(WNDCLASSEX)}; + wc.style = CS_NOCLOSE | CS_DBLCLKS; + wc.lpfnWndProc = InitialWndProc; + wc.hInstance = GetRainmeter().GetModuleInstance(); + wc.hCursor = nullptr; // The cursor should be controlled by using SetCursor() when needed. + wc.lpszClassName = METERWINDOW_CLASS_NAME; + RegisterClassEx(&wc); + } + + ++c_InstanceCount; +} + +/* +** Destructor +** +*/ +MeterWindow::~MeterWindow() +{ + m_State = STATE_CLOSING; + + if (!m_OnCloseAction.empty()) + { + GetRainmeter().ExecuteCommand(m_OnCloseAction.c_str(), this); + } + + Dispose(false); + + --c_InstanceCount; + + if (c_InstanceCount == 0) + { + UnregisterClass(METERWINDOW_CLASS_NAME, GetRainmeter().GetModuleInstance()); + + if (c_DwmInstance) + { + FreeLibrary(c_DwmInstance); + c_DwmInstance = nullptr; + + c_DwmEnableBlurBehindWindow = nullptr; + c_DwmGetColorizationColor = nullptr; + c_DwmSetWindowAttribute = nullptr; + c_DwmIsCompositionEnabled = nullptr; + } + } +} + +/* +** Kills timers/hooks and disposes buffers +** +*/ +void MeterWindow::Dispose(bool refresh) +{ + // Kill the timer/hook + KillTimer(m_Window, TIMER_METER); + KillTimer(m_Window, TIMER_MOUSE); + KillTimer(m_Window, TIMER_FADE); + KillTimer(m_Window, TIMER_TRANSITION); + + m_FadeStartTime = 0; + + UnregisterMouseInput(); + m_HasMouseScrollAction = false; + + m_ActiveTransition = false; + + m_MouseOver = false; + SetMouseLeaveEvent(true); + + // Destroy the meters + for (auto j = m_Meters.begin(); j != m_Meters.end(); ++j) + { + delete (*j); + } + m_Meters.clear(); + + // Destroy the measures + for (auto i = m_Measures.begin(); i != m_Measures.end(); ++i) + { + delete (*i); + } + m_Measures.clear(); + + delete m_Background; + m_Background = nullptr; + + m_BackgroundSize.cx = m_BackgroundSize.cy = 0; + m_BackgroundName.clear(); + + if (m_BlurRegion) + { + DeleteObject(m_BlurRegion); + m_BlurRegion = nullptr; + } + + if (m_FontCollection) + { + delete m_FontCollection; + m_FontCollection = nullptr; + } + + if (!refresh) + { + if (m_Window) + { + DestroyWindow(m_Window); + m_Window = nullptr; + } + } + + delete m_Canvas; + m_Canvas = nullptr; +} + +/* +** Initializes the window, creates the class and the window. +** +*/ +void MeterWindow::Initialize() +{ + m_Window = CreateWindowEx( + WS_EX_TOOLWINDOW | WS_EX_LAYERED, + METERWINDOW_CLASS_NAME, + nullptr, + WS_POPUP, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + nullptr, + nullptr, + GetRainmeter().GetModuleInstance(), + this); + + setlocale(LC_NUMERIC, "C"); + + // Mark the window to ignore the Aero peek + IgnoreAeroPeek(); + + Refresh(true, true); + if (!m_WindowStartHidden) + { + if (m_WindowHide == HIDEMODE_FADEOUT) + { + FadeWindow(0, 255); + } + else + { + FadeWindow(0, m_AlphaValue); + } + } +} + +/* +** Excludes this window from the Aero Peek. +** +*/ +void MeterWindow::IgnoreAeroPeek() +{ + if (c_DwmSetWindowAttribute) + { + BOOL bValue = TRUE; + c_DwmSetWindowAttribute(m_Window, DWMWA_EXCLUDED_FROM_PEEK, &bValue, sizeof(bValue)); + } +} + +/* +** Registers to receive WM_INPUT for the mouse events. +** +*/ +void MeterWindow::RegisterMouseInput() +{ + if (!m_MouseInputRegistered && m_HasMouseScrollAction) + { + RAWINPUTDEVICE rid; + rid.usUsagePage = 0x01; + rid.usUsage = 0x02; // HID mouse + rid.dwFlags = RIDEV_INPUTSINK; + rid.hwndTarget = m_Window; + if (RegisterRawInputDevices(&rid, 1, sizeof(rid))) + { + m_MouseInputRegistered = true; + } + } +} + +void MeterWindow::UnregisterMouseInput() +{ + if (m_MouseInputRegistered) + { + RAWINPUTDEVICE rid; + rid.usUsagePage = 0x01; + rid.usUsage = 0x02; // HID mouse + rid.dwFlags = RIDEV_REMOVE; + rid.hwndTarget = m_Window; + RegisterRawInputDevices(&rid, 1, sizeof(rid)); + m_MouseInputRegistered = false; + } +} + +void MeterWindow::AddWindowExStyle(LONG_PTR flag) +{ + LONG_PTR style = GetWindowLongPtr(m_Window, GWL_EXSTYLE); + if ((style & flag) == 0) + { + SetWindowLongPtr(m_Window, GWL_EXSTYLE, style | flag); + } +} + +void MeterWindow::RemoveWindowExStyle(LONG_PTR flag) +{ + LONG_PTR style = GetWindowLongPtr(m_Window, GWL_EXSTYLE); + if ((style & flag) != 0) + { + SetWindowLongPtr(m_Window, GWL_EXSTYLE, style & ~flag); + } +} + +/* +** Unloads the skin with delay to avoid crash (and for fade to complete). +** +*/ +void MeterWindow::Deactivate() +{ + if (m_State == STATE_CLOSING) return; + m_State = STATE_CLOSING; + + GetRainmeter().RemoveMeterWindow(this); + GetRainmeter().AddUnmanagedMeterWindow(this); + + HideFade(); + SetTimer(m_Window, TIMER_DEACTIVATE, m_FadeDuration + 50, nullptr); +} + +/* +** Rebuilds the skin. +** +*/ +void MeterWindow::Refresh(bool init, bool all) +{ + if (m_State == STATE_CLOSING) return; + m_State = STATE_REFRESHING; + + GetRainmeter().SetCurrentParser(&m_Parser); + + LogNoticeF(this, L"Refreshing skin"); + + SetResizeWindowMode(RESIZEMODE_RESET); + + if (!init) + { + Dispose(true); + } + + ZPOSITION oldZPos = m_WindowZPosition; + + if (!ReadSkin()) + { + GetRainmeter().DeactivateSkin(this, -1); + return; + } + + // Remove transparent flag + RemoveWindowExStyle(WS_EX_TRANSPARENT); + + m_Hidden = m_WindowStartHidden; + m_TransparencyValue = m_AlphaValue; + + Update(true); + + if (m_BlurMode == BLURMODE_NONE) + { + HideBlur(); + } + else + { + ShowBlur(); + } + + if (m_KeepOnScreen) + { + MapCoordsToScreen(m_ScreenX, m_ScreenY, m_WindowW, m_WindowH); + } + + SetWindowPos(m_Window, nullptr, m_ScreenX, m_ScreenY, m_WindowW, m_WindowH, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSENDCHANGING); + + ScreenToWindow(); + + if (init) + { + ChangeSingleZPos(m_WindowZPosition, all); + } + else if (all || oldZPos != m_WindowZPosition) + { + ChangeZPos(m_WindowZPosition, all); + } + + // Start the timers + if (m_WindowUpdate >= 0) + { + SetTimer(m_Window, TIMER_METER, m_WindowUpdate, nullptr); + } + + SetTimer(m_Window, TIMER_MOUSE, INTERVAL_MOUSE, nullptr); + + GetRainmeter().SetCurrentParser(nullptr); + + m_State = STATE_RUNNING; + + if (!m_OnRefreshAction.empty()) + { + GetRainmeter().ExecuteCommand(m_OnRefreshAction.c_str(), this); + } +} + +void MeterWindow::SetMouseLeaveEvent(bool cancel) +{ + if (!cancel && (!m_MouseOver || m_ClickThrough)) return; + + // Check whether the mouse event is set + TRACKMOUSEEVENT tme = {sizeof(TRACKMOUSEEVENT)}; + tme.hwndTrack = m_Window; + tme.dwFlags = TME_QUERY; + + if (TrackMouseEvent(&tme) != 0) + { + if (cancel) + { + if (tme.dwFlags == 0) return; + } + else + { + if (m_WindowDraggable) + { + if (tme.dwFlags == (TME_LEAVE | TME_NONCLIENT)) return; + } + else + { + if (tme.dwFlags == TME_LEAVE) return; + } + } + } + + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.hwndTrack = m_Window; + + // Cancel the mouse event set before + tme.dwFlags |= TME_CANCEL; + TrackMouseEvent(&tme); + + if (cancel) return; + + // Set the mouse event + tme.dwFlags = TME_LEAVE; + if (m_WindowDraggable) + { + tme.dwFlags |= TME_NONCLIENT; + } + TrackMouseEvent(&tme); +} + +void MeterWindow::MapCoordsToScreen(int& x, int& y, int w, int h) +{ + const size_t numOfMonitors = System::GetMonitorCount(); // intentional + const std::vector& monitors = System::GetMultiMonitorInfo().monitors; + + // Check that the window is inside the screen area + POINT pt = {x + w / 2, y + h / 2}; + for (int i = 0; i < 5; ++i) + { + switch (i) + { + case 0: + // Use initial value + break; + + case 1: + pt.x = x; + pt.y = y; + break; + + case 2: + pt.x = x + w; + pt.y = y + h; + break; + + case 3: + pt.x = x; + pt.y = y + h; + break; + + case 4: + pt.x = x + w; + pt.y = y; + break; + } + + for (auto iter = monitors.cbegin(); iter != monitors.cend(); ++iter) + { + if (!(*iter).active) continue; + + const RECT r = (*iter).screen; + if (pt.x >= r.left && pt.x < r.right && pt.y >= r.top && pt.y < r.bottom) + { + x = min(x, r.right - w); + x = max(x, r.left); + y = min(y, r.bottom - h); + y = max(y, r.top); + return; + } + } + } + + // No monitor found for the window -> Use the default work area + const RECT r = monitors[System::GetMultiMonitorInfo().primary - 1].work; + x = min(x, r.right - w); + x = max(x, r.left); + y = min(y, r.bottom - h); + y = max(y, r.top); +} + +/* +** Moves the window to a new place (on the virtual screen) +** +*/ +void MeterWindow::MoveWindow(int x, int y) +{ + SetWindowPos(m_Window, nullptr, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); + + SavePositionIfAppropriate(); +} + +/* +** Sets the window's z-position +** +*/ +void MeterWindow::ChangeZPos(ZPOSITION zPos, bool all) +{ + HWND winPos = HWND_NOTOPMOST; + m_WindowZPosition = zPos; + + switch (zPos) + { + case ZPOSITION_ONTOPMOST: + case ZPOSITION_ONTOP: + winPos = HWND_TOPMOST; + break; + + case ZPOSITION_ONBOTTOM: + if (all) + { + if (System::GetShowDesktop()) + { + // Insert after the system window temporarily to keep order + winPos = System::GetWindow(); + } + else + { + // Insert after the helper window + winPos = System::GetHelperWindow(); + } + } + else + { + winPos = HWND_BOTTOM; + } + break; + + case ZPOSITION_NORMAL: + if (all || !GetRainmeter().IsNormalStayDesktop()) break; + case ZPOSITION_ONDESKTOP: + if (System::GetShowDesktop()) + { + winPos = System::GetHelperWindow(); + + if (all) + { + // Insert after the helper window + } + else + { + // Find the "backmost" topmost window + while (winPos = ::GetNextWindow(winPos, GW_HWNDPREV)) + { + if (GetWindowLongPtr(winPos, GWL_EXSTYLE) & WS_EX_TOPMOST) + { + // Insert after the found window + if (0 != SetWindowPos(m_Window, winPos, 0, 0, 0, 0, ZPOS_FLAGS)) + { + break; + } + } + } + return; + } + } + else + { + if (all) + { + // Insert after the helper window + winPos = System::GetHelperWindow(); + } + else + { + winPos = HWND_BOTTOM; + } + } + break; + } + + SetWindowPos(m_Window, winPos, 0, 0, 0, 0, ZPOS_FLAGS); +} + +/* +** Sets the window's z-position in proper order. +** +*/ +void MeterWindow::ChangeSingleZPos(ZPOSITION zPos, bool all) +{ + if (zPos == ZPOSITION_NORMAL && GetRainmeter().IsNormalStayDesktop() && (!all || System::GetShowDesktop())) + { + m_WindowZPosition = zPos; + + // Set window on top of all other ZPOSITION_ONDESKTOP, ZPOSITION_BOTTOM, and ZPOSITION_NORMAL windows + SetWindowPos(m_Window, System::GetBackmostTopWindow(), 0, 0, 0, 0, ZPOS_FLAGS); + + // Bring window on top of other application windows + BringWindowToTop(m_Window); + } + else + { + ChangeZPos(zPos, all); + } +} + +/* +** Runs the bang command with the given arguments. +** Correct number of arguments must be passed (or use Rainmeter::ExecuteBang). +*/ +void MeterWindow::DoBang(Bang bang, const std::vector& args) +{ + switch (bang) + { + case Bang::Refresh: + // Refresh needs to be delayed since it crashes if done during Update() + PostMessage(m_Window, WM_METERWINDOW_DELAYED_REFRESH, (WPARAM)nullptr, (LPARAM)nullptr); + break; + + case Bang::Redraw: + Redraw(); + break; + + case Bang::Update: + KillTimer(m_Window, TIMER_METER); // Kill timer temporarily + Update(false); + if (m_WindowUpdate >= 0) + { + SetTimer(m_Window, TIMER_METER, m_WindowUpdate, nullptr); + } + break; + + case Bang::ShowBlur: + ShowBlur(); + break; + + case Bang::HideBlur: + HideBlur(); + break; + + case Bang::ToggleBlur: + DoBang(IsBlur() ? Bang::HideBlur : Bang::ShowBlur, args); + break; + + case Bang::AddBlur: + ResizeBlur(args[0], RGN_OR); + if (IsBlur()) ShowBlur(); + break; + + case Bang::RemoveBlur: + ResizeBlur(args[0], RGN_DIFF); + if (IsBlur()) ShowBlur(); + break; + + case Bang::ToggleMeter: + ToggleMeter(args[0]); + break; + + case Bang::ShowMeter: + ShowMeter(args[0]); + break; + + case Bang::HideMeter: + HideMeter(args[0]); + break; + + case Bang::UpdateMeter: + UpdateMeter(args[0]); + break; + + case Bang::ToggleMeterGroup: + ToggleMeter(args[0], true); + break; + + case Bang::ShowMeterGroup: + ShowMeter(args[0], true); + break; + + case Bang::HideMeterGroup: + HideMeter(args[0], true); + break; + + case Bang::UpdateMeterGroup: + UpdateMeter(args[0], true); + break; + + case Bang::ToggleMeasure: + ToggleMeasure(args[0]); + break; + + case Bang::EnableMeasure: + EnableMeasure(args[0]); + break; + + case Bang::DisableMeasure: + DisableMeasure(args[0]); + break; + + case Bang::PauseMeasure: + PauseMeasure(args[0]); + break; + + case Bang::UnpauseMeasure: + UnpauseMeasure(args[0]); + break; + + case Bang::TogglePauseMeasure: + TogglePauseMeasure(args[0]); + break; + + case Bang::UpdateMeasure: + UpdateMeasure(args[0]); + DialogAbout::UpdateMeasures(this); + break; + + case Bang::DisableMeasureGroup: + DisableMeasure(args[0], true); + break; + + case Bang::ToggleMeasureGroup: + ToggleMeasure(args[0], true); + break; + + case Bang::EnableMeasureGroup: + EnableMeasure(args[0], true); + break; + + case Bang::PauseMeasureGroup: + PauseMeasure(args[0], true); + break; + + case Bang::UnpauseMeasureGroup: + UnpauseMeasure(args[0], true); + break; + + case Bang::TogglePauseMeasureGroup: + TogglePauseMeasure(args[0], true); + break; + + case Bang::UpdateMeasureGroup: + UpdateMeasure(args[0], true); + DialogAbout::UpdateMeasures(this); + break; + + case Bang::Show: + m_Hidden = false; + ShowWindow(m_Window, SW_SHOWNOACTIVATE); + UpdateWindowTransparency((m_WindowHide == HIDEMODE_FADEOUT) ? 255 : m_AlphaValue); + break; + + case Bang::Hide: + m_Hidden = true; + ShowWindow(m_Window, SW_HIDE); + break; + + case Bang::Toggle: + DoBang(m_Hidden ? Bang::Show : Bang::Hide, args); + break; + + case Bang::ShowFade: + ShowFade(); + break; + + case Bang::HideFade: + HideFade(); + break; + + case Bang::ToggleFade: + DoBang(m_Hidden ? Bang::ShowFade : Bang::HideFade, args); + break; + + case Bang::Move: + { + int x = m_Parser.ParseInt(args[0].c_str(), 0); + int y = m_Parser.ParseInt(args[1].c_str(), 0); + MoveWindow(x, y); + } + break; + + case Bang::ZPos: + SetWindowZPosition((ZPOSITION)m_Parser.ParseInt(args[0].c_str(), 0)); + break; + + case Bang::ClickThrough: + { + int f = m_Parser.ParseInt(args[0].c_str(), 0); + SetClickThrough((f == -1) ? !m_ClickThrough : f != 0); + } + break; + + case Bang::Draggable: + { + int f = m_Parser.ParseInt(args[0].c_str(), 0); + SetWindowDraggable((f == -1) ? !m_WindowDraggable : f != 0); + } + break; + + case Bang::SnapEdges: + { + int f = m_Parser.ParseInt(args[0].c_str(), 0); + SetSnapEdges((f == -1) ? !m_SnapEdges : f != 0); + } + break; + + case Bang::KeepOnScreen: + { + int f = m_Parser.ParseInt(args[0].c_str(), 0); + SetKeepOnScreen((f == -1) ? !m_KeepOnScreen : f != 0); + } + break; + + case Bang::SetTransparency: + { + const std::wstring& arg = args[0]; + m_AlphaValue = ConfigParser::ParseInt(arg.c_str(), 255); + m_AlphaValue = max(m_AlphaValue, 0); + m_AlphaValue = min(m_AlphaValue, 255); + UpdateWindowTransparency(m_AlphaValue); + } + break; + + case Bang::MoveMeter: + { + int x = m_Parser.ParseInt(args[0].c_str(), 0); + int y = m_Parser.ParseInt(args[1].c_str(), 0); + MoveMeter(args[2], x, y); + } + break; + + case Bang::CommandMeasure: + { + const std::wstring& measure = args[0]; + Measure* m = GetMeasure(measure); + if (m) + { + m->Command(args[1]); + } + else + { + LogWarningF(this, L"!CommandMeasure: [%s] not found", measure.c_str()); + } + } + break; + + case Bang::PluginBang: + { + std::wstring arg = args[0]; + std::wstring::size_type pos; + while ((pos = arg.find(L'"')) != std::wstring::npos) + { + arg.erase(pos, 1); + } + + std::wstring measure; + pos = arg.find(L' '); + if (pos != std::wstring::npos) + { + measure.assign(arg, 0, pos); + ++pos; + } + else + { + measure = arg; + } + arg.erase(0, pos); + + if (!measure.empty()) + { + Measure* m = GetMeasure(measure); + if (m) + { + m->Command(arg); + return; + } + + LogWarningF(this, L"!PluginBang: [%s] not found", measure.c_str()); + } + else + { + LogErrorF(this, L"!PluginBang: Invalid parameters"); + } + } + break; + + case Bang::SetVariable: + SetVariable(args[0], args[1]); + break; + + case Bang::SetOption: + SetOption(args[0], args[1], args[2], false); + break; + + case Bang::SetOptionGroup: + SetOption(args[0], args[1], args[2], true); + break; + + case Bang::SkinCustomMenu: + GetRainmeter().ShowSkinCustomContextMenu(System::GetCursorPosition(), this); + break; + } +} + +/* +** Enables blurring of the window background (using Aero) +** +*/ +void MeterWindow::ShowBlur() +{ + if (c_DwmGetColorizationColor && c_DwmIsCompositionEnabled && c_DwmEnableBlurBehindWindow) + { + SetBlur(true); + + // Check that Aero and transparency is enabled + DWORD color; + BOOL opaque, enabled; + if (c_DwmGetColorizationColor(&color, &opaque) != S_OK) + { + opaque = TRUE; + } + if (c_DwmIsCompositionEnabled(&enabled) != S_OK) + { + enabled = FALSE; + } + if (opaque || !enabled) return; + + if (m_BlurMode == BLURMODE_FULL) + { + if (m_BlurRegion) DeleteObject(m_BlurRegion); + m_BlurRegion = CreateRectRgn(0, 0, GetW(), GetH()); + } + + BlurBehindWindow(TRUE); + } +} + +/* +** Disables Aero blur +** +*/ +void MeterWindow::HideBlur() +{ + if (c_DwmEnableBlurBehindWindow) + { + SetBlur(false); + + BlurBehindWindow(FALSE); + } +} + +/* +** Adds to or removes from blur region +** +*/ +void MeterWindow::ResizeBlur(const std::wstring& arg, int mode) +{ + if (IsWindowsVistaOrGreater()) + { + WCHAR* parseSz = _wcsdup(arg.c_str()); + int type, x, y, w = 0, h = 0; + + WCHAR* token = wcstok(parseSz, L","); + if (token) + { + while (token[0] == L' ') ++token; + type = m_Parser.ParseInt(token, 0); + + token = wcstok(nullptr, L","); + if (token) + { + while (token[0] == L' ') ++token; + x = m_Parser.ParseInt(token, 0); + + token = wcstok(nullptr, L","); + if (token) + { + while (token[0] == L' ') ++token; + y = m_Parser.ParseInt(token, 0); + + token = wcstok(nullptr, L","); + if (token) + { + while (token[0] == L' ') ++token; + w = m_Parser.ParseInt(token, 0); + + token = wcstok(nullptr, L","); + if (token) + { + while (token[0] == L' ') ++token; + h = m_Parser.ParseInt(token, 0); + } + } + } + } + } + + if (w && h) + { + HRGN tempRegion; + + switch (type) + { + case 1: + tempRegion = CreateRectRgn(x, y, w, h); + break; + + case 2: + token = wcstok(nullptr, L","); + if (token) + { + while (token[0] == L' ') ++token; + int r = m_Parser.ParseInt(token, 0); + tempRegion = CreateRoundRectRgn(x, y, w, h, r, r); + } + break; + + case 3: + tempRegion = CreateEllipticRgn(x, y, w, h); + break; + + default: // Unknown type + free(parseSz); + return; + } + + CombineRgn(m_BlurRegion, m_BlurRegion, tempRegion, mode); + DeleteObject(tempRegion); + } + free(parseSz); + } +} + +/* +** Helper function that compares the given name to section's name. +** +*/ +bool CompareName(const Section* section, const WCHAR* name, bool group) +{ + return (group) ? section->BelongsToGroup(name) : (_wcsicmp(section->GetName(), name) == 0); +} + +/* +** Shows the given meter +** +*/ +void MeterWindow::ShowMeter(const std::wstring& name, bool group) +{ + const WCHAR* meter = name.c_str(); + + std::vector::const_iterator j = m_Meters.begin(); + for ( ; j != m_Meters.end(); ++j) + { + if (CompareName((*j), meter, group)) + { + (*j)->Show(); + SetResizeWindowMode(RESIZEMODE_CHECK); // Need to recalculate the window size + if (!group) return; + } + } + + if (!group) LogErrorF(this, L"!ShowMeter: [%s] not found", meter); +} + +/* +** Hides the given meter +** +*/ +void MeterWindow::HideMeter(const std::wstring& name, bool group) +{ + const WCHAR* meter = name.c_str(); + + std::vector::const_iterator j = m_Meters.begin(); + for ( ; j != m_Meters.end(); ++j) + { + if (CompareName((*j), meter, group)) + { + (*j)->Hide(); + SetResizeWindowMode(RESIZEMODE_CHECK); // Need to recalculate the window size + if (!group) return; + } + } + + if (!group) LogErrorF(this, L"!HideMeter: [%s] not found", meter); +} + +/* +** Toggles the given meter +** +*/ +void MeterWindow::ToggleMeter(const std::wstring& name, bool group) +{ + const WCHAR* meter = name.c_str(); + + std::vector::const_iterator j = m_Meters.begin(); + for ( ; j != m_Meters.end(); ++j) + { + if (CompareName((*j), meter, group)) + { + if ((*j)->IsHidden()) + { + (*j)->Show(); + } + else + { + (*j)->Hide(); + } + SetResizeWindowMode(RESIZEMODE_CHECK); // Need to recalculate the window size + if (!group) return; + } + } + + if (!group) LogErrorF(this, L"!ToggleMeter: [%s] not found", meter); +} + +/* +** Moves the given meter +** +*/ +void MeterWindow::MoveMeter(const std::wstring& name, int x, int y) +{ + const WCHAR* meter = name.c_str(); + + std::vector::const_iterator j = m_Meters.begin(); + for ( ; j != m_Meters.end(); ++j) + { + if (CompareName((*j), meter, false)) + { + (*j)->SetX(x); + (*j)->SetY(y); + SetResizeWindowMode(RESIZEMODE_CHECK); // Need to recalculate the window size + return; + } + } + + LogErrorF(this, L"!MoveMeter: [%s] not found", meter); +} + +/* +** Updates the given meter +** +*/ +void MeterWindow::UpdateMeter(const std::wstring& name, bool group) +{ + const WCHAR* meter = name.c_str(); + bool all = false; + + if (!group && meter[0] == L'*' && meter[1] == L'\0') // Allow [!UpdateMeter *] + { + all = true; + group = true; + } + + bool bActiveTransition = false; + bool bContinue = true; + for (auto j = m_Meters.cbegin(); j != m_Meters.cend(); ++j) + { + if (all || (bContinue && CompareName((*j), meter, group))) + { + if (UpdateMeter((*j), bActiveTransition, true)) + { + (*j)->DoUpdateAction(); + } + + SetResizeWindowMode(RESIZEMODE_CHECK); // Need to recalculate the window size + if (!group) + { + bContinue = false; + if (bActiveTransition) break; + } + } + else + { + // Check for transitions + if (!bActiveTransition && (*j)->HasActiveTransition()) + { + bActiveTransition = true; + if (!group && !bContinue) break; + } + } + } + + // Post-updates + PostUpdate(bActiveTransition); + + if (!group && bContinue) LogErrorF(this, L"!UpdateMeter: [%s] not found", meter); +} + +/* +** Enables the given measure +** +*/ +void MeterWindow::EnableMeasure(const std::wstring& name, bool group) +{ + const WCHAR* measure = name.c_str(); + + std::vector::const_iterator i = m_Measures.begin(); + for ( ; i != m_Measures.end(); ++i) + { + if (CompareName((*i), measure, group)) + { + (*i)->Enable(); + if (!group) return; + } + } + + if (!group) LogErrorF(this, L"!EnableMeasure: [%s] not found", measure); +} + +/* +** Disables the given measure +** +*/ +void MeterWindow::DisableMeasure(const std::wstring& name, bool group) +{ + const WCHAR* measure = name.c_str(); + + std::vector::const_iterator i = m_Measures.begin(); + for ( ; i != m_Measures.end(); ++i) + { + if (CompareName((*i), measure, group)) + { + (*i)->Disable(); + if (!group) return; + } + } + + if (!group) LogErrorF(this, L"!DisableMeasure: [%s] not found", measure); +} + +/* +** Toggles the given measure +** +*/ +void MeterWindow::ToggleMeasure(const std::wstring& name, bool group) +{ + const WCHAR* measure = name.c_str(); + + std::vector::const_iterator i = m_Measures.begin(); + for ( ; i != m_Measures.end(); ++i) + { + if (CompareName((*i), measure, group)) + { + if ((*i)->IsDisabled()) + { + (*i)->Enable(); + } + else + { + (*i)->Disable(); + } + if (!group) return; + } + } + + if (!group) LogErrorF(this, L"!ToggleMeasure: [%s] not found", measure); +} + +/* +** Pauses the given measure +** +*/ +void MeterWindow::PauseMeasure(const std::wstring& name, bool group) +{ + const WCHAR* measure = name.c_str(); + + std::vector::const_iterator i = m_Measures.begin(); + for ( ; i != m_Measures.end(); ++i) + { + if (CompareName((*i), measure, group)) + { + (*i)->Pause(); + if (!group) return; + } + } + + if (!group) LogErrorF(this, L"!PauseMeasure: [%s] not found", measure); +} + +/* +** Unpauses the given measure +** +*/ +void MeterWindow::UnpauseMeasure(const std::wstring& name, bool group) +{ + const WCHAR* measure = name.c_str(); + + std::vector::const_iterator i = m_Measures.begin(); + for ( ; i != m_Measures.end(); ++i) + { + if (CompareName((*i), measure, group)) + { + (*i)->Unpause(); + if (!group) return; + } + } + + if (!group) LogErrorF(this, L"!UnpauseMeasure: [%s] not found", measure); +} + +/* +** Toggles the pause state of the given measure +** +*/ +void MeterWindow::TogglePauseMeasure(const std::wstring& name, bool group) +{ + const WCHAR* measure = name.c_str(); + + std::vector::const_iterator i = m_Measures.begin(); + for ( ; i != m_Measures.end(); ++i) + { + if (CompareName((*i), measure, group)) + { + if ((*i)->IsPaused()) + { + (*i)->Unpause(); + } + else + { + (*i)->Pause(); + } + if (!group) return; + } + } + + if (!group) LogErrorF(this, L"!TogglePauseMeasure: [%s] not found", measure); +} + +/* +** Updates the given measure +** +*/ +void MeterWindow::UpdateMeasure(const std::wstring& name, bool group) +{ + const WCHAR* measure = name.c_str(); + bool all = false; + + if (!group && measure[0] == L'*' && measure[1] == L'\0') // Allow [!UpdateMeasure *] + { + all = true; + group = true; + } + + bool bNetStats = m_HasNetMeasures; + for (auto i = m_Measures.cbegin(); i != m_Measures.cend(); ++i) + { + if (all || CompareName((*i), measure, group)) + { + if (bNetStats && (*i)->GetTypeID() == TypeID()) + { + MeasureNet::UpdateIFTable(); + MeasureNet::UpdateStats(); + bNetStats = false; + } + + if (UpdateMeasure((*i), true)) + { + (*i)->DoUpdateAction(); + (*i)->DoChangeAction(); + } + + if (!group) return; + } + } + + if (!group) LogErrorF(this, L"!UpdateMeasure: [%s] not found", measure); +} + +/* +** Sets variable to given value. +** +*/ +void MeterWindow::SetVariable(const std::wstring& variable, const std::wstring& value) +{ + double result; + if (m_Parser.ParseFormula(value, &result)) + { + WCHAR buffer[256]; + int len = _snwprintf_s(buffer, _TRUNCATE, L"%.5f", result); + Measure::RemoveTrailingZero(buffer, len); + + const std::wstring& resultString = buffer; + + m_Parser.SetVariable(variable, resultString); + } + else + { + m_Parser.SetVariable(variable, value); + } +} + +/* +** Changes the property of a meter or measure. +** +*/ +void MeterWindow::SetOption(const std::wstring& section, const std::wstring& option, const std::wstring& value, bool group) +{ + auto setValue = [&](Section* section, const std::wstring& option, const std::wstring& value) + { + // Force DynamicVariables temporarily (until next ReadOptions()). + section->SetDynamicVariables(true); + + if (value.empty()) + { + m_Parser.DeleteValue(section->GetOriginalName(), option); + } + else + { + m_Parser.SetValue(section->GetOriginalName(), option, value); + } + }; + + if (group) + { + for (auto j = m_Meters.begin(); j != m_Meters.end(); ++j) + { + if ((*j)->BelongsToGroup(section)) + { + setValue(*j, option, value); + } + } + + for (auto i = m_Measures.begin(); i != m_Measures.end(); ++i) + { + if ((*i)->BelongsToGroup(section)) + { + setValue(*i, option, value); + } + } + } + else + { + Meter* meter = GetMeter(section); + if (meter) + { + setValue(meter, option, value); + return; + } + + Measure* measure = GetMeasure(section); + if (measure) + { + setValue(measure, option, value); + return; + } + + // ContextTitle and ContextAction in [Rainmeter] are dynamic + if (_wcsicmp(section.c_str(), L"Rainmeter") == 0 && + _wcsnicmp(option.c_str(), L"Context", 7) == 0) + { + if (value.empty()) + { + m_Parser.DeleteValue(section, option); + } + else + { + m_Parser.SetValue(section, option, value); + } + } + + // Is it a style? + } +} + +/* +** Calculates the screen cordinates from the WindowX/Y options +** +*/ +void MeterWindow::WindowToScreen() +{ + std::wstring::size_type index, index2; + int pixel = 0; + float num; + int screenx, screeny, screenh, screenw; + + const int numOfMonitors = (int)System::GetMonitorCount(); + const MultiMonitorInfo& monitorsInfo = System::GetMultiMonitorInfo(); + const std::vector& monitors = monitorsInfo.monitors; + + // Clear position flags + m_WindowXScreen = m_WindowYScreen = monitorsInfo.primary; // Default to primary screen + m_WindowXScreenDefined = m_WindowYScreenDefined = false; + m_WindowXFromRight = m_WindowYFromBottom = false; // Default to from left/top + m_WindowXPercentage = m_WindowYPercentage = false; // Default to pixels + m_AnchorXFromRight = m_AnchorYFromBottom = false; + m_AnchorXPercentage = m_AnchorYPercentage = false; + + // --- Calculate AnchorScreenX --- + + index = m_AnchorX.find_first_not_of(L"0123456789."); + num = (float)_wtof(m_AnchorX.substr(0,index).c_str()); + index = m_AnchorX.find_last_of(L'%'); + if (index != std::wstring::npos) m_AnchorXPercentage = true; + index = m_AnchorX.find_last_of(L'R'); + if (index != std::wstring::npos) m_AnchorXFromRight = true; + if (m_AnchorXPercentage) //is a percentage + { + pixel = (int)(m_WindowW * num / 100.0f); + } + else + { + pixel = (int)num; + } + if (m_AnchorXFromRight) //measure from right + { + pixel = m_WindowW - pixel; + } + else + { + //pixel = pixel; + } + m_AnchorScreenX = pixel; + + // --- Calculate AnchorScreenY --- + + index = m_AnchorY.find_first_not_of(L"0123456789."); + num = (float)_wtof(m_AnchorY.substr(0,index).c_str()); + index = m_AnchorY.find_last_of(L'%'); + if (index != std::wstring::npos) m_AnchorYPercentage = true; + index = m_AnchorY.find_last_of(L'R'); + if (index != std::wstring::npos) m_AnchorYFromBottom = true; + if (m_AnchorYPercentage) //is a percentage + { + pixel = (int)(m_WindowH * num / 100.0f); + } + else + { + pixel = (int)num; + } + if (m_AnchorYFromBottom) //measure from bottom + { + pixel = m_WindowH - pixel; + } + else + { + //pixel = pixel; + } + m_AnchorScreenY = pixel; + + // --- Calculate ScreenX --- + + index = m_WindowX.find_first_not_of(L"-0123456789."); + num = (float)_wtof(m_WindowX.substr(0,index).c_str()); + index = m_WindowX.find_last_of(L'%'); + index2 = m_WindowX.find_last_of(L'#'); // for ignoring the non-replaced variables such as "#WORKAREAX@n#" + if (index != std::wstring::npos && (index2 == std::wstring::npos || index2 < index)) + { + m_WindowXPercentage = true; + } + index = m_WindowX.find_last_of(L'R'); + if (index != std::wstring::npos && (index2 == std::wstring::npos || index2 < index)) + { + m_WindowXFromRight = true; + } + index = m_WindowX.find_last_of(L'@'); + if (index != std::wstring::npos && (index2 == std::wstring::npos || index2 < index)) + { + index = index + 1; + index2 = m_WindowX.find_first_not_of(L"0123456789", index); + + std::wstring screenStr = m_WindowX.substr(index, (index2 != std::wstring::npos) ? index2 - index : std::wstring::npos); + if (!screenStr.empty()) + { + int screenIndex = _wtoi(screenStr.c_str()); + if (screenIndex >= 0 && (screenIndex == 0 || screenIndex <= numOfMonitors && monitors[screenIndex - 1].active)) + { + m_WindowXScreen = screenIndex; + m_WindowXScreenDefined = true; + m_WindowYScreen = m_WindowXScreen; //Default to X and Y on same screen if not overridden on WindowY + m_WindowYScreenDefined = true; + } + } + } + if (m_WindowXScreen == 0) + { + screenx = monitorsInfo.vsL; + screenw = monitorsInfo.vsW; + } + else + { + screenx = monitors[m_WindowXScreen - 1].screen.left; + screenw = monitors[m_WindowXScreen - 1].screen.right - monitors[m_WindowXScreen - 1].screen.left; + } + if (m_WindowXPercentage) //is a percentage + { + pixel = (int)(screenw * num / 100.0f); + } + else + { + pixel = (int)num; + } + if (m_WindowXFromRight) //measure from right + { + pixel = screenx + (screenw - pixel); + } + else + { + pixel = screenx + pixel; + } + m_ScreenX = pixel - m_AnchorScreenX; + + // --- Calculate ScreenY --- + + index = m_WindowY.find_first_not_of(L"-0123456789."); + num = (float)_wtof(m_WindowY.substr(0,index).c_str()); + index = m_WindowY.find_last_of(L'%'); + index2 = m_WindowX.find_last_of(L'#'); // for ignoring the non-replaced variables such as "#WORKAREAY@n#" + if (index != std::wstring::npos && (index2 == std::wstring::npos || index2 < index)) + { + m_WindowYPercentage = true; + } + index = m_WindowY.find_last_of(L'B'); + if (index != std::wstring::npos && (index2 == std::wstring::npos || index2 < index)) + { + m_WindowYFromBottom = true; + } + index = m_WindowY.find_last_of(L'@'); + if (index != std::wstring::npos && (index2 == std::wstring::npos || index2 < index)) + { + index = index + 1; + index2 = m_WindowY.find_first_not_of(L"0123456789", index); + + std::wstring screenStr = m_WindowY.substr(index, (index2 != std::wstring::npos) ? index2 - index : std::wstring::npos); + if (!screenStr.empty()) + { + int screenIndex = _wtoi(screenStr.c_str()); + if (screenIndex >= 0 && (screenIndex == 0 || screenIndex <= numOfMonitors && monitors[screenIndex - 1].active)) + { + m_WindowYScreen = screenIndex; + m_WindowYScreenDefined = true; + } + } + } + if (m_WindowYScreen == 0) + { + screeny = monitorsInfo.vsT; + screenh = monitorsInfo.vsH; + } + else + { + screeny = monitors[m_WindowYScreen - 1].screen.top; + screenh = monitors[m_WindowYScreen - 1].screen.bottom - monitors[m_WindowYScreen - 1].screen.top; + } + if (m_WindowYPercentage) //is a percentage + { + pixel = (int)(screenh * num / 100.0f); + } + else + { + pixel = (int)num; + } + if (m_WindowYFromBottom) //measure from right + { + pixel = screeny + (screenh - pixel); + } + else + { + pixel = screeny + pixel; + } + m_ScreenY = pixel - m_AnchorScreenY; +} + +/* ScreenToWindow +** +** Calculates the WindowX/Y cordinates from the ScreenX/Y +** +*/ +void MeterWindow::ScreenToWindow() +{ + WCHAR buffer[256]; + int pixel = 0; + float num; + int screenx, screeny, screenh, screenw; + + const size_t numOfMonitors = System::GetMonitorCount(); + const MultiMonitorInfo& monitorsInfo = System::GetMultiMonitorInfo(); + const std::vector& monitors = monitorsInfo.monitors; + + // Correct to auto-selected screen + if (m_AutoSelectScreen) + { + RECT rect = {m_ScreenX, m_ScreenY, m_ScreenX + m_WindowW, m_ScreenY + m_WindowH}; + HMONITOR hMonitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST); + + if (hMonitor != nullptr) + { + int screenIndex = 1; + for (auto iter = monitors.cbegin(); iter != monitors.cend(); ++iter, ++screenIndex) + { + if ((*iter).active && (*iter).handle == hMonitor) + { + bool reset = (!m_WindowXScreenDefined || !m_WindowYScreenDefined || + m_WindowXScreen != screenIndex || m_WindowYScreen != screenIndex); + + m_WindowXScreen = m_WindowYScreen = screenIndex; + m_WindowXScreenDefined = m_WindowYScreenDefined = true; + + if (reset) + { + m_Parser.ResetMonitorVariables(this); // Set present monitor variables + } + break; + } + } + } + } + + // --- Calculate WindowX --- + + if (m_WindowXScreen == 0) + { + screenx = monitorsInfo.vsL; + screenw = monitorsInfo.vsW; + } + else + { + screenx = monitors[m_WindowXScreen - 1].screen.left; + screenw = monitors[m_WindowXScreen - 1].screen.right - monitors[m_WindowXScreen - 1].screen.left; + } + if (m_WindowXFromRight) + { + pixel = (screenx + screenw) - m_ScreenX; + pixel -= m_AnchorScreenX; + } + else + { + pixel = m_ScreenX - screenx; + pixel += m_AnchorScreenX; + } + if (m_WindowXPercentage) + { + num = 100.0f * (float)pixel / (float)screenw; + _snwprintf_s(buffer, _TRUNCATE, L"%.5f%%", num); + } + else + { + _itow_s(pixel, buffer, 10); + } + if (m_WindowXFromRight) + { + wcscat_s(buffer, L"R"); + } + if (m_WindowXScreenDefined) + { + _snwprintf_s(buffer, _TRUNCATE, L"%s@%i", buffer, m_WindowXScreen); + } + m_WindowX = buffer; + + // --- Calculate WindowY --- + + if (m_WindowYScreen == 0) + { + screeny = monitorsInfo.vsT; + screenh = monitorsInfo.vsH; + } + else + { + screeny = monitors[m_WindowYScreen - 1].screen.top; + screenh = monitors[m_WindowYScreen - 1].screen.bottom - monitors[m_WindowYScreen - 1].screen.top; + } + if (m_WindowYFromBottom) + { + pixel = (screeny + screenh) - m_ScreenY; + pixel -= m_AnchorScreenY; + } + else + { + pixel = m_ScreenY - screeny; + pixel += m_AnchorScreenY; + } + if (m_WindowYPercentage) + { + num = 100.0f * (float)pixel / (float)screenh; + _snwprintf_s(buffer, _TRUNCATE, L"%.5f%%", num); + } + else + { + _itow_s(pixel, buffer, 10); + } + if (m_WindowYFromBottom) + { + wcscat_s(buffer, L"B"); + } + if (m_WindowYScreenDefined) + { + _snwprintf_s(buffer, _TRUNCATE, L"%s@%i", buffer, m_WindowYScreen); + } + m_WindowY = buffer; +} + +/* +** Reads the skin options from Rainmeter.ini +** +*/ +void MeterWindow::ReadOptions() +{ + WCHAR buffer[32]; + + const WCHAR* section = m_FolderPath.c_str(); + ConfigParser parser; + parser.Initialize(GetRainmeter().GetIniFile(), nullptr, section); + + INT writeFlags = 0; + auto addWriteFlag = [&](INT flag) + { + if (parser.GetLastDefaultUsed()) + { + writeFlags |= flag; + } + }; + + // Check if the window position should be read as a formula + double value; + m_WindowX = parser.ReadString(section, L"WindowX", L"0"); + addWriteFlag(OPTION_POSITION); + if (parser.ParseFormula(m_WindowX, &value)) + { + _itow_s((int)value, buffer, 10); + m_WindowX = buffer; + } + m_WindowY = parser.ReadString(section, L"WindowY", L"0"); + addWriteFlag(OPTION_POSITION); + if (parser.ParseFormula(m_WindowY, &value)) + { + _itow_s((int)value, buffer, 10); + m_WindowY = buffer; + } + + m_AnchorX = parser.ReadString(section, L"AnchorX", L"0"); + m_AnchorY = parser.ReadString(section, L"AnchorY", L"0"); + + int zPos = parser.ReadInt(section, L"AlwaysOnTop", ZPOSITION_NORMAL); + addWriteFlag(OPTION_ALWAYSONTOP); + m_WindowZPosition = (zPos >= ZPOSITION_ONDESKTOP && zPos <= ZPOSITION_ONTOPMOST) ? (ZPOSITION)zPos : ZPOSITION_NORMAL; + + int hideMode = parser.ReadInt(section, L"HideOnMouseOver", HIDEMODE_NONE); + m_WindowHide = (hideMode >= HIDEMODE_NONE && hideMode <= HIDEMODE_FADEOUT) ? (HIDEMODE)hideMode : HIDEMODE_NONE; + + m_WindowDraggable = parser.ReadBool(section, L"Draggable", true); + addWriteFlag(OPTION_DRAGGABLE); + + m_SnapEdges = parser.ReadBool(section, L"SnapEdges", true); + addWriteFlag(OPTION_SNAPEDGES); + + m_ClickThrough = parser.ReadBool(section, L"ClickThrough", false); + addWriteFlag(OPTION_CLICKTHROUGH); + + m_KeepOnScreen = parser.ReadBool(section, L"KeepOnScreen", true); + addWriteFlag(OPTION_KEEPONSCREEN); + + m_UseD2D = parser.ReadBool(section, L"UseD2D", true); + m_SavePosition = parser.ReadBool(section, L"SavePosition", true); + m_WindowStartHidden = parser.ReadBool(section, L"StartHidden", false); + m_AutoSelectScreen = parser.ReadBool(section, L"AutoSelectScreen", false); + + m_AlphaValue = parser.ReadInt(section, L"AlphaValue", 255); + m_AlphaValue = max(m_AlphaValue, 0); + m_AlphaValue = min(m_AlphaValue, 255); + + m_FadeDuration = parser.ReadInt(section, L"FadeDuration", 250); + + m_SkinGroup = parser.ReadString(section, L"Group", L""); + + if (writeFlags != 0) + { + WriteOptions(writeFlags); + } + + // Set WindowXScreen/WindowYScreen temporarily + WindowToScreen(); +} + +/* +** Writes the specified options to Rainmeter.ini +** +*/ +void MeterWindow::WriteOptions(INT setting) +{ + const WCHAR* iniFile = GetRainmeter().GetIniFile().c_str(); + + if (*iniFile) + { + WCHAR buffer[32]; + const WCHAR* section = m_FolderPath.c_str(); + + if (setting != OPTION_ALL) + { + DialogManage::UpdateSkins(this); + } + + if (setting & OPTION_POSITION) + { + ScreenToWindow(); + + // If position needs to be save, do so. + if (m_SavePosition) + { + WritePrivateProfileString(section, L"WindowX", m_WindowX.c_str(), iniFile); + WritePrivateProfileString(section, L"WindowY", m_WindowY.c_str(), iniFile); + } + + if (setting == OPTION_POSITION) return; + } + + if (setting & OPTION_ALPHAVALUE) + { + _itow_s(m_AlphaValue, buffer, 10); + WritePrivateProfileString(section, L"AlphaValue", buffer, iniFile); + } + + if (setting & OPTION_FADEDURATION) + { + _itow_s(m_FadeDuration, buffer, 10); + WritePrivateProfileString(section, L"FadeDuration", buffer, iniFile); + } + + if (setting & OPTION_CLICKTHROUGH) + { + WritePrivateProfileString(section, L"ClickThrough", m_ClickThrough ? L"1" : L"0", iniFile); + } + + if (setting & OPTION_DRAGGABLE) + { + WritePrivateProfileString(section, L"Draggable", m_WindowDraggable ? L"1" : L"0", iniFile); + } + + if (setting & OPTION_HIDEONMOUSEOVER) + { + _itow_s(m_WindowHide, buffer, 10); + WritePrivateProfileString(section, L"HideOnMouseOver", buffer, iniFile); + } + + if (setting & OPTION_SAVEPOSITION) + { + WritePrivateProfileString(section, L"SavePosition", m_SavePosition ? L"1" : L"0", iniFile); + } + + if (setting & OPTION_SNAPEDGES) + { + WritePrivateProfileString(section, L"SnapEdges", m_SnapEdges ? L"1" : L"0", iniFile); + } + + if (setting & OPTION_KEEPONSCREEN) + { + WritePrivateProfileString(section, L"KeepOnScreen", m_KeepOnScreen ? L"1" : L"0", iniFile); + } + + if (setting & OPTION_AUTOSELECTSCREEN) + { + WritePrivateProfileString(section, L"AutoSelectScreen", m_AutoSelectScreen ? L"1" : L"0", iniFile); + } + + if (setting & OPTION_ALWAYSONTOP) + { + _itow_s(m_WindowZPosition, buffer, 10); + WritePrivateProfileString(section, L"AlwaysOnTop", buffer, iniFile); + } + + if (setting & OPTION_USED2D) + { + WritePrivateProfileString(section, L"UseD2D", m_UseD2D ? L"1" : L"0", iniFile); + } + } +} + +/* +** Reads the skin file and creates the meters and measures. +** +*/ +bool MeterWindow::ReadSkin() +{ + WCHAR buffer[128]; + + std::wstring iniFile = GetFilePath(); + + // Verify whether the file exists + if (_waccess(iniFile.c_str(), 0) == -1) + { + std::wstring message = GetFormattedString(ID_STR_UNABLETOREFRESHSKIN, m_FolderPath.c_str(), m_FileName.c_str()); + GetRainmeter().ShowMessage(m_Window, message.c_str(), MB_OK | MB_ICONEXCLAMATION); + return false; + } + + std::wstring resourcePath = GetResourcesPath(); + bool hasResourcesFolder = (_waccess(resourcePath.c_str(), 0) == 0); + + // Read options from Rainmeter.ini. + ReadOptions(); + + m_Parser.Initialize(iniFile, this, nullptr, &resourcePath); + + m_Canvas = Gfx::Canvas::Create( + m_UseD2D && GetRainmeter().GetUseD2D() ? Gfx::Renderer::PreferD2D : Gfx::Renderer::GDIP); + m_Canvas->SetAccurateText(m_Parser.ReadBool(L"Rainmeter", L"AccurateText", false)); + + // Gotta have some kind of buffer during initialization + CreateDoubleBuffer(1, 1); + + // Check the version + UINT appVersion = m_Parser.ReadUInt(L"Rainmeter", L"AppVersion", 0); + if (appVersion > RAINMETER_VERSION) + { + if (appVersion % 1000 != 0) + { + _snwprintf_s(buffer, _TRUNCATE, L"%u.%u.%u", appVersion / 1000000, (appVersion / 1000) % 1000, appVersion % 1000); + } + else + { + _snwprintf_s(buffer, _TRUNCATE, L"%u.%u", appVersion / 1000000, (appVersion / 1000) % 1000); + } + + std::wstring text = GetFormattedString(ID_STR_NEWVERSIONREQUIRED, m_FolderPath.c_str(), m_FileName.c_str(), buffer); + GetRainmeter().ShowMessage(m_Window, text.c_str(), MB_OK | MB_ICONEXCLAMATION); + return false; + } + + // Initialize window variables + SetWindowPositionVariables(m_ScreenX, m_ScreenY); + SetWindowSizeVariables(0, 0); + + // Global settings + const std::wstring& group = m_Parser.ReadString(L"Rainmeter", L"Group", L""); + if (!group.empty()) + { + m_SkinGroup += L'|'; + m_SkinGroup += group; + } + InitializeGroup(m_SkinGroup); + + static const RECT defMargins = {0}; + m_BackgroundMargins = m_Parser.ReadRECT(L"Rainmeter", L"BackgroundMargins", defMargins); + m_DragMargins = m_Parser.ReadRECT(L"Rainmeter", L"DragMargins", defMargins); + + m_BackgroundMode = (BGMODE)m_Parser.ReadInt(L"Rainmeter", L"BackgroundMode", BGMODE_IMAGE); + m_SolidBevel = (BEVELTYPE)m_Parser.ReadInt(L"Rainmeter", L"BevelType", BEVELTYPE_NONE); + + m_SolidColor = m_Parser.ReadColor(L"Rainmeter", L"SolidColor", Color::Gray); + m_SolidColor2 = m_Parser.ReadColor(L"Rainmeter", L"SolidColor2", m_SolidColor.GetValue()); + m_SolidAngle = (Gdiplus::REAL)m_Parser.ReadFloat(L"Rainmeter", L"GradientAngle", 0.0); + + m_DynamicWindowSize = m_Parser.ReadBool(L"Rainmeter", L"DynamicWindowSize", false); + + if (m_BackgroundMode == BGMODE_IMAGE || m_BackgroundMode == BGMODE_SCALED_IMAGE || m_BackgroundMode == BGMODE_TILED_IMAGE) + { + m_BackgroundName = m_Parser.ReadString(L"Rainmeter", L"Background", L""); + if (!m_BackgroundName.empty()) + { + MakePathAbsolute(m_BackgroundName); + } + else + { + m_BackgroundMode = BGMODE_COPY; + } + } + + m_Mouse.ReadOptions(m_Parser, L"Rainmeter"); + + m_OnRefreshAction = m_Parser.ReadString(L"Rainmeter", L"OnRefreshAction", L"", false); + m_OnCloseAction = m_Parser.ReadString(L"Rainmeter", L"OnCloseAction", L"", false); + m_OnFocusAction = m_Parser.ReadString(L"Rainmeter", L"OnFocusAction", L"", false); + m_OnUnfocusAction = m_Parser.ReadString(L"Rainmeter", L"OnUnfocusAction", L"", false); + m_OnUpdateAction = m_Parser.ReadString(L"Rainmeter", L"OnUpdateAction", L"", false); + m_OnWakeAction = m_Parser.ReadString(L"Rainmeter", L"OnWakeAction", L"", false); + + m_WindowUpdate = m_Parser.ReadInt(L"Rainmeter", L"Update", INTERVAL_METER); + m_TransitionUpdate = m_Parser.ReadInt(L"Rainmeter", L"TransitionUpdate", INTERVAL_TRANSITION); + m_ToolTipHidden = m_Parser.ReadBool(L"Rainmeter", L"ToolTipHidden", false); + + if (IsWindowsVistaOrGreater()) + { + if (m_Parser.ReadBool(L"Rainmeter", L"Blur", false)) + { + const WCHAR* blurRegion = m_Parser.ReadString(L"Rainmeter", L"BlurRegion", L"", false).c_str(); + + if (*blurRegion) + { + m_BlurMode = BLURMODE_REGION; + m_BlurRegion = CreateRectRgn(0, 0, 0, 0); // Create empty region + int i = 1; + + do + { + ResizeBlur(blurRegion, RGN_OR); + + // Check for BlurRegion2, BlurRegion3, etc. + _snwprintf_s(buffer, _TRUNCATE, L"BlurRegion%i", ++i); + blurRegion = m_Parser.ReadString(L"Rainmeter", buffer, L"").c_str(); + } + while (*blurRegion); + } + else + { + m_BlurMode = BLURMODE_FULL; + } + } + else + { + m_BlurMode = BLURMODE_NONE; + } + } + + // Load fonts in Resources folder + if (hasResourcesFolder) + { + WIN32_FIND_DATA fd; + resourcePath += L"Fonts\\*"; + + HANDLE find = FindFirstFileEx( + resourcePath.c_str(), + (IsWindows7OrGreater()) ? FindExInfoBasic : FindExInfoStandard, + &fd, + FindExSearchNameMatch, + nullptr, + 0); + + if (find != INVALID_HANDLE_VALUE) + { + m_FontCollection = m_Canvas->CreateFontCollection(); + + do + { + if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + std::wstring file(resourcePath, 0, resourcePath.length() - 1); + file += fd.cFileName; + if (!m_FontCollection->AddFile(file.c_str())) + { + LogErrorF(this, L"Unable to load font: %s", file.c_str()); + } + } + } + while (FindNextFile(find, &fd)); + + FindClose(find); + } + } + + // Load local fonts + const WCHAR* localFont = m_Parser.ReadString(L"Rainmeter", L"LocalFont", L"").c_str(); + if (*localFont) + { + if (!m_FontCollection) + { + m_FontCollection = m_Canvas->CreateFontCollection(); + } + + int i = 1; + do + { + // Try program folder first + std::wstring szFontFile = GetRainmeter().GetPath() + L"Fonts\\"; + szFontFile += localFont; + if (!m_FontCollection->AddFile(szFontFile.c_str())) + { + szFontFile = localFont; + MakePathAbsolute(szFontFile); + if (!m_FontCollection->AddFile(szFontFile.c_str())) + { + LogErrorF(this, L"Unable to load font: %s", localFont); + } + } + + // Check for LocalFont2, LocalFont3, etc. + _snwprintf_s(buffer, _TRUNCATE, L"LocalFont%i", ++i); + localFont = m_Parser.ReadString(L"Rainmeter", buffer, L"").c_str(); + } + while (*localFont); + } + + // Create all meters and measures. The meters and measures are not initialized in this loop + // to avoid errors caused by referencing nonexistent [sections] in the options. + m_HasNetMeasures = false; + m_HasButtons = false; + Meter* prevMeter = nullptr; + for (auto iter = m_Parser.GetSections().cbegin(); iter != m_Parser.GetSections().cend(); ++iter) + { + const WCHAR* section = (*iter).c_str(); + + if (_wcsicmp(L"Rainmeter", section) != 0 && + _wcsicmp(L"Variables", section) != 0 && + _wcsicmp(L"Metadata", section) != 0) + { + const std::wstring& measureName = m_Parser.ReadString(section, L"Measure", L"", false); + if (!measureName.empty()) + { + Measure* measure = Measure::Create(measureName.c_str(), this, section); + if (measure) + { + m_Measures.push_back(measure); + m_Parser.AddMeasure(measure); + + if (measure->GetTypeID() == TypeID()) + { + m_HasNetMeasures = true; + } + } + + continue; + } + + const std::wstring& meterName = m_Parser.ReadString(section, L"Meter", L"", false); + if (!meterName.empty()) + { + // It's a meter + Meter* meter = Meter::Create(meterName.c_str(), this, section); + if (meter) + { + m_Meters.push_back(meter); + meter->SetRelativeMeter(prevMeter); + + if (meter->GetTypeID() == TypeID()) + { + m_HasButtons = true; + } + + prevMeter = meter; + } + + continue; + } + } + } + + if (m_Meters.empty()) + { + std::wstring text = GetFormattedString(ID_STR_NOMETERSINSKIN, m_FolderPath.c_str(), m_FileName.c_str()); + GetRainmeter().ShowMessage(m_Window, text.c_str(), MB_OK | MB_ICONEXCLAMATION); + return false; + } + + // Read measure options. This is done before the meters to ensure that e.g. Substitute is used + // when the meters get the value of the measure. The measures cannot be initialized yet as som + // measures (e.g. Script) except that the meters are ready when calling Initialize(). + for (auto iter = m_Measures.cbegin(); iter != m_Measures.cend(); ++iter) + { + Measure* measure = *iter; + measure->ReadOptions(m_Parser); + } + + // Initialize meters. + for (auto iter = m_Meters.cbegin(); iter != m_Meters.cend(); ++iter) + { + Meter* meter = *iter; + meter->ReadOptions(m_Parser); + meter->Initialize(); + + if (!meter->GetToolTipText().empty()) + { + meter->CreateToolTip(this); + } + } + + // Initialize measures. + for (auto iter = m_Measures.cbegin(); iter != m_Measures.cend(); ++iter) + { + Measure* measure = *iter; + measure->Initialize(); + } + + // Set window size (and CURRENTCONFIGWIDTH/HEIGHT) temporarily + for (auto iter = m_Meters.cbegin(); iter != m_Meters.cend(); ++iter) + { + bool bActiveTransition = true; // Do not track the change of ActiveTransition + UpdateMeter(*iter, bActiveTransition, true); + } + ResizeWindow(true); + + return true; +} + +/* +** Changes the size of the window and re-adjusts the background +*/ +bool MeterWindow::ResizeWindow(bool reset) +{ + int w = m_BackgroundMargins.left; + int h = m_BackgroundMargins.top; + + // Get the largest meter point + std::vector::const_iterator j = m_Meters.begin(); + for ( ; j != m_Meters.end(); ++j) + { + int mr = (*j)->GetX() + (*j)->GetW(); + w = max(w, mr); + int mb = (*j)->GetY() + (*j)->GetH(); + h = max(h, mb); + } + + w += m_BackgroundMargins.right; + h += m_BackgroundMargins.bottom; + + w = max(w, m_BackgroundSize.cx); + h = max(h, m_BackgroundSize.cy); + + if (!reset && m_WindowW == w && m_WindowH == h) + { + WindowToScreen(); + return false; // The window is already correct size + } + + // Reset size (this is calculated below) + + delete m_Background; + m_Background = nullptr; + + if ((m_BackgroundMode == BGMODE_IMAGE || m_BackgroundMode == BGMODE_SCALED_IMAGE || m_BackgroundMode == BGMODE_TILED_IMAGE) && !m_BackgroundName.empty()) + { + // Load the background + TintedImage* tintedBackground = new TintedImage(L"Background"); + tintedBackground->ReadOptions(m_Parser, L"Rainmeter"); + tintedBackground->LoadImage(m_BackgroundName, true); + + if (!tintedBackground->IsLoaded()) + { + m_BackgroundSize.cx = 0; + m_BackgroundSize.cy = 0; + + m_WindowW = 0; + m_WindowH = 0; + } + else + { + Bitmap* tempBackground = tintedBackground->GetImage(); + + // Calculate the window dimensions + m_BackgroundSize.cx = tempBackground->GetWidth(); + m_BackgroundSize.cy = tempBackground->GetHeight(); + + if (m_BackgroundMode == BGMODE_IMAGE) + { + w = m_BackgroundSize.cx; + h = m_BackgroundSize.cy; + } + else + { + w = max(w, m_BackgroundSize.cx); + h = max(h, m_BackgroundSize.cy); + } + + Bitmap* background = new Bitmap(w, h, PixelFormat32bppPARGB); + Graphics graphics(background); + + if (m_BackgroundMode == BGMODE_IMAGE) + { + Rect r(0, 0, w, h); + graphics.DrawImage(tempBackground, r, 0, 0, w, h, UnitPixel); + } + else + { + // Scale the background to fill the whole window + if (m_BackgroundMode == BGMODE_SCALED_IMAGE) + { + const RECT m = m_BackgroundMargins; + + if (m.top > 0) + { + if (m.left > 0) + { + // Top-Left + Rect r(0, 0, m.left, m.top); + graphics.DrawImage(tempBackground, r, 0, 0, m.left, m.top, UnitPixel); + } + + // Top + Rect r(m.left, 0, w - m.left - m.right, m.top); + graphics.DrawImage(tempBackground, r, m.left, 0, m_BackgroundSize.cx - m.left - m.right, m.top, UnitPixel); + + if (m.right > 0) + { + // Top-Right + Rect r(w - m.right, 0, m.right, m.top); + graphics.DrawImage(tempBackground, r, m_BackgroundSize.cx - m.right, 0, m.right, m.top, UnitPixel); + } + } + + if (m.left > 0) + { + // Left + Rect r(0, m.top, m.left, h - m.top - m.bottom); + graphics.DrawImage(tempBackground, r, 0, m.top, m.left, m_BackgroundSize.cy - m.top - m.bottom, UnitPixel); + } + + // Center + Rect r(m.left, m.top, w - m.left - m.right, h - m.top - m.bottom); + graphics.DrawImage(tempBackground, r, m.left, m.top, m_BackgroundSize.cx - m.left - m.right, m_BackgroundSize.cy - m.top - m.bottom, UnitPixel); + + if (m.right > 0) + { + // Right + Rect r(w - m.right, m.top, m.right, h - m.top - m.bottom); + graphics.DrawImage(tempBackground, r, m_BackgroundSize.cx - m.right, m.top, m.right, m_BackgroundSize.cy - m.top - m.bottom, UnitPixel); + } + + if (m.bottom > 0) + { + if (m.left > 0) + { + // Bottom-Left + Rect r(0, h - m.bottom, m.left, m.bottom); + graphics.DrawImage(tempBackground, r, 0, m_BackgroundSize.cy - m.bottom, m.left, m.bottom, UnitPixel); + } + + // Bottom + Rect r(m.left, h - m.bottom, w - m.left - m.right, m.bottom); + graphics.DrawImage(tempBackground, r, m.left, m_BackgroundSize.cy - m.bottom, m_BackgroundSize.cx - m.left - m.right, m.bottom, UnitPixel); + + if (m.right > 0) + { + // Bottom-Right + Rect r(w - m.right, h - m.bottom, m.right, m.bottom); + graphics.DrawImage(tempBackground, r, m_BackgroundSize.cx - m.right, m_BackgroundSize.cy - m.bottom, m.right, m.bottom, UnitPixel); + } + } + } + else + { + ImageAttributes imgAttr; + imgAttr.SetWrapMode(WrapModeTile); + + Rect r(0, 0, w, h); + graphics.DrawImage(tempBackground, r, 0, 0, w, h, UnitPixel, &imgAttr); + } + } + + m_Background = background; + + // Get the size form the background bitmap + m_WindowW = m_Background->GetWidth(); + m_WindowH = m_Background->GetHeight(); + + WindowToScreen(); + } + + delete tintedBackground; + } + else + { + m_WindowW = w; + m_WindowH = h; + WindowToScreen(); + } + + SetWindowSizeVariables(m_WindowW, m_WindowH); + + return true; +} + +/* +** Creates the back buffer bitmap. +** +*/ +void MeterWindow::CreateDoubleBuffer(int cx, int cy) +{ + m_Canvas->Resize(cx, cy); +} + +/* +** Redraws the meters and paints the window +** +*/ +void MeterWindow::Redraw() +{ + if (m_ResizeWindow) + { + ResizeWindow(m_ResizeWindow == RESIZEMODE_RESET); + SetResizeWindowMode(RESIZEMODE_NONE); + } + + // Create or clear the doublebuffer + { + int cx = m_WindowW; + int cy = m_WindowH; + + if (cx == 0 || cy == 0) + { + // Set dummy size to avoid invalid state + cx = 1; + cy = 1; + } + + if (cx != m_Canvas->GetW() || cy != m_Canvas->GetH()) + { + CreateDoubleBuffer(cx, cy); + } + } + + if (!m_Canvas->BeginDraw()) + { + return; + } + + m_Canvas->Clear(); + + if (m_WindowW != 0 && m_WindowH != 0) + { + if (m_Background) + { + const Rect dst(0, 0, m_WindowW, m_WindowH); + const Rect src(0, 0, m_Background->GetWidth(), m_Background->GetHeight()); + m_Canvas->DrawBitmap(m_Background, dst, src); + } + else if (m_BackgroundMode == BGMODE_SOLID) + { + // Draw the solid color background + Rect r(0, 0, m_WindowW, m_WindowH); + + if (m_SolidColor.GetA() != 0 || m_SolidColor2.GetA() != 0) + { + if (m_SolidColor.GetValue() == m_SolidColor2.GetValue()) + { + m_Canvas->Clear(m_SolidColor); + } + else + { + Gdiplus::Graphics& graphics = m_Canvas->BeginGdiplusContext(); + LinearGradientBrush gradient(r, m_SolidColor, m_SolidColor2, m_SolidAngle, TRUE); + graphics.FillRectangle(&gradient, r); + m_Canvas->EndGdiplusContext(); + } + } + + if (m_SolidBevel != BEVELTYPE_NONE) + { + Color lightColor(255, 255, 255, 255); + Color darkColor(255, 0, 0, 0); + + if (m_SolidBevel == BEVELTYPE_DOWN) + { + lightColor.SetValue(Color::MakeARGB(255, 0, 0, 0)); + darkColor.SetValue(Color::MakeARGB(255, 255, 255, 255)); + } + + Pen light(lightColor); + Pen dark(darkColor); + + Gdiplus::Graphics& graphics = m_Canvas->BeginGdiplusContext(); + Meter::DrawBevel(graphics, r, light, dark); + m_Canvas->EndGdiplusContext(); + } + } + + // Draw the meters + std::vector::const_iterator j = m_Meters.begin(); + for ( ; j != m_Meters.end(); ++j) + { + const Matrix* matrix = (*j)->GetTransformationMatrix(); + if (matrix && !matrix->IsIdentity()) + { + m_Canvas->SetTransform(*matrix); + (*j)->Draw(*m_Canvas); + m_Canvas->ResetTransform(); + } + else + { + (*j)->Draw(*m_Canvas); + } + } + } + + UpdateWindow(m_TransparencyValue, true); + + m_Canvas->EndDraw(); +} + +/* +** Updates the transition state +** +*/ +void MeterWindow::PostUpdate(bool bActiveTransition) +{ + // Start/stop the transition timer if necessary + if (bActiveTransition && !m_ActiveTransition) + { + SetTimer(m_Window, TIMER_TRANSITION, m_TransitionUpdate, nullptr); + m_ActiveTransition = true; + } + else if (m_ActiveTransition && !bActiveTransition) + { + KillTimer(m_Window, TIMER_TRANSITION); + m_ActiveTransition = false; + } +} + +/* +** Updates the given measure +** +*/ +bool MeterWindow::UpdateMeasure(Measure* measure, bool force) +{ + bool bUpdate = false; + + if (force) + { + measure->ResetUpdateCounter(); + } + + int updateDivider = measure->GetUpdateDivider(); + if (updateDivider >= 0 || force) + { + const bool rereadOptions = + measure->HasDynamicVariables() && (measure->GetUpdateCounter() + 1) >= updateDivider; + bUpdate = measure->Update(rereadOptions); + } + + return bUpdate; +} + +/* +** Updates the given meter +** +*/ +bool MeterWindow::UpdateMeter(Meter* meter, bool& bActiveTransition, bool force) +{ + bool bUpdate = false; + + if (force) + { + meter->ResetUpdateCounter(); + } + + int updateDivider = meter->GetUpdateDivider(); + if (updateDivider >= 0 || force) + { + if (meter->HasDynamicVariables() && + (meter->GetUpdateCounter() + 1) >= updateDivider) + { + meter->ReadOptions(m_Parser); + } + + bUpdate = meter->Update(); + } + + // Update tooltips + if (!meter->HasToolTip()) + { + if (!meter->GetToolTipText().empty()) + { + meter->CreateToolTip(this); + } + } + else + { + meter->UpdateToolTip(); + } + + // Check for transitions + if (!bActiveTransition && meter->HasActiveTransition()) + { + bActiveTransition = true; + } + + return bUpdate; +} + +/* +** Updates all the measures and redraws the meters +** +*/ +void MeterWindow::Update(bool refresh) +{ + ++m_UpdateCounter; + + if (!m_Measures.empty()) + { + // Pre-updates + if (m_HasNetMeasures) + { + MeasureNet::UpdateIFTable(); + MeasureNet::UpdateStats(); + } + + // Update all measures + std::vector::const_iterator i = m_Measures.begin(); + for ( ; i != m_Measures.end(); ++i) + { + if (UpdateMeasure((*i), refresh)) + { + (*i)->DoUpdateAction(); + (*i)->DoChangeAction(); + } + } + } + + DialogAbout::UpdateMeasures(this); + + // Update all meters + bool bActiveTransition = false; + bool bUpdate = false; + std::vector::const_iterator j = m_Meters.begin(); + for ( ; j != m_Meters.end(); ++j) + { + if (UpdateMeter((*j), bActiveTransition, refresh)) + { + bUpdate = true; + + (*j)->DoUpdateAction(); + } + } + + // Redraw all meters + if (bUpdate || m_ResizeWindow || refresh) + { + if (m_DynamicWindowSize) + { + // Resize the window + SetResizeWindowMode(RESIZEMODE_CHECK); + } + + // If our option is to disable when in an RDP session, then check if in an RDP session. + // Only redraw if we are not in a remote session + if (GetRainmeter().IsRedrawable()) + { + Redraw(); + } + } + + // Post-updates + PostUpdate(bActiveTransition); + + if (!m_OnUpdateAction.empty()) + { + GetRainmeter().ExecuteCommand(m_OnUpdateAction.c_str(), this); + } +} + +/* +** Updates the window contents +** +*/ +void MeterWindow::UpdateWindow(int alpha, bool canvasBeginDrawCalled) +{ + BLENDFUNCTION blendPixelFunction = {AC_SRC_OVER, 0, alpha, AC_SRC_ALPHA}; + POINT ptWindowScreenPosition = {m_ScreenX, m_ScreenY}; + POINT ptSrc = {0, 0}; + SIZE szWindow = {m_Canvas->GetW(), m_Canvas->GetH()}; + + if (!canvasBeginDrawCalled) m_Canvas->BeginDraw(); + + HDC dcMemory = m_Canvas->GetDC(); + if (!UpdateLayeredWindow(m_Window, nullptr, &ptWindowScreenPosition, &szWindow, dcMemory, &ptSrc, 0, &blendPixelFunction, ULW_ALPHA)) + { + // Retry after resetting WS_EX_LAYERED flag. + RemoveWindowExStyle(WS_EX_LAYERED); + AddWindowExStyle(WS_EX_LAYERED); + UpdateLayeredWindow(m_Window, nullptr, &ptWindowScreenPosition, &szWindow, dcMemory, &ptSrc, 0, &blendPixelFunction, ULW_ALPHA); + } + m_Canvas->ReleaseDC(dcMemory); + + if (!canvasBeginDrawCalled) m_Canvas->EndDraw(); + + m_TransparencyValue = alpha; +} + +/* +** Updates the window transparency (using existing contents). +** +*/ +void MeterWindow::UpdateWindowTransparency(int alpha) +{ + BLENDFUNCTION blendPixelFunction = {AC_SRC_OVER, 0, alpha, AC_SRC_ALPHA}; + UpdateLayeredWindow(m_Window, nullptr, nullptr, nullptr, nullptr, nullptr, 0, &blendPixelFunction, ULW_ALPHA); + m_TransparencyValue = alpha; +} + +/* +** Handles the timers. The METERTIMER updates all the measures +** MOUSETIMER is used to hide/show the window. +** +*/ +LRESULT MeterWindow::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (wParam) + { + case TIMER_METER: + Update(false); + break; + + case TIMER_MOUSE: + if (!GetRainmeter().IsMenuActive() && !m_Dragging) + { + ShowWindowIfAppropriate(); + + if (m_WindowZPosition == ZPOSITION_ONTOPMOST) + { + ChangeZPos(ZPOSITION_ONTOPMOST); + } + + if (m_MouseOver) + { + POINT pos = System::GetCursorPosition(); + + if (!m_ClickThrough) + { + if (WindowFromPoint(pos) == m_Window) + { + SetMouseLeaveEvent(false); + } + else + { + // Run all mouse leave actions + OnMouseLeave(m_WindowDraggable ? WM_NCMOUSELEAVE : WM_MOUSELEAVE, 0, 0); + } + } + else + { + bool keyDown = IsCtrlKeyDown() || IsShiftKeyDown() || IsAltKeyDown(); + + if (!keyDown || GetWindowFromPoint(pos) != m_Window) + { + // Run all mouse leave actions + OnMouseLeave(m_WindowDraggable ? WM_NCMOUSELEAVE : WM_MOUSELEAVE, 0, 0); + } + } + } + } + break; + + case TIMER_TRANSITION: + { + // Redraw only if there is active transition still going + bool bActiveTransition = false; + std::vector::const_iterator j = m_Meters.begin(); + for ( ; j != m_Meters.end(); ++j) + { + if ((*j)->HasActiveTransition()) + { + bActiveTransition = true; + break; + } + } + + if (bActiveTransition) + { + Redraw(); + } + else + { + // Stop the transition timer + KillTimer(m_Window, TIMER_TRANSITION); + m_ActiveTransition = false; + } + } + break; + + case TIMER_FADE: + { + ULONGLONG ticks = System::GetTickCount64(); + if (m_FadeStartTime == 0) + { + m_FadeStartTime = ticks; + } + + if (ticks - m_FadeStartTime > (ULONGLONG)m_FadeDuration) + { + KillTimer(m_Window, TIMER_FADE); + m_FadeStartTime = 0; + if (m_FadeEndValue == 0) + { + ShowWindow(m_Window, SW_HIDE); + } + else + { + UpdateWindowTransparency(m_FadeEndValue); + } + } + else + { + double value = (double)(__int64)(ticks - m_FadeStartTime); + value /= m_FadeDuration; + value *= m_FadeEndValue - m_FadeStartValue; + value += m_FadeStartValue; + value = min(value, 255); + value = max(value, 0); + + UpdateWindowTransparency((int)value); + } + } + break; + + case TIMER_DEACTIVATE: + if (m_FadeStartTime == 0) + { + KillTimer(m_Window, TIMER_DEACTIVATE); + GetRainmeter().RemoveUnmanagedMeterWindow(this); + delete this; + } + break; + } + + return 0; +} + +void MeterWindow::FadeWindow(int from, int to) +{ + if (m_FadeDuration == 0) + { + if (to == 0) + { + ShowWindow(m_Window, SW_HIDE); + } + else + { + if (m_FadeDuration == 0) + { + UpdateWindowTransparency(to); + } + if (from == 0) + { + if (!m_Hidden) + { + ShowWindow(m_Window, SW_SHOWNOACTIVATE); + } + } + } + } + else + { + m_FadeStartValue = from; + m_FadeEndValue = to; + UpdateWindowTransparency(from); + if (from == 0) + { + if (!m_Hidden) + { + ShowWindow(m_Window, SW_SHOWNOACTIVATE); + } + } + + SetTimer(m_Window, TIMER_FADE, INTERVAL_FADE, nullptr); + } +} + +void MeterWindow::HideFade() +{ + m_Hidden = true; + if (IsWindowVisible(m_Window)) + { + FadeWindow(m_AlphaValue, 0); + } +} + +void MeterWindow::ShowFade() +{ + m_Hidden = false; + if (!IsWindowVisible(m_Window)) + { + FadeWindow(0, (m_WindowHide == HIDEMODE_FADEOUT) ? 255 : m_AlphaValue); + } +} + +/* +** Show the window if it is temporarily hidden. +** +*/ +void MeterWindow::ShowWindowIfAppropriate() +{ + bool keyDown = IsCtrlKeyDown() || IsShiftKeyDown() || IsAltKeyDown(); + + POINT pos = System::GetCursorPosition(); + POINT posScr = pos; + + MapWindowPoints(nullptr, m_Window, &pos, 1); + bool inside = HitTest(pos.x, pos.y); + + if (inside) + { + inside = (GetWindowFromPoint(posScr) == m_Window); + } + + if (m_ClickThrough) + { + if (!inside || keyDown) + { + // If Alt, shift or control is down, remove the transparent flag + RemoveWindowExStyle(WS_EX_TRANSPARENT); + } + } + + if (m_WindowHide) + { + if (!m_Hidden && !inside && !keyDown) + { + switch (m_WindowHide) + { + case HIDEMODE_HIDE: + if (m_TransparencyValue == 0 || !IsWindowVisible(m_Window)) + { + ShowWindow(m_Window, SW_SHOWNOACTIVATE); + FadeWindow(0, m_AlphaValue); + } + break; + + case HIDEMODE_FADEIN: + if (m_AlphaValue != 255 && m_TransparencyValue == 255) + { + FadeWindow(255, m_AlphaValue); + } + break; + + case HIDEMODE_FADEOUT: + if (m_AlphaValue != 255 && m_TransparencyValue == m_AlphaValue) + { + FadeWindow(m_AlphaValue, 255); + } + break; + } + } + } + else + { + if (!m_Hidden) + { + if (m_TransparencyValue == 0 || !IsWindowVisible(m_Window)) + { + ShowWindow(m_Window, SW_SHOWNOACTIVATE); + FadeWindow(0, m_AlphaValue); + } + } + } +} + +/* +** Retrieves a handle to the window that contains the specified point. +** +*/ +HWND MeterWindow::GetWindowFromPoint(POINT pos) +{ + HWND hwndPos = WindowFromPoint(pos); + + if (hwndPos == m_Window || (!m_ClickThrough && m_WindowHide != HIDEMODE_HIDE)) + { + return hwndPos; + } + + MapWindowPoints(nullptr, m_Window, &pos, 1); + + if (HitTest(pos.x, pos.y)) + { + if (hwndPos) + { + HWND hWnd = GetAncestor(hwndPos, GA_ROOT); + while (hWnd = FindWindowEx(nullptr, hWnd, METERWINDOW_CLASS_NAME, nullptr)) + { + if (hWnd == m_Window) + { + return hwndPos; + } + } + } + return m_Window; + } + + return hwndPos; +} + +/* +** Checks if the given point is inside the window. +** +*/ +bool MeterWindow::HitTest(int x, int y) +{ + return m_Canvas->IsTransparentPixel(x, y); +} + +/* +** Handles all buttons and cursor. +** +*/ +void MeterWindow::HandleButtons(POINT pos, BUTTONPROC proc, bool execute) +{ + bool redraw = false; + HCURSOR cursor = nullptr; + + std::vector::const_reverse_iterator j = m_Meters.rbegin(); + for ( ; j != m_Meters.rend(); ++j) + { + // Hidden meters are ignored + if ((*j)->IsHidden()) continue; + + MeterButton* button = nullptr; + if (m_HasButtons && (*j)->GetTypeID() == TypeID()) + { + button = (MeterButton*)(*j); + if (button) + { + switch (proc) + { + case BUTTONPROC_DOWN: + redraw |= button->MouseDown(pos); + break; + + case BUTTONPROC_UP: + redraw |= button->MouseUp(pos, execute); + break; + + case BUTTONPROC_MOVE: + default: + redraw |= button->MouseMove(pos); + break; + } + } + } + + // Get cursor if required + if (!cursor && (*j)->GetMouse().GetCursorState()) + { + if ((*j)->HasMouseAction()) + { + if ((*j)->HitTest(pos.x, pos.y)) + { + cursor = (*j)->GetMouse().GetCursor(); + } + } + else + { + // Special case for Button meter: reacts only on valid pixel in button image + if (button && button->HitTest2(pos.x, pos.y)) + { + cursor = (*j)->GetMouse().GetCursor(); + } + } + } + } + + if (redraw) + { + Redraw(); + } + + if (!cursor) + { + cursor = LoadCursor(nullptr, IDC_ARROW); + } + + SetCursor(cursor); +} + +/* +** During setting the cursor do nothing. +** +*/ +LRESULT MeterWindow::OnSetCursor(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + return 0; +} + +/* +** Enters context menu loop. +** +*/ +LRESULT MeterWindow::OnEnterMenuLoop(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + // Set cursor to default + SetCursor(LoadCursor(nullptr, IDC_ARROW)); + + return 0; +} + +/* +** When we get WM_MOUSEMOVE messages, hide the window as the mouse is over it. +** +*/ +LRESULT MeterWindow::OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + bool keyDown = IsCtrlKeyDown() || IsShiftKeyDown() || IsAltKeyDown(); + + if (!keyDown) + { + if (m_ClickThrough) + { + AddWindowExStyle(WS_EX_TRANSPARENT); + } + + if (!m_Hidden) + { + // If Alt, shift or control is down, do not hide the window + switch (m_WindowHide) + { + case HIDEMODE_HIDE: + if (m_TransparencyValue == m_AlphaValue) + { + FadeWindow(m_AlphaValue, 0); + } + break; + + case HIDEMODE_FADEIN: + if (m_AlphaValue != 255 && m_TransparencyValue == m_AlphaValue) + { + FadeWindow(m_AlphaValue, 255); + } + break; + + case HIDEMODE_FADEOUT: + if (m_AlphaValue != 255 && m_TransparencyValue == 255) + { + FadeWindow(255, m_AlphaValue); + } + break; + } + } + } + + if (!m_ClickThrough || keyDown) + { + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + if (uMsg == WM_NCMOUSEMOVE) + { + // Map to local window + MapWindowPoints(nullptr, m_Window, &pos, 1); + } + + ++m_MouseMoveCounter; + + while (DoMoveAction(pos.x, pos.y, MOUSE_LEAVE)) ; + while (DoMoveAction(pos.x, pos.y, MOUSE_OVER)) ; + + // Handle buttons + HandleButtons(pos, BUTTONPROC_MOVE); + } + + return 0; +} + +/* +** When we get WM_MOUSELEAVE messages, run all leave actions. +** +*/ +LRESULT MeterWindow::OnMouseLeave(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + POINT pos = System::GetCursorPosition(); + HWND hWnd = WindowFromPoint(pos); + if (!hWnd || (hWnd != m_Window && GetParent(hWnd) != m_Window)) // ignore tooltips + { + ++m_MouseMoveCounter; + + POINT pos = {SHRT_MIN, SHRT_MIN}; + while (DoMoveAction(pos.x, pos.y, MOUSE_LEAVE)) ; // Leave all forcibly + + // Handle buttons + HandleButtons(pos, BUTTONPROC_MOVE); + } + + return 0; +} + +/* +** When we get WM_MOUSEWHEEL messages. +** +*/ +LRESULT MeterWindow::OnMouseScrollMove(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (uMsg == WM_MOUSEWHEEL) // If sent through WM_INPUT, uMsg is WM_INPUT. + { + // Fix for Notepad++, which sends WM_MOUSEWHEEL to unfocused windows. + if (m_Window != GetFocus()) + { + return 0; + } + } + + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + MapWindowPoints(nullptr, m_Window, &pos, 1); + + // Handle buttons + HandleButtons(pos, BUTTONPROC_MOVE); + + const int delta = GET_WHEEL_DELTA_WPARAM(wParam); + DoAction(pos.x, pos.y, (delta < 0) ? MOUSE_MW_DOWN : MOUSE_MW_UP, false); + + return 0; +} + +/* +** When we get WM_MOUSEHWHEEL messages. +** +*/ +LRESULT MeterWindow::OnMouseHScrollMove(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + MapWindowPoints(nullptr, m_Window, &pos, 1); + + // Handle buttons + HandleButtons(pos, BUTTONPROC_MOVE); + + const int delta = GET_WHEEL_DELTA_WPARAM(wParam); + DoAction(pos.x, pos.y, (delta < 0) ? MOUSE_MW_LEFT : MOUSE_MW_RIGHT, false); + + return 0; +} + +/* +** Handle the menu commands. +** +*/ +LRESULT MeterWindow::OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (wParam) + { + case IDM_SKIN_EDITSKIN: + GetRainmeter().EditSkinFile(m_FolderPath, m_FileName); + break; + + case IDM_SKIN_REFRESH: + Refresh(false); + break; + + case IDM_SKIN_OPENSKINSFOLDER: + GetRainmeter().OpenSkinFolder(m_FolderPath); + break; + + case IDM_SKIN_MANAGESKIN: + DialogManage::OpenSkin(this); + break; + + case IDM_SKIN_VERYTOPMOST: + SetWindowZPosition(ZPOSITION_ONTOPMOST); + break; + + case IDM_SKIN_TOPMOST: + SetWindowZPosition(ZPOSITION_ONTOP); + break; + + case IDM_SKIN_BOTTOM: + SetWindowZPosition(ZPOSITION_ONBOTTOM); + break; + + case IDM_SKIN_NORMAL: + SetWindowZPosition(ZPOSITION_NORMAL); + break; + + case IDM_SKIN_ONDESKTOP: + SetWindowZPosition(ZPOSITION_ONDESKTOP); + break; + + case IDM_SKIN_KEEPONSCREEN: + SetKeepOnScreen(!m_KeepOnScreen); + break; + + case IDM_SKIN_USED2D: + SetUseD2D(!m_UseD2D); + break; + + case IDM_SKIN_CLICKTHROUGH: + SetClickThrough(!m_ClickThrough); + break; + + case IDM_SKIN_DRAGGABLE: + SetWindowDraggable(!m_WindowDraggable); + break; + + case IDM_SKIN_HIDEONMOUSE: + SetWindowHide((m_WindowHide == HIDEMODE_NONE) ? HIDEMODE_HIDE : HIDEMODE_NONE); + break; + + case IDM_SKIN_TRANSPARENCY_FADEIN: + SetWindowHide((m_WindowHide == HIDEMODE_NONE) ? HIDEMODE_FADEIN : HIDEMODE_NONE); + break; + + case IDM_SKIN_TRANSPARENCY_FADEOUT: + SetWindowHide((m_WindowHide == HIDEMODE_NONE) ? HIDEMODE_FADEOUT : HIDEMODE_NONE); + break; + + case IDM_SKIN_REMEMBERPOSITION: + SetSavePosition(!m_SavePosition); + break; + + case IDM_SKIN_SNAPTOEDGES: + SetSnapEdges(!m_SnapEdges); + break; + + case IDM_CLOSESKIN: + if (m_State != STATE_CLOSING) + { + GetRainmeter().DeactivateSkin(this, -1); + } + break; + + case IDM_SKIN_FROMRIGHT: + m_WindowXFromRight = !m_WindowXFromRight; + + SavePositionIfAppropriate(); + break; + + case IDM_SKIN_FROMBOTTOM: + m_WindowYFromBottom = !m_WindowYFromBottom; + + SavePositionIfAppropriate(); + break; + + case IDM_SKIN_XPERCENTAGE: + m_WindowXPercentage = !m_WindowXPercentage; + + SavePositionIfAppropriate(); + break; + + case IDM_SKIN_YPERCENTAGE: + m_WindowYPercentage = !m_WindowYPercentage; + + SavePositionIfAppropriate(); + break; + + case IDM_SKIN_MONITOR_AUTOSELECT: + m_AutoSelectScreen = !m_AutoSelectScreen; + + WriteOptions(OPTION_POSITION | OPTION_AUTOSELECTSCREEN); + break; + + default: + if (wParam >= IDM_SKIN_TRANSPARENCY_0 && wParam <= IDM_SKIN_TRANSPARENCY_90) + { + m_AlphaValue = (int)(255.0 - (wParam - IDM_SKIN_TRANSPARENCY_0) * (230.0 / (IDM_SKIN_TRANSPARENCY_90 - IDM_SKIN_TRANSPARENCY_0))); + UpdateWindowTransparency(m_AlphaValue); + WriteOptions(OPTION_ALPHAVALUE); + } + else if (wParam == IDM_SKIN_MONITOR_PRIMARY || wParam >= ID_MONITOR_FIRST && wParam <= ID_MONITOR_LAST) + { + const int numOfMonitors = (int)System::GetMonitorCount(); + const MultiMonitorInfo& monitorsInfo = System::GetMultiMonitorInfo(); + const std::vector& monitors = monitorsInfo.monitors; + + int screenIndex; + bool screenDefined; + if (wParam == IDM_SKIN_MONITOR_PRIMARY) + { + screenIndex = monitorsInfo.primary; + screenDefined = false; + } + else + { + screenIndex = (wParam & 0x0ffff) - ID_MONITOR_FIRST; + screenDefined = true; + } + + if (screenIndex >= 0 && (screenIndex == 0 || screenIndex <= numOfMonitors && monitors[screenIndex - 1].active)) + { + m_AutoSelectScreen = false; + + m_WindowXScreen = m_WindowYScreen = screenIndex; + m_WindowXScreenDefined = m_WindowYScreenDefined = screenDefined; + + m_Parser.ResetMonitorVariables(this); // Set present monitor variables + WriteOptions(OPTION_POSITION | OPTION_AUTOSELECTSCREEN); + } + } + else if (wParam >= IDM_SKIN_CUSTOMCONTEXTMENU_FIRST && wParam <= IDM_SKIN_CUSTOMCONTEXTMENU_LAST) + { + std::wstring action; + + int position = (int)wParam - IDM_SKIN_CUSTOMCONTEXTMENU_FIRST + 1; + if (position == 1) + { + action = m_Parser.ReadString(L"Rainmeter", L"ContextAction", L"", false); + } + else + { + WCHAR buffer[128]; + + _snwprintf_s(buffer, _TRUNCATE, L"ContextAction%i", position); + action = m_Parser.ReadString(L"Rainmeter", buffer, L"", false); + } + + if (!action.empty()) + { + GetRainmeter().ExecuteCommand(action.c_str(), this); + } + } + else + { + // Forward to tray window, which handles all the other commands + HWND tray = GetRainmeter().GetTrayWindow()->GetWindow(); + + if (wParam == IDM_QUIT) + { + PostMessage(tray, WM_COMMAND, wParam, lParam); + } + else + { + SendMessage(tray, WM_COMMAND, wParam, lParam); + } + } + break; + } + + return 0; +} + +/* +** Helper function for setting ClickThrough +** +*/ +void MeterWindow::SetClickThrough(bool b) +{ + m_ClickThrough = b; + WriteOptions(OPTION_CLICKTHROUGH); + + if (!m_ClickThrough) + { + // Remove transparent flag + RemoveWindowExStyle(WS_EX_TRANSPARENT); + } + + if (m_MouseOver) + { + SetMouseLeaveEvent(m_ClickThrough); + } +} + +/* +** Helper function for setting KeepOnScreen +** +*/ +void MeterWindow::SetKeepOnScreen(bool b) +{ + m_KeepOnScreen = b; + WriteOptions(OPTION_KEEPONSCREEN); + + if (m_KeepOnScreen) + { + int x = m_ScreenX; + int y = m_ScreenY; + + MapCoordsToScreen(x, y, m_WindowW, m_WindowH); + + if (x != m_ScreenX || y != m_ScreenY) + { + MoveWindow(x, y); + } + } +} + +/* +** Helper function for setting UseD2D +** +*/ +void MeterWindow::SetUseD2D(bool b) +{ + m_UseD2D = b; + WriteOptions(OPTION_USED2D); + Refresh(false); +} + +/* +** Helper function for setting WindowDraggable +** +*/ +void MeterWindow::SetWindowDraggable(bool b) +{ + m_WindowDraggable = b; + WriteOptions(OPTION_DRAGGABLE); +} + +/* +** Helper function for setting SavePosition +** +*/ +void MeterWindow::SetSavePosition(bool b) +{ + m_SavePosition = b; + WriteOptions(OPTION_POSITION | OPTION_SAVEPOSITION); +} + +/* +** Helper function for setting SavePosition +** +*/ +void MeterWindow::SavePositionIfAppropriate() +{ + if (m_SavePosition) + { + WriteOptions(OPTION_POSITION); + } + else + { + ScreenToWindow(); + DialogManage::UpdateSkins(this); + } +} + +/* +** Helper function for setting SnapEdges +** +*/ +void MeterWindow::SetSnapEdges(bool b) +{ + m_SnapEdges = b; + WriteOptions(OPTION_SNAPEDGES); +} + +/* +** Helper function for setting WindowHide +** +*/ +void MeterWindow::SetWindowHide(HIDEMODE hide) +{ + m_WindowHide = hide; + UpdateWindowTransparency(m_AlphaValue); + WriteOptions(OPTION_HIDEONMOUSEOVER); +} + +/* +** Helper function for setting Position +** +*/ +void MeterWindow::SetWindowZPosition(ZPOSITION zpos) +{ + ChangeSingleZPos(zpos); + WriteOptions(OPTION_ALWAYSONTOP); +} + +/* +** Handle dragging the window +** +*/ +LRESULT MeterWindow::OnSysCommand(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if ((wParam & 0xFFF0) != SC_MOVE) + { + return DefWindowProc(m_Window, uMsg, wParam, lParam); + } + + // --- SC_MOVE --- + + // Prepare the dragging flags + m_Dragging = true; + m_Dragged = false; + + // Run the DefWindowProc so the dragging works + LRESULT result = DefWindowProc(m_Window, uMsg, wParam, lParam); + + if (m_Dragged) + { + SavePositionIfAppropriate(); + + POINT pos = System::GetCursorPosition(); + MapWindowPoints(nullptr, m_Window, &pos, 1); + + // Handle buttons + HandleButtons(pos, BUTTONPROC_UP, false); // redraw only + } + else // not dragged + { + if ((wParam & 0x000F) == 2) // triggered by mouse + { + // Post the WM_NCLBUTTONUP message so the LeftMouseUpAction works + PostMessage(m_Window, WM_NCLBUTTONUP, (WPARAM)HTCAPTION, lParam); + } + } + + // Clear the dragging flags + m_Dragging = false; + m_Dragged = false; + + return result; +} + +/* +** Starts dragging +** +*/ +LRESULT MeterWindow::OnEnterSizeMove(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (m_Dragging) + { + m_Dragged = true; // Don't post the WM_NCLBUTTONUP message! + + // Set cursor to default + SetCursor(LoadCursor(nullptr, IDC_ARROW)); + } + + return 0; +} + +/* +** Ends dragging +** +*/ +LRESULT MeterWindow::OnExitSizeMove(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + return 0; +} + +/* +** This is overwritten so that the window can be dragged +** +*/ +LRESULT MeterWindow::OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (m_WindowDraggable && !GetRainmeter().GetDisableDragging()) + { + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + MapWindowPoints(nullptr, m_Window, &pos, 1); + + int x1 = m_DragMargins.left; + if (x1 < 0) x1 += m_WindowW; + + int x2 = m_WindowW - m_DragMargins.right; + if (x2 > m_WindowW) x2 -= m_WindowW; + + if (pos.x >= x1 && pos.x < x2) + { + int y1 = m_DragMargins.top; + if (y1 < 0) y1 += m_WindowH; + + int y2 = m_WindowH - m_DragMargins.bottom; + if (y2 > m_WindowH) y2 -= m_WindowH; + + if (pos.y >= y1 && pos.y < y2) + { + return HTCAPTION; + } + } + } + return HTCLIENT; +} + +/* +** Called when windows position is about to change +** +*/ +LRESULT MeterWindow::OnWindowPosChanging(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + LPWINDOWPOS wp = (LPWINDOWPOS)lParam; + + if (m_State != STATE_REFRESHING) + { + if (m_WindowZPosition == ZPOSITION_NORMAL && GetRainmeter().IsNormalStayDesktop() && System::GetShowDesktop()) + { + if (!(wp->flags & (SWP_NOOWNERZORDER | SWP_NOACTIVATE))) + { + // Set window on top of all other ZPOSITION_ONDESKTOP, ZPOSITION_BOTTOM, and ZPOSITION_NORMAL windows + wp->hwndInsertAfter = System::GetBackmostTopWindow(); + } + } + else if (m_WindowZPosition == ZPOSITION_ONDESKTOP || m_WindowZPosition == ZPOSITION_ONBOTTOM) + { + // Do not change the z-order. This keeps the window on bottom. + wp->flags |= SWP_NOZORDER; + } + } + + if ((wp->flags & SWP_NOMOVE) == 0) + { + if (m_SnapEdges && !(IsCtrlKeyDown() || IsShiftKeyDown())) + { + // only process movement (ignore anything without winpos values) + if (wp->cx != 0 && wp->cy != 0) + { + // Search display monitor that has the largest area of intersection with the window + const size_t numOfMonitors = System::GetMonitorCount(); // intentional + const std::vector& monitors = System::GetMultiMonitorInfo().monitors; + + const RECT windowRect = {wp->x, wp->y, wp->x + (m_WindowW ? m_WindowW : 1), wp->y + (m_WindowH ? m_WindowH : 1)}; + const RECT* workArea = nullptr; + + size_t maxSize = 0; + for (auto iter = monitors.cbegin(); iter != monitors.cend(); ++iter) + { + RECT r; + if ((*iter).active && IntersectRect(&r, &windowRect, &(*iter).screen)) + { + size_t size = (r.right - r.left) * (r.bottom - r.top); + if (size > maxSize) + { + workArea = &(*iter).work; + maxSize = size; + } + } + } + + // Snap to other windows + for (auto iter = GetRainmeter().GetAllMeterWindows().cbegin(); iter != GetRainmeter().GetAllMeterWindows().cend(); ++iter) + { + if ((*iter).second != this) + { + SnapToWindow((*iter).second, wp); + } + } + + // Snap to work area if window is on the appropriate screen + if (workArea) + { + int w = workArea->right - m_WindowW; + int h = workArea->bottom - m_WindowH; + + if ((wp->x < SNAPDISTANCE + workArea->left) && (wp->x > workArea->left - SNAPDISTANCE)) wp->x = workArea->left; + if ((wp->y < SNAPDISTANCE + workArea->top) && (wp->y > workArea->top - SNAPDISTANCE)) wp->y = workArea->top; + if ((wp->x < SNAPDISTANCE + w) && (wp->x > -SNAPDISTANCE + w)) wp->x = w; + if ((wp->y < SNAPDISTANCE + h) && (wp->y > -SNAPDISTANCE + h)) wp->y = h; + } + } + } + + if (m_KeepOnScreen) + { + MapCoordsToScreen(wp->x, wp->y, m_WindowW, m_WindowH); + } + } + + return 0; +} + +void MeterWindow::SnapToWindow(MeterWindow* window, LPWINDOWPOS wp) +{ + int x = window->m_ScreenX; + int y = window->m_ScreenY; + int w = window->m_WindowW; + int h = window->m_WindowH; + + if (wp->y < y + h && wp->y + m_WindowH > y) + { + if ((wp->x < SNAPDISTANCE + x) && (wp->x > x - SNAPDISTANCE)) wp->x = x; + if ((wp->x < SNAPDISTANCE + x + w) && (wp->x > x + w - SNAPDISTANCE)) wp->x = x + w; + + if ((wp->x + m_WindowW < SNAPDISTANCE + x) && (wp->x + m_WindowW > x - SNAPDISTANCE)) wp->x = x - m_WindowW; + if ((wp->x + m_WindowW < SNAPDISTANCE + x + w) && (wp->x + m_WindowW > x + w - SNAPDISTANCE)) wp->x = x + w - m_WindowW; + } + + if (wp->x < x + w && wp->x + m_WindowW > x) + { + if ((wp->y < SNAPDISTANCE + y) && (wp->y > y - SNAPDISTANCE)) wp->y = y; + if ((wp->y < SNAPDISTANCE + y + h) && (wp->y > y + h - SNAPDISTANCE)) wp->y = y + h; + + if ((wp->y + m_WindowH < SNAPDISTANCE + y) && (wp->y + m_WindowH > y - SNAPDISTANCE)) wp->y = y - m_WindowH; + if ((wp->y + m_WindowH < SNAPDISTANCE + y + h) && (wp->y + m_WindowH > y + h - SNAPDISTANCE)) wp->y = y + h - m_WindowH; + } +} + +/* +** Disables blur when Aero transparency is disabled +** +*/ +LRESULT MeterWindow::OnDwmColorChange(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (m_BlurMode != BLURMODE_NONE && IsBlur() && c_DwmGetColorizationColor && c_DwmEnableBlurBehindWindow) + { + DWORD color; + BOOL opaque; + if (c_DwmGetColorizationColor(&color, &opaque) != S_OK) + { + opaque = TRUE; + } + + BlurBehindWindow(!opaque); + } + + return 0; +} + +/* +** Disables blur when desktop composition is disabled +** +*/ +LRESULT MeterWindow::OnDwmCompositionChange(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (m_BlurMode != BLURMODE_NONE && IsBlur() && c_DwmIsCompositionEnabled && c_DwmEnableBlurBehindWindow) + { + BOOL enabled; + if (c_DwmIsCompositionEnabled(&enabled) != S_OK) + { + enabled = FALSE; + } + + BlurBehindWindow(enabled); + } + + return 0; +} + +/* +** Adds the blur region to the window +** +*/ +void MeterWindow::BlurBehindWindow(BOOL fEnable) +{ + if (c_DwmEnableBlurBehindWindow) + { + DWM_BLURBEHIND bb = {0}; + bb.fEnable = fEnable; + + if (fEnable) + { + // Restore blur with whatever the region was prior to disabling + bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; + bb.hRgnBlur = m_BlurRegion; + c_DwmEnableBlurBehindWindow(m_Window, &bb); + } + else + { + // Disable blur + bb.dwFlags = DWM_BB_ENABLE; + c_DwmEnableBlurBehindWindow(m_Window, &bb); + } + } +} + +/* +** During resolution changes do nothing. +** (OnDelayedMove function is used instead.) +** +*/ +LRESULT MeterWindow::OnDisplayChange(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + return 0; +} + +/* +** During setting changes do nothing. +** (OnDelayedMove function is used instead.) +** +*/ +LRESULT MeterWindow::OnSettingChange(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + return 0; +} + +/* +** Runs the action when left mouse button is down +** +*/ +LRESULT MeterWindow::OnLeftButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + if (uMsg == WM_NCLBUTTONDOWN) + { + // Transform the point to client rect + MapWindowPoints(nullptr, m_Window, &pos, 1); + } + + // Handle buttons + HandleButtons(pos, BUTTONPROC_DOWN); + + if (IsCtrlKeyDown() || // Ctrl is pressed, so only run default action + (!DoAction(pos.x, pos.y, MOUSE_LMB_DOWN, false) && m_WindowDraggable)) + { + // Cancel the mouse event beforehand + SetMouseLeaveEvent(true); + + // Run the DefWindowProc so the dragging works + return DefWindowProc(m_Window, uMsg, wParam, lParam); + } + + return 0; +} + +/* +** Runs the action when left mouse button is up +** +*/ +LRESULT MeterWindow::OnLeftButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + if (uMsg == WM_NCLBUTTONUP) + { + // Transform the point to client rect + MapWindowPoints(nullptr, m_Window, &pos, 1); + } + + // Handle buttons + HandleButtons(pos, BUTTONPROC_UP); + + DoAction(pos.x, pos.y, MOUSE_LMB_UP, false); + + return 0; +} + +/* +** Runs the action when left mouse button is double-clicked +** +*/ +LRESULT MeterWindow::OnLeftButtonDoubleClick(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + if (uMsg == WM_NCLBUTTONDBLCLK) + { + // Transform the point to client rect + MapWindowPoints(nullptr, m_Window, &pos, 1); + } + + // Handle buttons + HandleButtons(pos, BUTTONPROC_DOWN); + + if (!DoAction(pos.x, pos.y, MOUSE_LMB_DBLCLK, false)) + { + DoAction(pos.x, pos.y, MOUSE_LMB_DOWN, false); + } + + return 0; +} + +/* +** Runs the action when right mouse button is down +** +*/ +LRESULT MeterWindow::OnRightButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + if (uMsg == WM_NCRBUTTONDOWN) + { + // Transform the point to client rect + MapWindowPoints(nullptr, m_Window, &pos, 1); + } + + // Handle buttons + HandleButtons(pos, BUTTONPROC_MOVE); + + DoAction(pos.x, pos.y, MOUSE_RMB_DOWN, false); + + return 0; +} + +/* +** Runs the action when right mouse button is up +** +*/ +LRESULT MeterWindow::OnRightButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + // Handle buttons + HandleButtons(pos, BUTTONPROC_MOVE); + + if (IsCtrlKeyDown() || // Ctrl is pressed, so only run default action + !DoAction(pos.x, pos.y, MOUSE_RMB_UP, false)) + { + // Run the DefWindowProc so the context menu works + return DefWindowProc(m_Window, WM_RBUTTONUP, wParam, lParam); + } + + return 0; +} + +/* +** Runs the action when right mouse button is double-clicked +** +*/ +LRESULT MeterWindow::OnRightButtonDoubleClick(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + if (uMsg == WM_NCRBUTTONDBLCLK) + { + // Transform the point to client rect + MapWindowPoints(nullptr, m_Window, &pos, 1); + } + + // Handle buttons + HandleButtons(pos, BUTTONPROC_MOVE); + + if (!DoAction(pos.x, pos.y, MOUSE_RMB_DBLCLK, false)) + { + DoAction(pos.x, pos.y, MOUSE_RMB_DOWN, false); + } + + return 0; +} + +/* +** Runs the action when middle mouse button is down +** +*/ +LRESULT MeterWindow::OnMiddleButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + if (uMsg == WM_NCMBUTTONDOWN) + { + // Transform the point to client rect + MapWindowPoints(nullptr, m_Window, &pos, 1); + } + + // Handle buttons + HandleButtons(pos, BUTTONPROC_MOVE); + + DoAction(pos.x, pos.y, MOUSE_MMB_DOWN, false); + + return 0; +} + +/* +** Runs the action when middle mouse button is up +** +*/ +LRESULT MeterWindow::OnMiddleButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + if (uMsg == WM_NCMBUTTONUP) + { + // Transform the point to client rect + MapWindowPoints(nullptr, m_Window, &pos, 1); + } + + // Handle buttons + HandleButtons(pos, BUTTONPROC_MOVE); + + DoAction(pos.x, pos.y, MOUSE_MMB_UP, false); + + return 0; +} + +/* +** Runs the action when middle mouse button is double-clicked +** +*/ +LRESULT MeterWindow::OnMiddleButtonDoubleClick(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + if (uMsg == WM_NCMBUTTONDBLCLK) + { + // Transform the point to client rect + MapWindowPoints(nullptr, m_Window, &pos, 1); + } + + // Handle buttons + HandleButtons(pos, BUTTONPROC_MOVE); + + if (!DoAction(pos.x, pos.y, MOUSE_MMB_DBLCLK, false)) + { + DoAction(pos.x, pos.y, MOUSE_MMB_DOWN, false); + } + + return 0; +} + +/* +** Runs the action when a X mouse button is down +** +*/ +LRESULT MeterWindow::OnXButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + if (uMsg == WM_NCXBUTTONDOWN) + { + // Transform the point to client rect + MapWindowPoints(nullptr, m_Window, &pos, 1); + } + + // Handle buttons + HandleButtons(pos, BUTTONPROC_MOVE); + + if (GET_XBUTTON_WPARAM (wParam) == XBUTTON1) + { + DoAction(pos.x, pos.y, MOUSE_X1MB_DOWN, false); + } + else if (GET_XBUTTON_WPARAM (wParam) == XBUTTON2) + { + DoAction(pos.x, pos.y, MOUSE_X2MB_DOWN, false); + } + + return 0; +} + +/* +** Runs the action when a X mouse button is up +** +*/ +LRESULT MeterWindow::OnXButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + if (uMsg == WM_NCXBUTTONUP) + { + // Transform the point to client rect + MapWindowPoints(nullptr, m_Window, &pos, 1); + } + + // Handle buttons + HandleButtons(pos, BUTTONPROC_MOVE); + + if (GET_XBUTTON_WPARAM (wParam) == XBUTTON1) + { + DoAction(pos.x, pos.y, MOUSE_X1MB_UP, false); + } + else if (GET_XBUTTON_WPARAM (wParam) == XBUTTON2) + { + DoAction(pos.x, pos.y, MOUSE_X2MB_UP, false); + } + + return 0; +} + +/* +** Runs the action when a X mouse button is double-clicked +** +*/ +LRESULT MeterWindow::OnXButtonDoubleClick(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + POINT pos; + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + if (uMsg == WM_NCXBUTTONDBLCLK) + { + // Transform the point to client rect + MapWindowPoints(nullptr, m_Window, &pos, 1); + } + + // Handle buttons + HandleButtons(pos, BUTTONPROC_MOVE); + + if (GET_XBUTTON_WPARAM (wParam) == XBUTTON1 && + !DoAction(pos.x, pos.y, MOUSE_X1MB_DBLCLK, false)) + { + DoAction(pos.x, pos.y, MOUSE_X1MB_DOWN, false); + } + else if (GET_XBUTTON_WPARAM (wParam) == XBUTTON2 && + !DoAction(pos.x, pos.y, MOUSE_X2MB_DBLCLK, false)) + { + DoAction(pos.x, pos.y, MOUSE_X2MB_DOWN, false); + } + + return 0; +} + +/* +** Runs the action when the MeterWindow gets or loses focus +** +*/ +LRESULT MeterWindow::OnSetWindowFocus(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_SETFOCUS: + if (!m_OnFocusAction.empty()) + { + GetRainmeter().ExecuteCommand(m_OnFocusAction.c_str(), this); + } + break; + + case WM_KILLFOCUS: + if (!m_OnUnfocusAction.empty()) + { + GetRainmeter().ExecuteCommand(m_OnUnfocusAction.c_str(), this); + } + break; + } + + return 0; +} + +/* +** Handles the context menu. The menu is recreated every time it is shown. +** +*/ +LRESULT MeterWindow::OnContextMenu(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + POINT pos; + RECT rect; + GetWindowRect(m_Window, &rect); + + if ((lParam & 0xFFFFFFFF) == 0xFFFFFFFF) // WM_CONTEXTMENU is generated from the keyboard (Shift+F10/VK_APPS) + { + // Set menu position to (0,0) on the window + pos.x = rect.left; + pos.y = rect.top; + } + else + { + pos.x = GET_X_LPARAM(lParam); + pos.y = GET_Y_LPARAM(lParam); + + // Transform the point to client rect + POINT posc = {pos.x - rect.left, pos.y - rect.top}; + + // Handle buttons + HandleButtons(posc, BUTTONPROC_MOVE); + + // If RMB up or RMB down or double-click cause actions, do not show the menu! + if (!IsCtrlKeyDown() && // Ctrl is pressed, so ignore any actions + (DoAction(posc.x, posc.y, MOUSE_RMB_UP, false) || DoAction(posc.x, posc.y, MOUSE_RMB_DOWN, true) || DoAction(posc.x, posc.y, MOUSE_RMB_DBLCLK, true))) + { + return 0; + } + } + + GetRainmeter().ShowContextMenu(pos, this); + + return 0; +} + +/* +** Executes the action if such are defined. Returns true, if action was executed. +** If the test is true, the action is not executed. +** +*/ +bool MeterWindow::DoAction(int x, int y, MOUSEACTION action, bool test) +{ + std::wstring command; + + // Check if the hitpoint was over some meter + std::vector::const_reverse_iterator j = m_Meters.rbegin(); + for ( ; j != m_Meters.rend(); ++j) + { + // Hidden meters are ignored + if ((*j)->IsHidden()) continue; + + const Mouse& mouse = (*j)->GetMouse(); + if (mouse.HasActionCommand(action) && (*j)->HitTest(x, y)) + { + command = mouse.GetActionCommand(action); + break; + } + } + + if (command.empty()) + { + if (m_Mouse.HasActionCommand(action) && HitTest(x, y)) + { + command = m_Mouse.GetActionCommand(action); + } + } + + if (!command.empty()) + { + if (!test) + { + GetRainmeter().ExecuteCommand(command.c_str(), this); + } + + return true; + } + + return false; +} + +/* +** Executes the action if such are defined. Returns true, if meter/window which should be processed still may exist. +** +*/ +bool MeterWindow::DoMoveAction(int x, int y, MOUSEACTION action) +{ + bool buttonFound = false; + + // Check if the hitpoint was over some meter + std::vector::const_reverse_iterator j = m_Meters.rbegin(); + for ( ; j != m_Meters.rend(); ++j) + { + if (!(*j)->IsHidden() && (*j)->HitTest(x, y)) + { + if (action == MOUSE_OVER) + { + if (!m_MouseOver) + { + // If the mouse is over a meter it's also over the main window + //LogDebugF(L"@Enter: %s", m_FolderPath.c_str()); + m_MouseOver = true; + SetMouseLeaveEvent(false); + RegisterMouseInput(); + + if (!m_Mouse.GetOverAction().empty()) + { + UINT currCounter = m_MouseMoveCounter; + GetRainmeter().ExecuteCommand(m_Mouse.GetOverAction().c_str(), this); + return (currCounter == m_MouseMoveCounter); + } + } + + // Handle button + MeterButton* button = nullptr; + if (m_HasButtons && (*j)->GetTypeID() == TypeID()) + { + button = (MeterButton*)(*j); + if (button) + { + if (!buttonFound) + { + button->SetFocus(true); + buttonFound = true; + } + else + { + button->SetFocus(false); + } + } + } + + if (!(*j)->IsMouseOver()) + { + const Mouse& mouse = (*j)->GetMouse(); + if (!mouse.GetOverAction().empty() || + !mouse.GetLeaveAction().empty() || + button) + { + //LogDebugF(L"MeterEnter: %s - [%s]", m_FolderPath.c_str(), (*j)->GetName()); + (*j)->SetMouseOver(true); + + if (!mouse.GetOverAction().empty()) + { + UINT currCounter = m_MouseMoveCounter; + GetRainmeter().ExecuteCommand(mouse.GetOverAction().c_str(), this); + return (currCounter == m_MouseMoveCounter); + } + } + } + } + } + else + { + if (action == MOUSE_LEAVE) + { + if ((*j)->IsMouseOver()) + { + // Handle button + if (m_HasButtons && (*j)->GetTypeID() == TypeID()) + { + MeterButton* button = (MeterButton*)(*j); + button->SetFocus(false); + } + + //LogDebugF(L"MeterLeave: %s - [%s]", m_FolderPath.c_str(), (*j)->GetName()); + (*j)->SetMouseOver(false); + + const Mouse& mouse = (*j)->GetMouse(); + if (!mouse.GetLeaveAction().empty()) + { + GetRainmeter().ExecuteCommand(mouse.GetLeaveAction().c_str(), this); + return true; + } + } + } + } + } + + if (HitTest(x, y)) + { + // If no meters caused actions, do the default actions + if (action == MOUSE_OVER) + { + if (!m_MouseOver) + { + //LogDebugF(L"Enter: %s", m_FolderPath.c_str()); + m_MouseOver = true; + SetMouseLeaveEvent(false); + RegisterMouseInput(); + + if (!m_Mouse.GetOverAction().empty()) + { + UINT currCounter = m_MouseMoveCounter; + GetRainmeter().ExecuteCommand(m_Mouse.GetOverAction().c_str(), this); + return (currCounter == m_MouseMoveCounter); + } + } + } + } + else + { + if (action == MOUSE_LEAVE) + { + // Mouse leave happens when the mouse is outside the window + if (m_MouseOver) + { + //LogDebugF(L"Leave: %s", m_FolderPath.c_str()); + m_MouseOver = false; + SetMouseLeaveEvent(true); + UnregisterMouseInput(); + + if (!m_Mouse.GetLeaveAction().empty()) + { + GetRainmeter().ExecuteCommand(m_Mouse.GetLeaveAction().c_str(), this); + return true; + } + } + } + } + + return false; +} + +/* +** Sends mouse wheel messages to the window if the window does not have focus. +** +*/ +LRESULT MeterWindow::OnMouseInput(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + const POINT pos = System::GetCursorPosition(); + + // Only process for unfocused skin window. + if (m_Window == WindowFromPoint(pos) && m_Window != GetFocus()) + { + RAWINPUT ri; + UINT riSize = sizeof(ri); + const UINT dataSize = GetRawInputData( + (HRAWINPUT)lParam, RID_INPUT, &ri, &riSize, sizeof(RAWINPUTHEADER)); + if (dataSize != (UINT)-1 && + ri.header.dwType == RIM_TYPEMOUSE) + { + const WPARAM wheelDelta = MAKEWPARAM(0, HIWORD((short)ri.data.mouse.usButtonData)); + const LPARAM wheelPos = MAKELPARAM(pos.x, pos.y); + if (ri.data.mouse.usButtonFlags == RI_MOUSE_WHEEL) + { + OnMouseScrollMove(WM_INPUT, wheelDelta, wheelPos); + } + else if (ri.data.mouse.usButtonFlags == RI_MOUSE_HORIZONTAL_WHEEL) + { + OnMouseHScrollMove(WM_MOUSEHWHEEL, wheelDelta, wheelPos); + } + } + } + + // DefWindowProc must be called after processing WM_INPUT. + DefWindowProc(m_Window, uMsg, wParam, lParam); + return 0; +} + +/* +** Stores the new place of the window, in screen coordinates. +** +*/ +LRESULT MeterWindow::OnMove(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + // The lParam's x/y parameters are given in screen coordinates for overlapped and pop-up windows + // and in parent-client coordinates for child windows. + + // Store the new window position + m_ScreenX = GET_X_LPARAM(lParam); + m_ScreenY = GET_Y_LPARAM(lParam); + + SetWindowPositionVariables(m_ScreenX, m_ScreenY); + + if (m_Dragging) + { + ScreenToWindow(); + } + + return 0; +} + +/* +** Performs an action when returning from sleep +** +*/ +LRESULT MeterWindow::OnWake(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (wParam == PBT_APMRESUMEAUTOMATIC && !m_OnWakeAction.empty()) + { + GetRainmeter().ExecuteCommand(m_OnWakeAction.c_str(), this); + } + + return 0; +} + +/* +** The main window procedure for the meter window. +** +*/ +LRESULT CALLBACK MeterWindow::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + MeterWindow* window = (MeterWindow*)GetWindowLongPtr(hWnd, GWLP_USERDATA); + + BEGIN_MESSAGEPROC + MESSAGE(OnMouseInput, WM_INPUT) + MESSAGE(OnMove, WM_MOVE) + MESSAGE(OnTimer, WM_TIMER) + MESSAGE(OnCommand, WM_COMMAND) + MESSAGE(OnSysCommand, WM_SYSCOMMAND) + MESSAGE(OnEnterSizeMove, WM_ENTERSIZEMOVE) + MESSAGE(OnExitSizeMove, WM_EXITSIZEMOVE) + MESSAGE(OnNcHitTest, WM_NCHITTEST) + MESSAGE(OnSetCursor, WM_SETCURSOR) + MESSAGE(OnEnterMenuLoop, WM_ENTERMENULOOP) + MESSAGE(OnMouseMove, WM_MOUSEMOVE) + MESSAGE(OnMouseMove, WM_NCMOUSEMOVE) + MESSAGE(OnMouseLeave, WM_MOUSELEAVE) + MESSAGE(OnMouseLeave, WM_NCMOUSELEAVE) + MESSAGE(OnMouseScrollMove, WM_MOUSEWHEEL) + MESSAGE(OnMouseHScrollMove, WM_MOUSEHWHEEL) + MESSAGE(OnContextMenu, WM_CONTEXTMENU) + MESSAGE(OnRightButtonDown, WM_NCRBUTTONDOWN) + MESSAGE(OnRightButtonDown, WM_RBUTTONDOWN) + MESSAGE(OnRightButtonUp, WM_RBUTTONUP) + MESSAGE(OnContextMenu, WM_NCRBUTTONUP) + MESSAGE(OnRightButtonDoubleClick, WM_RBUTTONDBLCLK) + MESSAGE(OnRightButtonDoubleClick, WM_NCRBUTTONDBLCLK) + MESSAGE(OnLeftButtonDown, WM_NCLBUTTONDOWN) + MESSAGE(OnLeftButtonDown, WM_LBUTTONDOWN) + MESSAGE(OnLeftButtonUp, WM_LBUTTONUP) + MESSAGE(OnLeftButtonUp, WM_NCLBUTTONUP) + MESSAGE(OnLeftButtonDoubleClick, WM_LBUTTONDBLCLK) + MESSAGE(OnLeftButtonDoubleClick, WM_NCLBUTTONDBLCLK) + MESSAGE(OnMiddleButtonDown, WM_NCMBUTTONDOWN) + MESSAGE(OnMiddleButtonDown, WM_MBUTTONDOWN) + MESSAGE(OnMiddleButtonUp, WM_MBUTTONUP) + MESSAGE(OnMiddleButtonUp, WM_NCMBUTTONUP) + MESSAGE(OnMiddleButtonDoubleClick, WM_MBUTTONDBLCLK) + MESSAGE(OnMiddleButtonDoubleClick, WM_NCMBUTTONDBLCLK) + MESSAGE(OnXButtonDown, WM_XBUTTONDOWN); + MESSAGE(OnXButtonDown, WM_NCXBUTTONDOWN); + MESSAGE(OnXButtonUp, WM_XBUTTONUP); + MESSAGE(OnXButtonUp, WM_NCXBUTTONUP); + MESSAGE(OnXButtonDoubleClick, WM_XBUTTONDBLCLK); + MESSAGE(OnXButtonDoubleClick, WM_NCXBUTTONDBLCLK); + MESSAGE(OnWindowPosChanging, WM_WINDOWPOSCHANGING) + MESSAGE(OnCopyData, WM_COPYDATA) + MESSAGE(OnDelayedRefresh, WM_METERWINDOW_DELAYED_REFRESH) + MESSAGE(OnDelayedMove, WM_METERWINDOW_DELAYED_MOVE) + MESSAGE(OnDwmColorChange, WM_DWMCOLORIZATIONCOLORCHANGED) + MESSAGE(OnDwmCompositionChange, WM_DWMCOMPOSITIONCHANGED) + MESSAGE(OnSettingChange, WM_SETTINGCHANGE) + MESSAGE(OnDisplayChange, WM_DISPLAYCHANGE) + MESSAGE(OnSetWindowFocus, WM_SETFOCUS) + MESSAGE(OnSetWindowFocus, WM_KILLFOCUS) + MESSAGE(OnWake, WM_POWERBROADCAST) + END_MESSAGEPROC +} + +/* +** The initial window procedure for the meter window. Passes control to WndProc after initial setup. +** +*/ +LRESULT CALLBACK MeterWindow::InitialWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (uMsg == WM_NCCREATE) + { + MeterWindow* window = (MeterWindow*)((LPCREATESTRUCT)lParam)->lpCreateParams; + SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)window); + + // Change the window procedure over to MainWndProc now that GWLP_USERDATA is set + SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)WndProc); + return TRUE; + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + +/* +** Handles delayed refresh +** +*/ +LRESULT MeterWindow::OnDelayedRefresh(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + Refresh(false); + return 0; +} + +/* +** Handles delayed move. +** Do not save the position in this handler for the sake of preventing move by temporal resolution/workarea change. +** +*/ +LRESULT MeterWindow::OnDelayedMove(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + m_Parser.ResetMonitorVariables(this); + + // Move the window temporarily + ResizeWindow(false); + SetWindowPos(m_Window, nullptr, m_ScreenX, m_ScreenY, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); + + return 0; +} + +/* +** Handles bangs from the exe +** +*/ +LRESULT MeterWindow::OnCopyData(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + COPYDATASTRUCT* pCopyDataStruct = (COPYDATASTRUCT*)lParam; + + if (pCopyDataStruct && (pCopyDataStruct->dwData == 1) && (pCopyDataStruct->cbData > 0)) + { + if (GetRainmeter().HasMeterWindow(this)) + { + const WCHAR* command = (const WCHAR*)pCopyDataStruct->lpData; + GetRainmeter().ExecuteCommand(command, this); + } + else + { + // This meterwindow has been deactivated + LogWarning(L"Unable to bang unloaded skin"); + } + + return TRUE; + } + + return FALSE; +} + +/* +** Sets up the window position variables. +** +*/ +void MeterWindow::SetWindowPositionVariables(int x, int y) +{ + WCHAR buffer[32]; + + _itow_s(x, buffer, 10); + m_Parser.SetBuiltInVariable(L"CURRENTCONFIGX", buffer); + _itow_s(y, buffer, 10); + m_Parser.SetBuiltInVariable(L"CURRENTCONFIGY", buffer); +} + +/* +** Sets up the window size variables. +** +*/ +void MeterWindow::SetWindowSizeVariables(int w, int h) +{ + WCHAR buffer[32]; + + _itow_s(w, buffer, 10); + m_Parser.SetBuiltInVariable(L"CURRENTCONFIGWIDTH", buffer); + _itow_s(h, buffer, 10); + m_Parser.SetBuiltInVariable(L"CURRENTCONFIGHEIGHT", buffer); +} + +/* +** Converts the path to absolute by adding the skin's path to it (unless it already is absolute). +** +*/ +void MeterWindow::MakePathAbsolute(std::wstring& path) +{ + if (path.empty() || PathUtil::IsAbsolute(path)) + { + return; // It's already absolute path (or it's empty) + } + else + { + std::wstring absolute; + absolute.reserve(GetRainmeter().GetSkinPath().size() + m_FolderPath.size() + 1 + path.size()); + absolute = GetRainmeter().GetSkinPath(); + absolute += m_FolderPath; + absolute += L'\\'; + absolute += path; + absolute.swap(path); + } +} + +std::wstring MeterWindow::GetFilePath() +{ + std::wstring file = GetRainmeter().GetSkinPath() + m_FolderPath; + file += L'\\'; + file += m_FileName; + return file; +} + +std::wstring MeterWindow::GetRootName() +{ + std::wstring::size_type loc; + if ((loc = m_FolderPath.find_first_of(L'\\')) != std::wstring::npos) + { + return m_FolderPath.substr(0, loc); + } + + return m_FolderPath; +} + +std::wstring MeterWindow::GetRootPath() +{ + std::wstring path = GetRainmeter().GetSkinPath(); + + std::wstring::size_type loc; + if ((loc = m_FolderPath.find_first_of(L'\\')) != std::wstring::npos) + { + path.append(m_FolderPath, 0, loc + 1); + } + else + { + path += m_FolderPath; + path += L'\\'; + } + + return path; +} + +std::wstring MeterWindow::GetResourcesPath() +{ + std::wstring path = GetRootPath(); + path += L"@Resources\\"; + return path; +} + +std::wstring MeterWindow::GetSkinPath() +{ + std::wstring path; + if (!m_FolderPath.empty()) + { + path += m_FolderPath; + path += L"\\"; + } + + path += m_FileName; + return path; +} + +Meter* MeterWindow::GetMeter(const std::wstring& meterName) +{ + const WCHAR* name = meterName.c_str(); + std::vector::const_iterator j = m_Meters.begin(); + for ( ; j != m_Meters.end(); ++j) + { + if (_wcsicmp((*j)->GetName(), name) == 0) + { + return (*j); + } + } + return nullptr; +} diff --git a/Library/Rainmeter.cpp b/Library/Rainmeter.cpp index e92be98d..d9166c24 100644 --- a/Library/Rainmeter.cpp +++ b/Library/Rainmeter.cpp @@ -1,1690 +1,1666 @@ -/* - Copyright (C) 2001 Kimmo Pekkola - - 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 "Rainmeter.h" -#include "TrayWindow.h" -#include "System.h" -#include "Error.h" -#include "DialogAbout.h" -#include "DialogManage.h" -#include "MeasureNet.h" -#include "MeasureCPU.h" -#include "MeterString.h" -#include "UpdateCheck.h" -#include "../Version.h" - -using namespace Gdiplus; - -enum TIMER -{ - TIMER_NETSTATS = 1 -}; -enum INTERVAL -{ - INTERVAL_NETSTATS = 120000 -}; - -/* -** Initializes Rainmeter. -** -*/ -int RainmeterMain(LPWSTR cmdLine) -{ - auto& rainmeter = Rainmeter::GetInstance(); - int ret = rainmeter.Initialize(nullptr, nullptr); - if (ret == 0) - { - ret = rainmeter.MessagePump(); - } - rainmeter.Finalize(); - - return ret; -} - -/* -** Initializes Rainmeter. -** -*/ -void* Rainmeter_Initialize() -{ - int res = Rainmeter::GetInstance().Initialize(nullptr, nullptr); - - // Success? - if (res == 0) - return &Rainmeter::GetInstance(); - - return nullptr; -} - -/* -** Finalizes Rainmeter. -** -*/ -void Rainmeter_Finalize(void* ptr) -{ - Rainmeter* rainmeter = (Rainmeter*)ptr; - rainmeter->Finalize(); -} - -/* -** Constructor -** -*/ -Rainmeter::Rainmeter() : - m_TrayWindow(), - m_UseD2D(true), - m_Debug(false), - m_DisableVersionCheck(false), - m_NewVersion(false), - m_DesktopWorkAreaChanged(false), - m_DesktopWorkAreaType(false), - m_NormalStayDesktop(true), - m_DisableRDP(false), - m_DisableDragging(false), - m_CurrentParser(), - m_Window(), - m_Mutex(), - m_Instance(), - m_ResourceInstance(), - m_ResourceLCID(), - m_GDIplusToken(), - m_GlobalOptions() -{ - CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); - - InitCommonControls(); - - // Initialize GDI+. - GdiplusStartupInput gdiplusStartupInput; - GdiplusStartup(&m_GDIplusToken, &gdiplusStartupInput, nullptr); -} - -/* -** Destructor -** -*/ -Rainmeter::~Rainmeter() -{ - CoUninitialize(); - - GdiplusShutdown(m_GDIplusToken); -} - -Rainmeter& Rainmeter::GetInstance() -{ - static Rainmeter s_Rainmeter; - return s_Rainmeter; -} - -/* -** The main initialization function for the module. -** -*/ -int Rainmeter::Initialize(LPCWSTR iniPath, LPCWSTR layout) -{ - m_Instance = GetModuleHandle(L"Rainmeter"); - - WCHAR* buffer = new WCHAR[MAX_LINE_LENGTH]; - GetModuleFileName(m_Instance, buffer, MAX_LINE_LENGTH); - - // Remove the module's name from the path - WCHAR* pos = wcsrchr(buffer, L'\\'); - m_Path.assign(buffer, pos ? pos - buffer + 1 : 0); - m_Drive = PathUtil::GetVolume(m_Path); - - bool bDefaultIniLocation = false; - if (iniPath) - { - // The command line defines the location of Rainmeter.ini (or whatever it calls it). - std::wstring iniFile = iniPath; - PathUtil::ExpandEnvironmentVariables(iniFile); - - if (iniFile.empty() || PathUtil::IsSeparator(iniFile[iniFile.length() - 1])) - { - iniFile += L"Rainmeter.ini"; - } - else if (iniFile.length() <= 4 || _wcsicmp(iniFile.c_str() + (iniFile.length() - 4), L".ini") != 0) - { - iniFile += L"\\Rainmeter.ini"; - } - - if (!PathUtil::IsSeparator(iniFile[0]) && iniFile.find_first_of(L':') == std::wstring::npos) - { - // Make absolute path - iniFile.insert(0, m_Path); - } - - m_IniFile = iniFile; - bDefaultIniLocation = true; - } - else - { - m_IniFile = m_Path; - m_IniFile += L"Rainmeter.ini"; - - // If the ini file doesn't exist in the program folder store it to the %APPDATA% instead so that things work better in Vista/Win7 - if (_waccess(m_IniFile.c_str(), 0) == -1) - { - m_IniFile = L"%APPDATA%\\Rainmeter\\Rainmeter.ini"; - PathUtil::ExpandEnvironmentVariables(m_IniFile); - bDefaultIniLocation = true; - } - } - - WNDCLASS wc = {0}; - wc.lpfnWndProc = (WNDPROC)MainWndProc; - wc.hInstance = m_Instance; - wc.lpszClassName = RAINMETER_CLASS_NAME; - ATOM className = RegisterClass(&wc); - - m_Window = CreateWindowEx( - WS_EX_TOOLWINDOW, - MAKEINTATOM(className), - RAINMETER_WINDOW_NAME, - WS_POPUP | WS_DISABLED, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - nullptr, - nullptr, - m_Instance, - nullptr); - - if (!m_Window) return 1; - - Logger& logger = GetLogger(); - const WCHAR* iniFile = m_IniFile.c_str(); - - // Set file locations - { - m_SettingsPath = PathUtil::GetFolderFromFilePath(m_IniFile); - - size_t len = m_IniFile.length(); - if (len > 4 && _wcsicmp(iniFile + (len - 4), L".ini") == 0) - { - len -= 4; - } - - std::wstring logFile(m_IniFile, 0, len); - m_DataFile = m_StatsFile = logFile; - logFile += L".log"; - m_StatsFile += L".stats"; - m_DataFile += L".data"; - - logger.SetLogFilePath(logFile); - } - - // Create a default Rainmeter.ini file if needed - if (_waccess(iniFile, 0) == -1) - { - CreateOptionsFile(); - } - - bool dataFileCreated = false; - if (_waccess(m_DataFile.c_str(), 0) == -1) - { - dataFileCreated = true; - CreateDataFile(); - } - - // Reset log file - System::RemoveFile(logger.GetLogFilePath()); - - m_Debug = 0!=GetPrivateProfileInt(L"Rainmeter", L"Debug", 0, iniFile); - - const bool logging = GetPrivateProfileInt(L"Rainmeter", L"Logging", 0, iniFile) != 0; - logger.SetLogToFile(logging); - if (logging) - { - logger.StartLogFile(); - } - - // Determine the language resource to load - std::wstring resource = m_Path + L"Languages\\"; - if (GetPrivateProfileString(L"Rainmeter", L"Language", L"", buffer, MAX_LINE_LENGTH, iniFile) == 0) - { - // Use whatever the user selected for the installer - DWORD size = MAX_LINE_LENGTH; - HKEY hKey; - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Rainmeter", 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hKey) == ERROR_SUCCESS) - { - DWORD type = 0; - if (RegQueryValueEx(hKey, L"Language", nullptr, &type, (LPBYTE)buffer, (LPDWORD)&size) != ERROR_SUCCESS || - type != REG_SZ) - { - buffer[0] = L'\0'; - } - RegCloseKey(hKey); - } - } - if (buffer[0] != L'\0') - { - // Try selected language - m_ResourceLCID = wcstoul(buffer, nullptr, 10); - resource += buffer; - resource += L".dll"; - - m_ResourceInstance = LoadLibraryEx(resource.c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE); - } - if (!m_ResourceInstance) - { - // Try English - resource = m_Path; - resource += L"Languages\\1033.dll"; - m_ResourceInstance = LoadLibraryEx(resource.c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE); - m_ResourceLCID = 1033; - if (!m_ResourceInstance) - { - MessageBox(nullptr, L"Unable to load language library", APPNAME, MB_OK | MB_TOPMOST | MB_ICONERROR); - return 1; - } - } - - // Get skin folder path - size_t len = GetPrivateProfileString(L"Rainmeter", L"SkinPath", L"", buffer, MAX_LINE_LENGTH, iniFile); - if (len > 0 && - _waccess(buffer, 0) != -1) // Temporary fix - { - // Try Rainmeter.ini first - m_SkinPath.assign(buffer, len); - PathUtil::ExpandEnvironmentVariables(m_SkinPath); - PathUtil::AppendBacklashIfMissing(m_SkinPath); - } - else if (bDefaultIniLocation && - SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_MYDOCUMENTS, nullptr, SHGFP_TYPE_CURRENT, buffer))) - { - // Use My Documents/Rainmeter/Skins - m_SkinPath = buffer; - m_SkinPath += L"\\Rainmeter\\"; - CreateDirectory(m_SkinPath.c_str(), nullptr); - m_SkinPath += L"Skins\\"; - - WritePrivateProfileString(L"Rainmeter", L"SkinPath", m_SkinPath.c_str(), iniFile); - } - else - { - m_SkinPath = m_Path + L"Skins\\"; - } - - // Create user skins, layouts, addons, and plugins folders if needed - CreateComponentFolders(bDefaultIniLocation); - - delete [] buffer; - buffer = nullptr; - - LogNoticeF(L"Path: %s", m_Path.c_str()); - LogNoticeF(L"IniFile: %s", iniFile); - LogNoticeF(L"SkinPath: %s", m_SkinPath.c_str()); - - // Test that the Rainmeter.ini file is writable - TestSettingsFile(bDefaultIniLocation); - - System::Initialize(m_Instance); - - MeasureNet::InitializeStatic(); - MeasureCPU::InitializeStatic(); - MeterString::InitializeStatic(); - - // Tray must exist before skins are read - m_TrayWindow = new TrayWindow(); - m_TrayWindow->Initialize(); - - ReloadSettings(); - - if (m_SkinRegistry.IsEmpty()) - { - std::wstring error = GetFormattedString(ID_STR_NOAVAILABLESKINS, m_SkinPath.c_str()); - ShowMessage(nullptr, error.c_str(), MB_OK | MB_ICONERROR); - } - - ResetStats(); - ReadStats(); - - // Change the work area if necessary - if (m_DesktopWorkAreaChanged) - { - UpdateDesktopWorkArea(false); - } - - bool layoutLoaded = false; - if (layout) - { - std::vector args = CommandHandler::ParseString(layout); - layoutLoaded = (args.size() == 1 && LoadLayout(args[0])); - } - - if (!layoutLoaded) - { - ActivateActiveSkins(); - } - - if (dataFileCreated) - { - m_TrayWindow->ShowWelcomeNotification(); - } - else if (!m_DisableVersionCheck) - { - CheckUpdate(); - } - - return 0; // All is OK -} - -void Rainmeter::Finalize() -{ - KillTimer(m_Window, TIMER_NETSTATS); - - DeleteAllUnmanagedMeterWindows(); - DeleteAllMeterWindows(); - DeleteAllUnmanagedMeterWindows(); // Redelete unmanaged windows caused by OnCloseAction - - delete m_TrayWindow; - - System::Finalize(); - - MeasureNet::UpdateIFTable(); - MeasureNet::UpdateStats(); - WriteStats(true); - - MeasureNet::FinalizeStatic(); - MeasureCPU::FinalizeStatic(); - MeterString::FinalizeStatic(); - - // Change the work area back - if (m_DesktopWorkAreaChanged) - { - UpdateDesktopWorkArea(true); - } - - if (m_ResourceInstance) FreeLibrary(m_ResourceInstance); - if (m_Mutex) ReleaseMutex(m_Mutex); -} - -int Rainmeter::MessagePump() -{ - MSG msg; - BOOL ret; - - // Run the standard window message loop - while ((ret = GetMessage(&msg, nullptr, 0, 0)) != 0) - { - if (ret == -1) - { - break; - } - - if (!Dialog::HandleMessage(msg)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - - return (int)msg.wParam; -} - -LRESULT CALLBACK Rainmeter::MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) - { - case WM_DESTROY: - PostQuitMessage(0); - break; - - case WM_COPYDATA: - { - COPYDATASTRUCT* cds = (COPYDATASTRUCT*)lParam; - if (cds) - { - const WCHAR* data = (const WCHAR*)cds->lpData; - if (cds->dwData == 1 && (cds->cbData > 0)) - { - Rainmeter::GetInstance().DelayedExecuteCommand(data); - } - } - } - break; - - case WM_TIMER: - if (wParam == TIMER_NETSTATS) - { - MeasureNet::UpdateIFTable(); - MeasureNet::UpdateStats(); - Rainmeter::GetInstance().WriteStats(false); - } - break; - - case WM_RAINMETER_DELAYED_REFRESH_ALL: - Rainmeter::GetInstance().RefreshAll(); - break; - - case WM_RAINMETER_DELAYED_EXECUTE: - if (lParam) - { - // Execute bang - WCHAR* bang = (WCHAR*)lParam; - Rainmeter::GetInstance().ExecuteCommand(bang, nullptr); - free(bang); // _wcsdup() - } - break; - - case WM_RAINMETER_EXECUTE: - if (Rainmeter::GetInstance().HasMeterWindow((MeterWindow*)wParam)) - { - Rainmeter::GetInstance().ExecuteCommand((const WCHAR*)lParam, (MeterWindow*)wParam); - } - break; - - default: - return DefWindowProc(hWnd, uMsg, wParam, lParam); - } - - return 0; -} - -void Rainmeter::SetNetworkStatisticsTimer() -{ - static bool set = SetTimer(m_Window, TIMER_NETSTATS, INTERVAL_NETSTATS, nullptr) != 0; -} - -void Rainmeter::CreateOptionsFile() -{ - CreateDirectory(m_SettingsPath.c_str(), nullptr); - - std::wstring defaultIni = GetDefaultLayoutPath(); - defaultIni += L"illustro default\\Rainmeter.ini"; - System::CopyFiles(defaultIni, m_IniFile); -} - -void Rainmeter::CreateDataFile() -{ - std::wstring tmpSz = m_SettingsPath + L"Plugins.ini"; - - const WCHAR* pluginsFile = tmpSz.c_str(); - const WCHAR* dataFile = m_DataFile.c_str(); - - if (_waccess(pluginsFile, 0) == 0) - { - MoveFile(pluginsFile, dataFile); - } - else - { - // Create empty file - HANDLE file = CreateFile(dataFile, GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); - if (file != INVALID_HANDLE_VALUE) - { - CloseHandle(file); - } - } -} - -void Rainmeter::CreateComponentFolders(bool defaultIniLocation) -{ - std::wstring path; - - if (CreateDirectory(m_SkinPath.c_str(), nullptr)) - { - // Folder just created, so copy default skins there - std::wstring from = GetDefaultSkinPath(); - from += L"*.*"; - System::CopyFiles(from, m_SkinPath); - } - else - { - path = m_SkinPath; - path += L"Backup"; - if (_waccess(path.c_str(), 0) != -1) - { - std::wstring newPath = m_SkinPath + L"@Backup"; - MoveFile(path.c_str(), newPath.c_str()); - } - } - - path = GetLayoutPath(); - if (_waccess(path.c_str(), 0) == -1) - { - std::wstring themesPath = m_SettingsPath + L"Themes"; - if (_waccess(themesPath.c_str(), 0) != -1) - { - // Migrate Themes into Layouts for backwards compatibility and rename - // Rainmeter.thm to Rainmeter.ini and RainThemes.bmp to Wallpaper.bmp. - MoveFile(themesPath.c_str(), path.c_str()); - - path += L'*'; // For FindFirstFile. - WIN32_FIND_DATA fd; - HANDLE hFind = FindFirstFile(path.c_str(), &fd); - path.pop_back(); // Remove '*'. - - if (hFind != INVALID_HANDLE_VALUE) - { - do - { - if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY && - PathUtil::IsDotOrDotDot(fd.cFileName)) - { - std::wstring layoutFolder = path + fd.cFileName; - layoutFolder += L'\\'; - - std::wstring file = layoutFolder + L"Rainmeter.thm"; - if (_waccess(file.c_str(), 0) != -1) - { - std::wstring newFile = layoutFolder + L"Rainmeter.ini"; - MoveFile(file.c_str(), newFile.c_str()); - } - - file = layoutFolder + L"RainThemes.bmp"; - if (_waccess(file.c_str(), 0) != -1) - { - std::wstring newFile = layoutFolder + L"Wallpaper.bmp"; - MoveFile(file.c_str(), newFile.c_str()); - } - } - } - while (FindNextFile(hFind, &fd)); - - FindClose(hFind); - } - } - else - { - std::wstring from = GetDefaultLayoutPath(); - if (_waccess(from.c_str(), 0) != -1) - { - System::CopyFiles(from, m_SettingsPath); - } - } - } - else - { - path += L"Backup"; - if (_waccess(path.c_str(), 0) != -1) - { - std::wstring newPath = GetLayoutPath(); - newPath += L"@Backup"; - MoveFile(path.c_str(), newPath.c_str()); - } - } - - if (defaultIniLocation) - { - path = GetUserPluginPath(); - if (_waccess(path.c_str(), 0) == -1) - { - std::wstring from = GetDefaultPluginPath(); - if (_waccess(from.c_str(), 0) != -1) - { - System::CopyFiles(from, m_SettingsPath); - } - } - - path = GetAddonPath(); - if (_waccess(path.c_str(), 0) == -1) - { - std::wstring from = GetDefaultAddonPath(); - if (_waccess(from.c_str(), 0) != -1) - { - System::CopyFiles(from, m_SettingsPath); - } - } - - path = m_SettingsPath; - path += L"Rainmeter.exe"; - const WCHAR* pathSz = path.c_str(); - if (_waccess(pathSz, 0) == -1) - { - // Create a hidden stub Rainmeter.exe into SettingsPath for old addon - // using relative path to Rainmeter.exe - std::wstring from = m_Path + L"Rainmeter.exe"; - System::CopyFiles(from, path); - - // Get rid of all resources from the stub executable - HANDLE stub = BeginUpdateResource(pathSz, TRUE); - - // Add the manifest of Rainmeter.dll to the stub - HRSRC manifest = FindResource(m_Instance, MAKEINTRESOURCE(2), RT_MANIFEST); - DWORD manifestSize = SizeofResource(m_Instance, manifest); - HGLOBAL manifestLoad = LoadResource(m_Instance, manifest); - void* manifestLoadData = LockResource(manifestLoad); - if (manifestLoadData) - { - LANGID langID = MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT); - UpdateResource(stub, RT_MANIFEST, MAKEINTRESOURCE(1), langID, manifestLoadData, manifestSize); - } - - EndUpdateResource(stub, FALSE); - SetFileAttributes(pathSz, FILE_ATTRIBUTE_HIDDEN); - } - } -} - -void Rainmeter::ReloadSettings() -{ - ScanForSkins(); - ScanForLayouts(); - ReadGeneralSettings(m_IniFile); -} - -void Rainmeter::EditSettings() -{ - std::wstring file = L'"' + m_IniFile; - file += L'"'; - CommandHandler::RunFile(m_SkinEditor.c_str(), file.c_str()); -} - -void Rainmeter::EditSkinFile(const std::wstring& name, const std::wstring& iniFile) -{ - std::wstring args = L'"' + m_SkinPath; - args += name; - args += L'\\'; - args += iniFile; - args += L'"'; - CommandHandler::RunFile(m_SkinEditor.c_str(), args.c_str()); -} - -void Rainmeter::OpenSkinFolder(const std::wstring& name) -{ - std::wstring folderPath = m_SkinPath + name; - CommandHandler::RunFile(folderPath.c_str()); -} - -void Rainmeter::ActivateActiveSkins() -{ - std::multimap::const_iterator iter = m_SkinOrders.begin(); - for ( ; iter != m_SkinOrders.end(); ++iter) - { - const SkinRegistry::Folder& skinFolder = m_SkinRegistry.GetFolder((*iter).second); - if (skinFolder.active > 0 && skinFolder.active <= (uint16_t)skinFolder.files.size()) - { - ActivateSkin((*iter).second, skinFolder.active - 1); - } - } -} - -/* -** Activates the skin, or, if it is already active, the next variant of the skin. Returns true -** if the skin was activated (or was already active). -*/ -bool Rainmeter::ActivateSkin(const std::wstring& folderPath) -{ - const int index = m_SkinRegistry.FindFolderIndex(folderPath); - if (index != -1) - { - const SkinRegistry::Folder& skinFolder = m_SkinRegistry.GetFolder(index); - if (!(skinFolder.active == 1 && skinFolder.files.size() == 1)) - { - // Activate the next index. - ActivateSkin( - index, (skinFolder.active < skinFolder.files.size()) ? skinFolder.active : 0); - } - - return true; - } - - return false; -} - -/* -** Activates the skin, or, if it is already active, the next variant of the skin. Returns true -** if the skin was activated (or was already active). -*/ -bool Rainmeter::ActivateSkin(const std::wstring& folderPath, const std::wstring& file) -{ - const SkinRegistry::Indexes indexes = m_SkinRegistry.FindIndexes(folderPath, file); - if (indexes.IsValid()) - { - ActivateSkin(indexes.folder, indexes.file); - return true; - } - - return false; -} - -void Rainmeter::ActivateSkin(int folderIndex, int fileIndex) -{ - if (folderIndex >= 0 && folderIndex < m_SkinRegistry.GetFolderCount() && - fileIndex >= 0 && fileIndex < m_SkinRegistry.GetFolder(folderIndex).files.size()) - { - auto& skinFolder = m_SkinRegistry.GetFolder(folderIndex); - const std::wstring& file = skinFolder.files[fileIndex]; - const WCHAR* fileSz = file.c_str(); - - std::wstring folderPath = m_SkinRegistry.GetFolderPath(folderIndex); - - // Verify that the skin is not already active - std::map::const_iterator iter = m_MeterWindows.find(folderPath); - if (iter != m_MeterWindows.end()) - { - if (wcscmp(((*iter).second)->GetFileName().c_str(), fileSz) == 0) - { - LogWarningF((*iter).second, L"!ActivateConfig: \"%s\" already active", folderPath.c_str()); - return; - } - else - { - // Deactivate the existing skin - DeactivateSkin((*iter).second, folderIndex); - } - } - - // Verify whether the ini-file exists - std::wstring skinIniPath = m_SkinPath + folderPath; - skinIniPath += L'\\'; - skinIniPath += file; - - if (_waccess(skinIniPath.c_str(), 0) == -1) - { - std::wstring message = GetFormattedString(ID_STR_UNABLETOACTIVATESKIN, folderPath.c_str(), fileSz); - ShowMessage(nullptr, message.c_str(), MB_OK | MB_ICONEXCLAMATION); - return; - } - - if (skinFolder.active != fileIndex + 1) - { - // Write only if changed. - skinFolder.active = fileIndex + 1; - WriteActive(folderPath, fileIndex); - } - - CreateMeterWindow(folderPath, file); - } -} - -void Rainmeter::DeactivateSkin(MeterWindow* meterWindow, int folderIndex, bool save) -{ - if (folderIndex >= 0 && folderIndex < m_SkinRegistry.GetFolderCount()) - { - m_SkinRegistry.GetFolder(folderIndex).active = 0; // Deactivate the skin - } - else if (folderIndex == -1 && meterWindow) - { - SkinRegistry::Folder* folder = m_SkinRegistry.FindFolder(meterWindow->GetFolderPath()); - if (folder) - { - folder->active = 0; - } - } - - if (meterWindow) - { - if (save) - { - // Disable the skin in the ini-file - WriteActive(meterWindow->GetFolderPath(), -1); - } - - meterWindow->Deactivate(); - } -} - -void Rainmeter::ToggleSkin(int folderIndex, int fileIndex) -{ - if (folderIndex >= 0 && folderIndex < m_SkinRegistry.GetFolderCount() && - fileIndex >= 0 && fileIndex < m_SkinRegistry.GetFolder(folderIndex).files.size()) - { - if (m_SkinRegistry.GetFolder(folderIndex).active == fileIndex + 1) - { - MeterWindow* meterWindow = GetMeterWindow(m_SkinRegistry.GetFolderPath(folderIndex)); - DeactivateSkin(meterWindow, folderIndex); - } - else - { - ActivateSkin(folderIndex, fileIndex); - } - } -} - -void Rainmeter::ToggleSkinWithID(UINT id) -{ - const SkinRegistry::Indexes indexes = m_SkinRegistry.FindIndexesForID(id); - if (indexes.IsValid()) - { - ToggleSkin(indexes.folder, indexes.file); - } -} - -void Rainmeter::SetSkinPath(const std::wstring& skinPath) -{ - WritePrivateProfileString(L"Rainmeter", L"SkinPath", skinPath.c_str(), m_IniFile.c_str()); -} - -void Rainmeter::SetSkinEditor(const std::wstring& path) -{ - if (!path.empty()) - { - m_SkinEditor = path; - WritePrivateProfileString(L"Rainmeter", L"ConfigEditor", path.c_str(), m_IniFile.c_str()); - } -} - -void Rainmeter::WriteActive(const std::wstring& folderPath, int fileIndex) -{ - WCHAR buffer[32]; - _itow_s(fileIndex + 1, buffer, 10); - WritePrivateProfileString(folderPath.c_str(), L"Active", buffer, m_IniFile.c_str()); -} - -void Rainmeter::CreateMeterWindow(const std::wstring& folderPath, const std::wstring& file) -{ - MeterWindow* mw = new MeterWindow(folderPath, file); - - // Note: May modify existing key - m_MeterWindows[folderPath] = mw; - - mw->Initialize(); - - DialogAbout::UpdateSkins(); - DialogManage::UpdateSkins(mw); -} - -void Rainmeter::DeleteAllMeterWindows() -{ - auto it = m_MeterWindows.cbegin(); - while (it != m_MeterWindows.cend()) - { - MeterWindow* mw = (*it).second; - m_MeterWindows.erase(it); // Remove before deleting MeterWindow - - DialogManage::UpdateSkins(mw, true); - delete mw; - - // Get next valid iterator (Fix for iterator invalidation caused by OnCloseAction) - it = m_MeterWindows.cbegin(); - } - - m_MeterWindows.clear(); - DialogAbout::UpdateSkins(); -} - -void Rainmeter::DeleteAllUnmanagedMeterWindows() -{ - for (auto it = m_UnmanagedMeterWindows.cbegin(); it != m_UnmanagedMeterWindows.cend(); ++it) - { - delete (*it); - } - - m_UnmanagedMeterWindows.clear(); -} - -/* -** Removes the skin from m_MeterWindows. The skin should delete itself. -** -*/ -void Rainmeter::RemoveMeterWindow(MeterWindow* meterWindow) -{ - for (auto it = m_MeterWindows.cbegin(); it != m_MeterWindows.cend(); ++it) - { - if ((*it).second == meterWindow) - { - m_MeterWindows.erase(it); - DialogManage::UpdateSkins(meterWindow, true); - DialogAbout::UpdateSkins(); - break; - } - } -} - -/* -** Adds the skin to m_UnmanagedMeterWindows. The skin should remove itself by calling RemoveUnmanagedMeterWindow(). -** -*/ -void Rainmeter::AddUnmanagedMeterWindow(MeterWindow* meterWindow) -{ - for (auto it = m_UnmanagedMeterWindows.cbegin(); it != m_UnmanagedMeterWindows.cend(); ++it) - { - if ((*it) == meterWindow) // already added - { - return; - } - } - - m_UnmanagedMeterWindows.push_back(meterWindow); -} - -void Rainmeter::RemoveUnmanagedMeterWindow(MeterWindow* meterWindow) -{ - for (auto it = m_UnmanagedMeterWindows.cbegin(); it != m_UnmanagedMeterWindows.cend(); ++it) - { - if ((*it) == meterWindow) - { - m_UnmanagedMeterWindows.erase(it); - break; - } - } -} - -bool Rainmeter::HasMeterWindow(const MeterWindow* meterWindow) const -{ - for (auto it = m_MeterWindows.begin(); it != m_MeterWindows.end(); ++it) - { - if ((*it).second == meterWindow) - { - return true; - } - } - - return false; -} - -MeterWindow* Rainmeter::GetMeterWindow(const std::wstring& folderPath) -{ - const WCHAR* folderSz = folderPath.c_str(); - std::map::const_iterator iter = m_MeterWindows.begin(); - for (; iter != m_MeterWindows.end(); ++iter) - { - if (_wcsicmp((*iter).first.c_str(), folderSz) == 0) - { - return (*iter).second; - } - } - - return nullptr; -} - -MeterWindow* Rainmeter::GetMeterWindowByINI(const std::wstring& ini_searching) -{ - if (_wcsnicmp(m_SkinPath.c_str(), ini_searching.c_str(), m_SkinPath.length()) == 0) - { - const std::wstring config_searching = ini_searching.substr(m_SkinPath.length()); - - std::map::const_iterator iter = m_MeterWindows.begin(); - for (; iter != m_MeterWindows.end(); ++iter) - { - std::wstring config_current = (*iter).second->GetFolderPath() + L'\\'; - config_current += (*iter).second->GetFileName(); - - if (_wcsicmp(config_current.c_str(), config_searching.c_str()) == 0) - { - return (*iter).second; - } - } - } - - return nullptr; -} - -MeterWindow* Rainmeter::GetMeterWindow(HWND hwnd) -{ - std::map::const_iterator iter = m_MeterWindows.begin(); - for (; iter != m_MeterWindows.end(); ++iter) - { - if ((*iter).second->GetWindow() == hwnd) - { - return (*iter).second; - } - } - - return nullptr; -} - -void Rainmeter::GetMeterWindowsByLoadOrder(std::multimap& windows, const std::wstring& group) -{ - std::map::const_iterator iter = m_MeterWindows.begin(); - for (; iter != m_MeterWindows.end(); ++iter) - { - MeterWindow* mw = (*iter).second; - if (mw && (group.empty() || mw->BelongsToGroup(group))) - { - windows.insert(std::pair(GetLoadOrder((*iter).first), mw)); - } - } -} - -void Rainmeter::SetLoadOrder(int folderIndex, int order) -{ - std::multimap::iterator iter = m_SkinOrders.begin(); - for ( ; iter != m_SkinOrders.end(); ++iter) - { - if ((*iter).second == folderIndex) // already exists - { - if ((*iter).first != order) - { - m_SkinOrders.erase(iter); - break; - } - else - { - return; - } - } - } - - m_SkinOrders.insert(std::pair(order, folderIndex)); -} - -int Rainmeter::GetLoadOrder(const std::wstring& folderPath) -{ - const int index = m_SkinRegistry.FindFolderIndex(folderPath); - if (index != -1) - { - std::multimap::const_iterator iter = m_SkinOrders.begin(); - for ( ; iter != m_SkinOrders.end(); ++iter) - { - if ((*iter).second == index) - { - return (*iter).first; - } - } - } - - // LoadOrder not specified - return 0; -} - -/* -** Scans all the subfolders and locates the ini-files. -*/ -void Rainmeter::ScanForSkins() -{ - m_SkinRegistry.Populate(m_SkinPath); - m_SkinOrders.clear(); -} - -/* -** Scans the given folder for layouts -*/ -void Rainmeter::ScanForLayouts() -{ - m_Layouts.clear(); - - WIN32_FIND_DATA fileData; // Data structure describes the file found - HANDLE hSearch; // Search handle returned by FindFirstFile - - // Scan for folders - std::wstring folders = GetLayoutPath(); - folders += L'*'; - - hSearch = FindFirstFileEx( - folders.c_str(), - (IsWindows7OrGreater()) ? FindExInfoBasic : FindExInfoStandard, - &fileData, - FindExSearchNameMatch, - nullptr, - 0); - - if (hSearch != INVALID_HANDLE_VALUE) - { - do - { - if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY && - !PathUtil::IsDotOrDotDot(fileData.cFileName)) - { - m_Layouts.push_back(fileData.cFileName); - } - } - while (FindNextFile(hSearch, &fileData)); - - FindClose(hSearch); - } - - DialogManage::UpdateLayouts(); -} - -void Rainmeter::ExecuteBang(const WCHAR* bang, std::vector& args, MeterWindow* meterWindow) -{ - m_CommandHandler.ExecuteBang(bang, args, meterWindow); -} - -/* -** Runs the given command or bang -** -*/ -void Rainmeter::ExecuteCommand(const WCHAR* command, MeterWindow* meterWindow, bool multi) -{ - m_CommandHandler.ExecuteCommand(command, meterWindow, multi); -} - -/* -** Executes command when current processing is done. -** -*/ -void Rainmeter::DelayedExecuteCommand(const WCHAR* command) -{ - WCHAR* bang = _wcsdup(command); - PostMessage(m_Window, WM_RAINMETER_DELAYED_EXECUTE, (WPARAM)nullptr, (LPARAM)bang); -} - -/* -** Reads the general settings from the Rainmeter.ini file -** -*/ -void Rainmeter::ReadGeneralSettings(const std::wstring& iniFile) -{ - WCHAR buffer[MAX_PATH]; - - // Clear old settings - m_DesktopWorkAreas.clear(); - - ConfigParser parser; - parser.Initialize(iniFile, nullptr, nullptr); - - m_UseD2D = parser.ReadBool(L"Rainmeter", L"UseD2D", true); - - m_Debug = parser.ReadBool(L"Rainmeter", L"Debug", false); - - // Read Logging settings - Logger& logger = GetLogger(); - const bool logging = parser.ReadBool(L"Rainmeter", L"Logging", false); - logger.SetLogToFile(logging); - if (logging) - { - logger.StartLogFile(); - } - - if (m_TrayWindow) - { - m_TrayWindow->ReadOptions(parser); - } - - m_GlobalOptions.netInSpeed = parser.ReadFloat(L"Rainmeter", L"NetInSpeed", 0.0); - m_GlobalOptions.netOutSpeed = parser.ReadFloat(L"Rainmeter", L"NetOutSpeed", 0.0); - - m_DisableDragging = parser.ReadBool(L"Rainmeter", L"DisableDragging", false); - m_DisableRDP = parser.ReadBool(L"Rainmeter", L"DisableRDP", false); - - m_SkinEditor = parser.ReadString(L"Rainmeter", L"ConfigEditor", L""); - if (m_SkinEditor.empty()) - { - // Get the program path associated with .ini files - DWORD cchOut = MAX_PATH; - HRESULT hr = AssocQueryString(ASSOCF_NOTRUNCATE, ASSOCSTR_EXECUTABLE, L".ini", L"open", buffer, &cchOut); - m_SkinEditor = (SUCCEEDED(hr) && cchOut > 0) ? buffer : L"Notepad"; - } - - if (m_Debug) - { - LogNoticeF(L"ConfigEditor: %s", m_SkinEditor.c_str()); - } - - m_TrayExecuteR = parser.ReadString(L"Rainmeter", L"TrayExecuteR", L"", false); - m_TrayExecuteM = parser.ReadString(L"Rainmeter", L"TrayExecuteM", L"", false); - m_TrayExecuteDR = parser.ReadString(L"Rainmeter", L"TrayExecuteDR", L"", false); - m_TrayExecuteDM = parser.ReadString(L"Rainmeter", L"TrayExecuteDM", L"", false); - - m_DisableVersionCheck = parser.ReadBool(L"Rainmeter", L"DisableVersionCheck", false); - - const std::wstring& area = parser.ReadString(L"Rainmeter", L"DesktopWorkArea", L""); - if (!area.empty()) - { - m_DesktopWorkAreas[0] = parser.ParseRECT(area.c_str()); - m_DesktopWorkAreaChanged = true; - } - - const size_t monitorCount = System::GetMonitorCount(); - for (UINT i = 1; i <= monitorCount; ++i) - { - _snwprintf_s(buffer, _TRUNCATE, L"DesktopWorkArea@%i", (int)i); - const std::wstring& area = parser.ReadString(L"Rainmeter", buffer, L""); - if (!area.empty()) - { - m_DesktopWorkAreas[i] = parser.ParseRECT(area.c_str()); - m_DesktopWorkAreaChanged = true; - } - } - - m_DesktopWorkAreaType = parser.ReadBool(L"Rainmeter", L"DesktopWorkAreaType", false); - - m_NormalStayDesktop = parser.ReadBool(L"Rainmeter", L"NormalStayDesktop", true); - - for (auto iter = parser.GetSections().cbegin(); iter != parser.GetSections().end(); ++iter) - { - const WCHAR* section = (*iter).c_str(); - - if (wcscmp(section, L"Rainmeter") == 0 || - wcscmp(section, L"TrayMeasure") == 0) - { - continue; - } - - const int index = m_SkinRegistry.FindFolderIndex(*iter); - if (index == -1) - { - continue; - } - - SkinRegistry::Folder& skinFolder = m_SkinRegistry.GetFolder(index); - - // Make sure there is a ini file available - int active = parser.ReadInt(section, L"Active", 0); - if (active > 0 && active <= (int)skinFolder.files.size()) - { - skinFolder.active = active; - } - - int order = parser.ReadInt(section, L"LoadOrder", 0); - SetLoadOrder(index, order); - } -} - -/* -** Refreshes all active meter windows. -** Note: This function calls MeterWindow::Refresh() directly for synchronization. Be careful about crash. -** -*/ -void Rainmeter::RefreshAll() -{ - // Read skins and settings - ReloadSettings(); - - // Change the work area if necessary - if (m_DesktopWorkAreaChanged) - { - UpdateDesktopWorkArea(false); - } - - // Make the sending order by using LoadOrder - std::multimap windows; - GetMeterWindowsByLoadOrder(windows); - - // Prepare the helper window - System::PrepareHelperWindow(); - - // Refresh all - std::multimap::const_iterator iter = windows.begin(); - for ( ; iter != windows.end(); ++iter) - { - MeterWindow* mw = (*iter).second; - if (mw) - { - // Verify whether the cached information is valid - const int index = m_SkinRegistry.FindFolderIndex(mw->GetFolderPath()); - if (index != -1) - { - SkinRegistry::Folder& skinFolder = m_SkinRegistry.GetFolder(index); - const WCHAR* skinIniFile = mw->GetFileName().c_str(); - - bool found = false; - for (int i = 0, isize = (int)skinFolder.files.size(); i < isize; ++i) - { - if (_wcsicmp(skinIniFile, skinFolder.files[i].c_str()) == 0) - { - found = true; - if (skinFolder.active != i + 1) - { - // Switch to new ini-file order - skinFolder.active = i + 1; - WriteActive(mw->GetFolderPath(), i); - } - break; - } - } - - if (!found) - { - const WCHAR* skinFolderPath = mw->GetFolderPath().c_str(); - std::wstring error = GetFormattedString(ID_STR_UNABLETOREFRESHSKIN, skinFolderPath, skinIniFile); - - DeactivateSkin(mw, index); - - ShowMessage(nullptr, error.c_str(), MB_OK | MB_ICONEXCLAMATION); - continue; - } - } - else - { - const WCHAR* skinFolderPath = mw->GetFolderPath().c_str(); - std::wstring error = GetFormattedString(ID_STR_UNABLETOREFRESHSKIN, skinFolderPath, L""); - - DeactivateSkin(mw, -2); // -2 = Force deactivate - - ShowMessage(nullptr, error.c_str(), MB_OK | MB_ICONEXCLAMATION); - continue; - } - - mw->Refresh(false, true); - } - } - - DialogAbout::UpdateSkins(); - DialogManage::UpdateSkins(nullptr); -} - -bool Rainmeter::LoadLayout(const std::wstring& name) -{ - // Replace Rainmeter.ini with layout - std::wstring layout = GetLayoutPath(); - layout += name; - std::wstring wallpaper = layout + L"\\Wallpaper.bmp"; - layout += L"\\Rainmeter.ini"; - - if (_waccess(layout.c_str(), 0) == -1) - { - return false; - } - - DeleteAllUnmanagedMeterWindows(); - DeleteAllMeterWindows(); - - std::wstring backup = GetLayoutPath(); - backup += L"@Backup"; - CreateDirectory(backup.c_str(), nullptr); - backup += L"\\Rainmeter.ini"; - - bool backupLayout = (_wcsicmp(name.c_str(), L"@Backup") == 0); - if (!backupLayout) - { - // Make a copy of current Rainmeter.ini - System::CopyFiles(m_IniFile, backup); - } - - System::CopyFiles(layout, m_IniFile); - - if (!backupLayout) - { - PreserveSetting(backup, L"SkinPath"); - PreserveSetting(backup, L"ConfigEditor"); - PreserveSetting(backup, L"LogViewer"); - PreserveSetting(backup, L"Logging"); - PreserveSetting(backup, L"DisableVersionCheck"); - PreserveSetting(backup, L"Language"); - PreserveSetting(backup, L"NormalStayDesktop"); - PreserveSetting(backup, L"TrayExecuteM", false); - PreserveSetting(backup, L"TrayExecuteR", false); - PreserveSetting(backup, L"TrayExecuteDM", false); - PreserveSetting(backup, L"TrayExecuteDR", false); - PreserveSetting(backup, L"UseD2D"); - - // Set wallpaper if it exists - if (_waccess(wallpaper.c_str(), 0) != -1) - { - SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, (void*)wallpaper.c_str(), SPIF_UPDATEINIFILE); - } - } - - ReloadSettings(); - - // Create meter windows for active skins - ActivateActiveSkins(); - - return true; -} - -void Rainmeter::PreserveSetting(const std::wstring& from, LPCTSTR key, bool replace) -{ - WCHAR* buffer = new WCHAR[MAX_LINE_LENGTH]; - - if ((replace || GetPrivateProfileString(L"Rainmeter", key, L"", buffer, 4, m_IniFile.c_str()) == 0) && - GetPrivateProfileString(L"Rainmeter", key, L"", buffer, MAX_LINE_LENGTH, from.c_str()) > 0) - { - WritePrivateProfileString(L"Rainmeter", key, buffer, m_IniFile.c_str()); - } - - delete [] buffer; -} - -/* -** Applies given DesktopWorkArea and DesktopWorkArea@n. -** -*/ -void Rainmeter::UpdateDesktopWorkArea(bool reset) -{ - bool changed = false; - - if (reset) - { - if (!m_OldDesktopWorkAreas.empty()) - { - int i = 1; - for (auto iter = m_OldDesktopWorkAreas.cbegin(); iter != m_OldDesktopWorkAreas.cend(); ++iter, ++i) - { - RECT r = (*iter); - - BOOL result = SystemParametersInfo(SPI_SETWORKAREA, 0, &r, 0); - - if (m_Debug) - { - std::wstring format = L"Resetting WorkArea@%i: L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)"; - if (!result) - { - format += L" => FAIL"; - } - LogDebugF(format.c_str(), i, r.left, r.top, r.right, r.bottom, r.right - r.left, r.bottom - r.top); - } - } - changed = true; - } - } - else - { - const size_t numOfMonitors = System::GetMonitorCount(); - const MultiMonitorInfo& monitorsInfo = System::GetMultiMonitorInfo(); - const std::vector& monitors = monitorsInfo.monitors; - - if (m_OldDesktopWorkAreas.empty()) - { - // Store old work areas for changing them back - for (size_t i = 0; i < numOfMonitors; ++i) - { - m_OldDesktopWorkAreas.push_back(monitors[i].work); - } - } - - if (m_Debug) - { - LogDebugF(L"DesktopWorkAreaType: %s", m_DesktopWorkAreaType ? L"Margin" : L"Default"); - } - - for (UINT i = 0; i <= numOfMonitors; ++i) - { - std::map::const_iterator it = m_DesktopWorkAreas.find(i); - if (it != m_DesktopWorkAreas.end()) - { - RECT r = (*it).second; - - // Move rect to correct offset - if (m_DesktopWorkAreaType) - { - RECT margin = r; - r = (i == 0) ? monitors[monitorsInfo.primary - 1].screen : monitors[i - 1].screen; - r.left += margin.left; - r.top += margin.top; - r.right -= margin.right; - r.bottom -= margin.bottom; - } - else - { - if (i != 0) - { - const RECT screenRect = monitors[i - 1].screen; - r.left += screenRect.left; - r.top += screenRect.top; - r.right += screenRect.left; - r.bottom += screenRect.top; - } - } - - BOOL result = SystemParametersInfo(SPI_SETWORKAREA, 0, &r, 0); - if (result) - { - changed = true; - } - - if (m_Debug) - { - std::wstring format = L"Applying DesktopWorkArea"; - if (i != 0) - { - WCHAR buffer[64]; - size_t len = _snwprintf_s(buffer, _TRUNCATE, L"@%i", i); - format.append(buffer, len); - } - format += L": L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)"; - if (!result) - { - format += L" => FAIL"; - } - LogDebugF(format.c_str(), r.left, r.top, r.right, r.bottom, r.right - r.left, r.bottom - r.top); - } - } - } - } - - if (changed && System::GetWindow()) - { - // Update System::MultiMonitorInfo for for work area variables - SendMessageTimeout(System::GetWindow(), WM_SETTINGCHANGE, SPI_SETWORKAREA, 0, SMTO_ABORTIFHUNG, 1000, nullptr); - } -} - -/* -** Reads the statistics from the ini-file -** -*/ -void Rainmeter::ReadStats() -{ - const WCHAR* statsFile = m_StatsFile.c_str(); - - // If m_StatsFile doesn't exist, create it and copy the stats section from m_IniFile - if (_waccess(statsFile, 0) == -1) - { - const WCHAR* iniFile = m_IniFile.c_str(); - WCHAR* tmpSz = new WCHAR[SHRT_MAX]; // Max size returned by GetPrivateProfileSection() - - if (GetPrivateProfileSection(L"Statistics", tmpSz, SHRT_MAX, iniFile) > 0) - { - WritePrivateProfileString(L"Statistics", nullptr, nullptr, iniFile); - } - else - { - tmpSz[0] = tmpSz[1] = L'\0'; - } - WritePrivateProfileSection(L"Statistics", tmpSz, statsFile); - - delete [] tmpSz; - } - - // Only Net measure has stats at the moment - MeasureNet::ReadStats(m_StatsFile, m_StatsDate); -} - -/* -** Writes the statistics to the ini-file. If bForce is false the stats are written only once per an appropriate interval. -** -*/ -void Rainmeter::WriteStats(bool bForce) -{ - static ULONGLONG lastWrite = 0; - - ULONGLONG ticks = System::GetTickCount64(); - - if (bForce || (lastWrite + INTERVAL_NETSTATS < ticks)) - { - lastWrite = ticks; - - // Only Net measure has stats at the moment - const WCHAR* statsFile = m_StatsFile.c_str(); - MeasureNet::WriteStats(statsFile, m_StatsDate); - - WritePrivateProfileString(nullptr, nullptr, nullptr, statsFile); - } -} - -/* -** Clears the statistics -** -*/ -void Rainmeter::ResetStats() -{ - // Set the stats-date string - tm* newtime; - time_t long_time; - time(&long_time); - newtime = localtime(&long_time); - m_StatsDate = _wasctime(newtime); - m_StatsDate.erase(m_StatsDate.size() - 1); - - // Only Net measure has stats at the moment - MeasureNet::ResetStats(); -} - -/* -** Wraps MessageBox(). Sets RTL flag if necessary. -** -*/ -int Rainmeter::ShowMessage(HWND parent, const WCHAR* text, UINT type) -{ - type |= MB_TOPMOST; - - if (*GetString(ID_STR_ISRTL) == L'1') - { - type |= MB_RTLREADING; - } - - return MessageBox(parent, text, APPNAME, type); -}; - -void Rainmeter::ShowLogFile() -{ - std::wstring logFile = L'"' + GetLogger().GetLogFilePath(); - logFile += L'"'; - - CommandHandler::RunFile(m_SkinEditor.c_str(), logFile.c_str()); -} - -void Rainmeter::SetDebug(bool debug) -{ - m_Debug = debug; - WritePrivateProfileString(L"Rainmeter", L"Debug", debug ? L"1" : L"0", m_IniFile.c_str()); -} - -void Rainmeter::SetDisableDragging(bool dragging) -{ - m_DisableDragging = dragging; - WritePrivateProfileString(L"Rainmeter", L"DisableDragging", dragging ? L"1" : L"0", m_IniFile.c_str()); -} - -void Rainmeter::SetDisableVersionCheck(bool check) -{ - m_DisableVersionCheck = check; - WritePrivateProfileString(L"Rainmeter", L"DisableVersionCheck", check ? L"1" : L"0" , m_IniFile.c_str()); -} - -void Rainmeter::TestSettingsFile(bool bDefaultIniLocation) -{ - const WCHAR* iniFile = m_IniFile.c_str(); - if (!System::IsFileWritable(iniFile)) - { - std::wstring error = GetString(ID_STR_SETTINGSNOTWRITABLE); - - if (!bDefaultIniLocation) - { - std::wstring strTarget = L"%APPDATA%\\Rainmeter\\"; - PathUtil::ExpandEnvironmentVariables(strTarget); - - error += GetFormattedString(ID_STR_SETTINGSMOVEFILE, iniFile, strTarget.c_str()); - } - else - { - error += GetFormattedString(ID_STR_SETTINGSREADONLY, iniFile); - } - - ShowMessage(nullptr, error.c_str(), MB_OK | MB_ICONERROR); - } -} - -void Rainmeter::SetUseD2D(bool enabled) -{ - m_UseD2D = enabled; - - // Save to Rainmeter.ini - WritePrivateProfileString(L"Rainmeter", L"UseD2D", enabled ? L"1" : L"0", m_IniFile.c_str()); - - RefreshAll(); -} +/* + Copyright (C) 2001 Kimmo Pekkola + + 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 "Rainmeter.h" +#include "TrayWindow.h" +#include "System.h" +#include "Error.h" +#include "DialogAbout.h" +#include "DialogManage.h" +#include "MeasureNet.h" +#include "MeasureCPU.h" +#include "MeterString.h" +#include "UpdateCheck.h" +#include "../Version.h" + +using namespace Gdiplus; + +enum TIMER +{ + TIMER_NETSTATS = 1 +}; +enum INTERVAL +{ + INTERVAL_NETSTATS = 120000 +}; + +/* +** Initializes Rainmeter. +** +*/ +int RainmeterMain(LPWSTR cmdLine) +{ + auto& rainmeter = GetRainmeter(); + int ret = rainmeter.Initialize(nullptr, nullptr); + if (ret == 0) + { + ret = rainmeter.MessagePump(); + } + rainmeter.Finalize(); + + return ret; +} + + +/* +** Constructor +** +*/ +Rainmeter::Rainmeter() : + m_TrayWindow(), + m_UseD2D(true), + m_Debug(false), + m_DisableVersionCheck(false), + m_NewVersion(false), + m_DesktopWorkAreaChanged(false), + m_DesktopWorkAreaType(false), + m_NormalStayDesktop(true), + m_DisableRDP(false), + m_DisableDragging(false), + m_CurrentParser(), + m_Window(), + m_Mutex(), + m_Instance(), + m_ResourceInstance(), + m_ResourceLCID(), + m_GDIplusToken(), + m_GlobalOptions() +{ + CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + + InitCommonControls(); + + // Initialize GDI+. + GdiplusStartupInput gdiplusStartupInput; + GdiplusStartup(&m_GDIplusToken, &gdiplusStartupInput, nullptr); +} + +/* +** Destructor +** +*/ +Rainmeter::~Rainmeter() +{ + CoUninitialize(); + + GdiplusShutdown(m_GDIplusToken); +} + +Rainmeter& GetRainmeter() +{ + static Rainmeter s_Rainmeter; + return s_Rainmeter; +} + +/* +** The main initialization function for the module. +** +*/ +int Rainmeter::Initialize(LPCWSTR iniPath, LPCWSTR layout) +{ + m_Instance = GetModuleHandle(L"Rainmeter"); + + WCHAR* buffer = new WCHAR[MAX_LINE_LENGTH]; + GetModuleFileName(m_Instance, buffer, MAX_LINE_LENGTH); + + // Remove the module's name from the path + WCHAR* pos = wcsrchr(buffer, L'\\'); + m_Path.assign(buffer, pos ? pos - buffer + 1 : 0); + m_Drive = PathUtil::GetVolume(m_Path); + + bool bDefaultIniLocation = false; + if (iniPath) + { + // The command line defines the location of Rainmeter.ini (or whatever it calls it). + std::wstring iniFile = iniPath; + PathUtil::ExpandEnvironmentVariables(iniFile); + + if (iniFile.empty() || PathUtil::IsSeparator(iniFile[iniFile.length() - 1])) + { + iniFile += L"Rainmeter.ini"; + } + else if (iniFile.length() <= 4 || _wcsicmp(iniFile.c_str() + (iniFile.length() - 4), L".ini") != 0) + { + iniFile += L"\\Rainmeter.ini"; + } + + if (!PathUtil::IsSeparator(iniFile[0]) && iniFile.find_first_of(L':') == std::wstring::npos) + { + // Make absolute path + iniFile.insert(0, m_Path); + } + + m_IniFile = iniFile; + bDefaultIniLocation = true; + } + else + { + m_IniFile = m_Path; + m_IniFile += L"Rainmeter.ini"; + + // If the ini file doesn't exist in the program folder store it to the %APPDATA% instead so that things work better in Vista/Win7 + if (_waccess(m_IniFile.c_str(), 0) == -1) + { + m_IniFile = L"%APPDATA%\\Rainmeter\\Rainmeter.ini"; + PathUtil::ExpandEnvironmentVariables(m_IniFile); + bDefaultIniLocation = true; + } + } + + WNDCLASS wc = {0}; + wc.lpfnWndProc = (WNDPROC)MainWndProc; + wc.hInstance = m_Instance; + wc.lpszClassName = RAINMETER_CLASS_NAME; + ATOM className = RegisterClass(&wc); + + m_Window = CreateWindowEx( + WS_EX_TOOLWINDOW, + MAKEINTATOM(className), + RAINMETER_WINDOW_NAME, + WS_POPUP | WS_DISABLED, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + nullptr, + nullptr, + m_Instance, + nullptr); + + if (!m_Window) return 1; + + Logger& logger = GetLogger(); + const WCHAR* iniFile = m_IniFile.c_str(); + + // Set file locations + { + m_SettingsPath = PathUtil::GetFolderFromFilePath(m_IniFile); + + size_t len = m_IniFile.length(); + if (len > 4 && _wcsicmp(iniFile + (len - 4), L".ini") == 0) + { + len -= 4; + } + + std::wstring logFile(m_IniFile, 0, len); + m_DataFile = m_StatsFile = logFile; + logFile += L".log"; + m_StatsFile += L".stats"; + m_DataFile += L".data"; + + logger.SetLogFilePath(logFile); + } + + // Create a default Rainmeter.ini file if needed + if (_waccess(iniFile, 0) == -1) + { + CreateOptionsFile(); + } + + bool dataFileCreated = false; + if (_waccess(m_DataFile.c_str(), 0) == -1) + { + dataFileCreated = true; + CreateDataFile(); + } + + // Reset log file + System::RemoveFile(logger.GetLogFilePath()); + + m_Debug = 0!=GetPrivateProfileInt(L"Rainmeter", L"Debug", 0, iniFile); + + const bool logging = GetPrivateProfileInt(L"Rainmeter", L"Logging", 0, iniFile) != 0; + logger.SetLogToFile(logging); + if (logging) + { + logger.StartLogFile(); + } + + // Determine the language resource to load + std::wstring resource = m_Path + L"Languages\\"; + if (GetPrivateProfileString(L"Rainmeter", L"Language", L"", buffer, MAX_LINE_LENGTH, iniFile) == 0) + { + // Use whatever the user selected for the installer + DWORD size = MAX_LINE_LENGTH; + HKEY hKey; + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Rainmeter", 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hKey) == ERROR_SUCCESS) + { + DWORD type = 0; + if (RegQueryValueEx(hKey, L"Language", nullptr, &type, (LPBYTE)buffer, (LPDWORD)&size) != ERROR_SUCCESS || + type != REG_SZ) + { + buffer[0] = L'\0'; + } + RegCloseKey(hKey); + } + } + if (buffer[0] != L'\0') + { + // Try selected language + m_ResourceLCID = wcstoul(buffer, nullptr, 10); + resource += buffer; + resource += L".dll"; + + m_ResourceInstance = LoadLibraryEx(resource.c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE); + } + if (!m_ResourceInstance) + { + // Try English + resource = m_Path; + resource += L"Languages\\1033.dll"; + m_ResourceInstance = LoadLibraryEx(resource.c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE); + m_ResourceLCID = 1033; + if (!m_ResourceInstance) + { + MessageBox(nullptr, L"Unable to load language library", APPNAME, MB_OK | MB_TOPMOST | MB_ICONERROR); + return 1; + } + } + + // Get skin folder path + size_t len = GetPrivateProfileString(L"Rainmeter", L"SkinPath", L"", buffer, MAX_LINE_LENGTH, iniFile); + if (len > 0 && + _waccess(buffer, 0) != -1) // Temporary fix + { + // Try Rainmeter.ini first + m_SkinPath.assign(buffer, len); + PathUtil::ExpandEnvironmentVariables(m_SkinPath); + PathUtil::AppendBacklashIfMissing(m_SkinPath); + } + else if (bDefaultIniLocation && + SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_MYDOCUMENTS, nullptr, SHGFP_TYPE_CURRENT, buffer))) + { + // Use My Documents/Rainmeter/Skins + m_SkinPath = buffer; + m_SkinPath += L"\\Rainmeter\\"; + CreateDirectory(m_SkinPath.c_str(), nullptr); + m_SkinPath += L"Skins\\"; + + WritePrivateProfileString(L"Rainmeter", L"SkinPath", m_SkinPath.c_str(), iniFile); + } + else + { + m_SkinPath = m_Path + L"Skins\\"; + } + + // Create user skins, layouts, addons, and plugins folders if needed + CreateComponentFolders(bDefaultIniLocation); + + delete [] buffer; + buffer = nullptr; + + LogNoticeF(L"Path: %s", m_Path.c_str()); + LogNoticeF(L"IniFile: %s", iniFile); + LogNoticeF(L"SkinPath: %s", m_SkinPath.c_str()); + + // Test that the Rainmeter.ini file is writable + TestSettingsFile(bDefaultIniLocation); + + System::Initialize(m_Instance); + + MeasureNet::InitializeStatic(); + MeasureCPU::InitializeStatic(); + MeterString::InitializeStatic(); + + // Tray must exist before skins are read + m_TrayWindow = new TrayWindow(); + m_TrayWindow->Initialize(); + + ReloadSettings(); + + if (m_SkinRegistry.IsEmpty()) + { + std::wstring error = GetFormattedString(ID_STR_NOAVAILABLESKINS, m_SkinPath.c_str()); + ShowMessage(nullptr, error.c_str(), MB_OK | MB_ICONERROR); + } + + ResetStats(); + ReadStats(); + + // Change the work area if necessary + if (m_DesktopWorkAreaChanged) + { + UpdateDesktopWorkArea(false); + } + + bool layoutLoaded = false; + if (layout) + { + std::vector args = CommandHandler::ParseString(layout); + layoutLoaded = (args.size() == 1 && LoadLayout(args[0])); + } + + if (!layoutLoaded) + { + ActivateActiveSkins(); + } + + if (dataFileCreated) + { + m_TrayWindow->ShowWelcomeNotification(); + } + else if (!m_DisableVersionCheck) + { + CheckUpdate(); + } + + return 0; // All is OK +} + +void Rainmeter::Finalize() +{ + KillTimer(m_Window, TIMER_NETSTATS); + + DeleteAllUnmanagedMeterWindows(); + DeleteAllMeterWindows(); + DeleteAllUnmanagedMeterWindows(); // Redelete unmanaged windows caused by OnCloseAction + + delete m_TrayWindow; + + System::Finalize(); + + MeasureNet::UpdateIFTable(); + MeasureNet::UpdateStats(); + WriteStats(true); + + MeasureNet::FinalizeStatic(); + MeasureCPU::FinalizeStatic(); + MeterString::FinalizeStatic(); + + // Change the work area back + if (m_DesktopWorkAreaChanged) + { + UpdateDesktopWorkArea(true); + } + + if (m_ResourceInstance) FreeLibrary(m_ResourceInstance); + if (m_Mutex) ReleaseMutex(m_Mutex); +} + +int Rainmeter::MessagePump() +{ + MSG msg; + BOOL ret; + + // Run the standard window message loop + while ((ret = GetMessage(&msg, nullptr, 0, 0)) != 0) + { + if (ret == -1) + { + break; + } + + if (!Dialog::HandleMessage(msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + return (int)msg.wParam; +} + +LRESULT CALLBACK Rainmeter::MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_DESTROY: + PostQuitMessage(0); + break; + + case WM_COPYDATA: + { + COPYDATASTRUCT* cds = (COPYDATASTRUCT*)lParam; + if (cds) + { + const WCHAR* data = (const WCHAR*)cds->lpData; + if (cds->dwData == 1 && (cds->cbData > 0)) + { + GetRainmeter().DelayedExecuteCommand(data); + } + } + } + break; + + case WM_TIMER: + if (wParam == TIMER_NETSTATS) + { + MeasureNet::UpdateIFTable(); + MeasureNet::UpdateStats(); + GetRainmeter().WriteStats(false); + } + break; + + case WM_RAINMETER_DELAYED_REFRESH_ALL: + GetRainmeter().RefreshAll(); + break; + + case WM_RAINMETER_DELAYED_EXECUTE: + if (lParam) + { + // Execute bang + WCHAR* bang = (WCHAR*)lParam; + GetRainmeter().ExecuteCommand(bang, nullptr); + free(bang); // _wcsdup() + } + break; + + case WM_RAINMETER_EXECUTE: + if (GetRainmeter().HasMeterWindow((MeterWindow*)wParam)) + { + GetRainmeter().ExecuteCommand((const WCHAR*)lParam, (MeterWindow*)wParam); + } + break; + + default: + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + return 0; +} + +void Rainmeter::SetNetworkStatisticsTimer() +{ + static bool set = SetTimer(m_Window, TIMER_NETSTATS, INTERVAL_NETSTATS, nullptr) != 0; +} + +void Rainmeter::CreateOptionsFile() +{ + CreateDirectory(m_SettingsPath.c_str(), nullptr); + + std::wstring defaultIni = GetDefaultLayoutPath(); + defaultIni += L"illustro default\\Rainmeter.ini"; + System::CopyFiles(defaultIni, m_IniFile); +} + +void Rainmeter::CreateDataFile() +{ + std::wstring tmpSz = m_SettingsPath + L"Plugins.ini"; + + const WCHAR* pluginsFile = tmpSz.c_str(); + const WCHAR* dataFile = m_DataFile.c_str(); + + if (_waccess(pluginsFile, 0) == 0) + { + MoveFile(pluginsFile, dataFile); + } + else + { + // Create empty file + HANDLE file = CreateFile(dataFile, GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file != INVALID_HANDLE_VALUE) + { + CloseHandle(file); + } + } +} + +void Rainmeter::CreateComponentFolders(bool defaultIniLocation) +{ + std::wstring path; + + if (CreateDirectory(m_SkinPath.c_str(), nullptr)) + { + // Folder just created, so copy default skins there + std::wstring from = GetDefaultSkinPath(); + from += L"*.*"; + System::CopyFiles(from, m_SkinPath); + } + else + { + path = m_SkinPath; + path += L"Backup"; + if (_waccess(path.c_str(), 0) != -1) + { + std::wstring newPath = m_SkinPath + L"@Backup"; + MoveFile(path.c_str(), newPath.c_str()); + } + } + + path = GetLayoutPath(); + if (_waccess(path.c_str(), 0) == -1) + { + std::wstring themesPath = m_SettingsPath + L"Themes"; + if (_waccess(themesPath.c_str(), 0) != -1) + { + // Migrate Themes into Layouts for backwards compatibility and rename + // Rainmeter.thm to Rainmeter.ini and RainThemes.bmp to Wallpaper.bmp. + MoveFile(themesPath.c_str(), path.c_str()); + + path += L'*'; // For FindFirstFile. + WIN32_FIND_DATA fd; + HANDLE hFind = FindFirstFile(path.c_str(), &fd); + path.pop_back(); // Remove '*'. + + if (hFind != INVALID_HANDLE_VALUE) + { + do + { + if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY && + PathUtil::IsDotOrDotDot(fd.cFileName)) + { + std::wstring layoutFolder = path + fd.cFileName; + layoutFolder += L'\\'; + + std::wstring file = layoutFolder + L"Rainmeter.thm"; + if (_waccess(file.c_str(), 0) != -1) + { + std::wstring newFile = layoutFolder + L"Rainmeter.ini"; + MoveFile(file.c_str(), newFile.c_str()); + } + + file = layoutFolder + L"RainThemes.bmp"; + if (_waccess(file.c_str(), 0) != -1) + { + std::wstring newFile = layoutFolder + L"Wallpaper.bmp"; + MoveFile(file.c_str(), newFile.c_str()); + } + } + } + while (FindNextFile(hFind, &fd)); + + FindClose(hFind); + } + } + else + { + std::wstring from = GetDefaultLayoutPath(); + if (_waccess(from.c_str(), 0) != -1) + { + System::CopyFiles(from, m_SettingsPath); + } + } + } + else + { + path += L"Backup"; + if (_waccess(path.c_str(), 0) != -1) + { + std::wstring newPath = GetLayoutPath(); + newPath += L"@Backup"; + MoveFile(path.c_str(), newPath.c_str()); + } + } + + if (defaultIniLocation) + { + path = GetUserPluginPath(); + if (_waccess(path.c_str(), 0) == -1) + { + std::wstring from = GetDefaultPluginPath(); + if (_waccess(from.c_str(), 0) != -1) + { + System::CopyFiles(from, m_SettingsPath); + } + } + + path = GetAddonPath(); + if (_waccess(path.c_str(), 0) == -1) + { + std::wstring from = GetDefaultAddonPath(); + if (_waccess(from.c_str(), 0) != -1) + { + System::CopyFiles(from, m_SettingsPath); + } + } + + path = m_SettingsPath; + path += L"Rainmeter.exe"; + const WCHAR* pathSz = path.c_str(); + if (_waccess(pathSz, 0) == -1) + { + // Create a hidden stub Rainmeter.exe into SettingsPath for old addon + // using relative path to Rainmeter.exe + std::wstring from = m_Path + L"Rainmeter.exe"; + System::CopyFiles(from, path); + + // Get rid of all resources from the stub executable + HANDLE stub = BeginUpdateResource(pathSz, TRUE); + + // Add the manifest of Rainmeter.dll to the stub + HRSRC manifest = FindResource(m_Instance, MAKEINTRESOURCE(2), RT_MANIFEST); + DWORD manifestSize = SizeofResource(m_Instance, manifest); + HGLOBAL manifestLoad = LoadResource(m_Instance, manifest); + void* manifestLoadData = LockResource(manifestLoad); + if (manifestLoadData) + { + LANGID langID = MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT); + UpdateResource(stub, RT_MANIFEST, MAKEINTRESOURCE(1), langID, manifestLoadData, manifestSize); + } + + EndUpdateResource(stub, FALSE); + SetFileAttributes(pathSz, FILE_ATTRIBUTE_HIDDEN); + } + } +} + +void Rainmeter::ReloadSettings() +{ + ScanForSkins(); + ScanForLayouts(); + ReadGeneralSettings(m_IniFile); +} + +void Rainmeter::EditSettings() +{ + std::wstring file = L'"' + m_IniFile; + file += L'"'; + CommandHandler::RunFile(m_SkinEditor.c_str(), file.c_str()); +} + +void Rainmeter::EditSkinFile(const std::wstring& name, const std::wstring& iniFile) +{ + std::wstring args = L'"' + m_SkinPath; + args += name; + args += L'\\'; + args += iniFile; + args += L'"'; + CommandHandler::RunFile(m_SkinEditor.c_str(), args.c_str()); +} + +void Rainmeter::OpenSkinFolder(const std::wstring& name) +{ + std::wstring folderPath = m_SkinPath + name; + CommandHandler::RunFile(folderPath.c_str()); +} + +void Rainmeter::ActivateActiveSkins() +{ + std::multimap::const_iterator iter = m_SkinOrders.begin(); + for ( ; iter != m_SkinOrders.end(); ++iter) + { + const SkinRegistry::Folder& skinFolder = m_SkinRegistry.GetFolder((*iter).second); + if (skinFolder.active > 0 && skinFolder.active <= (uint16_t)skinFolder.files.size()) + { + ActivateSkin((*iter).second, skinFolder.active - 1); + } + } +} + +/* +** Activates the skin, or, if it is already active, the next variant of the skin. Returns true +** if the skin was activated (or was already active). +*/ +bool Rainmeter::ActivateSkin(const std::wstring& folderPath) +{ + const int index = m_SkinRegistry.FindFolderIndex(folderPath); + if (index != -1) + { + const SkinRegistry::Folder& skinFolder = m_SkinRegistry.GetFolder(index); + if (!(skinFolder.active == 1 && skinFolder.files.size() == 1)) + { + // Activate the next index. + ActivateSkin( + index, (skinFolder.active < skinFolder.files.size()) ? skinFolder.active : 0); + } + + return true; + } + + return false; +} + +/* +** Activates the skin, or, if it is already active, the next variant of the skin. Returns true +** if the skin was activated (or was already active). +*/ +bool Rainmeter::ActivateSkin(const std::wstring& folderPath, const std::wstring& file) +{ + const SkinRegistry::Indexes indexes = m_SkinRegistry.FindIndexes(folderPath, file); + if (indexes.IsValid()) + { + ActivateSkin(indexes.folder, indexes.file); + return true; + } + + return false; +} + +void Rainmeter::ActivateSkin(int folderIndex, int fileIndex) +{ + if (folderIndex >= 0 && folderIndex < m_SkinRegistry.GetFolderCount() && + fileIndex >= 0 && fileIndex < m_SkinRegistry.GetFolder(folderIndex).files.size()) + { + auto& skinFolder = m_SkinRegistry.GetFolder(folderIndex); + const std::wstring& file = skinFolder.files[fileIndex]; + const WCHAR* fileSz = file.c_str(); + + std::wstring folderPath = m_SkinRegistry.GetFolderPath(folderIndex); + + // Verify that the skin is not already active + std::map::const_iterator iter = m_MeterWindows.find(folderPath); + if (iter != m_MeterWindows.end()) + { + if (wcscmp(((*iter).second)->GetFileName().c_str(), fileSz) == 0) + { + LogWarningF((*iter).second, L"!ActivateConfig: \"%s\" already active", folderPath.c_str()); + return; + } + else + { + // Deactivate the existing skin + DeactivateSkin((*iter).second, folderIndex); + } + } + + // Verify whether the ini-file exists + std::wstring skinIniPath = m_SkinPath + folderPath; + skinIniPath += L'\\'; + skinIniPath += file; + + if (_waccess(skinIniPath.c_str(), 0) == -1) + { + std::wstring message = GetFormattedString(ID_STR_UNABLETOACTIVATESKIN, folderPath.c_str(), fileSz); + ShowMessage(nullptr, message.c_str(), MB_OK | MB_ICONEXCLAMATION); + return; + } + + if (skinFolder.active != fileIndex + 1) + { + // Write only if changed. + skinFolder.active = fileIndex + 1; + WriteActive(folderPath, fileIndex); + } + + CreateMeterWindow(folderPath, file); + } +} + +void Rainmeter::DeactivateSkin(MeterWindow* meterWindow, int folderIndex, bool save) +{ + if (folderIndex >= 0 && folderIndex < m_SkinRegistry.GetFolderCount()) + { + m_SkinRegistry.GetFolder(folderIndex).active = 0; // Deactivate the skin + } + else if (folderIndex == -1 && meterWindow) + { + SkinRegistry::Folder* folder = m_SkinRegistry.FindFolder(meterWindow->GetFolderPath()); + if (folder) + { + folder->active = 0; + } + } + + if (meterWindow) + { + if (save) + { + // Disable the skin in the ini-file + WriteActive(meterWindow->GetFolderPath(), -1); + } + + meterWindow->Deactivate(); + } +} + +void Rainmeter::ToggleSkin(int folderIndex, int fileIndex) +{ + if (folderIndex >= 0 && folderIndex < m_SkinRegistry.GetFolderCount() && + fileIndex >= 0 && fileIndex < m_SkinRegistry.GetFolder(folderIndex).files.size()) + { + if (m_SkinRegistry.GetFolder(folderIndex).active == fileIndex + 1) + { + MeterWindow* meterWindow = GetMeterWindow(m_SkinRegistry.GetFolderPath(folderIndex)); + DeactivateSkin(meterWindow, folderIndex); + } + else + { + ActivateSkin(folderIndex, fileIndex); + } + } +} + +void Rainmeter::ToggleSkinWithID(UINT id) +{ + const SkinRegistry::Indexes indexes = m_SkinRegistry.FindIndexesForID(id); + if (indexes.IsValid()) + { + ToggleSkin(indexes.folder, indexes.file); + } +} + +void Rainmeter::SetSkinPath(const std::wstring& skinPath) +{ + WritePrivateProfileString(L"Rainmeter", L"SkinPath", skinPath.c_str(), m_IniFile.c_str()); +} + +void Rainmeter::SetSkinEditor(const std::wstring& path) +{ + if (!path.empty()) + { + m_SkinEditor = path; + WritePrivateProfileString(L"Rainmeter", L"ConfigEditor", path.c_str(), m_IniFile.c_str()); + } +} + +void Rainmeter::WriteActive(const std::wstring& folderPath, int fileIndex) +{ + WCHAR buffer[32]; + _itow_s(fileIndex + 1, buffer, 10); + WritePrivateProfileString(folderPath.c_str(), L"Active", buffer, m_IniFile.c_str()); +} + +void Rainmeter::CreateMeterWindow(const std::wstring& folderPath, const std::wstring& file) +{ + MeterWindow* mw = new MeterWindow(folderPath, file); + + // Note: May modify existing key + m_MeterWindows[folderPath] = mw; + + mw->Initialize(); + + DialogAbout::UpdateSkins(); + DialogManage::UpdateSkins(mw); +} + +void Rainmeter::DeleteAllMeterWindows() +{ + auto it = m_MeterWindows.cbegin(); + while (it != m_MeterWindows.cend()) + { + MeterWindow* mw = (*it).second; + m_MeterWindows.erase(it); // Remove before deleting MeterWindow + + DialogManage::UpdateSkins(mw, true); + delete mw; + + // Get next valid iterator (Fix for iterator invalidation caused by OnCloseAction) + it = m_MeterWindows.cbegin(); + } + + m_MeterWindows.clear(); + DialogAbout::UpdateSkins(); +} + +void Rainmeter::DeleteAllUnmanagedMeterWindows() +{ + for (auto it = m_UnmanagedMeterWindows.cbegin(); it != m_UnmanagedMeterWindows.cend(); ++it) + { + delete (*it); + } + + m_UnmanagedMeterWindows.clear(); +} + +/* +** Removes the skin from m_MeterWindows. The skin should delete itself. +** +*/ +void Rainmeter::RemoveMeterWindow(MeterWindow* meterWindow) +{ + for (auto it = m_MeterWindows.cbegin(); it != m_MeterWindows.cend(); ++it) + { + if ((*it).second == meterWindow) + { + m_MeterWindows.erase(it); + DialogManage::UpdateSkins(meterWindow, true); + DialogAbout::UpdateSkins(); + break; + } + } +} + +/* +** Adds the skin to m_UnmanagedMeterWindows. The skin should remove itself by calling RemoveUnmanagedMeterWindow(). +** +*/ +void Rainmeter::AddUnmanagedMeterWindow(MeterWindow* meterWindow) +{ + for (auto it = m_UnmanagedMeterWindows.cbegin(); it != m_UnmanagedMeterWindows.cend(); ++it) + { + if ((*it) == meterWindow) // already added + { + return; + } + } + + m_UnmanagedMeterWindows.push_back(meterWindow); +} + +void Rainmeter::RemoveUnmanagedMeterWindow(MeterWindow* meterWindow) +{ + for (auto it = m_UnmanagedMeterWindows.cbegin(); it != m_UnmanagedMeterWindows.cend(); ++it) + { + if ((*it) == meterWindow) + { + m_UnmanagedMeterWindows.erase(it); + break; + } + } +} + +bool Rainmeter::HasMeterWindow(const MeterWindow* meterWindow) const +{ + for (auto it = m_MeterWindows.begin(); it != m_MeterWindows.end(); ++it) + { + if ((*it).second == meterWindow) + { + return true; + } + } + + return false; +} + +MeterWindow* Rainmeter::GetMeterWindow(const std::wstring& folderPath) +{ + const WCHAR* folderSz = folderPath.c_str(); + std::map::const_iterator iter = m_MeterWindows.begin(); + for (; iter != m_MeterWindows.end(); ++iter) + { + if (_wcsicmp((*iter).first.c_str(), folderSz) == 0) + { + return (*iter).second; + } + } + + return nullptr; +} + +MeterWindow* Rainmeter::GetMeterWindowByINI(const std::wstring& ini_searching) +{ + if (_wcsnicmp(m_SkinPath.c_str(), ini_searching.c_str(), m_SkinPath.length()) == 0) + { + const std::wstring config_searching = ini_searching.substr(m_SkinPath.length()); + + std::map::const_iterator iter = m_MeterWindows.begin(); + for (; iter != m_MeterWindows.end(); ++iter) + { + std::wstring config_current = (*iter).second->GetFolderPath() + L'\\'; + config_current += (*iter).second->GetFileName(); + + if (_wcsicmp(config_current.c_str(), config_searching.c_str()) == 0) + { + return (*iter).second; + } + } + } + + return nullptr; +} + +MeterWindow* Rainmeter::GetMeterWindow(HWND hwnd) +{ + std::map::const_iterator iter = m_MeterWindows.begin(); + for (; iter != m_MeterWindows.end(); ++iter) + { + if ((*iter).second->GetWindow() == hwnd) + { + return (*iter).second; + } + } + + return nullptr; +} + +void Rainmeter::GetMeterWindowsByLoadOrder(std::multimap& windows, const std::wstring& group) +{ + std::map::const_iterator iter = m_MeterWindows.begin(); + for (; iter != m_MeterWindows.end(); ++iter) + { + MeterWindow* mw = (*iter).second; + if (mw && (group.empty() || mw->BelongsToGroup(group))) + { + windows.insert(std::pair(GetLoadOrder((*iter).first), mw)); + } + } +} + +void Rainmeter::SetLoadOrder(int folderIndex, int order) +{ + std::multimap::iterator iter = m_SkinOrders.begin(); + for ( ; iter != m_SkinOrders.end(); ++iter) + { + if ((*iter).second == folderIndex) // already exists + { + if ((*iter).first != order) + { + m_SkinOrders.erase(iter); + break; + } + else + { + return; + } + } + } + + m_SkinOrders.insert(std::pair(order, folderIndex)); +} + +int Rainmeter::GetLoadOrder(const std::wstring& folderPath) +{ + const int index = m_SkinRegistry.FindFolderIndex(folderPath); + if (index != -1) + { + std::multimap::const_iterator iter = m_SkinOrders.begin(); + for ( ; iter != m_SkinOrders.end(); ++iter) + { + if ((*iter).second == index) + { + return (*iter).first; + } + } + } + + // LoadOrder not specified + return 0; +} + +/* +** Scans all the subfolders and locates the ini-files. +*/ +void Rainmeter::ScanForSkins() +{ + m_SkinRegistry.Populate(m_SkinPath); + m_SkinOrders.clear(); +} + +/* +** Scans the given folder for layouts +*/ +void Rainmeter::ScanForLayouts() +{ + m_Layouts.clear(); + + WIN32_FIND_DATA fileData; // Data structure describes the file found + HANDLE hSearch; // Search handle returned by FindFirstFile + + // Scan for folders + std::wstring folders = GetLayoutPath(); + folders += L'*'; + + hSearch = FindFirstFileEx( + folders.c_str(), + (IsWindows7OrGreater()) ? FindExInfoBasic : FindExInfoStandard, + &fileData, + FindExSearchNameMatch, + nullptr, + 0); + + if (hSearch != INVALID_HANDLE_VALUE) + { + do + { + if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY && + !PathUtil::IsDotOrDotDot(fileData.cFileName)) + { + m_Layouts.push_back(fileData.cFileName); + } + } + while (FindNextFile(hSearch, &fileData)); + + FindClose(hSearch); + } + + DialogManage::UpdateLayouts(); +} + +void Rainmeter::ExecuteBang(const WCHAR* bang, std::vector& args, MeterWindow* meterWindow) +{ + m_CommandHandler.ExecuteBang(bang, args, meterWindow); +} + +/* +** Runs the given command or bang +** +*/ +void Rainmeter::ExecuteCommand(const WCHAR* command, MeterWindow* meterWindow, bool multi) +{ + m_CommandHandler.ExecuteCommand(command, meterWindow, multi); +} + +/* +** Executes command when current processing is done. +** +*/ +void Rainmeter::DelayedExecuteCommand(const WCHAR* command) +{ + WCHAR* bang = _wcsdup(command); + PostMessage(m_Window, WM_RAINMETER_DELAYED_EXECUTE, (WPARAM)nullptr, (LPARAM)bang); +} + +/* +** Reads the general settings from the Rainmeter.ini file +** +*/ +void Rainmeter::ReadGeneralSettings(const std::wstring& iniFile) +{ + WCHAR buffer[MAX_PATH]; + + // Clear old settings + m_DesktopWorkAreas.clear(); + + ConfigParser parser; + parser.Initialize(iniFile, nullptr, nullptr); + + m_UseD2D = parser.ReadBool(L"Rainmeter", L"UseD2D", true); + + m_Debug = parser.ReadBool(L"Rainmeter", L"Debug", false); + + // Read Logging settings + Logger& logger = GetLogger(); + const bool logging = parser.ReadBool(L"Rainmeter", L"Logging", false); + logger.SetLogToFile(logging); + if (logging) + { + logger.StartLogFile(); + } + + if (m_TrayWindow) + { + m_TrayWindow->ReadOptions(parser); + } + + m_GlobalOptions.netInSpeed = parser.ReadFloat(L"Rainmeter", L"NetInSpeed", 0.0); + m_GlobalOptions.netOutSpeed = parser.ReadFloat(L"Rainmeter", L"NetOutSpeed", 0.0); + + m_DisableDragging = parser.ReadBool(L"Rainmeter", L"DisableDragging", false); + m_DisableRDP = parser.ReadBool(L"Rainmeter", L"DisableRDP", false); + + m_SkinEditor = parser.ReadString(L"Rainmeter", L"ConfigEditor", L""); + if (m_SkinEditor.empty()) + { + // Get the program path associated with .ini files + DWORD cchOut = MAX_PATH; + HRESULT hr = AssocQueryString(ASSOCF_NOTRUNCATE, ASSOCSTR_EXECUTABLE, L".ini", L"open", buffer, &cchOut); + m_SkinEditor = (SUCCEEDED(hr) && cchOut > 0) ? buffer : L"Notepad"; + } + + if (m_Debug) + { + LogNoticeF(L"ConfigEditor: %s", m_SkinEditor.c_str()); + } + + m_TrayExecuteR = parser.ReadString(L"Rainmeter", L"TrayExecuteR", L"", false); + m_TrayExecuteM = parser.ReadString(L"Rainmeter", L"TrayExecuteM", L"", false); + m_TrayExecuteDR = parser.ReadString(L"Rainmeter", L"TrayExecuteDR", L"", false); + m_TrayExecuteDM = parser.ReadString(L"Rainmeter", L"TrayExecuteDM", L"", false); + + m_DisableVersionCheck = parser.ReadBool(L"Rainmeter", L"DisableVersionCheck", false); + + const std::wstring& area = parser.ReadString(L"Rainmeter", L"DesktopWorkArea", L""); + if (!area.empty()) + { + m_DesktopWorkAreas[0] = parser.ParseRECT(area.c_str()); + m_DesktopWorkAreaChanged = true; + } + + const size_t monitorCount = System::GetMonitorCount(); + for (UINT i = 1; i <= monitorCount; ++i) + { + _snwprintf_s(buffer, _TRUNCATE, L"DesktopWorkArea@%i", (int)i); + const std::wstring& area = parser.ReadString(L"Rainmeter", buffer, L""); + if (!area.empty()) + { + m_DesktopWorkAreas[i] = parser.ParseRECT(area.c_str()); + m_DesktopWorkAreaChanged = true; + } + } + + m_DesktopWorkAreaType = parser.ReadBool(L"Rainmeter", L"DesktopWorkAreaType", false); + + m_NormalStayDesktop = parser.ReadBool(L"Rainmeter", L"NormalStayDesktop", true); + + for (auto iter = parser.GetSections().cbegin(); iter != parser.GetSections().end(); ++iter) + { + const WCHAR* section = (*iter).c_str(); + + if (wcscmp(section, L"Rainmeter") == 0 || + wcscmp(section, L"TrayMeasure") == 0) + { + continue; + } + + const int index = m_SkinRegistry.FindFolderIndex(*iter); + if (index == -1) + { + continue; + } + + SkinRegistry::Folder& skinFolder = m_SkinRegistry.GetFolder(index); + + // Make sure there is a ini file available + int active = parser.ReadInt(section, L"Active", 0); + if (active > 0 && active <= (int)skinFolder.files.size()) + { + skinFolder.active = active; + } + + int order = parser.ReadInt(section, L"LoadOrder", 0); + SetLoadOrder(index, order); + } +} + +/* +** Refreshes all active meter windows. +** Note: This function calls MeterWindow::Refresh() directly for synchronization. Be careful about crash. +** +*/ +void Rainmeter::RefreshAll() +{ + // Read skins and settings + ReloadSettings(); + + // Change the work area if necessary + if (m_DesktopWorkAreaChanged) + { + UpdateDesktopWorkArea(false); + } + + // Make the sending order by using LoadOrder + std::multimap windows; + GetMeterWindowsByLoadOrder(windows); + + // Prepare the helper window + System::PrepareHelperWindow(); + + // Refresh all + std::multimap::const_iterator iter = windows.begin(); + for ( ; iter != windows.end(); ++iter) + { + MeterWindow* mw = (*iter).second; + if (mw) + { + // Verify whether the cached information is valid + const int index = m_SkinRegistry.FindFolderIndex(mw->GetFolderPath()); + if (index != -1) + { + SkinRegistry::Folder& skinFolder = m_SkinRegistry.GetFolder(index); + const WCHAR* skinIniFile = mw->GetFileName().c_str(); + + bool found = false; + for (int i = 0, isize = (int)skinFolder.files.size(); i < isize; ++i) + { + if (_wcsicmp(skinIniFile, skinFolder.files[i].c_str()) == 0) + { + found = true; + if (skinFolder.active != i + 1) + { + // Switch to new ini-file order + skinFolder.active = i + 1; + WriteActive(mw->GetFolderPath(), i); + } + break; + } + } + + if (!found) + { + const WCHAR* skinFolderPath = mw->GetFolderPath().c_str(); + std::wstring error = GetFormattedString(ID_STR_UNABLETOREFRESHSKIN, skinFolderPath, skinIniFile); + + DeactivateSkin(mw, index); + + ShowMessage(nullptr, error.c_str(), MB_OK | MB_ICONEXCLAMATION); + continue; + } + } + else + { + const WCHAR* skinFolderPath = mw->GetFolderPath().c_str(); + std::wstring error = GetFormattedString(ID_STR_UNABLETOREFRESHSKIN, skinFolderPath, L""); + + DeactivateSkin(mw, -2); // -2 = Force deactivate + + ShowMessage(nullptr, error.c_str(), MB_OK | MB_ICONEXCLAMATION); + continue; + } + + mw->Refresh(false, true); + } + } + + DialogAbout::UpdateSkins(); + DialogManage::UpdateSkins(nullptr); +} + +bool Rainmeter::LoadLayout(const std::wstring& name) +{ + // Replace Rainmeter.ini with layout + std::wstring layout = GetLayoutPath(); + layout += name; + std::wstring wallpaper = layout + L"\\Wallpaper.bmp"; + layout += L"\\Rainmeter.ini"; + + if (_waccess(layout.c_str(), 0) == -1) + { + return false; + } + + DeleteAllUnmanagedMeterWindows(); + DeleteAllMeterWindows(); + + std::wstring backup = GetLayoutPath(); + backup += L"@Backup"; + CreateDirectory(backup.c_str(), nullptr); + backup += L"\\Rainmeter.ini"; + + bool backupLayout = (_wcsicmp(name.c_str(), L"@Backup") == 0); + if (!backupLayout) + { + // Make a copy of current Rainmeter.ini + System::CopyFiles(m_IniFile, backup); + } + + System::CopyFiles(layout, m_IniFile); + + if (!backupLayout) + { + PreserveSetting(backup, L"SkinPath"); + PreserveSetting(backup, L"ConfigEditor"); + PreserveSetting(backup, L"LogViewer"); + PreserveSetting(backup, L"Logging"); + PreserveSetting(backup, L"DisableVersionCheck"); + PreserveSetting(backup, L"Language"); + PreserveSetting(backup, L"NormalStayDesktop"); + PreserveSetting(backup, L"TrayExecuteM", false); + PreserveSetting(backup, L"TrayExecuteR", false); + PreserveSetting(backup, L"TrayExecuteDM", false); + PreserveSetting(backup, L"TrayExecuteDR", false); + PreserveSetting(backup, L"UseD2D"); + + // Set wallpaper if it exists + if (_waccess(wallpaper.c_str(), 0) != -1) + { + SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, (void*)wallpaper.c_str(), SPIF_UPDATEINIFILE); + } + } + + ReloadSettings(); + + // Create meter windows for active skins + ActivateActiveSkins(); + + return true; +} + +void Rainmeter::PreserveSetting(const std::wstring& from, LPCTSTR key, bool replace) +{ + WCHAR* buffer = new WCHAR[MAX_LINE_LENGTH]; + + if ((replace || GetPrivateProfileString(L"Rainmeter", key, L"", buffer, 4, m_IniFile.c_str()) == 0) && + GetPrivateProfileString(L"Rainmeter", key, L"", buffer, MAX_LINE_LENGTH, from.c_str()) > 0) + { + WritePrivateProfileString(L"Rainmeter", key, buffer, m_IniFile.c_str()); + } + + delete [] buffer; +} + +/* +** Applies given DesktopWorkArea and DesktopWorkArea@n. +** +*/ +void Rainmeter::UpdateDesktopWorkArea(bool reset) +{ + bool changed = false; + + if (reset) + { + if (!m_OldDesktopWorkAreas.empty()) + { + int i = 1; + for (auto iter = m_OldDesktopWorkAreas.cbegin(); iter != m_OldDesktopWorkAreas.cend(); ++iter, ++i) + { + RECT r = (*iter); + + BOOL result = SystemParametersInfo(SPI_SETWORKAREA, 0, &r, 0); + + if (m_Debug) + { + std::wstring format = L"Resetting WorkArea@%i: L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)"; + if (!result) + { + format += L" => FAIL"; + } + LogDebugF(format.c_str(), i, r.left, r.top, r.right, r.bottom, r.right - r.left, r.bottom - r.top); + } + } + changed = true; + } + } + else + { + const size_t numOfMonitors = System::GetMonitorCount(); + const MultiMonitorInfo& monitorsInfo = System::GetMultiMonitorInfo(); + const std::vector& monitors = monitorsInfo.monitors; + + if (m_OldDesktopWorkAreas.empty()) + { + // Store old work areas for changing them back + for (size_t i = 0; i < numOfMonitors; ++i) + { + m_OldDesktopWorkAreas.push_back(monitors[i].work); + } + } + + if (m_Debug) + { + LogDebugF(L"DesktopWorkAreaType: %s", m_DesktopWorkAreaType ? L"Margin" : L"Default"); + } + + for (UINT i = 0; i <= numOfMonitors; ++i) + { + std::map::const_iterator it = m_DesktopWorkAreas.find(i); + if (it != m_DesktopWorkAreas.end()) + { + RECT r = (*it).second; + + // Move rect to correct offset + if (m_DesktopWorkAreaType) + { + RECT margin = r; + r = (i == 0) ? monitors[monitorsInfo.primary - 1].screen : monitors[i - 1].screen; + r.left += margin.left; + r.top += margin.top; + r.right -= margin.right; + r.bottom -= margin.bottom; + } + else + { + if (i != 0) + { + const RECT screenRect = monitors[i - 1].screen; + r.left += screenRect.left; + r.top += screenRect.top; + r.right += screenRect.left; + r.bottom += screenRect.top; + } + } + + BOOL result = SystemParametersInfo(SPI_SETWORKAREA, 0, &r, 0); + if (result) + { + changed = true; + } + + if (m_Debug) + { + std::wstring format = L"Applying DesktopWorkArea"; + if (i != 0) + { + WCHAR buffer[64]; + size_t len = _snwprintf_s(buffer, _TRUNCATE, L"@%i", i); + format.append(buffer, len); + } + format += L": L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)"; + if (!result) + { + format += L" => FAIL"; + } + LogDebugF(format.c_str(), r.left, r.top, r.right, r.bottom, r.right - r.left, r.bottom - r.top); + } + } + } + } + + if (changed && System::GetWindow()) + { + // Update System::MultiMonitorInfo for for work area variables + SendMessageTimeout(System::GetWindow(), WM_SETTINGCHANGE, SPI_SETWORKAREA, 0, SMTO_ABORTIFHUNG, 1000, nullptr); + } +} + +/* +** Reads the statistics from the ini-file +** +*/ +void Rainmeter::ReadStats() +{ + const WCHAR* statsFile = m_StatsFile.c_str(); + + // If m_StatsFile doesn't exist, create it and copy the stats section from m_IniFile + if (_waccess(statsFile, 0) == -1) + { + const WCHAR* iniFile = m_IniFile.c_str(); + WCHAR* tmpSz = new WCHAR[SHRT_MAX]; // Max size returned by GetPrivateProfileSection() + + if (GetPrivateProfileSection(L"Statistics", tmpSz, SHRT_MAX, iniFile) > 0) + { + WritePrivateProfileString(L"Statistics", nullptr, nullptr, iniFile); + } + else + { + tmpSz[0] = tmpSz[1] = L'\0'; + } + WritePrivateProfileSection(L"Statistics", tmpSz, statsFile); + + delete [] tmpSz; + } + + // Only Net measure has stats at the moment + MeasureNet::ReadStats(m_StatsFile, m_StatsDate); +} + +/* +** Writes the statistics to the ini-file. If bForce is false the stats are written only once per an appropriate interval. +** +*/ +void Rainmeter::WriteStats(bool bForce) +{ + static ULONGLONG lastWrite = 0; + + ULONGLONG ticks = System::GetTickCount64(); + + if (bForce || (lastWrite + INTERVAL_NETSTATS < ticks)) + { + lastWrite = ticks; + + // Only Net measure has stats at the moment + const WCHAR* statsFile = m_StatsFile.c_str(); + MeasureNet::WriteStats(statsFile, m_StatsDate); + + WritePrivateProfileString(nullptr, nullptr, nullptr, statsFile); + } +} + +/* +** Clears the statistics +** +*/ +void Rainmeter::ResetStats() +{ + // Set the stats-date string + tm* newtime; + time_t long_time; + time(&long_time); + newtime = localtime(&long_time); + m_StatsDate = _wasctime(newtime); + m_StatsDate.erase(m_StatsDate.size() - 1); + + // Only Net measure has stats at the moment + MeasureNet::ResetStats(); +} + +/* +** Wraps MessageBox(). Sets RTL flag if necessary. +** +*/ +int Rainmeter::ShowMessage(HWND parent, const WCHAR* text, UINT type) +{ + type |= MB_TOPMOST; + + if (*GetString(ID_STR_ISRTL) == L'1') + { + type |= MB_RTLREADING; + } + + return MessageBox(parent, text, APPNAME, type); +}; + +void Rainmeter::ShowLogFile() +{ + std::wstring logFile = L'"' + GetLogger().GetLogFilePath(); + logFile += L'"'; + + CommandHandler::RunFile(m_SkinEditor.c_str(), logFile.c_str()); +} + +void Rainmeter::SetDebug(bool debug) +{ + m_Debug = debug; + WritePrivateProfileString(L"Rainmeter", L"Debug", debug ? L"1" : L"0", m_IniFile.c_str()); +} + +void Rainmeter::SetDisableDragging(bool dragging) +{ + m_DisableDragging = dragging; + WritePrivateProfileString(L"Rainmeter", L"DisableDragging", dragging ? L"1" : L"0", m_IniFile.c_str()); +} + +void Rainmeter::SetDisableVersionCheck(bool check) +{ + m_DisableVersionCheck = check; + WritePrivateProfileString(L"Rainmeter", L"DisableVersionCheck", check ? L"1" : L"0" , m_IniFile.c_str()); +} + +void Rainmeter::TestSettingsFile(bool bDefaultIniLocation) +{ + const WCHAR* iniFile = m_IniFile.c_str(); + if (!System::IsFileWritable(iniFile)) + { + std::wstring error = GetString(ID_STR_SETTINGSNOTWRITABLE); + + if (!bDefaultIniLocation) + { + std::wstring strTarget = L"%APPDATA%\\Rainmeter\\"; + PathUtil::ExpandEnvironmentVariables(strTarget); + + error += GetFormattedString(ID_STR_SETTINGSMOVEFILE, iniFile, strTarget.c_str()); + } + else + { + error += GetFormattedString(ID_STR_SETTINGSREADONLY, iniFile); + } + + ShowMessage(nullptr, error.c_str(), MB_OK | MB_ICONERROR); + } +} + +void Rainmeter::SetUseD2D(bool enabled) +{ + m_UseD2D = enabled; + + // Save to Rainmeter.ini + WritePrivateProfileString(L"Rainmeter", L"UseD2D", enabled ? L"1" : L"0", m_IniFile.c_str()); + + RefreshAll(); +} diff --git a/Library/Rainmeter.h b/Library/Rainmeter.h index cd32cd13..c7bc0c9b 100644 --- a/Library/Rainmeter.h +++ b/Library/Rainmeter.h @@ -1,285 +1,286 @@ -/* - Copyright (C) 2001 Kimmo Pekkola - - 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. -*/ - -#ifndef __RAINMETER_H__ -#define __RAINMETER_H__ - -#include -#include -#include -#include -#include -#include "CommandHandler.h" -#include "ContextMenu.h" -#include "Logger.h" -#include "MeterWindow.h" -#include "SkinRegistry.h" - -#define MAX_LINE_LENGTH 4096 - -#define APPNAME L"Rainmeter" -#ifdef _WIN64 -#define APPBITS L"64-bit" -#else -#define APPBITS L"32-bit" -#endif -#define WIDEN2(x) L ## x -#define WIDEN(x) WIDEN2(x) -#define APPDATE WIDEN(__DATE__) - -#define RAINMETER_CLASS_NAME L"DummyRainWClass" -#define RAINMETER_WINDOW_NAME L"Rainmeter control window" - -#define WM_RAINMETER_DELAYED_REFRESH_ALL WM_APP + 0 -#define WM_RAINMETER_DELAYED_EXECUTE WM_APP + 1 -#define WM_RAINMETER_EXECUTE WM_APP + 2 - -struct GlobalOptions -{ - double netInSpeed; - double netOutSpeed; -}; - -class ConfigParser; -class TrayWindow; - -class Rainmeter -{ -public: - static Rainmeter& GetInstance(); - - int Initialize(LPCWSTR iniPath, LPCWSTR layout); - void Finalize(); - - int MessagePump(); - - void SetNetworkStatisticsTimer(); - - ConfigParser* GetCurrentParser() { return m_CurrentParser; } - void SetCurrentParser(ConfigParser* parser) { m_CurrentParser = parser; } - - TrayWindow* GetTrayWindow() { return m_TrayWindow; } - - bool HasMeterWindow(const MeterWindow* meterWindow) const; - - MeterWindow* GetMeterWindow(const std::wstring& folderPath); - MeterWindow* GetMeterWindowByINI(const std::wstring& ini_searching); - - MeterWindow* GetMeterWindow(HWND hwnd); - void GetMeterWindowsByLoadOrder(std::multimap& windows, const std::wstring& group = std::wstring()); - std::map& GetAllMeterWindows() { return m_MeterWindows; } - - const std::vector& GetAllLayouts() { return m_Layouts; } - - void RemoveMeterWindow(MeterWindow* meterWindow); - void AddUnmanagedMeterWindow(MeterWindow* meterWindow); - void RemoveUnmanagedMeterWindow(MeterWindow* meterWindow); - - bool ActivateSkin(const std::wstring& folderPath); - bool ActivateSkin(const std::wstring& folderPath, const std::wstring& file); - void ActivateSkin(int folderIndex, int fileIndex); - void DeactivateSkin(MeterWindow* meterWindow, int folderIndex, bool save = true); - void ToggleSkin(int folderIndex, int fileIndex); - void ToggleSkinWithID(UINT id); - - const std::wstring& GetPath() { return m_Path; } - const std::wstring& GetIniFile() { return m_IniFile; } - const std::wstring& GetDataFile() { return m_DataFile; } - const std::wstring& GetSettingsPath() { return m_SettingsPath; } - const std::wstring& GetSkinPath() { return m_SkinPath; } - void SetSkinPath(const std::wstring& skinPath); - std::wstring GetLayoutPath() { return m_SettingsPath + L"Layouts\\"; } - std::wstring GetPluginPath() { return m_Path + L"Plugins\\"; } - std::wstring GetUserPluginPath() { return m_SettingsPath + L"Plugins\\"; } - std::wstring GetAddonPath() { return m_SettingsPath + L"Addons\\"; } - - bool HasUserPluginPath() { return (_wcsicmp(m_Path.c_str(), m_SettingsPath.c_str()) != 0); } - - std::wstring GetDefaultSkinPath() { return m_Path + L"Defaults\\Skins\\"; } - std::wstring GetDefaultLayoutPath() { return m_Path + L"Defaults\\Layouts\\"; } - std::wstring GetDefaultPluginPath() { return m_Path + L"Defaults\\Plugins\\"; } - std::wstring GetDefaultAddonPath() { return m_Path + L"Defaults\\Addons\\"; } - - const std::wstring& GetDrive() { return m_Drive; } - - const std::wstring& GetSkinEditor() { return m_SkinEditor; } - void SetSkinEditor(const std::wstring& path); - const std::wstring& GetStatsDate() { return m_StatsDate; } - - HWND GetWindow() { return m_Window; } - - HINSTANCE GetModuleInstance() { return m_Instance; } - HINSTANCE GetResourceInstance() { return m_ResourceInstance; } - LCID GetResourceLCID() { return m_ResourceLCID; } - - bool GetUseD2D() const { return m_UseD2D; } - void SetUseD2D(bool enabled); - - bool GetDebug() { return m_Debug; } - - GlobalOptions& GetGlobalOptions() { return m_GlobalOptions; } - - void ReloadSettings(); - void EditSettings(); - void EditSkinFile(const std::wstring& name, const std::wstring& iniFile); - void OpenSkinFolder(const std::wstring& name = std::wstring()); - - void UpdateStats(); - void ReadStats(); - void WriteStats(bool bForce); - void ResetStats(); - - bool GetDisableVersionCheck() { return m_DisableVersionCheck; } - void SetDisableVersionCheck(bool check); - bool GetNewVersion() { return m_NewVersion; } - void SetNewVersion() { m_NewVersion = true; } - - void ShowLogFile(); - - bool GetDisableRDP() { return m_DisableRDP; } - bool IsRedrawable() { return (!GetDisableRDP() || !GetSystemMetrics(SM_REMOTESESSION)); } - - bool GetDisableDragging() { return m_DisableDragging; } - void SetDisableDragging(bool dragging); - - bool IsNormalStayDesktop() { return m_NormalStayDesktop; } - - void SetDebug(bool debug); - - int ShowMessage(HWND parent, const WCHAR* text, UINT type); - - bool IsMenuActive() { return m_ContextMenu.IsMenuActive(); } - void ShowContextMenu(POINT pos, MeterWindow* mw) { return m_ContextMenu.ShowMenu(pos, mw); } - void ShowSkinCustomContextMenu(POINT pos, MeterWindow* mw) { return m_ContextMenu.ShowSkinCustomMenu(pos, mw); } - - const std::wstring& GetTrayExecuteR() { return m_TrayExecuteR; } - const std::wstring& GetTrayExecuteM() { return m_TrayExecuteM; } - const std::wstring& GetTrayExecuteDR() { return m_TrayExecuteDR; } - const std::wstring& GetTrayExecuteDM() { return m_TrayExecuteDM; } - - void ExecuteBang(const WCHAR* bang, std::vector& args, MeterWindow* meterWindow); - void ExecuteCommand(const WCHAR* command, MeterWindow* meterWindow, bool multi = true); - void DelayedExecuteCommand(const WCHAR* command); - - void RefreshAll(); - - bool LoadLayout(const std::wstring& name); - void PreserveSetting(const std::wstring& from, LPCTSTR key, bool replace = true); - - friend class CommandHandler; - friend class ContextMenu; - friend class DialogManage; - -private: - Rainmeter(); - ~Rainmeter(); - - Rainmeter(const Rainmeter& other) = delete; - Rainmeter& operator=(Rainmeter other) = delete; - - static LRESULT CALLBACK MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - - void ActivateActiveSkins(); - void CreateMeterWindow(const std::wstring& folderPath, const std::wstring& file); - void DeleteAllMeterWindows(); - void DeleteAllUnmanagedMeterWindows(); - void WriteActive(const std::wstring& folderPath, int fileIndex); - void ScanForSkins(); - void ScanForLayouts(); - void ReadGeneralSettings(const std::wstring& iniFile); - void SetLoadOrder(int folderIndex, int order); - int GetLoadOrder(const std::wstring& folderPath); - void UpdateDesktopWorkArea(bool reset); - - void CreateOptionsFile(); - void CreateDataFile(); - void CreateComponentFolders(bool defaultIniLocation); - void TestSettingsFile(bool bDefaultIniLocation); - - TrayWindow* m_TrayWindow; - - std::multimap m_SkinOrders; - std::map m_MeterWindows; - std::list m_UnmanagedMeterWindows; - std::vector m_Layouts; - - std::wstring m_Path; - std::wstring m_IniFile; - std::wstring m_DataFile; - std::wstring m_StatsFile; - std::wstring m_SettingsPath; - std::wstring m_SkinPath; - - std::wstring m_Drive; - - std::wstring m_StatsDate; - - std::wstring m_TrayExecuteR; - std::wstring m_TrayExecuteM; - std::wstring m_TrayExecuteDR; - std::wstring m_TrayExecuteDM; - - bool m_UseD2D; - - bool m_Debug; - - bool m_DisableVersionCheck; - bool m_NewVersion; - - bool m_DesktopWorkAreaChanged; - bool m_DesktopWorkAreaType; - std::map m_DesktopWorkAreas; - std::vector m_OldDesktopWorkAreas; - - bool m_NormalStayDesktop; - - bool m_DisableRDP; - - bool m_DisableDragging; - - std::wstring m_SkinEditor; - - CommandHandler m_CommandHandler; - ContextMenu m_ContextMenu; - SkinRegistry m_SkinRegistry; - - ConfigParser* m_CurrentParser; - - HWND m_Window; - - HANDLE m_Mutex; - HINSTANCE m_Instance; - HMODULE m_ResourceInstance; - LCID m_ResourceLCID; - - ULONG_PTR m_GDIplusToken; - - GlobalOptions m_GlobalOptions; -}; - -#ifdef LIBRARY_EXPORTS -#define EXPORT_PLUGIN EXTERN_C __declspec(dllexport) -#else -#define EXPORT_PLUGIN EXTERN_C __declspec(dllimport) -#endif - -EXPORT_PLUGIN int RainmeterMain(LPWSTR cmdLine); -EXPORT_PLUGIN void* Rainmeter_Initialize(); -EXPORT_PLUGIN void Rainmeter_Finalize(void* ptr); - -#endif +/* + Copyright (C) 2001 Kimmo Pekkola + + 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. +*/ + +#ifndef __RAINMETER_H__ +#define __RAINMETER_H__ + +#include +#include +#include +#include +#include +#include "CommandHandler.h" +#include "ContextMenu.h" +#include "Logger.h" +#include "MeterWindow.h" +#include "SkinRegistry.h" + +#define MAX_LINE_LENGTH 4096 + +#define APPNAME L"Rainmeter" +#ifdef _WIN64 +#define APPBITS L"64-bit" +#else +#define APPBITS L"32-bit" +#endif +#define WIDEN2(x) L ## x +#define WIDEN(x) WIDEN2(x) +#define APPDATE WIDEN(__DATE__) + +#define RAINMETER_CLASS_NAME L"DummyRainWClass" +#define RAINMETER_WINDOW_NAME L"Rainmeter control window" + +#define WM_RAINMETER_DELAYED_REFRESH_ALL WM_APP + 0 +#define WM_RAINMETER_DELAYED_EXECUTE WM_APP + 1 +#define WM_RAINMETER_EXECUTE WM_APP + 2 + +struct GlobalOptions +{ + double netInSpeed; + double netOutSpeed; +}; + +class ConfigParser; +class TrayWindow; + +class Rainmeter +{ +public: + static Rainmeter& GetInstance(); + + int Initialize(LPCWSTR iniPath, LPCWSTR layout); + void Finalize(); + + int MessagePump(); + + void SetNetworkStatisticsTimer(); + + ConfigParser* GetCurrentParser() { return m_CurrentParser; } + void SetCurrentParser(ConfigParser* parser) { m_CurrentParser = parser; } + + TrayWindow* GetTrayWindow() { return m_TrayWindow; } + + bool HasMeterWindow(const MeterWindow* meterWindow) const; + + MeterWindow* GetMeterWindow(const std::wstring& folderPath); + MeterWindow* GetMeterWindowByINI(const std::wstring& ini_searching); + + MeterWindow* GetMeterWindow(HWND hwnd); + void GetMeterWindowsByLoadOrder(std::multimap& windows, const std::wstring& group = std::wstring()); + std::map& GetAllMeterWindows() { return m_MeterWindows; } + + const std::vector& GetAllLayouts() { return m_Layouts; } + + void RemoveMeterWindow(MeterWindow* meterWindow); + void AddUnmanagedMeterWindow(MeterWindow* meterWindow); + void RemoveUnmanagedMeterWindow(MeterWindow* meterWindow); + + bool ActivateSkin(const std::wstring& folderPath); + bool ActivateSkin(const std::wstring& folderPath, const std::wstring& file); + void ActivateSkin(int folderIndex, int fileIndex); + void DeactivateSkin(MeterWindow* meterWindow, int folderIndex, bool save = true); + void ToggleSkin(int folderIndex, int fileIndex); + void ToggleSkinWithID(UINT id); + + const std::wstring& GetPath() { return m_Path; } + const std::wstring& GetIniFile() { return m_IniFile; } + const std::wstring& GetDataFile() { return m_DataFile; } + const std::wstring& GetSettingsPath() { return m_SettingsPath; } + const std::wstring& GetSkinPath() { return m_SkinPath; } + void SetSkinPath(const std::wstring& skinPath); + std::wstring GetLayoutPath() { return m_SettingsPath + L"Layouts\\"; } + std::wstring GetPluginPath() { return m_Path + L"Plugins\\"; } + std::wstring GetUserPluginPath() { return m_SettingsPath + L"Plugins\\"; } + std::wstring GetAddonPath() { return m_SettingsPath + L"Addons\\"; } + + bool HasUserPluginPath() { return (_wcsicmp(m_Path.c_str(), m_SettingsPath.c_str()) != 0); } + + std::wstring GetDefaultSkinPath() { return m_Path + L"Defaults\\Skins\\"; } + std::wstring GetDefaultLayoutPath() { return m_Path + L"Defaults\\Layouts\\"; } + std::wstring GetDefaultPluginPath() { return m_Path + L"Defaults\\Plugins\\"; } + std::wstring GetDefaultAddonPath() { return m_Path + L"Defaults\\Addons\\"; } + + const std::wstring& GetDrive() { return m_Drive; } + + const std::wstring& GetSkinEditor() { return m_SkinEditor; } + void SetSkinEditor(const std::wstring& path); + const std::wstring& GetStatsDate() { return m_StatsDate; } + + HWND GetWindow() { return m_Window; } + + HINSTANCE GetModuleInstance() { return m_Instance; } + HINSTANCE GetResourceInstance() { return m_ResourceInstance; } + LCID GetResourceLCID() { return m_ResourceLCID; } + + bool GetUseD2D() const { return m_UseD2D; } + void SetUseD2D(bool enabled); + + bool GetDebug() { return m_Debug; } + + GlobalOptions& GetGlobalOptions() { return m_GlobalOptions; } + + void ReloadSettings(); + void EditSettings(); + void EditSkinFile(const std::wstring& name, const std::wstring& iniFile); + void OpenSkinFolder(const std::wstring& name = std::wstring()); + + void UpdateStats(); + void ReadStats(); + void WriteStats(bool bForce); + void ResetStats(); + + bool GetDisableVersionCheck() { return m_DisableVersionCheck; } + void SetDisableVersionCheck(bool check); + bool GetNewVersion() { return m_NewVersion; } + void SetNewVersion() { m_NewVersion = true; } + + void ShowLogFile(); + + bool GetDisableRDP() { return m_DisableRDP; } + bool IsRedrawable() { return (!GetDisableRDP() || !GetSystemMetrics(SM_REMOTESESSION)); } + + bool GetDisableDragging() { return m_DisableDragging; } + void SetDisableDragging(bool dragging); + + bool IsNormalStayDesktop() { return m_NormalStayDesktop; } + + void SetDebug(bool debug); + + int ShowMessage(HWND parent, const WCHAR* text, UINT type); + + bool IsMenuActive() { return m_ContextMenu.IsMenuActive(); } + void ShowContextMenu(POINT pos, MeterWindow* mw) { return m_ContextMenu.ShowMenu(pos, mw); } + void ShowSkinCustomContextMenu(POINT pos, MeterWindow* mw) { return m_ContextMenu.ShowSkinCustomMenu(pos, mw); } + + const std::wstring& GetTrayExecuteR() { return m_TrayExecuteR; } + const std::wstring& GetTrayExecuteM() { return m_TrayExecuteM; } + const std::wstring& GetTrayExecuteDR() { return m_TrayExecuteDR; } + const std::wstring& GetTrayExecuteDM() { return m_TrayExecuteDM; } + + void ExecuteBang(const WCHAR* bang, std::vector& args, MeterWindow* meterWindow); + void ExecuteCommand(const WCHAR* command, MeterWindow* meterWindow, bool multi = true); + void DelayedExecuteCommand(const WCHAR* command); + + void RefreshAll(); + + bool LoadLayout(const std::wstring& name); + void PreserveSetting(const std::wstring& from, LPCTSTR key, bool replace = true); + + friend class CommandHandler; + friend class ContextMenu; + friend class DialogManage; + +private: + Rainmeter(); + ~Rainmeter(); + + Rainmeter(const Rainmeter& other) = delete; + Rainmeter& operator=(Rainmeter other) = delete; + + static LRESULT CALLBACK MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + + void ActivateActiveSkins(); + void CreateMeterWindow(const std::wstring& folderPath, const std::wstring& file); + void DeleteAllMeterWindows(); + void DeleteAllUnmanagedMeterWindows(); + void WriteActive(const std::wstring& folderPath, int fileIndex); + void ScanForSkins(); + void ScanForLayouts(); + void ReadGeneralSettings(const std::wstring& iniFile); + void SetLoadOrder(int folderIndex, int order); + int GetLoadOrder(const std::wstring& folderPath); + void UpdateDesktopWorkArea(bool reset); + + void CreateOptionsFile(); + void CreateDataFile(); + void CreateComponentFolders(bool defaultIniLocation); + void TestSettingsFile(bool bDefaultIniLocation); + + TrayWindow* m_TrayWindow; + + std::multimap m_SkinOrders; + std::map m_MeterWindows; + std::list m_UnmanagedMeterWindows; + std::vector m_Layouts; + + std::wstring m_Path; + std::wstring m_IniFile; + std::wstring m_DataFile; + std::wstring m_StatsFile; + std::wstring m_SettingsPath; + std::wstring m_SkinPath; + + std::wstring m_Drive; + + std::wstring m_StatsDate; + + std::wstring m_TrayExecuteR; + std::wstring m_TrayExecuteM; + std::wstring m_TrayExecuteDR; + std::wstring m_TrayExecuteDM; + + bool m_UseD2D; + + bool m_Debug; + + bool m_DisableVersionCheck; + bool m_NewVersion; + + bool m_DesktopWorkAreaChanged; + bool m_DesktopWorkAreaType; + std::map m_DesktopWorkAreas; + std::vector m_OldDesktopWorkAreas; + + bool m_NormalStayDesktop; + + bool m_DisableRDP; + + bool m_DisableDragging; + + std::wstring m_SkinEditor; + + CommandHandler m_CommandHandler; + ContextMenu m_ContextMenu; + SkinRegistry m_SkinRegistry; + + ConfigParser* m_CurrentParser; + + HWND m_Window; + + HANDLE m_Mutex; + HINSTANCE m_Instance; + HMODULE m_ResourceInstance; + LCID m_ResourceLCID; + + ULONG_PTR m_GDIplusToken; + + GlobalOptions m_GlobalOptions; +}; + +// Convenience function. +inline Rainmeter& GetRainmeter() { return Rainmeter::GetInstance(); } + +#ifdef LIBRARY_EXPORTS +#define EXPORT_PLUGIN EXTERN_C +#else +#define EXPORT_PLUGIN EXTERN_C __declspec(dllimport) +#endif + +EXPORT_PLUGIN int RainmeterMain(LPWSTR cmdLine); + +#endif diff --git a/Library/Section.cpp b/Library/Section.cpp index 10373ea0..6b7a879c 100644 --- a/Library/Section.cpp +++ b/Library/Section.cpp @@ -1,87 +1,87 @@ -/* - Copyright (C) 2013 spx - - 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 "Section.h" -#include "ConfigParser.h" -#include "Rainmeter.h" - -/* -** The constructor -** -*/ -Section::Section(MeterWindow* meterWindow, const WCHAR* name) : m_MeterWindow(meterWindow), m_Name(name), - m_DynamicVariables(false), - m_UpdateDivider(1), - m_UpdateCounter(1) -{ -} - -/* -** The destructor -** -*/ -Section::~Section() -{ -} - -/* -** Read the common options specified in the ini file. The inherited classes must -** call this base implementation if they overwrite this method. -** -*/ -void Section::ReadOptions(ConfigParser& parser, const WCHAR* section) -{ - int updateDivider = parser.ReadInt(section, L"UpdateDivider", 1); - if (updateDivider != m_UpdateDivider) - { - m_UpdateCounter = m_UpdateDivider = updateDivider; - } - - m_DynamicVariables = parser.ReadBool(section, L"DynamicVariables", false); - - m_OnUpdateAction = parser.ReadString(section, L"OnUpdateAction", L"", false); - - const std::wstring& group = parser.ReadString(section, L"Group", L""); - InitializeGroup(group); -} - -/* -** Updates the counter value -** -*/ -bool Section::UpdateCounter() -{ - ++m_UpdateCounter; - if (m_UpdateCounter < m_UpdateDivider) return false; - m_UpdateCounter = 0; - - return true; -} - -/* -** Execute OnUpdateAction if action is set -** -*/ -void Section::DoUpdateAction() -{ - if (!m_OnUpdateAction.empty()) - { - Rainmeter::GetInstance().ExecuteCommand(m_OnUpdateAction.c_str(), m_MeterWindow); - } -} +/* + Copyright (C) 2013 spx + + 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 "Section.h" +#include "ConfigParser.h" +#include "Rainmeter.h" + +/* +** The constructor +** +*/ +Section::Section(MeterWindow* meterWindow, const WCHAR* name) : m_MeterWindow(meterWindow), m_Name(name), + m_DynamicVariables(false), + m_UpdateDivider(1), + m_UpdateCounter(1) +{ +} + +/* +** The destructor +** +*/ +Section::~Section() +{ +} + +/* +** Read the common options specified in the ini file. The inherited classes must +** call this base implementation if they overwrite this method. +** +*/ +void Section::ReadOptions(ConfigParser& parser, const WCHAR* section) +{ + int updateDivider = parser.ReadInt(section, L"UpdateDivider", 1); + if (updateDivider != m_UpdateDivider) + { + m_UpdateCounter = m_UpdateDivider = updateDivider; + } + + m_DynamicVariables = parser.ReadBool(section, L"DynamicVariables", false); + + m_OnUpdateAction = parser.ReadString(section, L"OnUpdateAction", L"", false); + + const std::wstring& group = parser.ReadString(section, L"Group", L""); + InitializeGroup(group); +} + +/* +** Updates the counter value +** +*/ +bool Section::UpdateCounter() +{ + ++m_UpdateCounter; + if (m_UpdateCounter < m_UpdateDivider) return false; + m_UpdateCounter = 0; + + return true; +} + +/* +** Execute OnUpdateAction if action is set +** +*/ +void Section::DoUpdateAction() +{ + if (!m_OnUpdateAction.empty()) + { + GetRainmeter().ExecuteCommand(m_OnUpdateAction.c_str(), m_MeterWindow); + } +} diff --git a/Library/System.cpp b/Library/System.cpp index 3dab216e..3b4341c4 100644 --- a/Library/System.cpp +++ b/Library/System.cpp @@ -1,1406 +1,1406 @@ -/* - Copyright (C) 2010 spx - - 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 "System.h" -#include "Litestep.h" -#include "Rainmeter.h" -#include "MeterWindow.h" -#include "MeasureNet.h" -#include "Error.h" -#include "../Common/PathUtil.h" - -using namespace Gdiplus; - -#define DEBUG_VERBOSE (0) // Set 1 if you need verbose logging. - -#define ZPOS_FLAGS (SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOSENDCHANGING) - -enum TIMER -{ - TIMER_SHOWDESKTOP = 1, - TIMER_RESUME = 2 -}; -enum INTERVAL -{ - INTERVAL_SHOWDESKTOP = 250, - INTERVAL_RESTOREWINDOWS = 100, - INTERVAL_RESUME = 1000 -}; - -MultiMonitorInfo System::c_Monitors = { 0 }; - -HWND System::c_Window = nullptr; -HWND System::c_HelperWindow = nullptr; - -HWINEVENTHOOK System::c_WinEventHook = nullptr; - -bool System::c_ShowDesktop = false; - -std::wstring System::c_WorkingDirectory; - -std::vector System::c_IniFileMappings; - -/* -** Creates a helper window to detect changes in the system. -** -*/ -void System::Initialize(HINSTANCE instance) -{ - WNDCLASS wc = {0}; - wc.lpfnWndProc = (WNDPROC)WndProc; - wc.hInstance = instance; - wc.lpszClassName = L"RainmeterSystem"; - ATOM className = RegisterClass(&wc); - - c_Window = CreateWindowEx( - WS_EX_TOOLWINDOW, - MAKEINTATOM(className), - L"System", - WS_POPUP | WS_DISABLED, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - nullptr, - nullptr, - instance, - nullptr); - - c_HelperWindow = CreateWindowEx( - WS_EX_TOOLWINDOW, - MAKEINTATOM(className), - L"PositioningHelper", - WS_POPUP | WS_DISABLED, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - nullptr, - nullptr, - instance, - nullptr); - - SetWindowPos(c_Window, HWND_BOTTOM, 0, 0, 0, 0, ZPOS_FLAGS); - SetWindowPos(c_HelperWindow, HWND_BOTTOM, 0, 0, 0, 0, ZPOS_FLAGS); - - c_Monitors.monitors.reserve(4); - SetMultiMonitorInfo(); - - WCHAR directory[MAX_PATH]; - DWORD len = GetCurrentDirectory(MAX_PATH, directory); - c_WorkingDirectory.assign(directory, len <= MAX_PATH ? len : 0); - - c_WinEventHook = SetWinEventHook( - EVENT_SYSTEM_FOREGROUND, - EVENT_SYSTEM_FOREGROUND, - nullptr, - MyWinEventProc, - 0, - 0, - WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); - - SetTimer(c_Window, TIMER_SHOWDESKTOP, INTERVAL_SHOWDESKTOP, nullptr); -} - -/* -** Destroys a window. -** -*/ -void System::Finalize() -{ - KillTimer(c_Window, TIMER_SHOWDESKTOP); - KillTimer(c_Window, TIMER_RESUME); - - if (c_WinEventHook) - { - UnhookWinEvent(c_WinEventHook); - c_WinEventHook = nullptr; - } - - if (c_HelperWindow) - { - DestroyWindow(c_HelperWindow); - c_HelperWindow = nullptr; - } - - if (c_Window) - { - DestroyWindow(c_Window); - c_Window = nullptr; - } -} - -/* -** Retrieves the multi-monitor information. -** -*/ -BOOL CALLBACK MyInfoEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) -{ - MultiMonitorInfo* m = (MultiMonitorInfo*)dwData; - - MONITORINFOEX info; - info.cbSize = sizeof(MONITORINFOEX); - GetMonitorInfo(hMonitor, &info); - - if (Rainmeter::GetInstance().GetDebug()) - { - LogDebug(info.szDevice); - LogDebugF(L" Flags : %s(0x%08X)", (info.dwFlags & MONITORINFOF_PRIMARY) ? L"PRIMARY " : L"", info.dwFlags); - LogDebugF(L" Handle : 0x%p", hMonitor); - LogDebugF(L" ScrArea : L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", - lprcMonitor->left, lprcMonitor->top, lprcMonitor->right, lprcMonitor->bottom, - lprcMonitor->right - lprcMonitor->left, lprcMonitor->bottom - lprcMonitor->top); - LogDebugF(L" WorkArea : L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", - info.rcWork.left, info.rcWork.top, info.rcWork.right, info.rcWork.bottom, - info.rcWork.right - info.rcWork.left, info.rcWork.bottom - info.rcWork.top); - } - if (m == nullptr) return TRUE; - - if (m->useEnumDisplayDevices) - { - for (auto iter = m->monitors.begin(); iter != m->monitors.end(); ++iter) - { - if ((*iter).handle == nullptr && _wcsicmp(info.szDevice, (*iter).deviceName.c_str()) == 0) - { - (*iter).handle = hMonitor; - (*iter).screen = *lprcMonitor; - (*iter).work = info.rcWork; - break; - } - } - } - else // use only EnumDisplayMonitors - { - MonitorInfo monitor; - monitor.active = true; - - monitor.handle = hMonitor; - monitor.screen = *lprcMonitor; - monitor.work = info.rcWork; - - monitor.deviceName = info.szDevice; // E.g. "\\.\DISPLAY1" - - // Get the monitor name (E.g. "Generic Non-PnP Monitor") - DISPLAY_DEVICE ddm = {sizeof(DISPLAY_DEVICE)}; - DWORD dwMon = 0; - while (EnumDisplayDevices(info.szDevice, dwMon++, &ddm, 0)) - { - if (ddm.StateFlags & DISPLAY_DEVICE_ACTIVE && ddm.StateFlags & DISPLAY_DEVICE_ATTACHED) - { - monitor.monitorName.assign(ddm.DeviceString, wcsnlen(ddm.DeviceString, _countof(ddm.DeviceString))); - break; - } - } - - m->monitors.push_back(monitor); - - if (info.dwFlags & MONITORINFOF_PRIMARY) - { - // It's primary monitor! - m->primary = (int)m->monitors.size(); - } - } - - return TRUE; -} - -/* -** Returns the number of monitors. -** -*/ -size_t System::GetMonitorCount() -{ - if (c_Monitors.monitors.empty()) - { - SetMultiMonitorInfo(); - } - return c_Monitors.monitors.size(); -} - -/* -** Sets the multi-monitor information. -** -*/ -void System::SetMultiMonitorInfo() -{ - std::vector& monitors = c_Monitors.monitors; - bool logging = Rainmeter::GetInstance().GetDebug(); - - c_Monitors.vsT = GetSystemMetrics(SM_YVIRTUALSCREEN); - c_Monitors.vsL = GetSystemMetrics(SM_XVIRTUALSCREEN); - c_Monitors.vsH = GetSystemMetrics(SM_CYVIRTUALSCREEN); - c_Monitors.vsW = GetSystemMetrics(SM_CXVIRTUALSCREEN); - - c_Monitors.primary = 1; // If primary screen is not found, 1st screen is assumed as primary screen. - - c_Monitors.useEnumDisplayDevices = true; - c_Monitors.useEnumDisplayMonitors = false; - - if (logging) - { - LogDebug(L"------------------------------"); - LogDebug(L"* EnumDisplayDevices / EnumDisplaySettings API"); - } - - DISPLAY_DEVICE dd = {sizeof(DISPLAY_DEVICE)}; - - if (EnumDisplayDevices(nullptr, 0, &dd, 0)) - { - DWORD dwDevice = 0; - - do - { - std::wstring msg; - - std::wstring deviceName(dd.DeviceName, wcsnlen(dd.DeviceName, _countof(dd.DeviceName))); - std::wstring deviceString; - - if (logging) - { - deviceString.assign(dd.DeviceString, wcsnlen(dd.DeviceString, _countof(dd.DeviceString))); - - LogDebug(deviceName.c_str()); - - if (dd.StateFlags & DISPLAY_DEVICE_ACTIVE) - { - msg += L"ACTIVE "; - } - if (dd.StateFlags & DISPLAY_DEVICE_MULTI_DRIVER) - { - msg += L"MULTI "; - } - if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) - { - msg += L"PRIMARY "; - } - if (dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) - { - msg += L"MIRROR "; - } - if (dd.StateFlags & DISPLAY_DEVICE_VGA_COMPATIBLE) - { - msg += L"VGA "; - } - if (dd.StateFlags & DISPLAY_DEVICE_REMOVABLE) - { - msg += L"REMOVABLE "; - } - if (dd.StateFlags & DISPLAY_DEVICE_MODESPRUNED) - { - msg += L"PRUNED "; - } - if (dd.StateFlags & DISPLAY_DEVICE_REMOTE) - { - msg += L"REMOTE "; - } - if (dd.StateFlags & DISPLAY_DEVICE_DISCONNECT) - { - msg += L"DISCONNECT "; - } - } - - if ((dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) == 0) - { - MonitorInfo monitor = {0}; - - monitor.handle = nullptr; - monitor.deviceName = deviceName; // E.g. "\\.\DISPLAY1" - - // Get the monitor name (E.g. "Generic Non-PnP Monitor") - DISPLAY_DEVICE ddm = {sizeof(DISPLAY_DEVICE)}; - DWORD dwMon = 0; - while (EnumDisplayDevices(deviceName.c_str(), dwMon++, &ddm, 0)) - { - if (ddm.StateFlags & DISPLAY_DEVICE_ACTIVE && ddm.StateFlags & DISPLAY_DEVICE_ATTACHED) - { - monitor.monitorName.assign(ddm.DeviceString, wcsnlen(ddm.DeviceString, _countof(ddm.DeviceString))); - - if (logging) - { - LogDebugF(L" Name : %s", monitor.monitorName.c_str()); - } - break; - } - } - - if (logging) - { - LogDebugF(L" Adapter : %s", deviceString.c_str()); - LogDebugF(L" Flags : %s(0x%08X)", msg.c_str(), dd.StateFlags); - } - - if (dd.StateFlags & DISPLAY_DEVICE_ACTIVE) - { - monitor.active = true; - - DEVMODE dm = {0}; - dm.dmSize = sizeof(DEVMODE); - - if (EnumDisplaySettings(deviceName.c_str(), ENUM_CURRENT_SETTINGS, &dm)) - { - POINT pos = {dm.dmPosition.x, dm.dmPosition.y}; - monitor.handle = MonitorFromPoint(pos, MONITOR_DEFAULTTONULL); - - if (logging) - { - LogDebugF(L" Handle : 0x%p", monitor.handle); - } - } - - if (monitor.handle != nullptr) - { - MONITORINFO info = {sizeof(MONITORINFO)}; - GetMonitorInfo(monitor.handle, &info); - - monitor.screen = info.rcMonitor; - monitor.work = info.rcWork; - - if (logging) - { - LogDebugF(L" ScrArea : L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", - info.rcMonitor.left, info.rcMonitor.top, info.rcMonitor.right, info.rcMonitor.bottom, - info.rcMonitor.right - info.rcMonitor.left, info.rcMonitor.bottom - info.rcMonitor.top); - LogDebugF(L" WorkArea : L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", - info.rcWork.left, info.rcWork.top, info.rcWork.right, info.rcWork.bottom, - info.rcWork.right - info.rcWork.left, info.rcWork.bottom - info.rcWork.top); - } - } - else // monitor not found - { - c_Monitors.useEnumDisplayMonitors = true; - } - } - else - { - monitor.active = false; - } - - monitors.push_back(monitor); - - if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) - { - // It's primary monitor! - c_Monitors.primary = (int)monitors.size(); - } - } - else - { - if (logging) - { - LogDebugF(L" Adapter : %s", deviceString.c_str()); - LogDebugF(L" Flags : %s(0x%08X)", msg.c_str(), dd.StateFlags); - } - } - ++dwDevice; - } - while (EnumDisplayDevices(nullptr, dwDevice, &dd, 0)); - } - - if (monitors.empty()) // Failed to enumerate the non-mirroring monitors - { - LogWarning(L"Failed to enumerate the non-mirroring monitors. Only EnumDisplayMonitors is used instead."); - c_Monitors.useEnumDisplayDevices = false; - c_Monitors.useEnumDisplayMonitors = true; - } - - if (logging) - { - LogDebug(L"------------------------------"); - LogDebug(L"* EnumDisplayMonitors API"); - } - - if (c_Monitors.useEnumDisplayMonitors) - { - EnumDisplayMonitors(nullptr, nullptr, MyInfoEnumProc, (LPARAM)(&c_Monitors)); - - if (monitors.empty()) // Failed to enumerate the monitors - { - LogWarning(L"Failed to enumerate monitors. Using dummy monitor info."); - c_Monitors.useEnumDisplayMonitors = false; - - MonitorInfo monitor; - monitor.active = true; - - POINT pos = {0, 0}; - monitor.handle = MonitorFromPoint(pos, MONITOR_DEFAULTTOPRIMARY); - monitor.screen.left = 0; - monitor.screen.top = 0; - monitor.screen.right = GetSystemMetrics(SM_CXSCREEN); - monitor.screen.bottom = GetSystemMetrics(SM_CYSCREEN); - if (SystemParametersInfo(SPI_GETWORKAREA, 0, &(monitor.work), 0) == 0) // failed - { - monitor.work = monitor.screen; - } - - monitor.deviceName = L"DUMMY"; - - monitors.push_back(monitor); - - c_Monitors.primary = 1; - } - } - else - { - if (logging) - { - EnumDisplayMonitors(nullptr, nullptr, MyInfoEnumProc, (LPARAM)nullptr); // Only logging - } - } - - if (logging) - { - LogDebug(L"------------------------------"); - - std::wstring method = L"* METHOD: "; - if (c_Monitors.useEnumDisplayDevices) - { - method += L"EnumDisplayDevices + "; - method += c_Monitors.useEnumDisplayMonitors ? L"EnumDisplayMonitors Mode" : L"EnumDisplaySettings Mode"; - } - else - { - method += c_Monitors.useEnumDisplayMonitors ? L"EnumDisplayMonitors Mode" : L"Dummy Mode"; - } - LogDebug(method.c_str()); - - LogDebugF(L"* MONITORS: Count=%i, Primary=@%i", (int)monitors.size(), c_Monitors.primary); - LogDebug(L"@0: Virtual screen"); - LogDebugF(L" L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", - c_Monitors.vsL, c_Monitors.vsT, c_Monitors.vsL + c_Monitors.vsW, c_Monitors.vsT + c_Monitors.vsH, - c_Monitors.vsW, c_Monitors.vsH); - - int i = 1; - for (auto iter = monitors.cbegin(); iter != monitors.cend(); ++iter, ++i) - { - if ((*iter).active) - { - LogDebugF(L"@%i: %s (active), MonitorName: %s", i, (*iter).deviceName.c_str(), (*iter).monitorName.c_str()); - LogDebugF(L" L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", - (*iter).screen.left, (*iter).screen.top, (*iter).screen.right, (*iter).screen.bottom, - (*iter).screen.right - (*iter).screen.left, (*iter).screen.bottom - (*iter).screen.top); - } - else - { - LogDebugF(L"@%i: %s (inactive), MonitorName: %s", i, (*iter).deviceName.c_str(), (*iter).monitorName.c_str()); - } - } - LogDebug(L"------------------------------"); - } -} - -/* -** Updates the workarea information. -** -*/ -void System::UpdateWorkareaInfo() -{ - std::vector& monitors = c_Monitors.monitors; - - if (monitors.empty()) - { - SetMultiMonitorInfo(); - return; - } - - int i = 1; - for (auto iter = monitors.begin(); iter != monitors.end(); ++iter, ++i) - { - if ((*iter).active && (*iter).handle != nullptr) - { - MONITORINFO info = {sizeof(MONITORINFO)}; - GetMonitorInfo((*iter).handle, &info); - - (*iter).work = info.rcWork; - - if (Rainmeter::GetInstance().GetDebug()) - { - LogDebugF(L"WorkArea@%i : L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", - i, - info.rcWork.left, info.rcWork.top, info.rcWork.right, info.rcWork.bottom, - info.rcWork.right - info.rcWork.left, info.rcWork.bottom - info.rcWork.top); - } - } - } -} - -/* -** Finds the Default Shell's window. -** -*/ -HWND System::GetDefaultShellWindow() -{ - static HWND c_ShellW = nullptr; // cache - HWND ShellW = GetShellWindow(); - - if (ShellW) - { - if (ShellW == c_ShellW) - { - return ShellW; - } - else - { - const int classLen = _countof(L"Progman") + 1; - WCHAR className[classLen]; - if (!(GetClassName(ShellW, className, classLen) > 0 && - wcscmp(className, L"Progman") == 0)) - { - ShellW = nullptr; - } - } - } - - c_ShellW = ShellW; - return ShellW; -} - -/* -** Finds the WorkerW window. -** If the WorkerW window is not active, returns nullptr. -** -*/ -HWND System::GetWorkerW() -{ - static HWND c_DefView = nullptr; // cache - HWND ShellW = GetDefaultShellWindow(); - if (!ShellW) return nullptr; // Default Shell (Explorer) not running - - if (c_DefView && IsWindow(c_DefView)) - { - HWND parent = GetAncestor(c_DefView, GA_PARENT); - if (parent) - { - if (parent == ShellW) - { - return nullptr; - } - else - { - const int classLen = _countof(L"WorkerW") + 1; - WCHAR className[classLen]; - if (GetClassName(parent, className, classLen) > 0 && - wcscmp(className, L"WorkerW") == 0) - { - return parent; - } - } - } - } - - HWND WorkerW = nullptr, DefView = FindWindowEx(ShellW, nullptr, L"SHELLDLL_DefView", L""); - if (DefView == nullptr) - { - while (WorkerW = FindWindowEx(nullptr, WorkerW, L"WorkerW", L"")) - { - if (IsWindowVisible(WorkerW) && - BelongToSameProcess(ShellW, WorkerW) && - (DefView = FindWindowEx(WorkerW, nullptr, L"SHELLDLL_DefView", L""))) - { - break; - } - } - } - - c_DefView = DefView; - return WorkerW; -} - -/* -** Returns the first window whose position is not ZPOSITION_ONDESKTOP, -** ZPOSITION_BOTTOM, or ZPOSITION_NORMAL. -** -*/ -HWND System::GetBackmostTopWindow() -{ - HWND winPos = c_HelperWindow; - - // Skip all ZPOSITION_ONDESKTOP, ZPOSITION_BOTTOM, and ZPOSITION_NORMAL windows - while (winPos = ::GetNextWindow(winPos, GW_HWNDPREV)) - { - MeterWindow* wnd = Rainmeter::GetInstance().GetMeterWindow(winPos); - if (!wnd || - (wnd->GetWindowZPosition() != ZPOSITION_NORMAL && - wnd->GetWindowZPosition() != ZPOSITION_ONDESKTOP && - wnd->GetWindowZPosition() != ZPOSITION_ONBOTTOM)) - { - break; - } - } - - return winPos; -} - -/* -** Checks whether the given windows belong to the same process. -** -*/ -bool System::BelongToSameProcess(HWND hwndA, HWND hwndB) -{ - DWORD procAId = 0, procBId = 0; - - GetWindowThreadProcessId(hwndA, &procAId); - GetWindowThreadProcessId(hwndB, &procBId); - - return (procAId == procBId); -} - -/* -** Retrieves the Rainmeter's meter windows in Z-order. -** -*/ -BOOL CALLBACK MyEnumWindowsProc(HWND hwnd, LPARAM lParam) -{ - bool logging = Rainmeter::GetInstance().GetDebug() && DEBUG_VERBOSE; - const int classLen = _countof(METERWINDOW_CLASS_NAME) + (DEBUG_VERBOSE ? 32 : 1); - WCHAR className[classLen]; - MeterWindow* Window; - WCHAR flag; - - if (GetClassName(hwnd, className, classLen) > 0 && - wcscmp(className, METERWINDOW_CLASS_NAME) == 0 && - (Window = Rainmeter::GetInstance().GetMeterWindow(hwnd))) - { - ZPOSITION zPos = Window->GetWindowZPosition(); - if (zPos == ZPOSITION_ONDESKTOP || - (zPos == ZPOSITION_NORMAL && Rainmeter::GetInstance().IsNormalStayDesktop()) || - zPos == ZPOSITION_ONBOTTOM) - { - if (lParam) - { - ((std::vector*)lParam)->push_back(Window); - } - - if (logging) flag = L'+'; - } - else - { - if (logging) flag = L'-'; - } - - if (logging) - { - LogDebugF(L"%c [%c] 0x%p : %s (Name: \"%s\", zPos=%i)", - flag, IsWindowVisible(hwnd) ? L'V' : L'H', hwnd, className, Window->GetFolderPath().c_str(), (int)zPos); - } - } - else - { - if (logging) - { - flag = (hwnd == System::GetHelperWindow()) ? L'o' : ' '; - LogDebugF(L"%c [%c] 0x%p : %s", flag, IsWindowVisible(hwnd) ? L'V' : L'H', hwnd, className); - } - } - - return TRUE; -} - -/* -** Arranges the meter window in Z-order. -** -*/ -void System::ChangeZPosInOrder() -{ - bool logging = Rainmeter::GetInstance().GetDebug() && DEBUG_VERBOSE; - std::vector windowsInZOrder; - - if (logging) LogDebug(L"1: ----- BEFORE -----"); - - // Retrieve the Rainmeter's meter windows in Z-order - EnumWindows(MyEnumWindowsProc, (LPARAM)(&windowsInZOrder)); - - auto resetZPos = [&](ZPOSITION zpos) - { - // Reset ZPos in Z-order (Bottom) - std::vector::const_iterator iter = windowsInZOrder.begin(); - for ( ; iter != windowsInZOrder.end(); ++iter) - { - if ((*iter)->GetWindowZPosition() == zpos) - { - (*iter)->ChangeZPos(zpos); // reset - } - } - }; - - if (Rainmeter::GetInstance().IsNormalStayDesktop()) - { - resetZPos(ZPOSITION_NORMAL); - } - - if (!c_ShowDesktop) - { - resetZPos(ZPOSITION_ONBOTTOM); - } - - resetZPos(ZPOSITION_ONDESKTOP); - - if (logging) - { - LogDebug(L"2: ----- AFTER -----"); - - // Log all windows in Z-order - EnumWindows(MyEnumWindowsProc, (LPARAM)nullptr); - } -} - -/* -** Moves the helper window to the reference position. -** -*/ -void System::PrepareHelperWindow(HWND WorkerW) -{ - bool logging = Rainmeter::GetInstance().GetDebug() && DEBUG_VERBOSE; - - SetWindowPos(c_Window, HWND_BOTTOM, 0, 0, 0, 0, ZPOS_FLAGS); // always on bottom - - if (c_ShowDesktop && WorkerW) - { - // Set WS_EX_TOPMOST flag - SetWindowPos(c_HelperWindow, HWND_TOPMOST, 0, 0, 0, 0, ZPOS_FLAGS); - - // Find the "backmost" topmost window - HWND hwnd = WorkerW; - while (hwnd = ::GetNextWindow(hwnd, GW_HWNDPREV)) - { - if (GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST) - { - WCHAR className[64], windowText[64]; - - if (logging) - { - GetClassName(hwnd, className, 64); - GetWindowText(hwnd, windowText, 64); - - SetLastError(ERROR_SUCCESS); - } - - // Insert the helper window after the found window - if (0 != SetWindowPos(c_HelperWindow, hwnd, 0, 0, 0, 0, ZPOS_FLAGS)) - { - if (logging) - { - LogDebugF(L"System: HelperWindow: hwnd=0x%p (WorkerW=0x%p), hwndInsertAfter=0x%p (\"%s\" %s) - %s", - c_HelperWindow, WorkerW, hwnd, windowText, className, (GetWindowLongPtr(c_HelperWindow, GWL_EXSTYLE) & WS_EX_TOPMOST) ? L"TOPMOST" : L"NORMAL"); - } - return; - } - - if (logging) - { - DWORD err = GetLastError(); - LogDebugF(L"System: HelperWindow: hwnd=0x%p (WorkerW=0x%p), hwndInsertAfter=0x%p (\"%s\" %s) - FAILED (ErrorCode=0x%08X)", - c_HelperWindow, WorkerW, hwnd, windowText, className, err); - } - } - } - - if (logging) - { - LogDebugF(L"System: HelperWindow: hwnd=0x%p (WorkerW=0x%p), hwndInsertAfter=HWND_TOPMOST - %s", - c_HelperWindow, WorkerW, (GetWindowLongPtr(c_HelperWindow, GWL_EXSTYLE) & WS_EX_TOPMOST) ? L"TOPMOST" : L"NORMAL"); - } - } - else - { - // Insert the helper window to the bottom - SetWindowPos(c_HelperWindow, HWND_BOTTOM, 0, 0, 0, 0, ZPOS_FLAGS); - - if (logging) - { - LogDebugF(L"System: HelperWindow: hwnd=0x%p (WorkerW=0x%p), hwndInsertAfter=HWND_BOTTOM - %s", - c_HelperWindow, WorkerW, (GetWindowLongPtr(c_HelperWindow, GWL_EXSTYLE) & WS_EX_TOPMOST) ? L"TOPMOST" : L"NORMAL"); - } - } -} - -/* -** Changes the "Show Desktop" state. -** -*/ -bool System::CheckDesktopState(HWND WorkerW) -{ - HWND hwnd = nullptr; - - if (WorkerW && IsWindowVisible(WorkerW)) - { - hwnd = FindWindowEx(nullptr, WorkerW, L"RainmeterSystem", L"System"); - } - - bool stateChanged = (hwnd && !c_ShowDesktop) || (!hwnd && c_ShowDesktop); - - if (stateChanged) - { - c_ShowDesktop = !c_ShowDesktop; - - if (Rainmeter::GetInstance().GetDebug()) - { - LogDebugF(L"System: \"Show %s\" has been detected.", - c_ShowDesktop ? L"desktop" : L"open windows"); - } - - PrepareHelperWindow(WorkerW); - - ChangeZPosInOrder(); - - if (c_ShowDesktop) - { - SetTimer(c_Window, TIMER_SHOWDESKTOP, INTERVAL_RESTOREWINDOWS, nullptr); - } - else - { - SetTimer(c_Window, TIMER_SHOWDESKTOP, INTERVAL_SHOWDESKTOP, nullptr); - } - } - - return stateChanged; -} - -/* -** The event hook procedure -** -*/ -void CALLBACK System::MyWinEventProc(HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime) -{ - if (event == EVENT_SYSTEM_FOREGROUND) - { - if (!c_ShowDesktop) - { - const int classLen = _countof(L"WorkerW") + 1; - WCHAR className[classLen]; - if (GetClassName(hwnd, className, classLen) > 0 && - wcscmp(className, L"WorkerW") == 0 && - BelongToSameProcess(GetDefaultShellWindow(), hwnd)) - { - const int max = 5; - int loop = 0; - while (loop < max && FindWindowEx(hwnd, nullptr, L"SHELLDLL_DefView", L"") == nullptr) - { - Sleep(2); // Wait for 2-16 ms before retrying - ++loop; - } - - if (loop < max) - { - loop = 0; - while (loop < max && !CheckDesktopState(hwnd)) - { - Sleep(2); // Wait for 2-16 ms before retrying - ++loop; - } - } - } - } - } -} - -/* -** The window procedure -** -*/ -LRESULT CALLBACK System::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - if (hWnd != c_Window) - { - if (uMsg == WM_WINDOWPOSCHANGING) - { - ((LPWINDOWPOS)lParam)->flags |= SWP_NOZORDER; - return 0; - } - return DefWindowProc(hWnd, uMsg, wParam, lParam); - } - - switch (uMsg) - { - case WM_WINDOWPOSCHANGING: - ((LPWINDOWPOS)lParam)->flags |= SWP_NOZORDER; - break; - - case WM_TIMER: - switch (wParam) - { - case TIMER_SHOWDESKTOP: - if (wParam == TIMER_SHOWDESKTOP) - { - CheckDesktopState(GetWorkerW()); - } - break; - - case TIMER_RESUME: - KillTimer(hWnd, TIMER_RESUME); - if (Rainmeter::GetInstance().IsRedrawable()) - { - std::map::const_iterator iter = Rainmeter::GetInstance().GetAllMeterWindows().begin(); - for ( ; iter != Rainmeter::GetInstance().GetAllMeterWindows().end(); ++iter) - { - (*iter).second->RedrawWindow(); - } - } - break; - } - break; - - case WM_DISPLAYCHANGE: - LogNotice(L"System: Display settings changed"); - ClearMultiMonitorInfo(); - ConfigParser::ClearMultiMonitorVariables(); - case WM_SETTINGCHANGE: - if (uMsg == WM_DISPLAYCHANGE || (/*uMsg == WM_SETTINGCHANGE &&*/ wParam == SPI_SETWORKAREA)) - { - if (uMsg == WM_SETTINGCHANGE) // SPI_SETWORKAREA - { - LogNotice(L"System: Work area changed"); - UpdateWorkareaInfo(); - ConfigParser::UpdateWorkareaVariables(); - } - - // Deliver WM_DISPLAYCHANGE / WM_SETTINGCHANGE message to all meter windows - std::map::const_iterator iter = Rainmeter::GetInstance().GetAllMeterWindows().begin(); - for ( ; iter != Rainmeter::GetInstance().GetAllMeterWindows().end(); ++iter) - { - PostMessage((*iter).second->GetWindow(), WM_METERWINDOW_DELAYED_MOVE, (WPARAM)uMsg, (LPARAM)0); - } - } - break; - - case WM_POWERBROADCAST: - if (wParam == PBT_APMRESUMESUSPEND) - { - // Deliver PBT_APMRESUMESUSPEND event to all meter windows - SetTimer(hWnd, TIMER_RESUME, INTERVAL_RESUME, nullptr); - } - return TRUE; - - default: - return DefWindowProc(hWnd, uMsg, wParam, lParam); - } - - return 0; -} - -/* -** Retrieves the number of milliseconds that have elapsed since the system was started. -** In XP, returns the predictive value due to the 32bit limitation. -** -*/ -ULONGLONG System::GetTickCount64() -{ - static auto s_GetTickCount64 = - (decltype(GetTickCount64)*)GetProcAddress(GetModuleHandle(L"kernel32"), "GetTickCount64"); - - if (s_GetTickCount64) - { - return s_GetTickCount64(); - } - else - { - static ULONGLONG lastTicks = 0; - ULONGLONG ticks = GetTickCount(); - while (ticks < lastTicks) ticks += 0x100000000; - lastTicks = ticks; - return ticks; - } -} - -/* -** Gets the cursor position in last message retrieved by GetMessage(). -** -*/ -POINT System::GetCursorPosition() -{ - DWORD pos = GetMessagePos(); - POINT pt = { GET_X_LPARAM(pos), GET_Y_LPARAM(pos) }; - return pt; -} - -/* -** Checks if file is writable. -** -*/ -bool System::IsFileWritable(LPCWSTR file) -{ - HANDLE hFile = CreateFile(file, GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr); - if (hFile == INVALID_HANDLE_VALUE) - { - return false; - } - - CloseHandle(hFile); - return true; -} - -/* -** This function is a wrapper function for LoadLibrary(). -** -** Avoids loading a DLL from current directory. -** -*/ -HMODULE System::RmLoadLibrary(LPCWSTR lpLibFileName, DWORD* dwError) -{ - // Remove current directory from DLL search path - SetDllDirectory(L""); - - SetLastError(ERROR_SUCCESS); - HMODULE hLib = LoadLibrary(lpLibFileName); - - if (dwError) - { - *dwError = GetLastError(); - } - - return hLib; -} - -/* -** Resets working directory to default. -** -*/ -void System::ResetWorkingDirectory() -{ - WCHAR directory[MAX_PATH] = {0}; - GetCurrentDirectory(MAX_PATH, directory); - - const WCHAR* workDir = c_WorkingDirectory.c_str(); - if (_wcsicmp(directory, workDir) != 0) - { - SetCurrentDirectory(workDir); - } -} - -/* -** Initializes a critical section object by using InitializeCriticalSectionEx function with CRITICAL_SECTION_NO_DEBUG_INFO flag. -** For more details: http://stackoverflow.com/questions/804848/critical-sections-leaking-memory-on-vista-win2008/ -** -*/ -void System::InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection) -{ - static auto s_InitializeCriticalSectionEx = IsWindowsVistaOrGreater() ? - (decltype(InitializeCriticalSectionEx)*)GetProcAddress(GetModuleHandle(L"kernel32"), "InitializeCriticalSectionEx") : nullptr; - - if (s_InitializeCriticalSectionEx && - s_InitializeCriticalSectionEx(lpCriticalSection, 0, CRITICAL_SECTION_NO_DEBUG_INFO)) - { - return; - } - - InitializeCriticalSectionAndSpinCount(lpCriticalSection, 0); -} - -/* -** Sets clipboard text to given string. -** -*/ -void System::SetClipboardText(const std::wstring& text) -{ - if (OpenClipboard(nullptr)) - { - // Include terminating null char - size_t len = text.length() + 1; - - HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, len * sizeof(WCHAR)); - if (hMem) - { - LPVOID data = GlobalLock(hMem); - memcpy(data, text.c_str(), len * sizeof(WCHAR)); - GlobalUnlock(hMem); - - EmptyClipboard(); - if (!SetClipboardData(CF_UNICODETEXT, hMem)) - { - GlobalFree(hMem); - } - } - - CloseClipboard(); - } -} - -/* -** Sets the system wallpapar. -** -*/ -void System::SetWallpaper(const std::wstring& wallpaper, const std::wstring& style) -{ - if (!wallpaper.empty()) - { - if (_waccess(wallpaper.c_str(), 0) == -1) - { - LogErrorF(L"!SetWallpaper: Unable to read file: %s", wallpaper.c_str()); - return; - } - - Bitmap bitmap(wallpaper.c_str()); - if (bitmap.GetLastStatus() == Ok) - { - std::wstring file = Rainmeter::GetInstance().GetSettingsPath() + L"Wallpaper.bmp"; - - const CLSID bmpClsid = { 0x557cf400, 0x1a04, 0x11d3, { 0x9a, 0x73, 0x0, 0x0, 0xf8, 0x1e, 0xf3, 0x2e } }; - if (bitmap.Save(file.c_str(), &bmpClsid) == Ok) - { - if (!style.empty()) - { - HKEY hKey; - if (RegOpenKeyEx(HKEY_CURRENT_USER, L"Control Panel\\Desktop", 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS) - { - const WCHAR* wallStyle = nullptr; - const WCHAR* wallTile = L"0"; - - const WCHAR* option = style.c_str(); - if (_wcsicmp(option, L"CENTER") == 0) - { - wallStyle = L"0"; - } - else if (_wcsicmp(option, L"TILE") == 0) - { - wallStyle = L"0"; - wallTile = L"1"; - } - else if (_wcsicmp(option, L"STRETCH") == 0) - { - wallStyle = L"2"; - } - else if (IsWindows7OrGreater()) - { - if (_wcsicmp(option, L"FIT") == 0) - { - wallStyle = L"6"; - } - else if (_wcsicmp(option, L"FILL") == 0) - { - wallStyle = L"10"; - } - } - - if (wallStyle) - { - RegSetValueEx(hKey, L"WallpaperStyle", 0, REG_SZ, (const BYTE*)wallStyle, sizeof(WCHAR) * 2); - RegSetValueEx(hKey, L"TileWallpaper", 0, REG_SZ, (const BYTE*)wallTile, sizeof(WCHAR) * 2); - } - else - { - LogError(L"!SetWallpaper: Invalid style"); - } - - RegCloseKey(hKey); - } - } - - SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, (void*)file.c_str(), SPIF_UPDATEINIFILE); - } - } - } -} - -/* -** Copies files and folders from one location to another. -** -*/ -bool System::CopyFiles(std::wstring from, std::wstring to, bool bMove) -{ - // If given "from" path ends with path separator, remove it (Workaround for XP: error code 1026) - size_t len; - while (len = from.size(), len > 0 && PathUtil::IsSeparator(from[len - 1])) - { - from.resize(len - 1); - } - - // The strings must end with double \0 - from.append(1, L'\0'); - to.append(1, L'\0'); - - SHFILEOPSTRUCT fo = - { - nullptr, - bMove ? FO_MOVE : FO_COPY, - from.c_str(), - to.c_str(), - FOF_NO_UI | FOF_NOCONFIRMATION | FOF_ALLOWUNDO - }; - - int result = SHFileOperation(&fo); - if (result != 0) - { - LogErrorF(L"Copy error: From %s to %s (%i)", from.c_str(), to.c_str(), result); - return false; - } - return true; -} - -/* -** Removes a file even if a file is read-only. -** -*/ -bool System::RemoveFile(const std::wstring& file) -{ - DWORD attr = GetFileAttributes(file.c_str()); - if (attr == -1 || (attr & FILE_ATTRIBUTE_READONLY)) - { - // Unset read-only - SetFileAttributes(file.c_str(), (attr == -1) ? FILE_ATTRIBUTE_NORMAL : attr - FILE_ATTRIBUTE_READONLY); - } - - return (DeleteFile(file.c_str()) != 0); -} - -/* -** Recursively removes folder. -** -*/ -bool System::RemoveFolder(std::wstring folder) -{ - // The strings must end with double nul - folder.append(1, L'\0'); - - SHFILEOPSTRUCT fo = - { - nullptr, - FO_DELETE, - folder.c_str(), - nullptr, - FOF_NO_UI | FOF_NOCONFIRMATION | FOF_ALLOWUNDO - }; - - int result = SHFileOperation(&fo); - if (result != 0) - { - LogErrorF(L"Unable to delete folder %s (%i)", folder.c_str(), result); - return false; - } - return true; -} - -/* -** Retrieves the "IniFileMapping" entries from Registry. -** -*/ -void System::UpdateIniFileMappingList() -{ - static ULONGLONG s_LastWriteTime = 0; - - HKEY hKey; - LONG ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\IniFileMapping", 0, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &hKey); - if (ret == ERROR_SUCCESS) - { - DWORD numSubKeys; - ULONGLONG ftLastWriteTime; - bool changed = false; - - ret = RegQueryInfoKey(hKey, nullptr, nullptr, nullptr, &numSubKeys, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, (LPFILETIME)&ftLastWriteTime); - if (ret == ERROR_SUCCESS) - { - //LogDebugF(L"IniFileMapping: numSubKeys=%u, ftLastWriteTime=%llu", numSubKeys, ftLastWriteTime); - - if (ftLastWriteTime != s_LastWriteTime || - numSubKeys != c_IniFileMappings.size()) - { - s_LastWriteTime = ftLastWriteTime; - if (numSubKeys > c_IniFileMappings.capacity()) - { - c_IniFileMappings.reserve(numSubKeys); - } - changed = true; - } - } - else - { - s_LastWriteTime = 0; - changed = true; - } - - if (changed) - { - if (!c_IniFileMappings.empty()) - { - c_IniFileMappings.clear(); - } - - WCHAR* buffer = new WCHAR[MAX_PATH]; - DWORD index = 0, cch = MAX_PATH; - - while ((ret = RegEnumKeyEx(hKey, index++, buffer, &cch, nullptr, nullptr, nullptr, nullptr)) != ERROR_NO_MORE_ITEMS) - { - if (ret == ERROR_SUCCESS) - { - c_IniFileMappings.push_back(buffer); - } - cch = MAX_PATH; - } - - delete [] buffer; - } - - RegCloseKey(hKey); - } -} - -/* -** Prepares a temporary file if iniFile is included in the "IniFileMapping" entries. -** If iniFile is not included, returns a empty string. If error occurred, returns "?". -** Note that a temporary file must be deleted by caller. -** -*/ -std::wstring System::GetTemporaryFile(const std::wstring& iniFile) -{ - std::wstring temporary; - - if (!c_IniFileMappings.empty()) - { - std::wstring::size_type pos = iniFile.find_last_of(L"\\/"); - const WCHAR* filename = iniFile.c_str() + ((pos != std::wstring::npos) ? pos + 1 : 0); - - std::vector::const_iterator iter = c_IniFileMappings.begin(); - for ( ; iter != c_IniFileMappings.end(); ++iter) - { - if (_wcsicmp((*iter).c_str(), filename) == 0) - { - WCHAR* buffer = new WCHAR[MAX_PATH]; - - if (GetTempPath(MAX_PATH, buffer) != 0 && - GetTempFileName(buffer, L"cfg", 0, buffer) != 0) - { - temporary = buffer; - - std::wstring tmp = GetTemporaryFile(temporary); - if (!tmp.empty() || !CopyFiles(iniFile, temporary)) // temporary is reserved or failed - { - RemoveFile(temporary); - - if (tmp.empty()) - { - temporary = L"?"; - } - else - { - temporary.swap(tmp); - } - } - } - else // failed - { - LogErrorF(L"Unable to create temporary file to: %s", temporary.c_str()); - temporary = L"?"; - } - - delete [] buffer; - break; - } - } - } - - return temporary; -} +/* + Copyright (C) 2010 spx + + 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 "System.h" +#include "Litestep.h" +#include "Rainmeter.h" +#include "MeterWindow.h" +#include "MeasureNet.h" +#include "Error.h" +#include "../Common/PathUtil.h" + +using namespace Gdiplus; + +#define DEBUG_VERBOSE (0) // Set 1 if you need verbose logging. + +#define ZPOS_FLAGS (SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOSENDCHANGING) + +enum TIMER +{ + TIMER_SHOWDESKTOP = 1, + TIMER_RESUME = 2 +}; +enum INTERVAL +{ + INTERVAL_SHOWDESKTOP = 250, + INTERVAL_RESTOREWINDOWS = 100, + INTERVAL_RESUME = 1000 +}; + +MultiMonitorInfo System::c_Monitors = { 0 }; + +HWND System::c_Window = nullptr; +HWND System::c_HelperWindow = nullptr; + +HWINEVENTHOOK System::c_WinEventHook = nullptr; + +bool System::c_ShowDesktop = false; + +std::wstring System::c_WorkingDirectory; + +std::vector System::c_IniFileMappings; + +/* +** Creates a helper window to detect changes in the system. +** +*/ +void System::Initialize(HINSTANCE instance) +{ + WNDCLASS wc = {0}; + wc.lpfnWndProc = (WNDPROC)WndProc; + wc.hInstance = instance; + wc.lpszClassName = L"RainmeterSystem"; + ATOM className = RegisterClass(&wc); + + c_Window = CreateWindowEx( + WS_EX_TOOLWINDOW, + MAKEINTATOM(className), + L"System", + WS_POPUP | WS_DISABLED, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + nullptr, + nullptr, + instance, + nullptr); + + c_HelperWindow = CreateWindowEx( + WS_EX_TOOLWINDOW, + MAKEINTATOM(className), + L"PositioningHelper", + WS_POPUP | WS_DISABLED, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + nullptr, + nullptr, + instance, + nullptr); + + SetWindowPos(c_Window, HWND_BOTTOM, 0, 0, 0, 0, ZPOS_FLAGS); + SetWindowPos(c_HelperWindow, HWND_BOTTOM, 0, 0, 0, 0, ZPOS_FLAGS); + + c_Monitors.monitors.reserve(4); + SetMultiMonitorInfo(); + + WCHAR directory[MAX_PATH]; + DWORD len = GetCurrentDirectory(MAX_PATH, directory); + c_WorkingDirectory.assign(directory, len <= MAX_PATH ? len : 0); + + c_WinEventHook = SetWinEventHook( + EVENT_SYSTEM_FOREGROUND, + EVENT_SYSTEM_FOREGROUND, + nullptr, + MyWinEventProc, + 0, + 0, + WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); + + SetTimer(c_Window, TIMER_SHOWDESKTOP, INTERVAL_SHOWDESKTOP, nullptr); +} + +/* +** Destroys a window. +** +*/ +void System::Finalize() +{ + KillTimer(c_Window, TIMER_SHOWDESKTOP); + KillTimer(c_Window, TIMER_RESUME); + + if (c_WinEventHook) + { + UnhookWinEvent(c_WinEventHook); + c_WinEventHook = nullptr; + } + + if (c_HelperWindow) + { + DestroyWindow(c_HelperWindow); + c_HelperWindow = nullptr; + } + + if (c_Window) + { + DestroyWindow(c_Window); + c_Window = nullptr; + } +} + +/* +** Retrieves the multi-monitor information. +** +*/ +BOOL CALLBACK MyInfoEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) +{ + MultiMonitorInfo* m = (MultiMonitorInfo*)dwData; + + MONITORINFOEX info; + info.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfo(hMonitor, &info); + + if (GetRainmeter().GetDebug()) + { + LogDebug(info.szDevice); + LogDebugF(L" Flags : %s(0x%08X)", (info.dwFlags & MONITORINFOF_PRIMARY) ? L"PRIMARY " : L"", info.dwFlags); + LogDebugF(L" Handle : 0x%p", hMonitor); + LogDebugF(L" ScrArea : L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", + lprcMonitor->left, lprcMonitor->top, lprcMonitor->right, lprcMonitor->bottom, + lprcMonitor->right - lprcMonitor->left, lprcMonitor->bottom - lprcMonitor->top); + LogDebugF(L" WorkArea : L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", + info.rcWork.left, info.rcWork.top, info.rcWork.right, info.rcWork.bottom, + info.rcWork.right - info.rcWork.left, info.rcWork.bottom - info.rcWork.top); + } + if (m == nullptr) return TRUE; + + if (m->useEnumDisplayDevices) + { + for (auto iter = m->monitors.begin(); iter != m->monitors.end(); ++iter) + { + if ((*iter).handle == nullptr && _wcsicmp(info.szDevice, (*iter).deviceName.c_str()) == 0) + { + (*iter).handle = hMonitor; + (*iter).screen = *lprcMonitor; + (*iter).work = info.rcWork; + break; + } + } + } + else // use only EnumDisplayMonitors + { + MonitorInfo monitor; + monitor.active = true; + + monitor.handle = hMonitor; + monitor.screen = *lprcMonitor; + monitor.work = info.rcWork; + + monitor.deviceName = info.szDevice; // E.g. "\\.\DISPLAY1" + + // Get the monitor name (E.g. "Generic Non-PnP Monitor") + DISPLAY_DEVICE ddm = {sizeof(DISPLAY_DEVICE)}; + DWORD dwMon = 0; + while (EnumDisplayDevices(info.szDevice, dwMon++, &ddm, 0)) + { + if (ddm.StateFlags & DISPLAY_DEVICE_ACTIVE && ddm.StateFlags & DISPLAY_DEVICE_ATTACHED) + { + monitor.monitorName.assign(ddm.DeviceString, wcsnlen(ddm.DeviceString, _countof(ddm.DeviceString))); + break; + } + } + + m->monitors.push_back(monitor); + + if (info.dwFlags & MONITORINFOF_PRIMARY) + { + // It's primary monitor! + m->primary = (int)m->monitors.size(); + } + } + + return TRUE; +} + +/* +** Returns the number of monitors. +** +*/ +size_t System::GetMonitorCount() +{ + if (c_Monitors.monitors.empty()) + { + SetMultiMonitorInfo(); + } + return c_Monitors.monitors.size(); +} + +/* +** Sets the multi-monitor information. +** +*/ +void System::SetMultiMonitorInfo() +{ + std::vector& monitors = c_Monitors.monitors; + bool logging = GetRainmeter().GetDebug(); + + c_Monitors.vsT = GetSystemMetrics(SM_YVIRTUALSCREEN); + c_Monitors.vsL = GetSystemMetrics(SM_XVIRTUALSCREEN); + c_Monitors.vsH = GetSystemMetrics(SM_CYVIRTUALSCREEN); + c_Monitors.vsW = GetSystemMetrics(SM_CXVIRTUALSCREEN); + + c_Monitors.primary = 1; // If primary screen is not found, 1st screen is assumed as primary screen. + + c_Monitors.useEnumDisplayDevices = true; + c_Monitors.useEnumDisplayMonitors = false; + + if (logging) + { + LogDebug(L"------------------------------"); + LogDebug(L"* EnumDisplayDevices / EnumDisplaySettings API"); + } + + DISPLAY_DEVICE dd = {sizeof(DISPLAY_DEVICE)}; + + if (EnumDisplayDevices(nullptr, 0, &dd, 0)) + { + DWORD dwDevice = 0; + + do + { + std::wstring msg; + + std::wstring deviceName(dd.DeviceName, wcsnlen(dd.DeviceName, _countof(dd.DeviceName))); + std::wstring deviceString; + + if (logging) + { + deviceString.assign(dd.DeviceString, wcsnlen(dd.DeviceString, _countof(dd.DeviceString))); + + LogDebug(deviceName.c_str()); + + if (dd.StateFlags & DISPLAY_DEVICE_ACTIVE) + { + msg += L"ACTIVE "; + } + if (dd.StateFlags & DISPLAY_DEVICE_MULTI_DRIVER) + { + msg += L"MULTI "; + } + if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) + { + msg += L"PRIMARY "; + } + if (dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) + { + msg += L"MIRROR "; + } + if (dd.StateFlags & DISPLAY_DEVICE_VGA_COMPATIBLE) + { + msg += L"VGA "; + } + if (dd.StateFlags & DISPLAY_DEVICE_REMOVABLE) + { + msg += L"REMOVABLE "; + } + if (dd.StateFlags & DISPLAY_DEVICE_MODESPRUNED) + { + msg += L"PRUNED "; + } + if (dd.StateFlags & DISPLAY_DEVICE_REMOTE) + { + msg += L"REMOTE "; + } + if (dd.StateFlags & DISPLAY_DEVICE_DISCONNECT) + { + msg += L"DISCONNECT "; + } + } + + if ((dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) == 0) + { + MonitorInfo monitor = {0}; + + monitor.handle = nullptr; + monitor.deviceName = deviceName; // E.g. "\\.\DISPLAY1" + + // Get the monitor name (E.g. "Generic Non-PnP Monitor") + DISPLAY_DEVICE ddm = {sizeof(DISPLAY_DEVICE)}; + DWORD dwMon = 0; + while (EnumDisplayDevices(deviceName.c_str(), dwMon++, &ddm, 0)) + { + if (ddm.StateFlags & DISPLAY_DEVICE_ACTIVE && ddm.StateFlags & DISPLAY_DEVICE_ATTACHED) + { + monitor.monitorName.assign(ddm.DeviceString, wcsnlen(ddm.DeviceString, _countof(ddm.DeviceString))); + + if (logging) + { + LogDebugF(L" Name : %s", monitor.monitorName.c_str()); + } + break; + } + } + + if (logging) + { + LogDebugF(L" Adapter : %s", deviceString.c_str()); + LogDebugF(L" Flags : %s(0x%08X)", msg.c_str(), dd.StateFlags); + } + + if (dd.StateFlags & DISPLAY_DEVICE_ACTIVE) + { + monitor.active = true; + + DEVMODE dm = {0}; + dm.dmSize = sizeof(DEVMODE); + + if (EnumDisplaySettings(deviceName.c_str(), ENUM_CURRENT_SETTINGS, &dm)) + { + POINT pos = {dm.dmPosition.x, dm.dmPosition.y}; + monitor.handle = MonitorFromPoint(pos, MONITOR_DEFAULTTONULL); + + if (logging) + { + LogDebugF(L" Handle : 0x%p", monitor.handle); + } + } + + if (monitor.handle != nullptr) + { + MONITORINFO info = {sizeof(MONITORINFO)}; + GetMonitorInfo(monitor.handle, &info); + + monitor.screen = info.rcMonitor; + monitor.work = info.rcWork; + + if (logging) + { + LogDebugF(L" ScrArea : L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", + info.rcMonitor.left, info.rcMonitor.top, info.rcMonitor.right, info.rcMonitor.bottom, + info.rcMonitor.right - info.rcMonitor.left, info.rcMonitor.bottom - info.rcMonitor.top); + LogDebugF(L" WorkArea : L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", + info.rcWork.left, info.rcWork.top, info.rcWork.right, info.rcWork.bottom, + info.rcWork.right - info.rcWork.left, info.rcWork.bottom - info.rcWork.top); + } + } + else // monitor not found + { + c_Monitors.useEnumDisplayMonitors = true; + } + } + else + { + monitor.active = false; + } + + monitors.push_back(monitor); + + if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) + { + // It's primary monitor! + c_Monitors.primary = (int)monitors.size(); + } + } + else + { + if (logging) + { + LogDebugF(L" Adapter : %s", deviceString.c_str()); + LogDebugF(L" Flags : %s(0x%08X)", msg.c_str(), dd.StateFlags); + } + } + ++dwDevice; + } + while (EnumDisplayDevices(nullptr, dwDevice, &dd, 0)); + } + + if (monitors.empty()) // Failed to enumerate the non-mirroring monitors + { + LogWarning(L"Failed to enumerate the non-mirroring monitors. Only EnumDisplayMonitors is used instead."); + c_Monitors.useEnumDisplayDevices = false; + c_Monitors.useEnumDisplayMonitors = true; + } + + if (logging) + { + LogDebug(L"------------------------------"); + LogDebug(L"* EnumDisplayMonitors API"); + } + + if (c_Monitors.useEnumDisplayMonitors) + { + EnumDisplayMonitors(nullptr, nullptr, MyInfoEnumProc, (LPARAM)(&c_Monitors)); + + if (monitors.empty()) // Failed to enumerate the monitors + { + LogWarning(L"Failed to enumerate monitors. Using dummy monitor info."); + c_Monitors.useEnumDisplayMonitors = false; + + MonitorInfo monitor; + monitor.active = true; + + POINT pos = {0, 0}; + monitor.handle = MonitorFromPoint(pos, MONITOR_DEFAULTTOPRIMARY); + monitor.screen.left = 0; + monitor.screen.top = 0; + monitor.screen.right = GetSystemMetrics(SM_CXSCREEN); + monitor.screen.bottom = GetSystemMetrics(SM_CYSCREEN); + if (SystemParametersInfo(SPI_GETWORKAREA, 0, &(monitor.work), 0) == 0) // failed + { + monitor.work = monitor.screen; + } + + monitor.deviceName = L"DUMMY"; + + monitors.push_back(monitor); + + c_Monitors.primary = 1; + } + } + else + { + if (logging) + { + EnumDisplayMonitors(nullptr, nullptr, MyInfoEnumProc, (LPARAM)nullptr); // Only logging + } + } + + if (logging) + { + LogDebug(L"------------------------------"); + + std::wstring method = L"* METHOD: "; + if (c_Monitors.useEnumDisplayDevices) + { + method += L"EnumDisplayDevices + "; + method += c_Monitors.useEnumDisplayMonitors ? L"EnumDisplayMonitors Mode" : L"EnumDisplaySettings Mode"; + } + else + { + method += c_Monitors.useEnumDisplayMonitors ? L"EnumDisplayMonitors Mode" : L"Dummy Mode"; + } + LogDebug(method.c_str()); + + LogDebugF(L"* MONITORS: Count=%i, Primary=@%i", (int)monitors.size(), c_Monitors.primary); + LogDebug(L"@0: Virtual screen"); + LogDebugF(L" L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", + c_Monitors.vsL, c_Monitors.vsT, c_Monitors.vsL + c_Monitors.vsW, c_Monitors.vsT + c_Monitors.vsH, + c_Monitors.vsW, c_Monitors.vsH); + + int i = 1; + for (auto iter = monitors.cbegin(); iter != monitors.cend(); ++iter, ++i) + { + if ((*iter).active) + { + LogDebugF(L"@%i: %s (active), MonitorName: %s", i, (*iter).deviceName.c_str(), (*iter).monitorName.c_str()); + LogDebugF(L" L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", + (*iter).screen.left, (*iter).screen.top, (*iter).screen.right, (*iter).screen.bottom, + (*iter).screen.right - (*iter).screen.left, (*iter).screen.bottom - (*iter).screen.top); + } + else + { + LogDebugF(L"@%i: %s (inactive), MonitorName: %s", i, (*iter).deviceName.c_str(), (*iter).monitorName.c_str()); + } + } + LogDebug(L"------------------------------"); + } +} + +/* +** Updates the workarea information. +** +*/ +void System::UpdateWorkareaInfo() +{ + std::vector& monitors = c_Monitors.monitors; + + if (monitors.empty()) + { + SetMultiMonitorInfo(); + return; + } + + int i = 1; + for (auto iter = monitors.begin(); iter != monitors.end(); ++iter, ++i) + { + if ((*iter).active && (*iter).handle != nullptr) + { + MONITORINFO info = {sizeof(MONITORINFO)}; + GetMonitorInfo((*iter).handle, &info); + + (*iter).work = info.rcWork; + + if (GetRainmeter().GetDebug()) + { + LogDebugF(L"WorkArea@%i : L=%i, T=%i, R=%i, B=%i (W=%i, H=%i)", + i, + info.rcWork.left, info.rcWork.top, info.rcWork.right, info.rcWork.bottom, + info.rcWork.right - info.rcWork.left, info.rcWork.bottom - info.rcWork.top); + } + } + } +} + +/* +** Finds the Default Shell's window. +** +*/ +HWND System::GetDefaultShellWindow() +{ + static HWND c_ShellW = nullptr; // cache + HWND ShellW = GetShellWindow(); + + if (ShellW) + { + if (ShellW == c_ShellW) + { + return ShellW; + } + else + { + const int classLen = _countof(L"Progman") + 1; + WCHAR className[classLen]; + if (!(GetClassName(ShellW, className, classLen) > 0 && + wcscmp(className, L"Progman") == 0)) + { + ShellW = nullptr; + } + } + } + + c_ShellW = ShellW; + return ShellW; +} + +/* +** Finds the WorkerW window. +** If the WorkerW window is not active, returns nullptr. +** +*/ +HWND System::GetWorkerW() +{ + static HWND c_DefView = nullptr; // cache + HWND ShellW = GetDefaultShellWindow(); + if (!ShellW) return nullptr; // Default Shell (Explorer) not running + + if (c_DefView && IsWindow(c_DefView)) + { + HWND parent = GetAncestor(c_DefView, GA_PARENT); + if (parent) + { + if (parent == ShellW) + { + return nullptr; + } + else + { + const int classLen = _countof(L"WorkerW") + 1; + WCHAR className[classLen]; + if (GetClassName(parent, className, classLen) > 0 && + wcscmp(className, L"WorkerW") == 0) + { + return parent; + } + } + } + } + + HWND WorkerW = nullptr, DefView = FindWindowEx(ShellW, nullptr, L"SHELLDLL_DefView", L""); + if (DefView == nullptr) + { + while (WorkerW = FindWindowEx(nullptr, WorkerW, L"WorkerW", L"")) + { + if (IsWindowVisible(WorkerW) && + BelongToSameProcess(ShellW, WorkerW) && + (DefView = FindWindowEx(WorkerW, nullptr, L"SHELLDLL_DefView", L""))) + { + break; + } + } + } + + c_DefView = DefView; + return WorkerW; +} + +/* +** Returns the first window whose position is not ZPOSITION_ONDESKTOP, +** ZPOSITION_BOTTOM, or ZPOSITION_NORMAL. +** +*/ +HWND System::GetBackmostTopWindow() +{ + HWND winPos = c_HelperWindow; + + // Skip all ZPOSITION_ONDESKTOP, ZPOSITION_BOTTOM, and ZPOSITION_NORMAL windows + while (winPos = ::GetNextWindow(winPos, GW_HWNDPREV)) + { + MeterWindow* wnd = GetRainmeter().GetMeterWindow(winPos); + if (!wnd || + (wnd->GetWindowZPosition() != ZPOSITION_NORMAL && + wnd->GetWindowZPosition() != ZPOSITION_ONDESKTOP && + wnd->GetWindowZPosition() != ZPOSITION_ONBOTTOM)) + { + break; + } + } + + return winPos; +} + +/* +** Checks whether the given windows belong to the same process. +** +*/ +bool System::BelongToSameProcess(HWND hwndA, HWND hwndB) +{ + DWORD procAId = 0, procBId = 0; + + GetWindowThreadProcessId(hwndA, &procAId); + GetWindowThreadProcessId(hwndB, &procBId); + + return (procAId == procBId); +} + +/* +** Retrieves the Rainmeter's meter windows in Z-order. +** +*/ +BOOL CALLBACK MyEnumWindowsProc(HWND hwnd, LPARAM lParam) +{ + bool logging = GetRainmeter().GetDebug() && DEBUG_VERBOSE; + const int classLen = _countof(METERWINDOW_CLASS_NAME) + (DEBUG_VERBOSE ? 32 : 1); + WCHAR className[classLen]; + MeterWindow* Window; + WCHAR flag; + + if (GetClassName(hwnd, className, classLen) > 0 && + wcscmp(className, METERWINDOW_CLASS_NAME) == 0 && + (Window = GetRainmeter().GetMeterWindow(hwnd))) + { + ZPOSITION zPos = Window->GetWindowZPosition(); + if (zPos == ZPOSITION_ONDESKTOP || + (zPos == ZPOSITION_NORMAL && GetRainmeter().IsNormalStayDesktop()) || + zPos == ZPOSITION_ONBOTTOM) + { + if (lParam) + { + ((std::vector*)lParam)->push_back(Window); + } + + if (logging) flag = L'+'; + } + else + { + if (logging) flag = L'-'; + } + + if (logging) + { + LogDebugF(L"%c [%c] 0x%p : %s (Name: \"%s\", zPos=%i)", + flag, IsWindowVisible(hwnd) ? L'V' : L'H', hwnd, className, Window->GetFolderPath().c_str(), (int)zPos); + } + } + else + { + if (logging) + { + flag = (hwnd == System::GetHelperWindow()) ? L'o' : ' '; + LogDebugF(L"%c [%c] 0x%p : %s", flag, IsWindowVisible(hwnd) ? L'V' : L'H', hwnd, className); + } + } + + return TRUE; +} + +/* +** Arranges the meter window in Z-order. +** +*/ +void System::ChangeZPosInOrder() +{ + bool logging = GetRainmeter().GetDebug() && DEBUG_VERBOSE; + std::vector windowsInZOrder; + + if (logging) LogDebug(L"1: ----- BEFORE -----"); + + // Retrieve the Rainmeter's meter windows in Z-order + EnumWindows(MyEnumWindowsProc, (LPARAM)(&windowsInZOrder)); + + auto resetZPos = [&](ZPOSITION zpos) + { + // Reset ZPos in Z-order (Bottom) + std::vector::const_iterator iter = windowsInZOrder.begin(); + for ( ; iter != windowsInZOrder.end(); ++iter) + { + if ((*iter)->GetWindowZPosition() == zpos) + { + (*iter)->ChangeZPos(zpos); // reset + } + } + }; + + if (GetRainmeter().IsNormalStayDesktop()) + { + resetZPos(ZPOSITION_NORMAL); + } + + if (!c_ShowDesktop) + { + resetZPos(ZPOSITION_ONBOTTOM); + } + + resetZPos(ZPOSITION_ONDESKTOP); + + if (logging) + { + LogDebug(L"2: ----- AFTER -----"); + + // Log all windows in Z-order + EnumWindows(MyEnumWindowsProc, (LPARAM)nullptr); + } +} + +/* +** Moves the helper window to the reference position. +** +*/ +void System::PrepareHelperWindow(HWND WorkerW) +{ + bool logging = GetRainmeter().GetDebug() && DEBUG_VERBOSE; + + SetWindowPos(c_Window, HWND_BOTTOM, 0, 0, 0, 0, ZPOS_FLAGS); // always on bottom + + if (c_ShowDesktop && WorkerW) + { + // Set WS_EX_TOPMOST flag + SetWindowPos(c_HelperWindow, HWND_TOPMOST, 0, 0, 0, 0, ZPOS_FLAGS); + + // Find the "backmost" topmost window + HWND hwnd = WorkerW; + while (hwnd = ::GetNextWindow(hwnd, GW_HWNDPREV)) + { + if (GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST) + { + WCHAR className[64], windowText[64]; + + if (logging) + { + GetClassName(hwnd, className, 64); + GetWindowText(hwnd, windowText, 64); + + SetLastError(ERROR_SUCCESS); + } + + // Insert the helper window after the found window + if (0 != SetWindowPos(c_HelperWindow, hwnd, 0, 0, 0, 0, ZPOS_FLAGS)) + { + if (logging) + { + LogDebugF(L"System: HelperWindow: hwnd=0x%p (WorkerW=0x%p), hwndInsertAfter=0x%p (\"%s\" %s) - %s", + c_HelperWindow, WorkerW, hwnd, windowText, className, (GetWindowLongPtr(c_HelperWindow, GWL_EXSTYLE) & WS_EX_TOPMOST) ? L"TOPMOST" : L"NORMAL"); + } + return; + } + + if (logging) + { + DWORD err = GetLastError(); + LogDebugF(L"System: HelperWindow: hwnd=0x%p (WorkerW=0x%p), hwndInsertAfter=0x%p (\"%s\" %s) - FAILED (ErrorCode=0x%08X)", + c_HelperWindow, WorkerW, hwnd, windowText, className, err); + } + } + } + + if (logging) + { + LogDebugF(L"System: HelperWindow: hwnd=0x%p (WorkerW=0x%p), hwndInsertAfter=HWND_TOPMOST - %s", + c_HelperWindow, WorkerW, (GetWindowLongPtr(c_HelperWindow, GWL_EXSTYLE) & WS_EX_TOPMOST) ? L"TOPMOST" : L"NORMAL"); + } + } + else + { + // Insert the helper window to the bottom + SetWindowPos(c_HelperWindow, HWND_BOTTOM, 0, 0, 0, 0, ZPOS_FLAGS); + + if (logging) + { + LogDebugF(L"System: HelperWindow: hwnd=0x%p (WorkerW=0x%p), hwndInsertAfter=HWND_BOTTOM - %s", + c_HelperWindow, WorkerW, (GetWindowLongPtr(c_HelperWindow, GWL_EXSTYLE) & WS_EX_TOPMOST) ? L"TOPMOST" : L"NORMAL"); + } + } +} + +/* +** Changes the "Show Desktop" state. +** +*/ +bool System::CheckDesktopState(HWND WorkerW) +{ + HWND hwnd = nullptr; + + if (WorkerW && IsWindowVisible(WorkerW)) + { + hwnd = FindWindowEx(nullptr, WorkerW, L"RainmeterSystem", L"System"); + } + + bool stateChanged = (hwnd && !c_ShowDesktop) || (!hwnd && c_ShowDesktop); + + if (stateChanged) + { + c_ShowDesktop = !c_ShowDesktop; + + if (GetRainmeter().GetDebug()) + { + LogDebugF(L"System: \"Show %s\" has been detected.", + c_ShowDesktop ? L"desktop" : L"open windows"); + } + + PrepareHelperWindow(WorkerW); + + ChangeZPosInOrder(); + + if (c_ShowDesktop) + { + SetTimer(c_Window, TIMER_SHOWDESKTOP, INTERVAL_RESTOREWINDOWS, nullptr); + } + else + { + SetTimer(c_Window, TIMER_SHOWDESKTOP, INTERVAL_SHOWDESKTOP, nullptr); + } + } + + return stateChanged; +} + +/* +** The event hook procedure +** +*/ +void CALLBACK System::MyWinEventProc(HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime) +{ + if (event == EVENT_SYSTEM_FOREGROUND) + { + if (!c_ShowDesktop) + { + const int classLen = _countof(L"WorkerW") + 1; + WCHAR className[classLen]; + if (GetClassName(hwnd, className, classLen) > 0 && + wcscmp(className, L"WorkerW") == 0 && + BelongToSameProcess(GetDefaultShellWindow(), hwnd)) + { + const int max = 5; + int loop = 0; + while (loop < max && FindWindowEx(hwnd, nullptr, L"SHELLDLL_DefView", L"") == nullptr) + { + Sleep(2); // Wait for 2-16 ms before retrying + ++loop; + } + + if (loop < max) + { + loop = 0; + while (loop < max && !CheckDesktopState(hwnd)) + { + Sleep(2); // Wait for 2-16 ms before retrying + ++loop; + } + } + } + } + } +} + +/* +** The window procedure +** +*/ +LRESULT CALLBACK System::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (hWnd != c_Window) + { + if (uMsg == WM_WINDOWPOSCHANGING) + { + ((LPWINDOWPOS)lParam)->flags |= SWP_NOZORDER; + return 0; + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) + { + case WM_WINDOWPOSCHANGING: + ((LPWINDOWPOS)lParam)->flags |= SWP_NOZORDER; + break; + + case WM_TIMER: + switch (wParam) + { + case TIMER_SHOWDESKTOP: + if (wParam == TIMER_SHOWDESKTOP) + { + CheckDesktopState(GetWorkerW()); + } + break; + + case TIMER_RESUME: + KillTimer(hWnd, TIMER_RESUME); + if (GetRainmeter().IsRedrawable()) + { + std::map::const_iterator iter = GetRainmeter().GetAllMeterWindows().begin(); + for ( ; iter != GetRainmeter().GetAllMeterWindows().end(); ++iter) + { + (*iter).second->RedrawWindow(); + } + } + break; + } + break; + + case WM_DISPLAYCHANGE: + LogNotice(L"System: Display settings changed"); + ClearMultiMonitorInfo(); + ConfigParser::ClearMultiMonitorVariables(); + case WM_SETTINGCHANGE: + if (uMsg == WM_DISPLAYCHANGE || (/*uMsg == WM_SETTINGCHANGE &&*/ wParam == SPI_SETWORKAREA)) + { + if (uMsg == WM_SETTINGCHANGE) // SPI_SETWORKAREA + { + LogNotice(L"System: Work area changed"); + UpdateWorkareaInfo(); + ConfigParser::UpdateWorkareaVariables(); + } + + // Deliver WM_DISPLAYCHANGE / WM_SETTINGCHANGE message to all meter windows + std::map::const_iterator iter = GetRainmeter().GetAllMeterWindows().begin(); + for ( ; iter != GetRainmeter().GetAllMeterWindows().end(); ++iter) + { + PostMessage((*iter).second->GetWindow(), WM_METERWINDOW_DELAYED_MOVE, (WPARAM)uMsg, (LPARAM)0); + } + } + break; + + case WM_POWERBROADCAST: + if (wParam == PBT_APMRESUMESUSPEND) + { + // Deliver PBT_APMRESUMESUSPEND event to all meter windows + SetTimer(hWnd, TIMER_RESUME, INTERVAL_RESUME, nullptr); + } + return TRUE; + + default: + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + return 0; +} + +/* +** Retrieves the number of milliseconds that have elapsed since the system was started. +** In XP, returns the predictive value due to the 32bit limitation. +** +*/ +ULONGLONG System::GetTickCount64() +{ + static auto s_GetTickCount64 = + (decltype(GetTickCount64)*)GetProcAddress(GetModuleHandle(L"kernel32"), "GetTickCount64"); + + if (s_GetTickCount64) + { + return s_GetTickCount64(); + } + else + { + static ULONGLONG lastTicks = 0; + ULONGLONG ticks = GetTickCount(); + while (ticks < lastTicks) ticks += 0x100000000; + lastTicks = ticks; + return ticks; + } +} + +/* +** Gets the cursor position in last message retrieved by GetMessage(). +** +*/ +POINT System::GetCursorPosition() +{ + DWORD pos = GetMessagePos(); + POINT pt = { GET_X_LPARAM(pos), GET_Y_LPARAM(pos) }; + return pt; +} + +/* +** Checks if file is writable. +** +*/ +bool System::IsFileWritable(LPCWSTR file) +{ + HANDLE hFile = CreateFile(file, GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr); + if (hFile == INVALID_HANDLE_VALUE) + { + return false; + } + + CloseHandle(hFile); + return true; +} + +/* +** This function is a wrapper function for LoadLibrary(). +** +** Avoids loading a DLL from current directory. +** +*/ +HMODULE System::RmLoadLibrary(LPCWSTR lpLibFileName, DWORD* dwError) +{ + // Remove current directory from DLL search path + SetDllDirectory(L""); + + SetLastError(ERROR_SUCCESS); + HMODULE hLib = LoadLibrary(lpLibFileName); + + if (dwError) + { + *dwError = GetLastError(); + } + + return hLib; +} + +/* +** Resets working directory to default. +** +*/ +void System::ResetWorkingDirectory() +{ + WCHAR directory[MAX_PATH] = {0}; + GetCurrentDirectory(MAX_PATH, directory); + + const WCHAR* workDir = c_WorkingDirectory.c_str(); + if (_wcsicmp(directory, workDir) != 0) + { + SetCurrentDirectory(workDir); + } +} + +/* +** Initializes a critical section object by using InitializeCriticalSectionEx function with CRITICAL_SECTION_NO_DEBUG_INFO flag. +** For more details: http://stackoverflow.com/questions/804848/critical-sections-leaking-memory-on-vista-win2008/ +** +*/ +void System::InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection) +{ + static auto s_InitializeCriticalSectionEx = IsWindowsVistaOrGreater() ? + (decltype(InitializeCriticalSectionEx)*)GetProcAddress(GetModuleHandle(L"kernel32"), "InitializeCriticalSectionEx") : nullptr; + + if (s_InitializeCriticalSectionEx && + s_InitializeCriticalSectionEx(lpCriticalSection, 0, CRITICAL_SECTION_NO_DEBUG_INFO)) + { + return; + } + + InitializeCriticalSectionAndSpinCount(lpCriticalSection, 0); +} + +/* +** Sets clipboard text to given string. +** +*/ +void System::SetClipboardText(const std::wstring& text) +{ + if (OpenClipboard(nullptr)) + { + // Include terminating null char + size_t len = text.length() + 1; + + HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, len * sizeof(WCHAR)); + if (hMem) + { + LPVOID data = GlobalLock(hMem); + memcpy(data, text.c_str(), len * sizeof(WCHAR)); + GlobalUnlock(hMem); + + EmptyClipboard(); + if (!SetClipboardData(CF_UNICODETEXT, hMem)) + { + GlobalFree(hMem); + } + } + + CloseClipboard(); + } +} + +/* +** Sets the system wallpapar. +** +*/ +void System::SetWallpaper(const std::wstring& wallpaper, const std::wstring& style) +{ + if (!wallpaper.empty()) + { + if (_waccess(wallpaper.c_str(), 0) == -1) + { + LogErrorF(L"!SetWallpaper: Unable to read file: %s", wallpaper.c_str()); + return; + } + + Bitmap bitmap(wallpaper.c_str()); + if (bitmap.GetLastStatus() == Ok) + { + std::wstring file = GetRainmeter().GetSettingsPath() + L"Wallpaper.bmp"; + + const CLSID bmpClsid = { 0x557cf400, 0x1a04, 0x11d3, { 0x9a, 0x73, 0x0, 0x0, 0xf8, 0x1e, 0xf3, 0x2e } }; + if (bitmap.Save(file.c_str(), &bmpClsid) == Ok) + { + if (!style.empty()) + { + HKEY hKey; + if (RegOpenKeyEx(HKEY_CURRENT_USER, L"Control Panel\\Desktop", 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS) + { + const WCHAR* wallStyle = nullptr; + const WCHAR* wallTile = L"0"; + + const WCHAR* option = style.c_str(); + if (_wcsicmp(option, L"CENTER") == 0) + { + wallStyle = L"0"; + } + else if (_wcsicmp(option, L"TILE") == 0) + { + wallStyle = L"0"; + wallTile = L"1"; + } + else if (_wcsicmp(option, L"STRETCH") == 0) + { + wallStyle = L"2"; + } + else if (IsWindows7OrGreater()) + { + if (_wcsicmp(option, L"FIT") == 0) + { + wallStyle = L"6"; + } + else if (_wcsicmp(option, L"FILL") == 0) + { + wallStyle = L"10"; + } + } + + if (wallStyle) + { + RegSetValueEx(hKey, L"WallpaperStyle", 0, REG_SZ, (const BYTE*)wallStyle, sizeof(WCHAR) * 2); + RegSetValueEx(hKey, L"TileWallpaper", 0, REG_SZ, (const BYTE*)wallTile, sizeof(WCHAR) * 2); + } + else + { + LogError(L"!SetWallpaper: Invalid style"); + } + + RegCloseKey(hKey); + } + } + + SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, (void*)file.c_str(), SPIF_UPDATEINIFILE); + } + } + } +} + +/* +** Copies files and folders from one location to another. +** +*/ +bool System::CopyFiles(std::wstring from, std::wstring to, bool bMove) +{ + // If given "from" path ends with path separator, remove it (Workaround for XP: error code 1026) + size_t len; + while (len = from.size(), len > 0 && PathUtil::IsSeparator(from[len - 1])) + { + from.resize(len - 1); + } + + // The strings must end with double \0 + from.append(1, L'\0'); + to.append(1, L'\0'); + + SHFILEOPSTRUCT fo = + { + nullptr, + bMove ? FO_MOVE : FO_COPY, + from.c_str(), + to.c_str(), + FOF_NO_UI | FOF_NOCONFIRMATION | FOF_ALLOWUNDO + }; + + int result = SHFileOperation(&fo); + if (result != 0) + { + LogErrorF(L"Copy error: From %s to %s (%i)", from.c_str(), to.c_str(), result); + return false; + } + return true; +} + +/* +** Removes a file even if a file is read-only. +** +*/ +bool System::RemoveFile(const std::wstring& file) +{ + DWORD attr = GetFileAttributes(file.c_str()); + if (attr == -1 || (attr & FILE_ATTRIBUTE_READONLY)) + { + // Unset read-only + SetFileAttributes(file.c_str(), (attr == -1) ? FILE_ATTRIBUTE_NORMAL : attr - FILE_ATTRIBUTE_READONLY); + } + + return (DeleteFile(file.c_str()) != 0); +} + +/* +** Recursively removes folder. +** +*/ +bool System::RemoveFolder(std::wstring folder) +{ + // The strings must end with double nul + folder.append(1, L'\0'); + + SHFILEOPSTRUCT fo = + { + nullptr, + FO_DELETE, + folder.c_str(), + nullptr, + FOF_NO_UI | FOF_NOCONFIRMATION | FOF_ALLOWUNDO + }; + + int result = SHFileOperation(&fo); + if (result != 0) + { + LogErrorF(L"Unable to delete folder %s (%i)", folder.c_str(), result); + return false; + } + return true; +} + +/* +** Retrieves the "IniFileMapping" entries from Registry. +** +*/ +void System::UpdateIniFileMappingList() +{ + static ULONGLONG s_LastWriteTime = 0; + + HKEY hKey; + LONG ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\IniFileMapping", 0, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &hKey); + if (ret == ERROR_SUCCESS) + { + DWORD numSubKeys; + ULONGLONG ftLastWriteTime; + bool changed = false; + + ret = RegQueryInfoKey(hKey, nullptr, nullptr, nullptr, &numSubKeys, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, (LPFILETIME)&ftLastWriteTime); + if (ret == ERROR_SUCCESS) + { + //LogDebugF(L"IniFileMapping: numSubKeys=%u, ftLastWriteTime=%llu", numSubKeys, ftLastWriteTime); + + if (ftLastWriteTime != s_LastWriteTime || + numSubKeys != c_IniFileMappings.size()) + { + s_LastWriteTime = ftLastWriteTime; + if (numSubKeys > c_IniFileMappings.capacity()) + { + c_IniFileMappings.reserve(numSubKeys); + } + changed = true; + } + } + else + { + s_LastWriteTime = 0; + changed = true; + } + + if (changed) + { + if (!c_IniFileMappings.empty()) + { + c_IniFileMappings.clear(); + } + + WCHAR* buffer = new WCHAR[MAX_PATH]; + DWORD index = 0, cch = MAX_PATH; + + while ((ret = RegEnumKeyEx(hKey, index++, buffer, &cch, nullptr, nullptr, nullptr, nullptr)) != ERROR_NO_MORE_ITEMS) + { + if (ret == ERROR_SUCCESS) + { + c_IniFileMappings.push_back(buffer); + } + cch = MAX_PATH; + } + + delete [] buffer; + } + + RegCloseKey(hKey); + } +} + +/* +** Prepares a temporary file if iniFile is included in the "IniFileMapping" entries. +** If iniFile is not included, returns a empty string. If error occurred, returns "?". +** Note that a temporary file must be deleted by caller. +** +*/ +std::wstring System::GetTemporaryFile(const std::wstring& iniFile) +{ + std::wstring temporary; + + if (!c_IniFileMappings.empty()) + { + std::wstring::size_type pos = iniFile.find_last_of(L"\\/"); + const WCHAR* filename = iniFile.c_str() + ((pos != std::wstring::npos) ? pos + 1 : 0); + + std::vector::const_iterator iter = c_IniFileMappings.begin(); + for ( ; iter != c_IniFileMappings.end(); ++iter) + { + if (_wcsicmp((*iter).c_str(), filename) == 0) + { + WCHAR* buffer = new WCHAR[MAX_PATH]; + + if (GetTempPath(MAX_PATH, buffer) != 0 && + GetTempFileName(buffer, L"cfg", 0, buffer) != 0) + { + temporary = buffer; + + std::wstring tmp = GetTemporaryFile(temporary); + if (!tmp.empty() || !CopyFiles(iniFile, temporary)) // temporary is reserved or failed + { + RemoveFile(temporary); + + if (tmp.empty()) + { + temporary = L"?"; + } + else + { + temporary.swap(tmp); + } + } + } + else // failed + { + LogErrorF(L"Unable to create temporary file to: %s", temporary.c_str()); + temporary = L"?"; + } + + delete [] buffer; + break; + } + } + } + + return temporary; +} diff --git a/Library/TrayWindow.cpp b/Library/TrayWindow.cpp index 1f80edd4..9ae6f355 100644 --- a/Library/TrayWindow.cpp +++ b/Library/TrayWindow.cpp @@ -1,736 +1,736 @@ -/* - Copyright (C) 2004 Kimmo Pekkola - - 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 "TrayWindow.h" -#include "Measure.h" -#include "resource.h" -#include "Litestep.h" -#include "Rainmeter.h" -#include "DialogAbout.h" -#include "DialogManage.h" -#include "System.h" -#include "Error.h" -#include "RainmeterQuery.h" -#include "resource.h" -#include "../Version.h" - -#define RAINMETER_OFFICIAL L"http://rainmeter.net/cms/" -#define RAINMETER_HELP L"http://docs.rainmeter.net/" - -#define ZPOS_FLAGS (SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOSENDCHANGING) - -enum TIMER -{ - TIMER_ADDTRAYICON = 1, - TIMER_TRAYMEASURE = 3 -}; -enum INTERVAL -{ - INTERVAL_ADDTRAYICON = 3000, - INTERVAL_TRAYMEASURE = 1000 -}; - -const UINT WM_TASKBARCREATED = ::RegisterWindowMessage(L"TaskbarCreated"); - -using namespace Gdiplus; - -TrayWindow::TrayWindow() : - m_Icon(), - m_Measure(), - m_MeterType(TRAY_METER_TYPE_HISTOGRAM), - m_Color1(0, 100, 0), - m_Color2(0, 255, 0), - m_Bitmap(), - m_Values(), - m_Pos(), - m_Notification(TRAY_NOTIFICATION_NONE), - m_TrayContextMenuEnabled(true), - m_IconEnabled(true) -{ -} - -TrayWindow::~TrayWindow() -{ - KillTimer(m_Window, TIMER_ADDTRAYICON); - KillTimer(m_Window, TIMER_TRAYMEASURE); - RemoveTrayIcon(); - - delete m_Bitmap; - delete m_Measure; - - for (size_t i = 0, isize = m_Icons.size(); i < isize; ++i) - { - DestroyIcon(m_Icons[i]); - } - m_Icons.clear(); - - if (m_Window) DestroyWindow(m_Window); -} - -void TrayWindow::Initialize() -{ - WNDCLASS wc = {0}; - wc.lpfnWndProc = (WNDPROC)WndProc; - wc.hInstance = Rainmeter::GetInstance().GetModuleInstance(); - wc.lpszClassName = L"RainmeterTrayClass"; - wc.hIcon = GetIcon(IDI_RAINMETER); - - RegisterClass(&wc); - - m_Window = CreateWindowEx( - WS_EX_TOOLWINDOW, - L"RainmeterTrayClass", - nullptr, - WS_POPUP | WS_DISABLED, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - nullptr, - nullptr, - wc.hInstance, - this); - - SetWindowPos(m_Window, HWND_BOTTOM, 0, 0, 0, 0, ZPOS_FLAGS); -} - -bool TrayWindow::AddTrayIcon() -{ - NOTIFYICONDATA tnid = {sizeof(NOTIFYICONDATA)}; - tnid.hWnd = m_Window; - tnid.uID = IDI_TRAY; - tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; - tnid.uCallbackMessage = WM_TRAY_NOTIFYICON; - tnid.hIcon = m_Icon; - wcsncpy_s(tnid.szTip, APPNAME, _TRUNCATE); - - return (Shell_NotifyIcon(NIM_ADD, &tnid) || GetLastError() != ERROR_TIMEOUT); -} - -bool TrayWindow::IsTrayIconReady() -{ - NOTIFYICONDATA tnid = {sizeof(NOTIFYICONDATA)}; - tnid.hWnd = m_Window; - tnid.uID = IDI_TRAY; - - return Shell_NotifyIcon(NIM_MODIFY, &tnid) != FALSE; -} - -void TrayWindow::TryAddTrayIcon() -{ - if (IsTrayIconReady()) - { - ModifyTrayIcon(0); - return; - } - - if (m_Icon) - { - DestroyIcon(m_Icon); - m_Icon = nullptr; - } - - m_Icon = CreateTrayIcon(0); - - if (!AddTrayIcon()) - { - SetTimer(m_Window, TIMER_ADDTRAYICON, INTERVAL_ADDTRAYICON, nullptr); - } -} - -void TrayWindow::CheckTrayIcon() -{ - if (IsTrayIconReady() || AddTrayIcon()) - { - KillTimer(m_Window, TIMER_ADDTRAYICON); - } -} - -void TrayWindow::RemoveTrayIcon() -{ - NOTIFYICONDATA tnid = {sizeof(NOTIFYICONDATA)}; - tnid.hWnd = m_Window; - tnid.uID = IDI_TRAY; - tnid.uFlags = 0; - - Shell_NotifyIcon(NIM_DELETE, &tnid); - - if (m_Icon) - { - DestroyIcon(m_Icon); - m_Icon = nullptr; - } -} - -void TrayWindow::ModifyTrayIcon(double value) -{ - if (m_Icon) - { - DestroyIcon(m_Icon); - m_Icon = nullptr; - } - - m_Icon = CreateTrayIcon(value); - - NOTIFYICONDATA tnid = {sizeof(NOTIFYICONDATA)}; - tnid.hWnd = m_Window; - tnid.uID = IDI_TRAY; - tnid.uFlags = NIF_ICON; - tnid.hIcon = m_Icon; - - Shell_NotifyIcon(NIM_MODIFY, &tnid); -} - -HICON TrayWindow::CreateTrayIcon(double value) -{ - if (m_Measure != nullptr) - { - if (m_MeterType == TRAY_METER_TYPE_HISTOGRAM) - { - m_Values[m_Pos] = value; - m_Pos = (m_Pos + 1) % TRAYICON_SIZE; - - Bitmap trayBitmap(TRAYICON_SIZE, TRAYICON_SIZE); - Graphics graphics(&trayBitmap); - graphics.SetSmoothingMode(SmoothingModeAntiAlias); - - Point points[TRAYICON_SIZE + 2]; - points[0].X = 0; - points[0].Y = TRAYICON_SIZE; - points[TRAYICON_SIZE + 1].X = TRAYICON_SIZE - 1; - points[TRAYICON_SIZE + 1].Y = TRAYICON_SIZE; - - for (int i = 0; i < TRAYICON_SIZE; ++i) - { - points[i + 1].X = i; - points[i + 1].Y = (int)(TRAYICON_SIZE * (1.0 - m_Values[(m_Pos + i) % TRAYICON_SIZE])); - } - - SolidBrush brush(m_Color1); - graphics.FillRectangle(&brush, 0, 0, TRAYICON_SIZE, TRAYICON_SIZE); - - SolidBrush brush2(m_Color2); - graphics.FillPolygon(&brush2, points, TRAYICON_SIZE + 2); - - HICON icon = nullptr; - trayBitmap.GetHICON(&icon); - return icon; - } - else if (m_MeterType == TRAY_METER_TYPE_BITMAP && (m_Bitmap || !m_Icons.empty())) - { - if (!m_Icons.empty()) - { - size_t frame = 0; - size_t frameCount = m_Icons.size(); - - // Select the correct frame linearly - frame = (size_t)(value * frameCount); - frame = min((frameCount - 1), frame); - - return CopyIcon(m_Icons[frame]); - } - else - { - int frame = 0; - int frameCount = 0; - int newX, newY; - - if (m_Bitmap->GetWidth() > m_Bitmap->GetHeight()) - { - frameCount = m_Bitmap->GetWidth() / TRAYICON_SIZE; - } - else - { - frameCount = m_Bitmap->GetHeight() / TRAYICON_SIZE; - } - - // Select the correct frame linearly - frame = (int)(value * frameCount); - frame = min((frameCount - 1), frame); - - if (m_Bitmap->GetWidth() > m_Bitmap->GetHeight()) - { - newX = frame * TRAYICON_SIZE; - newY = 0; - } - else - { - newX = 0; - newY = frame * TRAYICON_SIZE; - } - - Bitmap trayBitmap(TRAYICON_SIZE, TRAYICON_SIZE); - Graphics graphics(&trayBitmap); - graphics.SetSmoothingMode(SmoothingModeAntiAlias); - - // Blit the image - Rect r(0, 0, TRAYICON_SIZE, TRAYICON_SIZE); - graphics.DrawImage(m_Bitmap, r, newX, newY, TRAYICON_SIZE, TRAYICON_SIZE, UnitPixel); - - HICON icon = nullptr; - trayBitmap.GetHICON(&icon); - return icon; - } - } - } - - // Return the default icon if there is no valid measure - return GetIcon(IDI_TRAY); -} - -void TrayWindow::ShowNotification(TRAY_NOTIFICATION id, const WCHAR* title, const WCHAR* text) -{ - if (m_Notification == TRAY_NOTIFICATION_NONE) - { - NOTIFYICONDATA nid = {sizeof(NOTIFYICONDATA)}; - nid.hWnd = m_Window; - nid.uID = IDI_TRAY; - nid.uFlags = NIF_INFO; - nid.uTimeout = 30000; - nid.dwInfoFlags = NIIF_USER; - wcsncpy_s(nid.szInfoTitle, title, _TRUNCATE); - wcsncpy_s(nid.szInfo, text, _TRUNCATE); - - if (IsWindows7OrGreater()) - { - nid.dwInfoFlags |= NIIF_LARGE_ICON; - nid.hBalloonIcon = GetIcon(IDI_RAINMETER, true); - } - - if (Shell_NotifyIcon(NIM_MODIFY, &nid)) - { - m_Notification = id; - } - } -} - -void TrayWindow::ShowWelcomeNotification() -{ - ShowNotification(TRAY_NOTIFICATION_WELCOME, GetString(ID_STR_WELCOME), GetString(ID_STR_CLICKTOMANAGE)); -} - -void TrayWindow::ShowUpdateNotification(const WCHAR* newVersion) -{ - std::wstring text = GetFormattedString(ID_STR_CLICKTODOWNLOAD, newVersion); - ShowNotification(TRAY_NOTIFICATION_UPDATE, GetString(ID_STR_UPDATEAVAILABLE), text.c_str()); -} - -void TrayWindow::SetTrayIcon(bool enabled) -{ - enabled ? TryAddTrayIcon() : RemoveTrayIcon(); - m_IconEnabled = enabled; - - // Save to Rainmeter.ini. - const std::wstring& iniFile = Rainmeter::GetInstance().GetIniFile(); - WritePrivateProfileString(L"Rainmeter", L"TrayIcon", enabled ? nullptr : L"0", iniFile.c_str()); -} - -void TrayWindow::ReadOptions(ConfigParser& parser) -{ - // Clear old Settings - KillTimer(m_Window, TIMER_ADDTRAYICON); - KillTimer(m_Window, TIMER_TRAYMEASURE); - - delete m_Measure; - m_Measure = nullptr; - - delete m_Bitmap; - m_Bitmap = nullptr; - - std::vector::const_iterator iter = m_Icons.begin(); - for ( ; iter != m_Icons.end(); ++iter) - { - DestroyIcon((*iter)); - } - m_Icons.clear(); - - m_MeterType = TRAY_METER_TYPE_NONE; - - // Read tray settings - m_IconEnabled = parser.ReadBool(L"Rainmeter", L"TrayIcon", true); - if (m_IconEnabled) - { - const std::wstring& measureName = parser.ReadString(L"TrayMeasure", L"Measure", L""); - - if (!measureName.empty()) - { - ConfigParser* oldParser = Rainmeter::GetInstance().GetCurrentParser(); - Rainmeter::GetInstance().SetCurrentParser(&parser); - - m_Measure = Measure::Create(measureName.c_str(), nullptr, L"TrayMeasure"); - if (m_Measure) - { - m_Measure->ReadOptions(parser); - } - - Rainmeter::GetInstance().SetCurrentParser(oldParser); - } - - const WCHAR* type = parser.ReadString(L"TrayMeasure", L"TrayMeter", m_Measure ? L"HISTOGRAM" : L"NONE").c_str(); - if (_wcsicmp(type, L"NONE") == 0) - { - // Use main icon - } - else if (_wcsicmp(type, L"HISTOGRAM") == 0) - { - m_MeterType = TRAY_METER_TYPE_HISTOGRAM; - m_Color1 = parser.ReadColor(L"TrayMeasure", L"TrayColor1", Color::MakeARGB(255, 0, 100, 0)); - m_Color2 = parser.ReadColor(L"TrayMeasure", L"TrayColor2", Color::MakeARGB(255, 0, 255, 0)); - } - else if (_wcsicmp(type, L"BITMAP") == 0) - { - m_MeterType = TRAY_METER_TYPE_BITMAP; - - std::wstring imageName = parser.ReadString(L"TrayMeasure", L"TrayBitmap", L""); - - // Load the bitmaps if defined - if (!imageName.empty()) - { - imageName.insert(0, Rainmeter::GetInstance().GetSkinPath()); - const WCHAR* imagePath = imageName.c_str(); - if (_wcsicmp(imagePath + (imageName.size() - 4), L".ico") == 0) - { - int count = 1; - HICON hIcon = nullptr; - - // Load the icons - do - { - WCHAR buffer[MAX_PATH]; - _snwprintf_s(buffer, _TRUNCATE, imagePath, count++); - - hIcon = (HICON)LoadImage(nullptr, buffer, IMAGE_ICON, TRAYICON_SIZE, TRAYICON_SIZE, LR_LOADFROMFILE); - if (hIcon) m_Icons.push_back(hIcon); - if (wcscmp(imagePath, buffer) == 0) break; - } - while(hIcon != nullptr); - } - - if (m_Icons.empty()) - { - // No icons found so load as bitmap - delete m_Bitmap; - m_Bitmap = new Bitmap(imagePath); - Status status = m_Bitmap->GetLastStatus(); - if (Ok != status) - { - delete m_Bitmap; - m_Bitmap = nullptr; - LogWarningF(L"Bitmap image not found: %s", imagePath); - } - } - } - } - else - { - LogErrorF(L"No such TrayMeter: %s", type); - } - - TryAddTrayIcon(); - - if (m_Measure) - { - SetTimer(m_Window, TIMER_TRAYMEASURE, INTERVAL_TRAYMEASURE, nullptr); // Update the tray once per sec - } - } - else - { - RemoveTrayIcon(); - } -} - -LRESULT CALLBACK TrayWindow::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - TrayWindow* tray = Rainmeter::GetInstance().GetTrayWindow(); - - switch (uMsg) - { - case WM_COMMAND: - switch (wParam) - { - case IDM_MANAGE: - DialogManage::Open(); - break; - - case IDM_ABOUT: - DialogAbout::Open(); - break; - - case IDM_SHOW_HELP: - CommandHandler::RunFile(RAINMETER_HELP); - break; - - case IDM_NEW_VERSION: - CommandHandler::RunFile(RAINMETER_OFFICIAL); - break; - - case IDM_REFRESH: - PostMessage(Rainmeter::GetInstance().GetWindow(), WM_RAINMETER_DELAYED_REFRESH_ALL, (WPARAM)nullptr, (LPARAM)nullptr); - break; - - case IDM_SHOWLOGFILE: - Rainmeter::GetInstance().ShowLogFile(); - break; - - case IDM_STARTLOG: - GetLogger().StartLogFile(); - break; - - case IDM_STOPLOG: - GetLogger().StopLogFile(); - break; - - case IDM_DELETELOGFILE: - GetLogger().DeleteLogFile(); - break; - - case IDM_DEBUGLOG: - Rainmeter::GetInstance().SetDebug(!Rainmeter::GetInstance().GetDebug()); - break; - - case IDM_DISABLEDRAG: - Rainmeter::GetInstance().SetDisableDragging(!Rainmeter::GetInstance().GetDisableDragging()); - break; - - case IDM_EDITCONFIG: - Rainmeter::GetInstance().EditSettings(); - break; - - case IDM_QUIT: - PostQuitMessage(0); - break; - - case IDM_OPENSKINSFOLDER: - Rainmeter::GetInstance().OpenSkinFolder(); - break; - - default: - { - UINT mID = wParam & 0x0FFFF; - - if (mID >= ID_THEME_FIRST && mID <= ID_THEME_LAST) - { - int pos = mID - ID_THEME_FIRST; - - const std::vector& layouts = Rainmeter::GetInstance().GetAllLayouts(); - if (pos >= 0 && pos < (int)layouts.size()) - { - Rainmeter::GetInstance().LoadLayout(layouts[pos]); - } - } - else if (mID >= ID_CONFIG_FIRST && mID <= ID_CONFIG_LAST) - { - Rainmeter::GetInstance().ToggleSkinWithID(mID); - } - else - { - // Forward the message to correct window - int index = (int)(wParam >> 16); - const std::map& windows = Rainmeter::GetInstance().GetAllMeterWindows(); - - if (index < (int)windows.size()) - { - std::map::const_iterator iter = windows.begin(); - for ( ; iter != windows.end(); ++iter) - { - --index; - if (index < 0) - { - MeterWindow* meterWindow = (*iter).second; - SendMessage(meterWindow->GetWindow(), WM_COMMAND, mID, 0); - break; - } - } - } - } - } - break; - } - break; // Don't send WM_COMMANDS any further - - case WM_TRAY_NOTIFYICON: - { - UINT uMouseMsg = (UINT)lParam; - LPCWSTR bang; - - // Check TrayExecute actions - switch (uMouseMsg) - { - case WM_MBUTTONDOWN: - bang = Rainmeter::GetInstance().GetTrayExecuteM().c_str(); - break; - - case WM_RBUTTONDOWN: - bang = Rainmeter::GetInstance().GetTrayExecuteR().c_str(); - break; - - case WM_MBUTTONDBLCLK: - bang = Rainmeter::GetInstance().GetTrayExecuteDM().c_str(); - break; - - case WM_RBUTTONDBLCLK: - bang = Rainmeter::GetInstance().GetTrayExecuteDR().c_str(); - break; - - default: - bang = L""; - break; - } - - if (*bang && - !IsCtrlKeyDown()) // Ctrl is pressed, so only run default action - { - Rainmeter::GetInstance().ExecuteCommand(bang, nullptr); - tray->m_TrayContextMenuEnabled = (uMouseMsg != WM_RBUTTONDOWN); - break; - } - - // Run default UI action - switch (uMouseMsg) - { - case WM_RBUTTONDOWN: - tray->m_TrayContextMenuEnabled = true; - break; - - case WM_RBUTTONUP: - if (tray->m_TrayContextMenuEnabled) - { - POINT pos = System::GetCursorPosition(); - Rainmeter::GetInstance().ShowContextMenu(pos, nullptr); - } - break; - - case WM_LBUTTONUP: - case WM_LBUTTONDBLCLK: - DialogManage::Open(); - break; - - case NIN_BALLOONUSERCLICK: - if (tray->m_Notification == TRAY_NOTIFICATION_WELCOME) - { - DialogManage::Open(); - } - else if (tray->m_Notification == TRAY_NOTIFICATION_UPDATE) - { - CommandHandler::RunFile(RAINMETER_OFFICIAL); - } - tray->m_Notification = TRAY_NOTIFICATION_NONE; - break; - - case NIN_BALLOONHIDE: - case NIN_BALLOONTIMEOUT: - tray->m_Notification = TRAY_NOTIFICATION_NONE; - break; - } - } - break; - - case WM_QUERY_RAINMETER: - if (IsWindow((HWND)lParam)) - { - auto sendCopyData = [&](const std::wstring& data) - { - COPYDATASTRUCT cds; - cds.dwData = wParam; - cds.cbData = (DWORD)((data.length() + 1) * sizeof(WCHAR)); - cds.lpData = (PVOID)data.c_str(); - SendMessage((HWND)lParam, WM_COPYDATA, (WPARAM)hWnd, (LPARAM)&cds); - }; - - switch (wParam) - { - case RAINMETER_QUERY_ID_SKINS_PATH: - sendCopyData(Rainmeter::GetInstance().GetSkinPath()); - return 0; - - case RAINMETER_QUERY_ID_SETTINGS_PATH: - sendCopyData(Rainmeter::GetInstance().GetSettingsPath()); - return 0; - - case RAINMETER_QUERY_ID_PLUGINS_PATH: - sendCopyData(Rainmeter::GetInstance().GetPluginPath()); - return 0; - - case RAINMETER_QUERY_ID_PROGRAM_PATH: - sendCopyData(Rainmeter::GetInstance().GetPath()); - return 0; - - case RAINMETER_QUERY_ID_LOG_PATH: - sendCopyData(GetLogger().GetLogFilePath()); - return 0; - - case RAINMETER_QUERY_ID_CONFIG_EDITOR: - sendCopyData(Rainmeter::GetInstance().GetSkinEditor()); - return 0; - - case RAINMETER_QUERY_ID_IS_DEBUGGING: - { - BOOL debug = Rainmeter::GetInstance().GetDebug(); - SendMessage((HWND)lParam, WM_QUERY_RAINMETER_RETURN, (WPARAM)hWnd, (LPARAM)debug); - } - return 0; - } - } - return 1; - - case WM_COPYDATA: - { - COPYDATASTRUCT* cds = (COPYDATASTRUCT*)lParam; - if (cds->dwData == RAINMETER_QUERY_ID_SKIN_WINDOWHANDLE) - { - LPCWSTR folderPath = (LPCWSTR)cds->lpData; - MeterWindow* mw = Rainmeter::GetInstance().GetMeterWindow(folderPath); - return (mw) ? (LRESULT)mw->GetWindow() : 0; - } - } - return 1; - - case WM_TIMER: - if (wParam == TIMER_TRAYMEASURE) - { - if (tray->m_Measure) - { - tray->m_Measure->Update(); - tray->ModifyTrayIcon(tray->m_Measure->GetRelativeValue()); - } - } - else if (wParam == TIMER_ADDTRAYICON) - { - tray->CheckTrayIcon(); - } - break; - - case WM_DESTROY: - PostQuitMessage(0); - break; - - default: - if (uMsg == WM_TASKBARCREATED) - { - if (tray->IsTrayIconEnabled()) - { - tray->RemoveTrayIcon(); - tray->TryAddTrayIcon(); - } - } - return DefWindowProc(hWnd, uMsg, wParam, lParam); - } - - return 0; -} +/* + Copyright (C) 2004 Kimmo Pekkola + + 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 "TrayWindow.h" +#include "Measure.h" +#include "resource.h" +#include "Litestep.h" +#include "Rainmeter.h" +#include "DialogAbout.h" +#include "DialogManage.h" +#include "System.h" +#include "Error.h" +#include "RainmeterQuery.h" +#include "resource.h" +#include "../Version.h" + +#define RAINMETER_OFFICIAL L"http://rainmeter.net/cms/" +#define RAINMETER_HELP L"http://docs.rainmeter.net/" + +#define ZPOS_FLAGS (SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOSENDCHANGING) + +enum TIMER +{ + TIMER_ADDTRAYICON = 1, + TIMER_TRAYMEASURE = 3 +}; +enum INTERVAL +{ + INTERVAL_ADDTRAYICON = 3000, + INTERVAL_TRAYMEASURE = 1000 +}; + +const UINT WM_TASKBARCREATED = ::RegisterWindowMessage(L"TaskbarCreated"); + +using namespace Gdiplus; + +TrayWindow::TrayWindow() : + m_Icon(), + m_Measure(), + m_MeterType(TRAY_METER_TYPE_HISTOGRAM), + m_Color1(0, 100, 0), + m_Color2(0, 255, 0), + m_Bitmap(), + m_Values(), + m_Pos(), + m_Notification(TRAY_NOTIFICATION_NONE), + m_TrayContextMenuEnabled(true), + m_IconEnabled(true) +{ +} + +TrayWindow::~TrayWindow() +{ + KillTimer(m_Window, TIMER_ADDTRAYICON); + KillTimer(m_Window, TIMER_TRAYMEASURE); + RemoveTrayIcon(); + + delete m_Bitmap; + delete m_Measure; + + for (size_t i = 0, isize = m_Icons.size(); i < isize; ++i) + { + DestroyIcon(m_Icons[i]); + } + m_Icons.clear(); + + if (m_Window) DestroyWindow(m_Window); +} + +void TrayWindow::Initialize() +{ + WNDCLASS wc = {0}; + wc.lpfnWndProc = (WNDPROC)WndProc; + wc.hInstance = GetRainmeter().GetModuleInstance(); + wc.lpszClassName = L"RainmeterTrayClass"; + wc.hIcon = GetIcon(IDI_RAINMETER); + + RegisterClass(&wc); + + m_Window = CreateWindowEx( + WS_EX_TOOLWINDOW, + L"RainmeterTrayClass", + nullptr, + WS_POPUP | WS_DISABLED, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + nullptr, + nullptr, + wc.hInstance, + this); + + SetWindowPos(m_Window, HWND_BOTTOM, 0, 0, 0, 0, ZPOS_FLAGS); +} + +bool TrayWindow::AddTrayIcon() +{ + NOTIFYICONDATA tnid = {sizeof(NOTIFYICONDATA)}; + tnid.hWnd = m_Window; + tnid.uID = IDI_TRAY; + tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; + tnid.uCallbackMessage = WM_TRAY_NOTIFYICON; + tnid.hIcon = m_Icon; + wcsncpy_s(tnid.szTip, APPNAME, _TRUNCATE); + + return (Shell_NotifyIcon(NIM_ADD, &tnid) || GetLastError() != ERROR_TIMEOUT); +} + +bool TrayWindow::IsTrayIconReady() +{ + NOTIFYICONDATA tnid = {sizeof(NOTIFYICONDATA)}; + tnid.hWnd = m_Window; + tnid.uID = IDI_TRAY; + + return Shell_NotifyIcon(NIM_MODIFY, &tnid) != FALSE; +} + +void TrayWindow::TryAddTrayIcon() +{ + if (IsTrayIconReady()) + { + ModifyTrayIcon(0); + return; + } + + if (m_Icon) + { + DestroyIcon(m_Icon); + m_Icon = nullptr; + } + + m_Icon = CreateTrayIcon(0); + + if (!AddTrayIcon()) + { + SetTimer(m_Window, TIMER_ADDTRAYICON, INTERVAL_ADDTRAYICON, nullptr); + } +} + +void TrayWindow::CheckTrayIcon() +{ + if (IsTrayIconReady() || AddTrayIcon()) + { + KillTimer(m_Window, TIMER_ADDTRAYICON); + } +} + +void TrayWindow::RemoveTrayIcon() +{ + NOTIFYICONDATA tnid = {sizeof(NOTIFYICONDATA)}; + tnid.hWnd = m_Window; + tnid.uID = IDI_TRAY; + tnid.uFlags = 0; + + Shell_NotifyIcon(NIM_DELETE, &tnid); + + if (m_Icon) + { + DestroyIcon(m_Icon); + m_Icon = nullptr; + } +} + +void TrayWindow::ModifyTrayIcon(double value) +{ + if (m_Icon) + { + DestroyIcon(m_Icon); + m_Icon = nullptr; + } + + m_Icon = CreateTrayIcon(value); + + NOTIFYICONDATA tnid = {sizeof(NOTIFYICONDATA)}; + tnid.hWnd = m_Window; + tnid.uID = IDI_TRAY; + tnid.uFlags = NIF_ICON; + tnid.hIcon = m_Icon; + + Shell_NotifyIcon(NIM_MODIFY, &tnid); +} + +HICON TrayWindow::CreateTrayIcon(double value) +{ + if (m_Measure != nullptr) + { + if (m_MeterType == TRAY_METER_TYPE_HISTOGRAM) + { + m_Values[m_Pos] = value; + m_Pos = (m_Pos + 1) % TRAYICON_SIZE; + + Bitmap trayBitmap(TRAYICON_SIZE, TRAYICON_SIZE); + Graphics graphics(&trayBitmap); + graphics.SetSmoothingMode(SmoothingModeAntiAlias); + + Point points[TRAYICON_SIZE + 2]; + points[0].X = 0; + points[0].Y = TRAYICON_SIZE; + points[TRAYICON_SIZE + 1].X = TRAYICON_SIZE - 1; + points[TRAYICON_SIZE + 1].Y = TRAYICON_SIZE; + + for (int i = 0; i < TRAYICON_SIZE; ++i) + { + points[i + 1].X = i; + points[i + 1].Y = (int)(TRAYICON_SIZE * (1.0 - m_Values[(m_Pos + i) % TRAYICON_SIZE])); + } + + SolidBrush brush(m_Color1); + graphics.FillRectangle(&brush, 0, 0, TRAYICON_SIZE, TRAYICON_SIZE); + + SolidBrush brush2(m_Color2); + graphics.FillPolygon(&brush2, points, TRAYICON_SIZE + 2); + + HICON icon = nullptr; + trayBitmap.GetHICON(&icon); + return icon; + } + else if (m_MeterType == TRAY_METER_TYPE_BITMAP && (m_Bitmap || !m_Icons.empty())) + { + if (!m_Icons.empty()) + { + size_t frame = 0; + size_t frameCount = m_Icons.size(); + + // Select the correct frame linearly + frame = (size_t)(value * frameCount); + frame = min((frameCount - 1), frame); + + return CopyIcon(m_Icons[frame]); + } + else + { + int frame = 0; + int frameCount = 0; + int newX, newY; + + if (m_Bitmap->GetWidth() > m_Bitmap->GetHeight()) + { + frameCount = m_Bitmap->GetWidth() / TRAYICON_SIZE; + } + else + { + frameCount = m_Bitmap->GetHeight() / TRAYICON_SIZE; + } + + // Select the correct frame linearly + frame = (int)(value * frameCount); + frame = min((frameCount - 1), frame); + + if (m_Bitmap->GetWidth() > m_Bitmap->GetHeight()) + { + newX = frame * TRAYICON_SIZE; + newY = 0; + } + else + { + newX = 0; + newY = frame * TRAYICON_SIZE; + } + + Bitmap trayBitmap(TRAYICON_SIZE, TRAYICON_SIZE); + Graphics graphics(&trayBitmap); + graphics.SetSmoothingMode(SmoothingModeAntiAlias); + + // Blit the image + Rect r(0, 0, TRAYICON_SIZE, TRAYICON_SIZE); + graphics.DrawImage(m_Bitmap, r, newX, newY, TRAYICON_SIZE, TRAYICON_SIZE, UnitPixel); + + HICON icon = nullptr; + trayBitmap.GetHICON(&icon); + return icon; + } + } + } + + // Return the default icon if there is no valid measure + return GetIcon(IDI_TRAY); +} + +void TrayWindow::ShowNotification(TRAY_NOTIFICATION id, const WCHAR* title, const WCHAR* text) +{ + if (m_Notification == TRAY_NOTIFICATION_NONE) + { + NOTIFYICONDATA nid = {sizeof(NOTIFYICONDATA)}; + nid.hWnd = m_Window; + nid.uID = IDI_TRAY; + nid.uFlags = NIF_INFO; + nid.uTimeout = 30000; + nid.dwInfoFlags = NIIF_USER; + wcsncpy_s(nid.szInfoTitle, title, _TRUNCATE); + wcsncpy_s(nid.szInfo, text, _TRUNCATE); + + if (IsWindows7OrGreater()) + { + nid.dwInfoFlags |= NIIF_LARGE_ICON; + nid.hBalloonIcon = GetIcon(IDI_RAINMETER, true); + } + + if (Shell_NotifyIcon(NIM_MODIFY, &nid)) + { + m_Notification = id; + } + } +} + +void TrayWindow::ShowWelcomeNotification() +{ + ShowNotification(TRAY_NOTIFICATION_WELCOME, GetString(ID_STR_WELCOME), GetString(ID_STR_CLICKTOMANAGE)); +} + +void TrayWindow::ShowUpdateNotification(const WCHAR* newVersion) +{ + std::wstring text = GetFormattedString(ID_STR_CLICKTODOWNLOAD, newVersion); + ShowNotification(TRAY_NOTIFICATION_UPDATE, GetString(ID_STR_UPDATEAVAILABLE), text.c_str()); +} + +void TrayWindow::SetTrayIcon(bool enabled) +{ + enabled ? TryAddTrayIcon() : RemoveTrayIcon(); + m_IconEnabled = enabled; + + // Save to Rainmeter.ini. + const std::wstring& iniFile = GetRainmeter().GetIniFile(); + WritePrivateProfileString(L"Rainmeter", L"TrayIcon", enabled ? nullptr : L"0", iniFile.c_str()); +} + +void TrayWindow::ReadOptions(ConfigParser& parser) +{ + // Clear old Settings + KillTimer(m_Window, TIMER_ADDTRAYICON); + KillTimer(m_Window, TIMER_TRAYMEASURE); + + delete m_Measure; + m_Measure = nullptr; + + delete m_Bitmap; + m_Bitmap = nullptr; + + std::vector::const_iterator iter = m_Icons.begin(); + for ( ; iter != m_Icons.end(); ++iter) + { + DestroyIcon((*iter)); + } + m_Icons.clear(); + + m_MeterType = TRAY_METER_TYPE_NONE; + + // Read tray settings + m_IconEnabled = parser.ReadBool(L"Rainmeter", L"TrayIcon", true); + if (m_IconEnabled) + { + const std::wstring& measureName = parser.ReadString(L"TrayMeasure", L"Measure", L""); + + if (!measureName.empty()) + { + ConfigParser* oldParser = GetRainmeter().GetCurrentParser(); + GetRainmeter().SetCurrentParser(&parser); + + m_Measure = Measure::Create(measureName.c_str(), nullptr, L"TrayMeasure"); + if (m_Measure) + { + m_Measure->ReadOptions(parser); + } + + GetRainmeter().SetCurrentParser(oldParser); + } + + const WCHAR* type = parser.ReadString(L"TrayMeasure", L"TrayMeter", m_Measure ? L"HISTOGRAM" : L"NONE").c_str(); + if (_wcsicmp(type, L"NONE") == 0) + { + // Use main icon + } + else if (_wcsicmp(type, L"HISTOGRAM") == 0) + { + m_MeterType = TRAY_METER_TYPE_HISTOGRAM; + m_Color1 = parser.ReadColor(L"TrayMeasure", L"TrayColor1", Color::MakeARGB(255, 0, 100, 0)); + m_Color2 = parser.ReadColor(L"TrayMeasure", L"TrayColor2", Color::MakeARGB(255, 0, 255, 0)); + } + else if (_wcsicmp(type, L"BITMAP") == 0) + { + m_MeterType = TRAY_METER_TYPE_BITMAP; + + std::wstring imageName = parser.ReadString(L"TrayMeasure", L"TrayBitmap", L""); + + // Load the bitmaps if defined + if (!imageName.empty()) + { + imageName.insert(0, GetRainmeter().GetSkinPath()); + const WCHAR* imagePath = imageName.c_str(); + if (_wcsicmp(imagePath + (imageName.size() - 4), L".ico") == 0) + { + int count = 1; + HICON hIcon = nullptr; + + // Load the icons + do + { + WCHAR buffer[MAX_PATH]; + _snwprintf_s(buffer, _TRUNCATE, imagePath, count++); + + hIcon = (HICON)LoadImage(nullptr, buffer, IMAGE_ICON, TRAYICON_SIZE, TRAYICON_SIZE, LR_LOADFROMFILE); + if (hIcon) m_Icons.push_back(hIcon); + if (wcscmp(imagePath, buffer) == 0) break; + } + while(hIcon != nullptr); + } + + if (m_Icons.empty()) + { + // No icons found so load as bitmap + delete m_Bitmap; + m_Bitmap = new Bitmap(imagePath); + Status status = m_Bitmap->GetLastStatus(); + if (Ok != status) + { + delete m_Bitmap; + m_Bitmap = nullptr; + LogWarningF(L"Bitmap image not found: %s", imagePath); + } + } + } + } + else + { + LogErrorF(L"No such TrayMeter: %s", type); + } + + TryAddTrayIcon(); + + if (m_Measure) + { + SetTimer(m_Window, TIMER_TRAYMEASURE, INTERVAL_TRAYMEASURE, nullptr); // Update the tray once per sec + } + } + else + { + RemoveTrayIcon(); + } +} + +LRESULT CALLBACK TrayWindow::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + TrayWindow* tray = GetRainmeter().GetTrayWindow(); + + switch (uMsg) + { + case WM_COMMAND: + switch (wParam) + { + case IDM_MANAGE: + DialogManage::Open(); + break; + + case IDM_ABOUT: + DialogAbout::Open(); + break; + + case IDM_SHOW_HELP: + CommandHandler::RunFile(RAINMETER_HELP); + break; + + case IDM_NEW_VERSION: + CommandHandler::RunFile(RAINMETER_OFFICIAL); + break; + + case IDM_REFRESH: + PostMessage(GetRainmeter().GetWindow(), WM_RAINMETER_DELAYED_REFRESH_ALL, (WPARAM)nullptr, (LPARAM)nullptr); + break; + + case IDM_SHOWLOGFILE: + GetRainmeter().ShowLogFile(); + break; + + case IDM_STARTLOG: + GetLogger().StartLogFile(); + break; + + case IDM_STOPLOG: + GetLogger().StopLogFile(); + break; + + case IDM_DELETELOGFILE: + GetLogger().DeleteLogFile(); + break; + + case IDM_DEBUGLOG: + GetRainmeter().SetDebug(!GetRainmeter().GetDebug()); + break; + + case IDM_DISABLEDRAG: + GetRainmeter().SetDisableDragging(!GetRainmeter().GetDisableDragging()); + break; + + case IDM_EDITCONFIG: + GetRainmeter().EditSettings(); + break; + + case IDM_QUIT: + PostQuitMessage(0); + break; + + case IDM_OPENSKINSFOLDER: + GetRainmeter().OpenSkinFolder(); + break; + + default: + { + UINT mID = wParam & 0x0FFFF; + + if (mID >= ID_THEME_FIRST && mID <= ID_THEME_LAST) + { + int pos = mID - ID_THEME_FIRST; + + const std::vector& layouts = GetRainmeter().GetAllLayouts(); + if (pos >= 0 && pos < (int)layouts.size()) + { + GetRainmeter().LoadLayout(layouts[pos]); + } + } + else if (mID >= ID_CONFIG_FIRST && mID <= ID_CONFIG_LAST) + { + GetRainmeter().ToggleSkinWithID(mID); + } + else + { + // Forward the message to correct window + int index = (int)(wParam >> 16); + const std::map& windows = GetRainmeter().GetAllMeterWindows(); + + if (index < (int)windows.size()) + { + std::map::const_iterator iter = windows.begin(); + for ( ; iter != windows.end(); ++iter) + { + --index; + if (index < 0) + { + MeterWindow* meterWindow = (*iter).second; + SendMessage(meterWindow->GetWindow(), WM_COMMAND, mID, 0); + break; + } + } + } + } + } + break; + } + break; // Don't send WM_COMMANDS any further + + case WM_TRAY_NOTIFYICON: + { + UINT uMouseMsg = (UINT)lParam; + LPCWSTR bang; + + // Check TrayExecute actions + switch (uMouseMsg) + { + case WM_MBUTTONDOWN: + bang = GetRainmeter().GetTrayExecuteM().c_str(); + break; + + case WM_RBUTTONDOWN: + bang = GetRainmeter().GetTrayExecuteR().c_str(); + break; + + case WM_MBUTTONDBLCLK: + bang = GetRainmeter().GetTrayExecuteDM().c_str(); + break; + + case WM_RBUTTONDBLCLK: + bang = GetRainmeter().GetTrayExecuteDR().c_str(); + break; + + default: + bang = L""; + break; + } + + if (*bang && + !IsCtrlKeyDown()) // Ctrl is pressed, so only run default action + { + GetRainmeter().ExecuteCommand(bang, nullptr); + tray->m_TrayContextMenuEnabled = (uMouseMsg != WM_RBUTTONDOWN); + break; + } + + // Run default UI action + switch (uMouseMsg) + { + case WM_RBUTTONDOWN: + tray->m_TrayContextMenuEnabled = true; + break; + + case WM_RBUTTONUP: + if (tray->m_TrayContextMenuEnabled) + { + POINT pos = System::GetCursorPosition(); + GetRainmeter().ShowContextMenu(pos, nullptr); + } + break; + + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: + DialogManage::Open(); + break; + + case NIN_BALLOONUSERCLICK: + if (tray->m_Notification == TRAY_NOTIFICATION_WELCOME) + { + DialogManage::Open(); + } + else if (tray->m_Notification == TRAY_NOTIFICATION_UPDATE) + { + CommandHandler::RunFile(RAINMETER_OFFICIAL); + } + tray->m_Notification = TRAY_NOTIFICATION_NONE; + break; + + case NIN_BALLOONHIDE: + case NIN_BALLOONTIMEOUT: + tray->m_Notification = TRAY_NOTIFICATION_NONE; + break; + } + } + break; + + case WM_QUERY_RAINMETER: + if (IsWindow((HWND)lParam)) + { + auto sendCopyData = [&](const std::wstring& data) + { + COPYDATASTRUCT cds; + cds.dwData = wParam; + cds.cbData = (DWORD)((data.length() + 1) * sizeof(WCHAR)); + cds.lpData = (PVOID)data.c_str(); + SendMessage((HWND)lParam, WM_COPYDATA, (WPARAM)hWnd, (LPARAM)&cds); + }; + + switch (wParam) + { + case RAINMETER_QUERY_ID_SKINS_PATH: + sendCopyData(GetRainmeter().GetSkinPath()); + return 0; + + case RAINMETER_QUERY_ID_SETTINGS_PATH: + sendCopyData(GetRainmeter().GetSettingsPath()); + return 0; + + case RAINMETER_QUERY_ID_PLUGINS_PATH: + sendCopyData(GetRainmeter().GetPluginPath()); + return 0; + + case RAINMETER_QUERY_ID_PROGRAM_PATH: + sendCopyData(GetRainmeter().GetPath()); + return 0; + + case RAINMETER_QUERY_ID_LOG_PATH: + sendCopyData(GetLogger().GetLogFilePath()); + return 0; + + case RAINMETER_QUERY_ID_CONFIG_EDITOR: + sendCopyData(GetRainmeter().GetSkinEditor()); + return 0; + + case RAINMETER_QUERY_ID_IS_DEBUGGING: + { + BOOL debug = GetRainmeter().GetDebug(); + SendMessage((HWND)lParam, WM_QUERY_RAINMETER_RETURN, (WPARAM)hWnd, (LPARAM)debug); + } + return 0; + } + } + return 1; + + case WM_COPYDATA: + { + COPYDATASTRUCT* cds = (COPYDATASTRUCT*)lParam; + if (cds->dwData == RAINMETER_QUERY_ID_SKIN_WINDOWHANDLE) + { + LPCWSTR folderPath = (LPCWSTR)cds->lpData; + MeterWindow* mw = GetRainmeter().GetMeterWindow(folderPath); + return (mw) ? (LRESULT)mw->GetWindow() : 0; + } + } + return 1; + + case WM_TIMER: + if (wParam == TIMER_TRAYMEASURE) + { + if (tray->m_Measure) + { + tray->m_Measure->Update(); + tray->ModifyTrayIcon(tray->m_Measure->GetRelativeValue()); + } + } + else if (wParam == TIMER_ADDTRAYICON) + { + tray->CheckTrayIcon(); + } + break; + + case WM_DESTROY: + PostQuitMessage(0); + break; + + default: + if (uMsg == WM_TASKBARCREATED) + { + if (tray->IsTrayIconEnabled()) + { + tray->RemoveTrayIcon(); + tray->TryAddTrayIcon(); + } + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + return 0; +} diff --git a/Library/UpdateCheck.cpp b/Library/UpdateCheck.cpp index bffdf565..fdf9fffd 100644 --- a/Library/UpdateCheck.cpp +++ b/Library/UpdateCheck.cpp @@ -1,97 +1,97 @@ -/* - Copyright (C) 2004 Kimmo Pekkola - - 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 "Litestep.h" -#include "Rainmeter.h" -#include "TrayWindow.h" -#include "../Version.h" - -void CheckVersion(void* dummy) -{ - HINTERNET hRootHandle = InternetOpen( - L"Rainmeter", - INTERNET_OPEN_TYPE_PRECONFIG, - nullptr, - nullptr, - 0); - - if (hRootHandle == nullptr) - { - return; - } - - HINTERNET hUrlDump = InternetOpenUrl( - hRootHandle, L"http://rainmeter.github.io/rainmeter/release", nullptr, 0, INTERNET_FLAG_RESYNCHRONIZE, 0); - if (hUrlDump) - { - DWORD dwSize; - char urlData[16] = {0}; - if (InternetReadFile(hUrlDump, (LPVOID)urlData, sizeof(urlData) - 1, &dwSize)) - { - auto parseVersion = [](const WCHAR* str)->int - { - int version = _wtoi(str) * 1000000; - const WCHAR* pos = wcschr(str, L'.'); - if (pos) - { - ++pos; // Skip . - version += _wtoi(pos) * 1000; - - pos = wcschr(pos, '.'); - if (pos) - { - ++pos; // Skip . - version += _wtoi(pos); - } - } - return version; - }; - - std::wstring tmpSz = StringUtil::Widen(urlData); - const WCHAR* version = tmpSz.c_str(); - - int availableVersion = parseVersion(version); - if (availableVersion > RAINMETER_VERSION || - (revision_beta && availableVersion == RAINMETER_VERSION)) - { - Rainmeter::GetInstance().SetNewVersion(); - - WCHAR buffer[32]; - const WCHAR* dataFile = Rainmeter::GetInstance().GetDataFile().c_str(); - GetPrivateProfileString(L"Rainmeter", L"LastCheck", L"0", buffer, _countof(buffer), dataFile); - - // Show tray notification only once per new version - int lastVersion = parseVersion(buffer); - if (availableVersion > lastVersion) - { - Rainmeter::GetInstance().GetTrayWindow()->ShowUpdateNotification(version); - WritePrivateProfileString(L"Rainmeter", L"LastCheck", version, dataFile); - } - } - } - InternetCloseHandle(hUrlDump); - } - - InternetCloseHandle(hRootHandle); -} - -void CheckUpdate() -{ - _beginthread(CheckVersion, 0, nullptr); -} +/* + Copyright (C) 2004 Kimmo Pekkola + + 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 "Litestep.h" +#include "Rainmeter.h" +#include "TrayWindow.h" +#include "../Version.h" + +void CheckVersion(void* dummy) +{ + HINTERNET hRootHandle = InternetOpen( + L"Rainmeter", + INTERNET_OPEN_TYPE_PRECONFIG, + nullptr, + nullptr, + 0); + + if (hRootHandle == nullptr) + { + return; + } + + HINTERNET hUrlDump = InternetOpenUrl( + hRootHandle, L"http://rainmeter.github.io/rainmeter/release", nullptr, 0, INTERNET_FLAG_RESYNCHRONIZE, 0); + if (hUrlDump) + { + DWORD dwSize; + char urlData[16] = {0}; + if (InternetReadFile(hUrlDump, (LPVOID)urlData, sizeof(urlData) - 1, &dwSize)) + { + auto parseVersion = [](const WCHAR* str)->int + { + int version = _wtoi(str) * 1000000; + const WCHAR* pos = wcschr(str, L'.'); + if (pos) + { + ++pos; // Skip . + version += _wtoi(pos) * 1000; + + pos = wcschr(pos, '.'); + if (pos) + { + ++pos; // Skip . + version += _wtoi(pos); + } + } + return version; + }; + + std::wstring tmpSz = StringUtil::Widen(urlData); + const WCHAR* version = tmpSz.c_str(); + + int availableVersion = parseVersion(version); + if (availableVersion > RAINMETER_VERSION || + (revision_beta && availableVersion == RAINMETER_VERSION)) + { + GetRainmeter().SetNewVersion(); + + WCHAR buffer[32]; + const WCHAR* dataFile = GetRainmeter().GetDataFile().c_str(); + GetPrivateProfileString(L"Rainmeter", L"LastCheck", L"0", buffer, _countof(buffer), dataFile); + + // Show tray notification only once per new version + int lastVersion = parseVersion(buffer); + if (availableVersion > lastVersion) + { + GetRainmeter().GetTrayWindow()->ShowUpdateNotification(version); + WritePrivateProfileString(L"Rainmeter", L"LastCheck", version, dataFile); + } + } + } + InternetCloseHandle(hUrlDump); + } + + InternetCloseHandle(hRootHandle); +} + +void CheckUpdate() +{ + _beginthread(CheckVersion, 0, nullptr); +} diff --git a/Library/lua/glue/LuaMeterWindow.cpp b/Library/lua/glue/LuaMeterWindow.cpp index 7658b224..ab8bc204 100644 --- a/Library/lua/glue/LuaMeterWindow.cpp +++ b/Library/lua/glue/LuaMeterWindow.cpp @@ -1,240 +1,240 @@ -/* - Copyright (C) 2010 Matt King, Birunthan Mohanathas - - 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 "../LuaManager.h" -#include "../../Rainmeter.h" -#include "../../MeterWindow.h" -#include "../../MeterString.h" - -#define DECLARE_SELF(L) \ - void* selfData = lua_touserdata(L, 1); \ - if (!selfData) return 0; \ - MeterWindow* self = *(MeterWindow**)selfData; - -static int Bang(lua_State* L) -{ - DECLARE_SELF(L) - ConfigParser& parser = self->GetParser(); - - std::wstring bang = LuaManager::ToWide(2); - - int top = lua_gettop(L); - if (top == 2) // 1 argument - { - parser.ReplaceVariables(bang); - Rainmeter::GetInstance().ExecuteCommand(bang.c_str(), self); - } - else - { - const WCHAR* bangSz = bang.c_str(); - if (*bangSz == L'!') - { - ++bangSz; // Skip "!" - std::vector args; - for (int i = 3; i <= top; ++i) - { - std::wstring tmpSz = LuaManager::ToWide(i); - parser.ReplaceVariables(tmpSz); - args.push_back(tmpSz); - } - - Rainmeter::GetInstance().ExecuteBang(bangSz, args, self); - } - } - - return 0; -} - -static int GetMeter(lua_State* L) -{ - DECLARE_SELF(L) - const std::wstring meterName = LuaManager::ToWide(2); - - Meter* meter = self->GetMeter(meterName); - if (meter) - { - *(Meter**)lua_newuserdata(L, sizeof(Meter*)) = meter; - lua_getglobal(L, "Meter"); - lua_setmetatable(L, -2); - } - else - { - lua_pushnil(L); - } - - return 1; -} - -static int GetMeasure(lua_State* L) -{ - DECLARE_SELF(L) - const std::wstring measureName = LuaManager::ToWide(2); - - Measure* measure = self->GetMeasure(measureName); - if (measure) - { - *(Measure**)lua_newuserdata(L, sizeof(Measure*)) = measure; - lua_getglobal(L, "Measure"); - lua_setmetatable(L, -2); - } - else - { - lua_pushnil(L); - } - - return 1; -} - -static int GetVariable(lua_State* L) -{ - DECLARE_SELF(L) - - const std::wstring name = LuaManager::ToWide(2); - const std::wstring* value = self->GetParser().GetVariable(name); - if (value) - { - LuaManager::PushWide(*value); - } - else if (lua_gettop(L) >= 3) - { - lua_pushvalue(L, 3); - } - else - { - lua_pushnil(L); - } - - return 1; -} - -static int ReplaceVariables(lua_State* L) -{ - DECLARE_SELF(L) - std::wstring strTmp = LuaManager::ToWide(2); - - self->GetParser().ReplaceVariables(strTmp); - self->GetParser().ReplaceMeasures(strTmp); - LuaManager::PushWide(strTmp); - - return 1; -} - -static int ParseFormula(lua_State* L) -{ - DECLARE_SELF(L) - std::wstring strTmp = LuaManager::ToWide(2); - - double result; - if (!self->GetParser().ParseFormula(strTmp, &result)) - { - result = lua_tonumber(L, 2); - } - - lua_pushnumber(L, result); - - return 1; -} - -static int MoveWindow(lua_State* L) -{ - DECLARE_SELF(L) - int x = (int)lua_tonumber(L, 2); - int y = (int)lua_tonumber(L, 3); - self->MoveWindow(x, y); - - return 0; -} - -static int FadeWindow(lua_State* L) -{ - DECLARE_SELF(L) - int from = (int)lua_tonumber(L, 2); - int to = (int)lua_tonumber(L, 3); - self->FadeWindow(from, to); - - return 0; -} - -static int GetW(lua_State* L) -{ - DECLARE_SELF(L) - lua_pushnumber(L, self->GetW()); - - return 1; -} - -static int GetH(lua_State* L) -{ - DECLARE_SELF(L) - lua_pushnumber(L, self->GetH()); - - return 1; -} - -static int GetX(lua_State* L) -{ - DECLARE_SELF(L) - lua_pushnumber(L, self->GetX()); - - return 1; -} - -static int GetY(lua_State* L) -{ - DECLARE_SELF(L) - lua_pushnumber(L, self->GetY()); - - return 1; -} - -static int MakePathAbsolute(lua_State* L) -{ - DECLARE_SELF(L) - std::wstring path = LuaManager::ToWide(2); - self->MakePathAbsolute(path); - LuaManager::PushWide(path); - - return 1; -} - -void LuaManager::RegisterMeterWindow(lua_State* L) -{ - const luaL_Reg functions[] = - { - { "Bang", Bang }, - { "GetMeter", GetMeter }, - { "GetMeasure", GetMeasure }, - { "GetVariable", GetVariable }, - { "ReplaceVariables", ReplaceVariables }, - { "ParseFormula", ParseFormula }, - { "MoveWindow", MoveWindow }, - { "FadeWindow", FadeWindow }, - { "GetW", GetW }, - { "GetH", GetH }, - { "GetX", GetX }, - { "GetY", GetY }, - { "MakePathAbsolute", MakePathAbsolute }, - { nullptr, nullptr } - }; - - luaL_register(L, "MeterWindow", functions); - lua_pushvalue(L, -1); - lua_setfield(L, -2, "__index"); - lua_pop(L, 1); -} +/* + Copyright (C) 2010 Matt King, Birunthan Mohanathas + + 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 "../LuaManager.h" +#include "../../Rainmeter.h" +#include "../../MeterWindow.h" +#include "../../MeterString.h" + +#define DECLARE_SELF(L) \ + void* selfData = lua_touserdata(L, 1); \ + if (!selfData) return 0; \ + MeterWindow* self = *(MeterWindow**)selfData; + +static int Bang(lua_State* L) +{ + DECLARE_SELF(L) + ConfigParser& parser = self->GetParser(); + + std::wstring bang = LuaManager::ToWide(2); + + int top = lua_gettop(L); + if (top == 2) // 1 argument + { + parser.ReplaceVariables(bang); + GetRainmeter().ExecuteCommand(bang.c_str(), self); + } + else + { + const WCHAR* bangSz = bang.c_str(); + if (*bangSz == L'!') + { + ++bangSz; // Skip "!" + std::vector args; + for (int i = 3; i <= top; ++i) + { + std::wstring tmpSz = LuaManager::ToWide(i); + parser.ReplaceVariables(tmpSz); + args.push_back(tmpSz); + } + + GetRainmeter().ExecuteBang(bangSz, args, self); + } + } + + return 0; +} + +static int GetMeter(lua_State* L) +{ + DECLARE_SELF(L) + const std::wstring meterName = LuaManager::ToWide(2); + + Meter* meter = self->GetMeter(meterName); + if (meter) + { + *(Meter**)lua_newuserdata(L, sizeof(Meter*)) = meter; + lua_getglobal(L, "Meter"); + lua_setmetatable(L, -2); + } + else + { + lua_pushnil(L); + } + + return 1; +} + +static int GetMeasure(lua_State* L) +{ + DECLARE_SELF(L) + const std::wstring measureName = LuaManager::ToWide(2); + + Measure* measure = self->GetMeasure(measureName); + if (measure) + { + *(Measure**)lua_newuserdata(L, sizeof(Measure*)) = measure; + lua_getglobal(L, "Measure"); + lua_setmetatable(L, -2); + } + else + { + lua_pushnil(L); + } + + return 1; +} + +static int GetVariable(lua_State* L) +{ + DECLARE_SELF(L) + + const std::wstring name = LuaManager::ToWide(2); + const std::wstring* value = self->GetParser().GetVariable(name); + if (value) + { + LuaManager::PushWide(*value); + } + else if (lua_gettop(L) >= 3) + { + lua_pushvalue(L, 3); + } + else + { + lua_pushnil(L); + } + + return 1; +} + +static int ReplaceVariables(lua_State* L) +{ + DECLARE_SELF(L) + std::wstring strTmp = LuaManager::ToWide(2); + + self->GetParser().ReplaceVariables(strTmp); + self->GetParser().ReplaceMeasures(strTmp); + LuaManager::PushWide(strTmp); + + return 1; +} + +static int ParseFormula(lua_State* L) +{ + DECLARE_SELF(L) + std::wstring strTmp = LuaManager::ToWide(2); + + double result; + if (!self->GetParser().ParseFormula(strTmp, &result)) + { + result = lua_tonumber(L, 2); + } + + lua_pushnumber(L, result); + + return 1; +} + +static int MoveWindow(lua_State* L) +{ + DECLARE_SELF(L) + int x = (int)lua_tonumber(L, 2); + int y = (int)lua_tonumber(L, 3); + self->MoveWindow(x, y); + + return 0; +} + +static int FadeWindow(lua_State* L) +{ + DECLARE_SELF(L) + int from = (int)lua_tonumber(L, 2); + int to = (int)lua_tonumber(L, 3); + self->FadeWindow(from, to); + + return 0; +} + +static int GetW(lua_State* L) +{ + DECLARE_SELF(L) + lua_pushnumber(L, self->GetW()); + + return 1; +} + +static int GetH(lua_State* L) +{ + DECLARE_SELF(L) + lua_pushnumber(L, self->GetH()); + + return 1; +} + +static int GetX(lua_State* L) +{ + DECLARE_SELF(L) + lua_pushnumber(L, self->GetX()); + + return 1; +} + +static int GetY(lua_State* L) +{ + DECLARE_SELF(L) + lua_pushnumber(L, self->GetY()); + + return 1; +} + +static int MakePathAbsolute(lua_State* L) +{ + DECLARE_SELF(L) + std::wstring path = LuaManager::ToWide(2); + self->MakePathAbsolute(path); + LuaManager::PushWide(path); + + return 1; +} + +void LuaManager::RegisterMeterWindow(lua_State* L) +{ + const luaL_Reg functions[] = + { + { "Bang", Bang }, + { "GetMeter", GetMeter }, + { "GetMeasure", GetMeasure }, + { "GetVariable", GetVariable }, + { "ReplaceVariables", ReplaceVariables }, + { "ParseFormula", ParseFormula }, + { "MoveWindow", MoveWindow }, + { "FadeWindow", FadeWindow }, + { "GetW", GetW }, + { "GetH", GetH }, + { "GetX", GetX }, + { "GetY", GetY }, + { "MakePathAbsolute", MakePathAbsolute }, + { nullptr, nullptr } + }; + + luaL_register(L, "MeterWindow", functions); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + lua_pop(L, 1); +}