PowerToys/src/modules/powerrename/dll/PowerRenameExt.cpp
Stefan Markovic 5cfbd72fa8
[PowerRename] Fluent UX (#13678)
* PowerRename new UI

* Add scrollviewer

* Don't deploy PowerRenameUI_new

* Visual updates

* Visual updates

* Updates

* Update Resources.resw

* Added docs button

* Update MainWindow.xaml

* Wire Docs button

* RegEx -> regular expressions

* Update Show only renamed list on search/replace text changed

* Update Show only renamed list on search/replace text changed - proper fix
Set searchTerm to NULL when cleared - fix Show only renamed files on clear searchTerm

* Files/folders input error handling

* Fix renaming with keeping UI window opened

After renaming folder, all of it's children need path update.
Without path update, further renaming of children items would
fail.

* Update only children, not all items with greater depth

* Fix dictionary false positives

* Remove .NET dep

* Rename PowerRenameUI_new to PowerRenameUILib
Rename executable PowerRenameUIHost to PowerRename

Co-authored-by: Laute <Niels.Laute@philips.com>
2021-10-25 14:40:19 +01:00

319 lines
9.7 KiB
C++

#include "pch.h"
#include "PowerRenameExt.h"
#include <trace.h>
#include <Helpers.h>
#include <common/themes/icon_helpers.h>
#include <Settings.h>
#include "Generated Files/resource.h"
#include <common/utils/resources.h>
#include <common/utils/process_path.h>
#include <common/utils/HDropIterator.h>
extern HINSTANCE g_hInst;
struct InvokeStruct
{
HWND hwndParent;
IStream* pstrm;
};
CPowerRenameMenu::CPowerRenameMenu()
{
ModuleAddRef();
app_name = GET_RESOURCE_STRING(IDS_POWERRENAME_APP_NAME);
}
CPowerRenameMenu::~CPowerRenameMenu()
{
m_spdo = nullptr;
DeleteObject(m_hbmpIcon);
ModuleRelease();
}
HRESULT CPowerRenameMenu::s_CreateInstance(_In_opt_ IUnknown*, _In_ REFIID riid, _Outptr_ void** ppv)
{
*ppv = nullptr;
HRESULT hr = E_OUTOFMEMORY;
CPowerRenameMenu* pprm = new CPowerRenameMenu();
if (pprm)
{
hr = pprm->QueryInterface(riid, ppv);
pprm->Release();
}
return hr;
}
// IShellExtInit
HRESULT CPowerRenameMenu::Initialize(_In_opt_ PCIDLIST_ABSOLUTE, _In_ IDataObject* pdtobj, HKEY)
{
// Check if we have disabled ourselves
if (!CSettingsInstance().GetEnabled())
return E_FAIL;
// Cache the data object to be used later
m_spdo = pdtobj;
return S_OK;
}
// IContextMenu
HRESULT CPowerRenameMenu::QueryContextMenu(HMENU hMenu, UINT index, UINT uIDFirst, UINT, UINT uFlags)
{
// Check if we have disabled ourselves
if (!CSettingsInstance().GetEnabled())
return E_FAIL;
// Check if we should only be on the extended context menu
if (CSettingsInstance().GetExtendedContextMenuOnly() && (!(uFlags & CMF_EXTENDEDVERBS)))
return E_FAIL;
// Check if at least one of the selected items is actually renamable.
if (!DataObjectContainsRenamableItem(m_spdo))
return E_FAIL;
HRESULT hr = E_UNEXPECTED;
if (m_spdo && !(uFlags & (CMF_DEFAULTONLY | CMF_VERBSONLY | CMF_OPTIMIZEFORINVOKE)))
{
wchar_t menuName[64] = { 0 };
LoadString(g_hInst, IDS_POWERRENAME, menuName, ARRAYSIZE(menuName));
MENUITEMINFO mii;
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_STATE;
mii.wID = uIDFirst++;
mii.fType = MFT_STRING;
mii.dwTypeData = (PWSTR)menuName;
mii.fState = MFS_ENABLED;
if (CSettingsInstance().GetShowIconOnMenu())
{
HICON hIcon = (HICON)LoadImage(g_hInst, MAKEINTRESOURCE(IDI_RENAME), IMAGE_ICON, 16, 16, 0);
if (hIcon)
{
mii.fMask |= MIIM_BITMAP;
if (m_hbmpIcon == NULL)
{
m_hbmpIcon = CreateBitmapFromIcon(hIcon);
}
mii.hbmpItem = m_hbmpIcon;
DestroyIcon(hIcon);
}
}
if (!InsertMenuItem(hMenu, index, TRUE, &mii))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
else
{
hr = MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1);
}
}
return hr;
}
HRESULT CPowerRenameMenu::InvokeCommand(_In_ LPCMINVOKECOMMANDINFO pici)
{
return RunPowerRename(pici, nullptr);
}
HRESULT CPowerRenameMenu::RunPowerRename(CMINVOKECOMMANDINFO* pici, IShellItemArray* psiItemArray)
{
HRESULT hr = E_FAIL;
if (CSettingsInstance().GetEnabled() &&
(IS_INTRESOURCE(pici->lpVerb)) &&
(LOWORD(pici->lpVerb) == 0))
{
Trace::Invoked();
// Set the application path based on the location of the dll
std::wstring path = get_module_folderpath(g_hInst);
path = path + L"\\PowerRename.exe";
LPTSTR lpApplicationName = (LPTSTR)path.c_str();
// Create an anonymous pipe to stream filenames
SECURITY_ATTRIBUTES sa;
HANDLE hReadPipe;
HANDLE hWritePipe;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0))
{
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
if (!SetHandleInformation(hWritePipe, HANDLE_FLAG_INHERIT, 0))
{
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
CAtlFile writePipe(hWritePipe);
CString commandLine;
commandLine.Format(_T("\"%s\""), lpApplicationName);
int nSize = commandLine.GetLength() + 1;
LPTSTR lpszCommandLine = new TCHAR[nSize];
_tcscpy_s(lpszCommandLine, nSize, commandLine);
STARTUPINFO startupInfo;
ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
startupInfo.cb = sizeof(STARTUPINFO);
startupInfo.hStdInput = hReadPipe;
startupInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
if (pici)
{
startupInfo.wShowWindow = pici->nShow;
}
else
{
startupInfo.wShowWindow = SW_SHOWNORMAL;
}
PROCESS_INFORMATION processInformation;
// Start the resizer
CreateProcess(
NULL,
lpszCommandLine,
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&startupInfo,
&processInformation);
delete[] lpszCommandLine;
if (!CloseHandle(processInformation.hProcess))
{
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
if (!CloseHandle(processInformation.hThread))
{
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
// psiItemArray is NULL if called from InvokeCommand. This part is used for the MSI installer. It is not NULL if it is called from Invoke (MSIX).
if (!psiItemArray)
{
// Stream the input files
HDropIterator i(m_spdo);
for (i.First(); !i.IsDone(); i.Next())
{
CString fileName(i.CurrentItem());
// File name can't contain '?'
fileName.Append(_T("?"));
writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR));
}
}
else
{
//m_pdtobj will be NULL when invoked from the MSIX build as Initialize is never called (IShellExtInit functions aren't called in case of MSIX).
DWORD fileCount = 0;
// Gets the list of files currently selected using the IShellItemArray
psiItemArray->GetCount(&fileCount);
// Iterate over the list of files
for (DWORD i = 0; i < fileCount; i++)
{
IShellItem* shellItem;
psiItemArray->GetItemAt(i, &shellItem);
LPWSTR itemName;
// Retrieves the entire file system path of the file from its shell item
shellItem->GetDisplayName(SIGDN_FILESYSPATH, &itemName);
CString fileName(itemName);
// File name can't contain '?'
fileName.Append(_T("?"));
// Write the file path into the input stream for image resizer
writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR));
}
}
writePipe.Close();
}
Trace::InvokedRet(hr);
return hr;
}
HRESULT __stdcall CPowerRenameMenu::GetTitle(IShellItemArray* /*psiItemArray*/, LPWSTR* ppszName)
{
return SHStrDup(app_name.c_str(), ppszName);
}
HRESULT __stdcall CPowerRenameMenu::GetIcon(IShellItemArray* /*psiItemArray*/, LPWSTR* ppszIcon)
{
if (!CSettingsInstance().GetShowIconOnMenu())
{
*ppszIcon = nullptr;
return E_NOTIMPL;
}
std::wstring iconResourcePath = get_module_filename();
iconResourcePath += L",-";
iconResourcePath += std::to_wstring(IDI_RENAME);
return SHStrDup(iconResourcePath.c_str(), ppszIcon);
}
HRESULT __stdcall CPowerRenameMenu::GetToolTip(IShellItemArray* /*psiItemArray*/, LPWSTR* ppszInfotip)
{
*ppszInfotip = nullptr;
return E_NOTIMPL;
}
HRESULT __stdcall CPowerRenameMenu::GetCanonicalName(GUID* pguidCommandName)
{
*pguidCommandName = __uuidof(this);
return S_OK;
}
HRESULT __stdcall CPowerRenameMenu::GetState(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState)
{
*pCmdState = CSettingsInstance().GetEnabled() ? ECS_ENABLED : ECS_HIDDEN;
return S_OK;
}
//#define DEBUG_TELL_PID
HRESULT __stdcall CPowerRenameMenu::Invoke(IShellItemArray* psiItemArray, IBindCtx* /*pbc*/)
{
#if defined(DEBUG_TELL_PID)
wchar_t buffer[256];
swprintf_s(buffer, L"%d", GetCurrentProcessId());
MessageBoxW(nullptr, buffer, L"PID", MB_OK);
#endif
Trace::Invoked();
InvokeStruct* pInvokeData = new (std::nothrow) InvokeStruct;
HRESULT hr = E_OUTOFMEMORY;
if (pInvokeData)
{
pInvokeData->hwndParent = nullptr;
hr = CoMarshalInterThreadInterfaceInStream(__uuidof(psiItemArray), psiItemArray, &(pInvokeData->pstrm));
if (!SUCCEEDED(hr))
{
return E_FAIL;
}
// Prevent Shutting down before PowerRenameUI is created
ModuleAddRef();
hr = RunPowerRename(nullptr, psiItemArray);
}
Trace::InvokedRet(hr);
return S_OK;
}
HRESULT __stdcall CPowerRenameMenu::GetFlags(EXPCMDFLAGS* pFlags)
{
*pFlags = ECF_DEFAULT;
return S_OK;
}
HRESULT __stdcall CPowerRenameMenu::EnumSubCommands(IEnumExplorerCommand** ppEnum)
{
*ppEnum = nullptr;
return E_NOTIMPL;
}