[CLEANMGR] Add basic cleanmgr implementation

CORE-18941
This commit is contained in:
Mark Jansen
2023-04-12 23:27:08 +02:00
parent 0cd7e2cfb0
commit f9bedd5ca5
15 changed files with 1110 additions and 0 deletions

View File

@@ -3,6 +3,7 @@ add_subdirectory(atactl)
add_subdirectory(cacls)
add_subdirectory(calc)
add_subdirectory(charmap)
add_subdirectory(cleanmgr)
add_subdirectory(clipbrd)
add_subdirectory(cmdutils)
add_subdirectory(control)

View File

@@ -0,0 +1,9 @@
project(cleanmgr)
# The main application
add_subdirectory(cleanmgr)
# Cleanup handlers
#add_subdirectory(dataclen) # Data Driven Cleaner

View File

@@ -0,0 +1,212 @@
/*
* PROJECT: ReactOS Disk Cleanup
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: CCleanupHandler implementation
* COPYRIGHT: Copyright 2023-2025 Mark Jansen <mark.jansen@reactos.org>
*/
#include "cleanmgr.h"
CCleanupHandler::CCleanupHandler(CRegKey &subKey, const CStringW &keyName, const GUID &guid)
: hSubKey(subKey)
, KeyName(keyName)
, Guid(guid)
, dwFlags(0)
, Priority(0)
, StateFlags(0)
, SpaceUsed(0)
, ShowHandler(true)
, hIcon(NULL)
{
}
CCleanupHandler::~CCleanupHandler()
{
Deactivate();
::DestroyIcon(hIcon);
}
void
CCleanupHandler::Deactivate()
{
if (Handler)
{
DWORD dwFlags = 0;
Handler->Deactivate(&dwFlags);
if (dwFlags & EVCF_REMOVEFROMLIST)
UNIMPLEMENTED_DBGBREAK();
}
}
bool
CCleanupHandler::Initialize(LPCWSTR pcwszVolume)
{
if (FAILED_UNEXPECTEDLY(
::CoCreateInstance(Guid, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IEmptyVolumeCache, &Handler))))
{
return false;
}
DWORD dwSize = sizeof(Priority);
if (hSubKey.QueryBinaryValue(L"Priority", &Priority, &dwSize) != ERROR_SUCCESS)
{
if (hSubKey.QueryDWORDValue(L"Priority", Priority) != ERROR_SUCCESS)
Priority = 200;
}
dwSize = sizeof(StateFlags);
if (hSubKey.QueryDWORDValue(L"StateFlags", StateFlags) != ERROR_SUCCESS)
StateFlags = 0;
WCHAR PathBuffer[MAX_PATH] = {};
ULONG nChars = _countof(PathBuffer);
if (hSubKey.QueryStringValue(L"IconPath", PathBuffer, &nChars) != ERROR_SUCCESS)
{
CStringW Tmp;
WCHAR GuidStr[50] = {};
if (StringFromGUID2(Guid, GuidStr, _countof(GuidStr)))
{
Tmp.Format(L"CLSID\\%s\\DefaultIcon", GuidStr);
CRegKey clsid;
nChars = _countof(PathBuffer);
if (clsid.Open(HKEY_CLASSES_ROOT, Tmp, KEY_READ) != ERROR_SUCCESS ||
clsid.QueryStringValue(NULL, PathBuffer, &nChars) != ERROR_SUCCESS)
{
PathBuffer[0] = UNICODE_NULL;
}
}
}
if (!PathBuffer[0])
StringCchCopyW(PathBuffer, _countof(PathBuffer), L"%systemroot%\\system32\\shell32.dll");
int Index = 0;
WCHAR *ptr = wcschr(PathBuffer, L',');
if (ptr)
{
*ptr++ = UNICODE_NULL;
Index = wcstol(ptr, NULL, 10);
}
HICON Large, Small;
UINT Result = ExtractIconExW(PathBuffer, Index, &Large, &Small, 1);
if (Result < 1)
Result = ExtractIconExW(L"%systemroot%\\system32\\shell32.dll", 0, &Large, &Small, 1);
if (Result >= 1)
{
hIcon = Small;
if (!hIcon)
{
hIcon = Large;
}
else
{
::DestroyIcon(Large);
}
}
// These options should come from the command line
// dwFlags |= EVCF_SETTINGSMODE;
// dwFlags |= EVCF_OUTOFDISKSPACE;
CComPtr<IEmptyVolumeCache2> spHandler2;
HRESULT hr = Handler->QueryInterface(IID_PPV_ARG(IEmptyVolumeCache2, &spHandler2));
if (SUCCEEDED(hr))
{
hr = spHandler2->InitializeEx(
hSubKey, pcwszVolume, KeyName, &wszDisplayName, &wszDescription, &wszBtnText, &dwFlags);
if (FAILED_UNEXPECTEDLY(hr))
return false;
// No files to clean will return S_FALSE;
if (hr != S_OK)
return false;
}
else
{
// Observed behavior:
// When Initialize is called, wszDescription is actually pointing to data
// wszDescription.AllocateBytes(0x400u);
hr = Handler->Initialize(hSubKey, pcwszVolume, &wszDisplayName, &wszDescription, &dwFlags);
if (FAILED_UNEXPECTEDLY(hr))
return false;
// No files to clean will return S_FALSE;
if (hr != S_OK)
return false;
CComPtr<IPropertyBag> spBag;
WCHAR GuidStr[50] = {};
nChars = _countof(GuidStr);
if (hSubKey.QueryStringValue(L"PropertyBag", GuidStr, &nChars) == ERROR_SUCCESS)
{
GUID guid = {};
if (!FAILED_UNEXPECTEDLY(CLSIDFromString(GuidStr, &guid)))
{
FAILED_UNEXPECTEDLY(
CoCreateInstance(guid, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IPropertyBag, &spBag)));
}
}
ReadProperty(L"Display", spBag, wszDisplayName);
ReadProperty(L"Description", spBag, wszDescription);
if (dwFlags & EVCF_HASSETTINGS)
{
ReadProperty(L"AdvancedButtonText", spBag, wszBtnText);
}
}
if ((dwFlags & EVCF_ENABLEBYDEFAULT) && !(StateFlags & HANDLER_STATE_SELECTED))
{
StateFlags |= HANDLER_STATE_SELECTED;
}
// For convenience
if (!wszDisplayName)
SHStrDupW(KeyName, &wszDisplayName);
return true;
}
void
CCleanupHandler::ReadProperty(LPCWSTR Name, IPropertyBag *pBag, CComHeapPtr<WCHAR> &storage)
{
if (storage)
return;
if (pBag)
{
CComVariant tmp;
tmp.vt = VT_BSTR;
HRESULT hr = pBag->Read(Name, &tmp, NULL);
if (!FAILED_UNEXPECTEDLY(hr) && tmp.vt == VT_BSTR)
{
SHStrDupW(tmp.bstrVal, &storage);
}
}
if (!storage)
{
WCHAR TmpStr[0x200] = {};
DWORD dwSize = _countof(TmpStr);
if (hSubKey.QueryStringValue(Name, TmpStr, &dwSize) == ERROR_SUCCESS)
{
WCHAR ResolvedStr[0x200] = {};
SHLoadIndirectString(TmpStr, ResolvedStr, _countof(ResolvedStr), NULL);
SHStrDupW(ResolvedStr, &storage);
}
}
}
BOOL
CCleanupHandler::HasSettings() const
{
return !!(dwFlags & EVCF_HASSETTINGS);
}
BOOL
CCleanupHandler::DontShowIfZero() const
{
return !!(dwFlags & EVCF_DONTSHOWIFZERO);
}

View File

@@ -0,0 +1,48 @@
/*
* PROJECT: ReactOS Disk Cleanup
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: CCleanupHandler definition
* COPYRIGHT: Copyright 2023-2025 Mark Jansen <mark.jansen@reactos.org>
*/
#define HANDLER_STATE_SELECTED 1
struct CCleanupHandler
{
CCleanupHandler(CRegKey &subKey, const CStringW &keyName, const GUID &guid);
~CCleanupHandler();
void Deactivate();
bool
Initialize(LPCWSTR pcwszVolume);
void
ReadProperty(LPCWSTR Name, IPropertyBag *pBag, CComHeapPtr<WCHAR> &storage);
BOOL
HasSettings() const;
BOOL
DontShowIfZero() const;
CRegKey hSubKey;
CStringW KeyName;
GUID Guid;
CComHeapPtr<WCHAR> wszDisplayName;
CComHeapPtr<WCHAR> wszDescription;
CComHeapPtr<WCHAR> wszBtnText;
CStringW IconPath;
DWORD dwFlags;
DWORD Priority;
DWORD StateFlags;
CComPtr<IEmptyVolumeCache> Handler;
DWORDLONG SpaceUsed;
bool ShowHandler;
HICON hIcon;
};

View File

@@ -0,0 +1,163 @@
/*
* PROJECT: ReactOS Disk Cleanup
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: CCleanupHandlerList implementation
* COPYRIGHT: Copyright 2023-2025 Mark Jansen <mark.jansen@reactos.org>
*/
#include "cleanmgr.h"
void CCleanupHandlerList::LoadHandlers(WCHAR Drive)
{
m_DriveStr.Format(L"%c:", Drive);
CRegKey VolumeCaches;
if (VolumeCaches.Open(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VolumeCaches", KEY_READ) != ERROR_SUCCESS)
return;
LONG ItemIndex = 0;
WCHAR szKeyName[MAX_PATH];
WCHAR wszVolume[] = { Drive, L':', L'\\', UNICODE_NULL };
while (TRUE)
{
DWORD dwSize = _countof(szKeyName);
if (VolumeCaches.EnumKey(ItemIndex++, szKeyName, &dwSize) != ERROR_SUCCESS)
{
break;
}
CRegKey hSubKey;
if (hSubKey.Open(VolumeCaches, szKeyName, KEY_READ) == ERROR_SUCCESS)
{
WCHAR GuidStr[50] = {};
dwSize = _countof(GuidStr);
if (hSubKey.QueryStringValue(NULL, GuidStr, &dwSize) != ERROR_SUCCESS)
{
continue;
}
GUID guid = {};
if (FAILED_UNEXPECTEDLY(CLSIDFromString(GuidStr, &guid)))
continue;
CCleanupHandler* handler = new CCleanupHandler(hSubKey, szKeyName, guid);
if (!handler->Initialize(wszVolume))
{
delete handler;
continue;
}
m_Handlers.AddTail(handler);
}
}
// Sort handlers
BOOL fChanged = m_Handlers.GetCount() > 0;
while (fChanged)
{
fChanged = FALSE;
for (size_t n = 0; n < m_Handlers.GetCount() - 1; n++)
{
POSITION leftPos = m_Handlers.FindIndex(n);
POSITION rightPos = m_Handlers.FindIndex(n+1);
CCleanupHandler* left = m_Handlers.GetAt(leftPos);
CCleanupHandler* right = m_Handlers.GetAt(rightPos);
if (right->Priority < left->Priority)
{
m_Handlers.SwapElements(leftPos, rightPos);
fChanged = TRUE;
}
else if (right->Priority == left->Priority)
{
CStringW leftStr(left->wszDisplayName);
if (leftStr.Compare(right->wszDisplayName) > 0)
{
m_Handlers.SwapElements(leftPos, rightPos);
fChanged = TRUE;
}
}
}
}
}
DWORDLONG
CCleanupHandlerList::ScanDrive(IEmptyVolumeCacheCallBack *picb)
{
CProgressDlg progress;
CString Caption;
Caption.Format(IDS_CALCULATING, m_DriveStr.GetString());
CStringW Title(MAKEINTRESOURCE(IDS_DISK_CLEANUP));
progress.Start((DWORD)m_Handlers.GetCount(), Title, Caption);
int ItemIndex = 0;
DWORDLONG TotalSpaceUsed = 0;
ForEach(
[&](CCleanupHandler *current)
{
Caption.Format(IDS_SCANNING, current->wszDisplayName.m_pData);
progress.Step(++ItemIndex, Caption);
HRESULT hr = current->Handler->GetSpaceUsed(&current->SpaceUsed, picb);
if (FAILED_UNEXPECTEDLY(hr))
{
current->ShowHandler = false;
current->StateFlags &= ~HANDLER_STATE_SELECTED;
return;
}
if (current->SpaceUsed == 0 && current->DontShowIfZero())
{
current->ShowHandler = false;
current->StateFlags &= ~HANDLER_STATE_SELECTED;
}
TotalSpaceUsed += current->SpaceUsed;
});
progress.Stop();
return TotalSpaceUsed;
}
void
CCleanupHandlerList::ExecuteCleanup(IEmptyVolumeCacheCallBack *picb)
{
CProgressDlg progress;
CString Caption;
Caption.Format(IDS_CLEANING_CAPTION, m_DriveStr.GetString());
DWORD TotalSelected = 0;
ForEach(
[&](CCleanupHandler *current)
{
if (current->StateFlags & HANDLER_STATE_SELECTED)
TotalSelected++;
});
CStringW Title(MAKEINTRESOURCE(IDS_DISK_CLEANUP));
progress.Start(TotalSelected, Title, Caption);
int ItemIndex = 0;
ForEach(
[&](CCleanupHandler *current)
{
if (!(current->StateFlags & HANDLER_STATE_SELECTED))
return;
Caption.Format(IDS_CLEANING, current->wszDisplayName.m_pData);
progress.Step(++ItemIndex, Caption);
// If there is nothing to clean, we might get STG_E_NOMOREFILES
if (current->SpaceUsed > 0)
{
HRESULT hr = current->Handler->Purge(-1, picb);
if (FAILED_UNEXPECTEDLY(hr))
return;
}
});
progress.Stop();
}

View File

@@ -0,0 +1,31 @@
/*
* PROJECT: ReactOS Disk Cleanup
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: CCleanupHandlerList definition
* COPYRIGHT: Copyright 2023-2025 Mark Jansen <mark.jansen@reactos.org>
*/
class CCleanupHandlerList
{
private:
CAtlList<CCleanupHandler *> m_Handlers;
CStringW m_DriveStr;
public:
void LoadHandlers(WCHAR Drive);
DWORDLONG ScanDrive(IEmptyVolumeCacheCallBack* picb);
void ExecuteCleanup(IEmptyVolumeCacheCallBack *picb);
template<typename Fn>
void ForEach(Fn callback)
{
for (POSITION it = m_Handlers.GetHeadPosition(); it; m_Handlers.GetNext(it))
{
CCleanupHandler *current = m_Handlers.GetAt(it);
callback(current);
}
}
};

View File

@@ -0,0 +1,54 @@
/*
* PROJECT: ReactOS Disk Cleanup
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: CEmptyVolumeCacheCallBack definition / implementation
* COPYRIGHT: Copyright 2023-2025 Mark Jansen <mark.jansen@reactos.org>
*/
// We don't really use this, but some windows handlers crash without it
struct CEmptyVolumeCacheCallBack
: public IEmptyVolumeCacheCallBack
{
STDMETHOD_(ULONG, AddRef)() throw()
{
return 2;
}
STDMETHOD_(ULONG, Release)() throw()
{
return 1;
}
STDMETHOD(QueryInterface)(
REFIID riid,
_COM_Outptr_ void** ppvObject) throw()
{
if (riid == IID_IUnknown || riid == IID_IEmptyVolumeCacheCallBack)
{
*ppvObject = (IUnknown*)this;
return S_OK;
}
*ppvObject = NULL;
return E_NOINTERFACE;
}
STDMETHODIMP ScanProgress(
_In_ DWORDLONG dwlSpaceUsed,
_In_ DWORD dwFlags,
_In_ LPCWSTR pcwszStatus) override
{
DPRINT("dwlSpaceUsed: %lld, dwFlags: %x\n", dwlSpaceUsed, dwFlags);
return S_OK;
}
STDMETHODIMP PurgeProgress(
_In_ DWORDLONG dwlSpaceFreed,
_In_ DWORDLONG dwlSpaceToFree,
_In_ DWORD dwFlags,
_In_ LPCWSTR pcwszStatus) override
{
DPRINT("dwlSpaceFreed: %lld, dwlSpaceToFree: %lld, dwFlags: %x\n", dwlSpaceFreed, dwlSpaceToFree, dwFlags);
return S_OK;
}
};

View File

@@ -0,0 +1,19 @@
add_executable(cleanmgr
cleanmgr.cpp
cleanmgr.h
cleanmgr.rc
resource.h
CEmptyVolumeCacheCallBack.hpp
CProgressDlg.hpp
CSelectDriveDlg.cpp
CCleanupHandler.cpp
CCleanupHandler.hpp
CCleanupHandlerList.cpp
CCleanupHandlerList.hpp
)
set_module_type(cleanmgr win32gui UNICODE)
target_link_libraries(cleanmgr uuid cpprt atl_classes)
add_importlibs(cleanmgr shlwapi oleaut32 ole32 shell32 comctl32 user32 advapi32 msvcrt kernel32 ntdll)
add_dependencies(cleanmgr psdk)
add_cd_file(TARGET cleanmgr DESTINATION reactos/system32 FOR all)

View File

@@ -0,0 +1,50 @@
/*
* PROJECT: ReactOS Disk Cleanup
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: Progress dialog implementation
* COPYRIGHT: Copyright 2023-2025 Mark Jansen <mark.jansen@reactos.org>
*/
#pragma once
class CProgressDlg
{
CComPtr<IProgressDialog> m_spProgress;
DWORD m_dwTotal = 0;
public:
~CProgressDlg()
{
Stop();
}
void Start(DWORD dwTotalSteps, LPCWSTR Title, LPCWSTR Text)
{
HRESULT hr = CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC, IID_PPV_ARG(IProgressDialog, &m_spProgress));
if (FAILED_UNEXPECTEDLY(hr))
return;
m_dwTotal = dwTotalSteps;
m_spProgress->SetTitle(Title);
m_spProgress->SetLine(2, Text, TRUE, NULL);
m_spProgress->StartProgressDialog(NULL, NULL, PROGDLG_NOMINIMIZE, NULL);
m_spProgress->SetProgress(0, m_dwTotal);
}
void Step(DWORD dwProgress, LPCWSTR Text)
{
m_spProgress->SetProgress(dwProgress, m_dwTotal);
m_spProgress->SetLine(1, Text, TRUE, NULL);
}
void Stop()
{
if (m_spProgress)
{
m_spProgress->StopProgressDialog();
m_spProgress.Release();
}
}
};

View File

@@ -0,0 +1,67 @@
/*
* PROJECT: ReactOS Disk Cleanup
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: Drive selection dialog
* COPYRIGHT: Copyright 2023-2025 Mark Jansen <mark.jansen@reactos.org>
*/
#include "cleanmgr.h"
class CSelectDriveDlg : public CDialogImpl<CSelectDriveDlg>
{
public:
enum { IDD = IDD_SELECTDRIVE };
BEGIN_MSG_MAP(CSelectDriveDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(IDOK, OnEndDialog)
COMMAND_ID_HANDLER(IDCANCEL, OnEndDialog)
END_MSG_MAP()
CSelectDriveDlg()
:m_SelectedDrive(UNICODE_NULL)
{
}
LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&)
{
CWindow cbo = GetDlgItem(IDC_DRIVES);
WCHAR VolumeNameBuffer[MAX_PATH + 1];
CStringW Tmp;
for (WCHAR Drive = 'A'; Drive <= 'Z'; ++Drive)
{
WCHAR RootPathName[] = { Drive,':','\\',0 };
UINT Type = GetDriveTypeW(RootPathName);
if (Type == DRIVE_FIXED)
{
GetVolumeInformationW(RootPathName, VolumeNameBuffer, _countof(VolumeNameBuffer), 0, 0, 0, 0, 0);
Tmp.Format(L"%s (%.2s)", VolumeNameBuffer, RootPathName);
int index = (int)cbo.SendMessage(CB_ADDSTRING, NULL, (LPARAM)Tmp.GetString());
cbo.SendMessage(CB_SETITEMDATA, index, Drive);
}
}
cbo.SendMessage(CB_SETCURSEL, 0);
return 1;
}
LRESULT OnEndDialog(WORD, WORD wID, HWND, BOOL&)
{
CWindow cbo = GetDlgItem(IDC_DRIVES);
m_SelectedDrive = (WCHAR)cbo.SendMessage(CB_GETITEMDATA, cbo.SendMessage(CB_GETCURSEL));
EndDialog(wID);
return 0;
}
WCHAR m_SelectedDrive;
};
void
SelectDrive(WCHAR &Drive)
{
CSelectDriveDlg dlgSelectDrive;
if (dlgSelectDrive.DoModal() == IDOK)
{
Drive = dlgSelectDrive.m_SelectedDrive;
}
}

View File

@@ -0,0 +1,296 @@
/*
* PROJECT: ReactOS Disk Cleanup
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: Disk cleanup entrypoint
* COPYRIGHT: Copyright 2023-2025 Mark Jansen <mark.jansen@reactos.org>
*/
#include "cleanmgr.h"
// for listview with extend style LVS_EX_CHECKBOXES, State image 1 is the unchecked box, and state image 2 is the
// checked box. see this: https://docs.microsoft.com/en-us/windows/win32/controls/extended-list-view-styles
#define STATEIMAGETOINDEX(x) (((x)&LVIS_STATEIMAGEMASK) >> 12)
#define STATEIMAGE_UNCHECKED 1
#define STATEIMAGE_CHECKED 2
struct CCleanMgrProperties :
public CPropertyPageImpl<CCleanMgrProperties>
{
enum { IDD = IDD_PROPERTIES_MAIN };
CWindow m_HandlerListControl;
WCHAR m_Drive;
DWORDLONG m_TotalSpaceUsed;
CCleanupHandlerList* m_HandlerList;
bool m_IgnoreChanges = true;
CCleanMgrProperties(WCHAR Drive, DWORDLONG TotalSpaceUsed, CCleanupHandlerList *handlerList)
: m_Drive(Drive)
, m_TotalSpaceUsed(TotalSpaceUsed)
, m_HandlerList(handlerList)
{
}
int OnApply()
{
CStringW Title(MAKEINTRESOURCE(IDS_DISK_CLEANUP));
CStringW Text(MAKEINTRESOURCE(IDS_CONFIRM_DELETE));
if (MessageBoxW(Text, Title, MB_YESNO | MB_ICONQUESTION) != IDYES)
return PSNRET_INVALID;
return PSNRET_NOERROR;
}
LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
HICON hIcon = (HICON)::LoadImageW(
_AtlBaseModule.GetResourceInstance(), MAKEINTRESOURCEW(IDI_CLEANMGR), IMAGE_ICON, 0, 0,
LR_DEFAULTSIZE | LR_SHARED);
SendDlgItemMessage(IDC_DISKICON, STM_SETICON, (WPARAM)hIcon);
m_HandlerListControl = GetDlgItem(IDC_HANDLERLIST);
RECT rc;
m_HandlerListControl.GetClientRect(&rc);
rc.right -= GetSystemMetrics(SM_CXVSCROLL);
LV_COLUMN column = {};
column.mask = LVCF_FMT | LVCF_WIDTH;
column.fmt = LVCFMT_LEFT;
column.cx = rc.right * 80 / 100;
ListView_InsertColumn(m_HandlerListControl, 0, &column);
column.fmt = LVCFMT_RIGHT;
column.cx = rc.right * 20 / 100;
ListView_InsertColumn(m_HandlerListControl, 1, &column);
HIMAGELIST hImagelist = ImageList_Create(16, 16, ILC_MASK | ILC_COLOR32, 1, 1);
ListView_SetImageList(m_HandlerListControl, hImagelist, LVSIL_SMALL);
ListView_SetExtendedListViewStyleEx(m_HandlerListControl, LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT, LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT);
m_HandlerList->ForEach(
[&](CCleanupHandler *current)
{
if (!current->ShowHandler)
return;
LV_ITEM item = {};
item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
item.lParam = (LPARAM)current;
item.pszText = (LPWSTR)current->wszDisplayName;
item.iItem = ListView_GetItemCount(m_HandlerListControl);
item.iImage = ImageList_AddIcon(hImagelist, current->hIcon);
item.iItem = ListView_InsertItem(m_HandlerListControl, &item);
ListView_SetCheckState(
m_HandlerListControl, item.iItem, !!(current->StateFlags & HANDLER_STATE_SELECTED));
item.mask = LVIF_TEXT;
WCHAR ByteSize[100] = {};
StrFormatByteSizeW(current->SpaceUsed, ByteSize, _countof(ByteSize));
ListView_SetItemText(m_HandlerListControl, item.iItem, 1, ByteSize);
});
// Now we should start responding to changes
m_IgnoreChanges = false;
// Select the first item
ListView_SetItemState(m_HandlerListControl, 0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
UpdateSpaceUsed();
return TRUE;
}
CCleanupHandler* GetHandler(int Index)
{
LVITEMW item = {};
item.iItem = Index;
if (item.iItem >= 0)
{
item.mask = LVIF_PARAM;
ListView_GetItem(m_HandlerListControl, &item);
return (CCleanupHandler*)item.lParam;
}
return nullptr;
}
LRESULT OnDetails(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
CCleanupHandler *handler = GetHandler(ListView_GetNextItem(m_HandlerListControl, -1, LVIS_FOCUSED));
if (handler)
{
handler->Handler->ShowProperties(m_hWnd);
}
return 0L;
}
LRESULT OnHandlerItemchanged(int idCtrl, LPNMHDR pnmh, BOOL& bHandled)
{
if (idCtrl == IDC_HANDLERLIST)
{
// We are still initializing, don't respond to changes just yet!
if (m_IgnoreChanges)
return 0L;
LPNMLISTVIEW pnic = (LPNMLISTVIEW)pnmh;
// We only care about state changes
if (!(pnic->uChanged & LVIF_STATE))
return 0L;
INT ItemIndex = pnic->iItem;
if (ItemIndex == -1 || ItemIndex >= ListView_GetItemCount(pnic->hdr.hwndFrom))
{
return 0L;
}
bool GotSelected = (pnic->uNewState & LVIS_SELECTED) && !(pnic->uOldState & LVIS_SELECTED);
if (GotSelected)
{
CWindow DetailsButton = GetDlgItem(IDC_DETAILS);
CCleanupHandler* handler = (CCleanupHandler*)pnic->lParam;
SetDlgItemText(IDC_DESCRIPTION, handler->wszDescription ? handler->wszDescription : L"");
if (handler->HasSettings())
{
DetailsButton.ShowWindow(SW_SHOW);
DetailsButton.SetWindowText(handler->wszBtnText);
}
else
{
DetailsButton.ShowWindow(SW_HIDE);
}
}
int iOldState = STATEIMAGETOINDEX(pnic->uOldState);
int iNewState = STATEIMAGETOINDEX(pnic->uNewState);
if ((iOldState ^ iNewState) == (STATEIMAGE_UNCHECKED ^ STATEIMAGE_CHECKED))
{
CCleanupHandler* handler = (CCleanupHandler*)pnic->lParam;
if (iNewState == STATEIMAGE_CHECKED)
handler->StateFlags |= HANDLER_STATE_SELECTED;
else
handler->StateFlags &= ~HANDLER_STATE_SELECTED;
UpdateSpaceUsed();
}
}
return 0L;
}
void UpdateSpaceUsed()
{
CStringW tmp;
WCHAR ByteSize[100];
StrFormatByteSizeW(m_TotalSpaceUsed, ByteSize, _countof(ByteSize));
tmp.Format(IDS_TOTAL_CLEANABLE_CAPTION, ByteSize, m_Drive);
SetDlgItemText(IDC_TOTAL_CLEANABLE, tmp);
DWORDLONG SelectedGained = 0;
m_HandlerList->ForEach(
[&](CCleanupHandler *current)
{
if (current->StateFlags & HANDLER_STATE_SELECTED)
{
SelectedGained += current->SpaceUsed;
}
});
StrFormatByteSizeW(SelectedGained, ByteSize, _countof(ByteSize));
SetDlgItemText(IDC_SELECTED_GAINED, ByteSize);
}
BEGIN_MSG_MAP(CCleanMgrProperties)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(IDC_DETAILS, OnDetails)
NOTIFY_HANDLER(IDC_HANDLERLIST, LVN_ITEMCHANGED, OnHandlerItemchanged)
CHAIN_MSG_MAP(CPropertyPageImpl<CCleanMgrProperties>) // Allow the default handler to call 'OnApply' etc
END_MSG_MAP()
};
class CCleanMgrModule : public ATL::CAtlExeModuleT< CCleanMgrModule >
{
public:
WCHAR m_Drive = UNICODE_NULL;
bool ParseCommandLine(
_In_z_ LPCTSTR lpCmdLine,
_Out_ HRESULT* pnRetCode) throw()
{
int argc = 0;
CLocalPtr<LPWSTR> argv(CommandLineToArgvW(lpCmdLine, &argc));
for (int n = 1; n < argc; ++n)
{
if ((argv[n][0] == '/' || argv[n][0] == '-') && towlower(argv[n][1]) == 'd')
{
if (iswalpha(argv[n][2]))
{
m_Drive = towupper(argv[n][2]);
continue;
}
if ((n + 1) < argc)
{
m_Drive = towupper(argv[n + 1][0]);
++n;
continue;
}
}
}
*pnRetCode = S_OK;
return true;
}
HRESULT Run(_In_ int nShowCmd) throw()
{
if (m_Drive == UNICODE_NULL)
{
SelectDrive(m_Drive);
}
if (m_Drive == UNICODE_NULL)
return E_FAIL;
CCleanupHandlerList Handlers;
CEmptyVolumeCacheCallBack CacheCallBack;
Handlers.LoadHandlers(m_Drive);
DWORDLONG TotalSpaceUsed = Handlers.ScanDrive(&CacheCallBack);
CCleanMgrProperties cleanMgr(m_Drive, TotalSpaceUsed, &Handlers);
HPROPSHEETPAGE hpsp[1] = { cleanMgr.Create() };
PROPSHEETHEADERW psh = { };
psh.dwSize = sizeof(psh);
psh.dwFlags = PSH_NOAPPLYNOW | PSH_USEICONID | PSH_NOCONTEXTHELP;
psh.hInstance = _AtlBaseModule.GetResourceInstance();
psh.pszIcon = MAKEINTRESOURCEW(IDI_CLEANMGR);
CStringW Title;
Title.Format(IDS_PROPERTIES_MAIN_TITLE, m_Drive);
psh.pszCaption = Title;
psh.nPages = _countof(hpsp);
psh.phpage = hpsp;
if (PropertySheetW(&psh) >= 1)
{
Handlers.ExecuteCleanup(&CacheCallBack);
}
return S_OK;
}
};
CCleanMgrModule _AtlModule;
extern "C" int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/,
LPWSTR /*lpCmdLine*/, int nShowCmd)
{
return _AtlModule.WinMain(nShowCmd);
}

View File

@@ -0,0 +1,66 @@
/*
* PROJECT: ReactOS Disk Cleanup
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: Main header file
* COPYRIGHT: Copyright 2023-2025 Mark Jansen <mark.jansen@reactos.org>
*/
#pragma once
#ifndef STRICT
#define STRICT
#endif
#define _ATL_APARTMENT_THREADED
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit
#define ATL_NO_ASSERT_ON_DESTROY_NONEXISTENT_WINDOW
#define _FORCENAMELESSUNION
#include <ndk/rtlfuncs.h>
#include <windef.h>
#include <winbase.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <atlbase.h>
#include <atlcom.h>
#include <atlstr.h>
#include <strsafe.h>
#include <emptyvc.h>
#include <atlcoll.h>
using namespace ATL;
#define NDEBUG
#include <reactos/debug.h>
#include <reactos/shellutils.h>
#include <ui/rosdlgs.h>
template <class T> class CLocalPtr
: public CHeapPtr<T, CLocalAllocator>
{
public:
CLocalPtr() throw()
{
}
explicit CLocalPtr(_In_ T* pData) throw() :
CHeapPtr<T, CLocalAllocator>(pData)
{
}
};
#include "resource.h"
#include "CProgressDlg.hpp"
#include "CCleanupHandler.hpp"
#include "CCleanupHandlerList.hpp"
#include "CEmptyVolumeCacheCallBack.hpp"
// CSelectDriveDlg.cpp
void
SelectDrive(WCHAR &Drive);

View File

@@ -0,0 +1,64 @@
/*
* PROJECT: ReactOS Disk Cleanup
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: Resources
* COPYRIGHT: Copyright 2023-2025 Mark Jansen <mark.jansen@reactos.org>
*/
#include <windef.h>
#include <winuser.h>
#include <commctrl.h>
#include "resource.h"
#define REACTOS_STR_FILE_DESCRIPTION "ReactOS Disk Cleanup"
#define REACTOS_STR_INTERNAL_NAME "cleanmgr"
#define REACTOS_STR_ORIGINAL_FILENAME "cleanmgr.exe"
#include <reactos/version.rc>
#include <reactos/manifest_exe.rc>
IDI_CLEANMGR ICON "resources/cleanmgr.ico"
#pragma code_page(65001)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
STRINGTABLE
BEGIN
IDS_PROPERTIES_MAIN_TITLE "Disk Cleanup for (%c:)"
IDS_TOTAL_CLEANABLE_CAPTION "You can use Disk Cleanup to free up to %s of disk space on (%c:)."
IDS_DISK_CLEANUP "Disk Cleanup"
IDS_CONFIRM_DELETE "Are you sure you want to delete these files permanently?"
IDS_CALCULATING "Disk Cleanup is calculating how much space can be gained on (%s)."
IDS_SCANNING "Scanning: %s"
IDS_CLEANING_CAPTION "Disk Cleanup is cleaning up files on %s."
IDS_CLEANING "Cleaning: %s"
END
IDD_PROPERTIES_MAIN DIALOGEX 0, 0, 235, 215
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CENTER | WS_CHILD | WS_DISABLED | WS_CAPTION
CAPTION "Disk Cleanup"
FONT 8, "MS Shell Dlg", 400, 0, 0x0
BEGIN
CONTROL "",IDC_DISKICON,"Static",SS_ICON,6,6,20,20
LTEXT "You can use Disk Cleanup to free up to 0.0MB of disk space on drive C.",IDC_TOTAL_CLEANABLE,36,6,192,18
LTEXT "Files to delete:",IDC_STATIC,6,30,222,8
CONTROL "",IDC_HANDLERLIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOCOLUMNHEADER | WS_BORDER | WS_TABSTOP,6,42,222,66
LTEXT "Total amount of disk space gained:",IDC_STATIC,6,114,144,8
RTEXT "",IDC_SELECTED_GAINED,156,114,73,8
GROUPBOX "Description",IDC_STATIC,6,126,222,84
LTEXT "",IDC_DESCRIPTION,12,138,210,54
PUSHBUTTON "Details...",IDC_DETAILS,150,192,74,14
END
IDD_SELECTDRIVE DIALOGEX 0, 0, 177, 74
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Disk Cleanup - Select Drive"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "OK",IDOK,30,48,50,14
PUSHBUTTON "E&xit",IDCANCEL,96,48,50,14
LTEXT "Select the drive to clean up.",IDC_STATIC,12,6,150,8
COMBOBOX IDC_DRIVES,12,24,150,90,CBS_DROPDOWNLIST | CBS_HASSTRINGS | CBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
END

View File

@@ -0,0 +1,30 @@
/*
* PROJECT: ReactOS Disk Cleanup
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: Resource definitions
* COPYRIGHT: Copyright 2023-2025 Mark Jansen <mark.jansen@reactos.org>
*/
#define IDC_STATIC -1
#define IDI_CLEANMGR 100
#define IDD_PROPERTIES_MAIN 200
#define IDC_DISKICON 201
#define IDC_TOTAL_CLEANABLE 202
#define IDC_HANDLERLIST 203
#define IDC_SELECTED_GAINED 204
#define IDC_DESCRIPTION 205
#define IDC_DETAILS 206
#define IDD_SELECTDRIVE 220
#define IDC_DRIVES 221
#define IDS_PROPERTIES_MAIN_TITLE 1000
#define IDS_TOTAL_CLEANABLE_CAPTION 1001
#define IDS_DISK_CLEANUP 1002
#define IDS_CONFIRM_DELETE 1003
#define IDS_CALCULATING 1004
#define IDS_SCANNING 1005
#define IDS_CLEANING_CAPTION 1006
#define IDS_CLEANING 1007

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB