rainmeter-studio/Library/Rainmeter.cpp

1691 lines
40 KiB
C++

/*
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<std::wstring> 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<int, int>::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<std::wstring, MeterWindow*>::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<std::wstring, MeterWindow*>::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<std::wstring, MeterWindow*>::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<std::wstring, MeterWindow*>::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<int, MeterWindow*>& windows, const std::wstring& group)
{
std::map<std::wstring, MeterWindow*>::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<int, MeterWindow*>(GetLoadOrder((*iter).first), mw));
}
}
}
void Rainmeter::SetLoadOrder(int folderIndex, int order)
{
std::multimap<int, int>::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<int, int>(order, folderIndex));
}
int Rainmeter::GetLoadOrder(const std::wstring& folderPath)
{
const int index = m_SkinRegistry.FindFolderIndex(folderPath);
if (index != -1)
{
std::multimap<int, int>::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<std::wstring>& 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<int, MeterWindow*> windows;
GetMeterWindowsByLoadOrder(windows);
// Prepare the helper window
System::PrepareHelperWindow();
// Refresh all
std::multimap<int, MeterWindow*>::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<MonitorInfo>& 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<UINT, RECT>::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();
}