/* 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/MenuTemplate.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 "resource.h" #include "UpdateCheck.h" #include "../Version.h" #include "DisableThreadLibraryCalls.h" // contains DllMain entry point using namespace Gdiplus; enum TIMER { TIMER_NETSTATS = 1 }; enum INTERVAL { INTERVAL_NETSTATS = 120000 }; Rainmeter* g_Rainmeter; // The module /* ** Initializes Rainmeter. ** */ int RainmeterMain(LPWSTR cmdLine) { // Avoid loading a dll from current directory SetDllDirectory(L""); const WCHAR* layout = nullptr; if (cmdLine[0] == L'!' || cmdLine[0] == L'[') { HWND wnd = FindWindow(RAINMETER_CLASS_NAME, RAINMETER_WINDOW_NAME); if (wnd) { // Deliver bang to existing Rainmeter instance COPYDATASTRUCT cds; cds.dwData = 1; cds.cbData = (DWORD)((wcslen(cmdLine) + 1) * sizeof(WCHAR)); cds.lpData = (PVOID)cmdLine; SendMessage(wnd, WM_COPYDATA, 0, (LPARAM)&cds); return 0; } // Disallow everything except !LoadLayout. if (_wcsnicmp(cmdLine, L"!LoadLayout ", 12) == 0) { layout = cmdLine + 12; // Skip "!LoadLayout ". } else { return 1; } } else if (cmdLine[0] == L'"') { // Strip quotes ++cmdLine; WCHAR* pos = wcsrchr(cmdLine, L'"'); if (pos) { *pos = L'\0'; } } const WCHAR* iniFile = (*cmdLine && !layout) ? cmdLine : nullptr; g_Rainmeter = new Rainmeter; int ret = g_Rainmeter->Initialize(iniFile, layout); if (ret == 0) { ret = g_Rainmeter->MessagePump(); } delete g_Rainmeter; g_Rainmeter = nullptr; return ret; } /* ** Constructor ** */ Rainmeter::Rainmeter() : m_TrayWindow(), m_UseD2D(false), m_Debug(false), m_DisableVersionCheck(false), m_NewVersion(false), m_DesktopWorkAreaChanged(false), m_DesktopWorkAreaType(false), m_NormalStayDesktop(true), m_MenuActive(false), 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() { KillTimer(m_Window, TIMER_NETSTATS); DeleteAllUnmanagedMeterWindows(); DeleteAllMeterWindows(); 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); CoUninitialize(); GdiplusShutdown(m_GDIplusToken); } /* ** 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); bool bDefaultIniLocation = false; if (iniPath) { // The command line defines the location of Rainmeter.ini (or whatever it calls it). std::wstring iniFile = iniPath; ExpandEnvironmentVariables(iniFile); if (iniFile.empty() || System::IsPathSeparator(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 (!System::IsPathSeparator(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"; ExpandEnvironmentVariables(m_IniFile); bDefaultIniLocation = true; } } if (IsAlreadyRunning()) { // Instance already running with same .ini file return 1; } 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 = Logger::GetInstance(); const WCHAR* iniFile = m_IniFile.c_str(); // Set file locations { m_SettingsPath = ExtractPath(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); ExpandEnvironmentVariables(m_SkinPath); if (!m_SkinPath.empty() && !System::IsPathSeparator(m_SkinPath[m_SkinPath.length() - 1])) { m_SkinPath += L'\\'; } } 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()); // Extract volume path from program path // E.g.: // "C:\path\" to "C:" // "\\server\share\" to "\\server\share" // "\\server\C:\path\" to "\\server\C:" std::wstring::size_type loc; if ((loc = m_Path.find_first_of(L':')) != std::wstring::npos) { m_Drive.assign(m_Path, 0, loc + 1); } else if (System::IsUNCPath(m_Path)) { if ((loc = m_Path.find_first_of(L"\\/", 2)) != std::wstring::npos) { std::wstring::size_type loc2; if ((loc2 = m_Path.find_first_of(L"\\/", loc + 1)) != std::wstring::npos || loc != (m_Path.length() - 1)) { loc = loc2; } } m_Drive.assign(m_Path, 0, loc); } // 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_SkinFolders.empty()) { 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 } bool Rainmeter::IsAlreadyRunning() { typedef struct { ULONG i[2]; ULONG buf[4]; unsigned char in[64]; unsigned char digest[16]; } MD5_CTX; typedef void (WINAPI * FPMD5INIT)(MD5_CTX* context); typedef void (WINAPI * FPMD5UPDATE)(MD5_CTX* context, const unsigned char* input, unsigned int inlen); typedef void (WINAPI * FPMD5FINAL)(MD5_CTX* context); bool alreadyRunning = false; // Create MD5 digest from command line HMODULE cryptDll = System::RmLoadLibrary(L"cryptdll.dll"); if (cryptDll) { FPMD5INIT MD5Init = (FPMD5INIT)GetProcAddress(cryptDll, "MD5Init"); FPMD5UPDATE MD5Update = (FPMD5UPDATE)GetProcAddress(cryptDll, "MD5Update"); FPMD5FINAL MD5Final = (FPMD5FINAL)GetProcAddress(cryptDll, "MD5Final"); if (MD5Init && MD5Update && MD5Final) { std::wstring data = m_IniFile; _wcsupr(&data[0]); MD5_CTX ctx = {0}; MD5Init(&ctx); MD5Update(&ctx, (LPBYTE)&data[0], data.length() * sizeof(WCHAR)); MD5Final(&ctx); FreeLibrary(cryptDll); // Convert MD5 digest to mutex string (e.g. "Rainmeter0123456789abcdef0123456789abcdef") const WCHAR hexChars[] = L"0123456789abcdef"; WCHAR mutexName[64] = L"Rainmeter"; WCHAR* pos = mutexName + (_countof(L"Rainmeter") - 1); for (size_t i = 0; i < 16; ++i) { *(pos++) = hexChars[ctx.digest[i] >> 4]; *(pos++) = hexChars[ctx.digest[i] & 0xF]; } *pos = L'\0'; m_Mutex = CreateMutex(nullptr, FALSE, mutexName); if (GetLastError() == ERROR_ALREADY_EXISTS) { alreadyRunning = true; m_Mutex = nullptr; } } FreeLibrary(cryptDll); } return alreadyRunning; } 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)) { g_Rainmeter->DelayedExecuteCommand(data); } } } break; case WM_TIMER: if (wParam == TIMER_NETSTATS) { MeasureNet::UpdateIFTable(); MeasureNet::UpdateStats(); g_Rainmeter->WriteStats(false); } break; case WM_RAINMETER_DELAYED_REFRESH_ALL: g_Rainmeter->RefreshAll(); break; case WM_RAINMETER_DELAYED_EXECUTE: if (lParam) { // Execute bang WCHAR* bang = (WCHAR*)lParam; g_Rainmeter->ExecuteCommand(bang, nullptr); free(bang); // _wcsdup() } break; case WM_RAINMETER_EXECUTE: if (g_Rainmeter->HasMeterWindow((MeterWindow*)wParam)) { g_Rainmeter->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); } 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 && wcscmp(L".", fd.cFileName) != 0 && wcscmp(L"..", fd.cFileName) != 0) { 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 SkinFolder& skinFolder = m_SkinFolders[(*iter).second]; if (skinFolder.active > 0 && skinFolder.active <= (int)skinFolder.files.size()) { ActivateSkin((*iter).second, skinFolder.active - 1); } } } void Rainmeter::ActivateSkin(int folderIndex, int fileIndex) { if (folderIndex >= 0 && folderIndex < (int)m_SkinFolders.size() && fileIndex >= 0 && fileIndex < (int)m_SkinFolders[folderIndex].files.size()) { SkinFolder& skinFolder = m_SkinFolders[folderIndex]; const std::wstring& file = skinFolder.files[fileIndex]; const WCHAR* fileSz = file.c_str(); std::wstring folderPath = 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(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 < (int)m_SkinFolders.size()) { m_SkinFolders[folderIndex].active = 0; // Deactivate the skin } else if (folderIndex == -1 && meterWindow) { folderIndex = FindSkinFolderIndex(meterWindow->GetFolderPath()); if (folderIndex != -1) { m_SkinFolders[folderIndex].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 < (int)m_SkinFolders.size() && fileIndex >= 0 && fileIndex < (int)m_SkinFolders[folderIndex].files.size()) { if (m_SkinFolders[folderIndex].active == fileIndex + 1) { MeterWindow* meterWindow = g_Rainmeter->GetMeterWindow(GetFolderPath(folderIndex)); DeactivateSkin(meterWindow, folderIndex); } else { ActivateSkin(folderIndex, fileIndex); } } } 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; } 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; } std::pair Rainmeter::GetMeterWindowIndex(const std::wstring& folderPath, const std::wstring& file) { int index = FindSkinFolderIndex(folderPath); if (index != -1) { const SkinFolder& skinFolder = m_SkinFolders[index]; const WCHAR* fileSz = file.c_str(); for (size_t i = 0, isize = skinFolder.files.size(); i < isize; ++i) { if (_wcsicmp(skinFolder.files[i].c_str(), fileSz) == 0) { return std::make_pair(index, (int)i); } } } return std::make_pair(-1, -1); // Error } std::pair Rainmeter::GetMeterWindowIndex(UINT menuCommand) { if (menuCommand >= ID_CONFIG_FIRST && menuCommand <= ID_CONFIG_LAST) { // Check which skin was selected for (size_t i = 0, isize = m_SkinFolders.size(); i < isize; ++i) { const SkinFolder& skinFolder = m_SkinFolders[i]; if (menuCommand >= skinFolder.commandBase && menuCommand < (skinFolder.commandBase + skinFolder.files.size())) { return std::make_pair((int)i, (int)(menuCommand - skinFolder.commandBase)); } } } return std::make_pair(-1, -1); // error; } 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)); } } } /* ** Returns the skin folder path relative to the skin folder (e.g. illustro\Clock). ** */ std::wstring Rainmeter::GetFolderPath(int folderIndex) { const SkinFolder& skinFolder = m_SkinFolders[folderIndex]; std::wstring path = skinFolder.name; for (int i = skinFolder.level - 1, index = folderIndex; i >= 1; --i) { while (m_SkinFolders[index].level != i) { --index; } path.insert(0, L"\\"); path.insert(0, m_SkinFolders[index].name); } return path; } int Rainmeter::FindSkinFolderIndex(const std::wstring& folderPath) { if (!folderPath.empty()) { const WCHAR* path = folderPath.c_str(); int len = 0; while (path[len] && path[len] != L'\\') ++len; int level = 1; for (int i = 0, isize = (int)m_SkinFolders.size(); i < isize; ++i) { const SkinFolder& skinFolder = m_SkinFolders[i]; if (skinFolder.level == level) { if (skinFolder.name.length() == len && _wcsnicmp(skinFolder.name.c_str(), path, len) == 0) { path += len; if (*path) { ++path; // Skip backslash len = 0; while (path[len] && path[len] != L'\\') ++len; } else { // Match found return i; } ++level; } } else if (skinFolder.level < level) { break; } } } return -1; } 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) { int index = FindSkinFolderIndex(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_SkinFolders.clear(); m_SkinOrders.clear(); ScanForSkinsRecursive(m_SkinPath, L"", 0, 0); } int Rainmeter::ScanForSkinsRecursive(const std::wstring& path, std::wstring base, int index, UINT level) { WIN32_FIND_DATA fileData; // Data structure describes the file found HANDLE hSearch; // Search handle returned by FindFirstFile std::list subfolders; // Find all .ini files and subfolders std::wstring filter = path + base; filter += L"\\*"; hSearch = FindFirstFileEx( filter.c_str(), (Platform::IsAtLeastWin7()) ? FindExInfoBasic : FindExInfoStandard, &fileData, FindExSearchNameMatch, nullptr, 0); bool foundFiles = false; if (hSearch != INVALID_HANDLE_VALUE) { SkinFolder skinFolder; skinFolder.commandBase = ID_CONFIG_FIRST + index; skinFolder.active = 0; skinFolder.level = level; do { const std::wstring filename = fileData.cFileName; if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (wcscmp(L".", fileData.cFileName) != 0 && wcscmp(L"..", fileData.cFileName) != 0 && !(level == 0 && wcscmp(L"@Backup", fileData.cFileName) == 0) && !(level == 0 && wcscmp(L"Backup", fileData.cFileName) == 0) && !(level == 1 && wcscmp(L"@Resources", fileData.cFileName) == 0)) { subfolders.push_back(filename); } } else if (level != 0) { // Check whether the extension is ".ini" size_t filenameLen = filename.size(); if (filenameLen >= 4 && _wcsicmp(fileData.cFileName + (filenameLen - 4), L".ini") == 0) { foundFiles = true; skinFolder.files.push_back(filename); ++index; } } } while (FindNextFile(hSearch, &fileData)); FindClose(hSearch); if (level > 0 && (foundFiles || !subfolders.empty())) { if (level == 1) { skinFolder.name = base; } else { std::wstring::size_type pos = base.rfind(L'\\') + 1; skinFolder.name.assign(base, pos, base.length() - pos); } m_SkinFolders.push_back(std::move(skinFolder)); } } if (level != 0) { base += L'\\'; } if (!subfolders.empty()) { bool popFolder = !foundFiles; std::list::const_iterator iter = subfolders.begin(); for ( ; iter != subfolders.end(); ++iter) { int newIndex = ScanForSkinsRecursive(path, base + (*iter), index, level + 1); if (newIndex != index) { popFolder = false; } index = newIndex; } if (popFolder) { m_SkinFolders.pop_back(); } } return index; } /* ** 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(), (Platform::IsAtLeastWin7()) ? FindExInfoBasic : FindExInfoStandard, &fileData, FindExSearchNameMatch, nullptr, 0); if (hSearch != INVALID_HANDLE_VALUE) { do { if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY && wcscmp(L".", fileData.cFileName) != 0 && wcscmp(L"..", fileData.cFileName) != 0) { m_Layouts.push_back(fileData.cFileName); } } while (FindNextFile(hSearch, &fileData)); FindClose(hSearch); } } 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 = 0!=parser.ReadInt(L"Rainmeter", L"UseD2D", 0); m_Debug = 0!=parser.ReadInt(L"Rainmeter", L"Debug", 0); // Read Logging settings Logger& logger = Logger::GetInstance(); const bool logging = parser.ReadInt(L"Rainmeter", L"Logging", 0) != 0; 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 = 0!=parser.ReadInt(L"Rainmeter", L"DisableDragging", 0); m_DisableRDP = 0!=parser.ReadInt(L"Rainmeter", L"DisableRDP", 0); 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 = 0!=parser.ReadInt(L"Rainmeter", L"DisableVersionCheck", 0); 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; } for (UINT i = 1, isize = System::GetMonitorCount(); i <= isize; ++i) { _snwprintf_s(buffer, _TRUNCATE, L"DesktopWorkArea@%i", 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 = 0!=parser.ReadInt(L"Rainmeter", L"DesktopWorkAreaType", 0); m_NormalStayDesktop = 0!=parser.ReadInt(L"Rainmeter", L"NormalStayDesktop", 1); 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; } int index = FindSkinFolderIndex(*iter); if (index == -1) { continue; } SkinFolder& skinFolder = m_SkinFolders[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 int index = FindSkinFolderIndex(mw->GetFolderPath()); if (index != -1) { SkinFolder& skinFolder = m_SkinFolders[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); }; /* ** Opens the context menu in given coordinates. ** */ void Rainmeter::ShowContextMenu(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())) { m_MenuActive = true; // Show context menu, if no actions were executed HMENU menu = MenuTemplate::CreateMenu(s_Menu, _countof(s_Menu), GetString); if (menu) { SetMenuDefaultItem(menu, IDM_MANAGE, MF_BYCOMMAND); if (_waccess(Logger::GetInstance().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, (Logger::GetInstance().IsLogToFile()) ? IDM_STARTLOG : IDM_STOPLOG, MF_BYCOMMAND | MF_GRAYED); } if (m_Debug) { CheckMenuItem(menu, IDM_DEBUGLOG, MF_BYCOMMAND | MF_CHECKED); } HMENU allSkinsMenu = GetSubMenu(menu, 4); if (allSkinsMenu) { if (!m_SkinFolders.empty()) { DeleteMenu(allSkinsMenu, 0, MF_BYPOSITION); // "No skins available" menuitem CreateAllSkinsMenu(allSkinsMenu); } if (m_DisableDragging) { CheckMenuItem(allSkinsMenu, IDM_DISABLEDRAG, MF_BYCOMMAND | MF_CHECKED); } } HMENU layoutMenu = GetSubMenu(menu, 5); if (layoutMenu) { if (!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 = m_MeterWindows.begin(); for (; iter != 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 (m_NewVersion) { InsertMenu(menu, 0, MF_BYPOSITION, IDM_NEW_VERSION, GetString(ID_STR_UPDATEAVAILABLE)); HiliteMenuItem(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 = GetMeterWindow(hWnd); if (mw) { // Cancel the mouse event beforehand mw->SetMouseLeaveEvent(true); } } // Set the window to foreground hWnd = meterWindow ? meterWindow->GetWindow() : m_TrayWindow->GetWindow(); HWND hWndForeground = GetForegroundWindow(); if (hWndForeground != hWnd) { DWORD foregroundThreadID = GetWindowThreadProcessId(hWndForeground, nullptr); DWORD currentThreadID = GetCurrentThreadId(); AttachThreadInput(currentThreadID, foregroundThreadID, TRUE); SetForegroundWindow(hWnd); 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, hWnd, nullptr); if (meterWindow) { DestroyMenu(menu); } } DestroyMenu(menu); m_MenuActive = false; } } int Rainmeter::CreateAllSkinsMenuRecursive(HMENU skinMenu, int index) { int initialLevel = m_SkinFolders[index].level; int menuIndex = 0; const size_t max = g_Rainmeter->m_SkinFolders.size(); while (index < max) { const SkinFolder& skinFolder = g_Rainmeter->m_SkinFolders[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 && m_SkinFolders[index + 1].level == initialLevel + 1; if (hasSubfolder) { index = CreateAllSkinsMenuRecursive(subMenu, index + 1); } // Add files { int fileIndex = 0; int fileCount = (int)skinFolder.files.size(); for ( ; fileIndex < fileCount; ++fileIndex) { InsertMenu(subMenu, fileIndex, MF_STRING | MF_BYPOSITION, skinFolder.commandBase + 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; } HMENU Rainmeter::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_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) { // Tick the position HMENU settingsMenu = GetSubMenu(skinMenu, 4); if (settingsMenu) { HMENU posMenu = GetSubMenu(settingsMenu, 0); if (posMenu) { 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 (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); } } // 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 SkinFolder& skinFolder = m_SkinFolders[FindSkinFolderIndex(skinName)]; for (int i = 0, isize = (int)skinFolder.files.size(); i < isize; ++i) { InsertMenu(variantsMenu, i, MF_BYPOSITION, skinFolder.commandBase + 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) { 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; } } } } // Add custom actions to the context menu std::wstring contextTitle = meterWindow->GetParser().ReadString(L"Rainmeter", L"ContextTitle", L""); if (!contextTitle.empty()) { 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)) { 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 size_t titleSize = cTitles.size(); if (titleSize <= 3) { size_t position = 0; for (size_t i = 0; i < titleSize; ++i) { if (isTitleSeparator(cTitles[i])) { // Separators not allowed in main top-level menu --position; } else { InsertMenu(skinMenu, position + 1, MF_BYPOSITION | MF_STRING, (index << 16) | (IDM_SKIN_CUSTOMCONTEXTMENU_FIRST + i), cTitles[i].c_str()); } ++position; } if (position != 0) { InsertMenu(skinMenu, 1, MF_BYPOSITION | MF_STRING | MF_GRAYED, 0, L"Custom skin actions"); InsertMenu(skinMenu, 1, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); } } else { HMENU customMenu = CreatePopupMenu(); InsertMenu(skinMenu, 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 { AppendMenu(customMenu, MF_BYPOSITION | MF_STRING, (index << 16) | (IDM_SKIN_CUSTOMCONTEXTMENU_FIRST + i), cTitles[i].c_str()); } } InsertMenu(skinMenu, 1, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); } } } } return skinMenu; } void Rainmeter::CreateLayoutMenu(HMENU layoutMenu) { for (size_t i = 0, isize = m_Layouts.size(); i < isize; ++i) { InsertMenu(layoutMenu, i, MF_BYPOSITION, ID_THEME_FIRST + i, m_Layouts[i].c_str()); } } void Rainmeter::CreateMonitorMenu(HMENU monitorMenu, MeterWindow* meterWindow) { bool screenDefined = meterWindow->GetXScreenDefined(); 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; } InsertMenu(monitorMenu, i + 2, MF_BYPOSITION | ((screenDefined && screenIndex == i) ? MF_CHECKED : MF_UNCHECKED) | ((!(*iter).active) ? MF_GRAYED : MF_ENABLED), 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 Rainmeter::ChangeSkinIndex(HMENU menu, int index) { if (index > 0) { 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); } } } } } void Rainmeter::ShowLogFile() { std::wstring logFile = L'"' + Logger::GetInstance().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\\"; 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); } } std::wstring Rainmeter::ExtractPath(const std::wstring& strFilePath) { std::wstring::size_type pos = strFilePath.find_last_of(L"\\/"); if (pos != std::wstring::npos) { return strFilePath.substr(0, pos + 1); } return L".\\"; } void Rainmeter::ExpandEnvironmentVariables(std::wstring& strPath) { std::wstring::size_type pos; if ((pos = strPath.find(L'%')) != std::wstring::npos && strPath.find(L'%', pos + 2) != std::wstring::npos) { DWORD bufSize = 4096; WCHAR* buffer = new WCHAR[bufSize]; // lets hope the buffer is large enough... // %APPDATA% is a special case pos = strPath.find(L"%APPDATA%", pos); if (pos != std::wstring::npos) { HRESULT hr = SHGetFolderPath(nullptr, CSIDL_APPDATA, nullptr, SHGFP_TYPE_CURRENT, buffer); if (SUCCEEDED(hr)) { size_t len = wcslen(buffer); do { strPath.replace(pos, 9, buffer, len); } while ((pos = strPath.find(L"%APPDATA%", pos + len)) != std::wstring::npos); } } if ((pos = strPath.find(L'%')) != std::wstring::npos && strPath.find(L'%', pos + 2) != std::wstring::npos) { // Expand the environment variables do { DWORD ret = ExpandEnvironmentStrings(strPath.c_str(), buffer, bufSize); if (ret == 0) // Error { LogWarningF(L"Unable to expand environment strings in: %s", strPath.c_str()); break; } if (ret <= bufSize) // Fits in the buffer { strPath.assign(buffer, ret - 1); break; } delete [] buffer; bufSize = ret; buffer = new WCHAR[bufSize]; } while (true); } delete [] buffer; } }