/* Copyright (C) 2005 Kimmo Pekkola, 2009 Greg Schoppe 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. */ #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include "../../Library/Export.h" // Rainmeter's exported functions #include "../../Library/DisableThreadLibraryCalls.h" // contains DllMain entry point struct MeasureData { bool count; MeasureData() : count(false) {} }; struct BinData { union { ULONGLONG lastWrite; UINT lastCount; }; HANDLE directory; WCHAR drive; bool isFAT; }; unsigned int __stdcall QueryRecycleBinThreadProc(void* pParam); HRESULT GetFolderCLSID(LPCWSTR pszPath, CLSID* pathCLSID); LPWSTR GetCurrentUserSid(); HANDLE GetRecycleBinHandle(WCHAR drive, bool& isFAT); std::vector g_BinData; double g_BinCount = 0; double g_BinSize = 0; bool g_IsXP = false; int g_UpdateCount = 0; int g_InstanceCount = 0; HANDLE g_Thread = NULL; PLUGIN_EXPORT void Initialize(void** data, void* rm) { MeasureData* measure = new MeasureData; *data = measure; if (g_InstanceCount == 0) { OSVERSIONINFOEX osvi = {sizeof(OSVERSIONINFOEX)}; if (GetVersionEx((OSVERSIONINFO*)&osvi)) { if (osvi.dwMajorVersion == 5) { // Not checking for osvi.dwMinorVersion >= 1 because we won't run on pre-XP g_IsXP = true; } } } ++g_InstanceCount; } PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) { MeasureData* measure = (MeasureData*)data; LPCWSTR value = RmReadString(rm, L"RecycleType", L"COUNT"); if (_wcsicmp(L"COUNT", value) == 0) { measure->count = true; } else if (_wcsicmp(L"SIZE", value) == 0) { measure->count = false; } else { WCHAR buffer[256]; _snwprintf_s(buffer, _TRUNCATE, L"RecycleManager.dll: RecycleType=%s is not valid in [%s]", value, RmGetMeasureName(rm)); RmLog(LOG_ERROR, buffer); } } PLUGIN_EXPORT double Update(void* data) { MeasureData* measure = (MeasureData*)data; ++g_UpdateCount; if (g_UpdateCount > g_InstanceCount && !g_Thread) { g_UpdateCount = 0; WCHAR buffer[128]; DWORD len = GetLogicalDriveStrings(128, buffer); // Convert "A:\\\0B:\\\0" -> "AB\0" int index = 0; for (int i = 0; i < len; i += 4) { buffer[index] = buffer[i]; ++index; } buffer[index] = L'\0'; const WCHAR DRIVE_HANDLED = 1; bool changed = false; auto iter = g_BinData.begin(); while (iter != g_BinData.end()) { BinData& data = (*iter); WCHAR* pos = wcschr(buffer, data.drive); if (pos != NULL) { if (data.isFAT) { // FAT/FAT32 doesn't update directory last write time. // Use directory content count instead. WCHAR filter[] = L"\0:\\$RECYCLE.BIN\\*"; WCHAR filterXP[] = L"\0:\\RECYCLED\\*"; filter[0] = *pos; filterXP[0] = *pos; WIN32_FIND_DATA fd; HANDLE hSearch = FindFirstFile(g_IsXP ? filterXP : filter, &fd); if (hSearch != INVALID_HANDLE_VALUE) { UINT count = 0; do { ++count; } while (FindNextFile(hSearch, &fd)); FindClose(hSearch); if (count != data.lastCount) { data.lastCount = count; changed = true; } } } else if (data.directory) { ULONGLONG lastWrite; GetFileTime(data.directory, NULL, NULL, (FILETIME*)&lastWrite); if (data.lastWrite != lastWrite) { data.lastWrite = lastWrite; changed = true; } } *pos = DRIVE_HANDLED; ++iter; } else { // Drive removed if (data.directory) { CloseHandle(data.directory); } iter = g_BinData.erase(iter); } } for (int i = 0; i < index; ++i) { if (buffer[i] != DRIVE_HANDLED) { // New drive BinData data = {0}; data.drive = buffer[i]; WCHAR drive[] = L"\0:\\"; drive[0] = buffer[i]; if (GetDriveType(drive) == DRIVE_FIXED) { data.directory = GetRecycleBinHandle(buffer[i], data.isFAT); } g_BinData.push_back(data); } } if (changed) { g_UpdateCount = -8; g_Thread = (HANDLE)_beginthreadex(NULL, 0, QueryRecycleBinThreadProc, NULL, 0, NULL); } } return measure->count ? g_BinCount : g_BinSize; } PLUGIN_EXPORT void Finalize(void* data) { MeasureData* measure = (MeasureData*)data; delete measure; --g_InstanceCount; if (g_InstanceCount == 0) { for (auto iter = g_BinData.cbegin(); iter != g_BinData.cend(); ++iter) { if ((*iter).directory) { CloseHandle((*iter).directory); } } WaitForSingleObject(g_Thread, INFINITE); } } PLUGIN_EXPORT void ExecuteBang(void* data, LPCWSTR args) { MeasureData* measure = (MeasureData*)data; if (_wcsicmp(args, L"EmptyBin") == 0) { SHEmptyRecycleBin(NULL, NULL, 0); } else if (_wcsicmp(args, L"EmptyBinSilent") == 0) { SHEmptyRecycleBin(NULL, NULL, SHERB_NOCONFIRMATION | SHERB_NOPROGRESSUI | SHERB_NOSOUND); } else if (_wcsicmp(args, L"OpenBin") == 0) { ShellExecute(NULL, L"open", L"explorer.exe", L"/N,::{645FF040-5081-101B-9F08-00AA002F954E}", NULL, SW_SHOW); } } unsigned int __stdcall QueryRecycleBinThreadProc(void* pParam) { SHQUERYRBINFO rbi = {0}; rbi.cbSize = sizeof(SHQUERYRBINFO); SHQueryRecycleBin(NULL, &rbi); g_BinCount = (double)rbi.i64NumItems; g_BinSize = (double)rbi.i64Size; g_Thread = NULL; _endthreadex(0); return 0; } HRESULT GetFolderCLSID(LPCWSTR path, CLSID* clsid) { LPITEMIDLIST pidl; HRESULT hr = SHParseDisplayName(path, NULL, &pidl, 0, NULL); if (SUCCEEDED(hr)) { IShellFolder* sf; LPCITEMIDLIST pidlLast; hr = SHBindToParent(pidl, IID_IShellFolder, (void**)&sf, &pidlLast); if (SUCCEEDED(hr)) { SHDESCRIPTIONID did; hr = SHGetDataFromIDList(sf, pidlLast, SHGDFIL_DESCRIPTIONID, &did, sizeof(did)); *clsid = did.clsid; sf->Release(); } CoTaskMemFree(pidl); } return hr; } // Return value must be freed with LocalFree LPWSTR GetCurrentUserSid() { LPWSTR sidStr = NULL; HANDLE hToken; if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { DWORD dwBufSize = 0; GetTokenInformation(hToken, TokenUser, NULL, 0, &dwBufSize); BYTE* buf = new BYTE[dwBufSize]; if (GetTokenInformation(hToken, TokenUser, buf, dwBufSize, &dwBufSize)) { TOKEN_USER* tu = (TOKEN_USER*)buf; if (ConvertSidToStringSid(tu->User.Sid, &sidStr)) { } } delete [] buf; CloseHandle(hToken); } return sidStr; } HANDLE GetRecycleBinHandle(WCHAR drive, bool& isFAT) { WCHAR search[] = L"\0:\\"; search[0] = drive; WCHAR filesystem[16]; if (!GetVolumeInformation(search, NULL, 0, NULL, NULL, NULL, filesystem, _countof(filesystem))) { return NULL; } if (wcscmp(filesystem, L"NTFS") == 0) { isFAT = false; } else if (wcscmp(filesystem, L"FAT") == 0 || wcscmp(filesystem, L"FAT32") == 0) { isFAT = true; } else { RmLog(LOG_ERROR, L"RecycleManager.dll: Unsupported filesystem"); return NULL; } const WCHAR* binFolder; if (g_IsXP) { binFolder = isFAT ? L"RECYCLED" : L"RECYCLER"; } else { binFolder = L"$RECYCLE.BIN"; } HANDLE hDir = NULL; WCHAR binPath[MAX_PATH]; _snwprintf_s(binPath, _TRUNCATE, L"%s%s\\", search, binFolder); DWORD binAttributes = GetFileAttributes(binPath); if (binAttributes != INVALID_FILE_ATTRIBUTES && binAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_DIRECTORY)) { if (isFAT) { if (_waccess(binPath, 0) != -1) { isFAT = true; return hDir; } } else { // Get the correct, SID-specific bin for NTFS LPWSTR currentSid = GetCurrentUserSid(); if (currentSid) { wcscat(binPath, currentSid); binAttributes = GetFileAttributes(binPath); if (binAttributes != INVALID_FILE_ATTRIBUTES && binAttributes & FILE_ATTRIBUTE_DIRECTORY) { CLSID id; HRESULT hr = GetFolderCLSID(binPath, &id); if (SUCCEEDED(hr) && IsEqualGUID(CLSID_RecycleBin, id)) { hDir = CreateFile( binPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); } } LocalFree(currentSid); } } } if (!hDir) RmLog(LOG_ERROR, L"RecycleManager.dll: Unable to find bin"); return hDir; }