terminal/src/propsheet/PropSheetHandler.cpp
Mike Griese b97db63030 Prevent the v1 propsheet from zeroing colors, causing black text on black background. (#2651)
* This fixes the registry path

  What's happening is the console is writing the Forcev2 setting, then the v1
  console is ignoring those settings, then when you check the checkbox to save
  the v2 settings, we'll write the zeros out. That's obviously bad. So we'll
  only write the v2 settings back to the registry if the propsheet was launched
  from a v2 console.

  This does not fix the shortcut path. That'll be the next commit.

* Fix the shortcut loading too

  fixes #2319

* remove the redundant property I added

* add some notes to the bx.ps1 change
2019-10-02 16:04:59 -07:00

259 lines
9.9 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include <wrl\implements.h>
#include <wrl\module.h>
#include <wil\resource.h>
#include <shlwapi.h>
#include <shellapi.h>
#include <shlguid.h>
#define PEMAGIC ((WORD)'P' + ((WORD)'E' << 8))
static CONSOLE_STATE_INFO g_csi;
using namespace Microsoft::WRL;
// This class exposes console property sheets for use when launching the filesystem shortcut properties dialog.
// clang-format off
[uuid(D2942F8E-478E-41D3-870A-35A16238F4EE)]
class ConsolePropertySheetHandler WrlFinal : public RuntimeClass<RuntimeClassFlags<ClassicCom>,
IShellExtInit,
IShellPropSheetExt,
IPersist,
FtmBase>
// clang-format on
{
public:
HRESULT RuntimeClassInitialize()
{
return S_OK;
}
// IPersist
STDMETHODIMP GetClassID(_Out_ CLSID* clsid) override
{
*clsid = __uuidof(this);
return S_OK;
}
// IShellExtInit
// Shell QI's for IShellExtInit and calls Initialize first. If we return a succeeding HRESULT, the shell will QI for
// IShellPropSheetExt and call AddPages. A failing HRESULT causes the shell to skip us.
STDMETHODIMP Initialize(_In_ PCIDLIST_ABSOLUTE /*pidlFolder*/, _In_ IDataObject* pdtobj, _In_ HKEY /*hkeyProgID*/)
{
WCHAR szLinkFileName[MAX_PATH];
HRESULT hr = _ShouldAddPropertySheet(pdtobj, szLinkFileName, ARRAYSIZE(szLinkFileName));
if (SUCCEEDED(hr))
{
hr = InitializeConsoleState() ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
hr = _InitializeGlobalStateInfo(szLinkFileName);
}
}
return hr;
}
// IShellPropSheetExt
STDMETHODIMP AddPages(_In_ LPFNADDPROPSHEETPAGE pfnAddPage, _In_ LPARAM lParam)
{
PROPSHEETPAGE psp[NUMBER_OF_PAGES] = {};
HRESULT hr = PopulatePropSheetPageArray(psp, ARRAYSIZE(psp), TRUE /*fRegisterCallbacks*/) ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
for (UINT ipsp = 0; ipsp < ARRAYSIZE(psp) && SUCCEEDED(hr); ipsp++)
{
HPROPSHEETPAGE hPage = CreatePropertySheetPage(&psp[ipsp]);
hr = (hPage == nullptr) ? E_FAIL : S_OK;
if (SUCCEEDED(hr))
{
pfnAddPage(hPage, lParam);
}
}
}
return hr;
}
STDMETHODIMP ReplacePage(_In_ UINT /*uPageID*/, _In_ LPFNADDPROPSHEETPAGE /*pfnReplacePage*/, _In_ LPARAM /*lParam*/)
{
// Implementation not needed -- MSDN says "Replaces a page in a property sheet for a Control Panel object.",
// which we don't need to do.
return E_NOTIMPL;
}
private:
~ConsolePropertySheetHandler() = default;
HRESULT _InitializeGlobalStateInfo(_In_ PCWSTR pszLinkFileName)
{
g_fHostedInFileProperties = TRUE;
gpStateInfo = &g_csi;
// Initialize the fIsV2Console with whatever the current v2 seeting is
// in the registry. Usually this is set by conhost, but in this path,
// we're being launched straight from explorer. See GH#2319, GH#2651
gpStateInfo->fIsV2Console = GetConsoleBoolValue(CONSOLE_REGISTRY_FORCEV2, TRUE);
InitRegistryValues(gpStateInfo);
gpStateInfo->Defaults = TRUE;
GetRegistryValues(gpStateInfo);
PWSTR pszAllocatedFileName;
HRESULT hr = SHStrDup(pszLinkFileName, &pszAllocatedFileName);
if (SUCCEEDED(hr))
{
hr = StringCchCopyW(pszAllocatedFileName, MAX_PATH, pszLinkFileName);
if (SUCCEEDED(hr))
{
// gpStateInfo now owns lifetime of the allocated filename
gpStateInfo->LinkTitle = pszAllocatedFileName;
pszAllocatedFileName = nullptr;
// Not all console shortcuts have console-specific properties. We just take the registry defaults in
// those cases.
BOOL readSettings = FALSE;
NTSTATUS s = ShortcutSerialization::s_GetLinkValues(gpStateInfo, &readSettings, nullptr, 0, nullptr, 0, nullptr, nullptr, nullptr);
hr = HRESULT_FROM_NT(s);
}
else
{
CoTaskMemFree(pszAllocatedFileName);
}
}
if (SUCCEEDED(hr))
{
InitializeFonts();
hr = FindFontAndUpdateState();
}
return hr;
}
///////////////////////////////////////////////////////////////////////////
// CODE FROM THE SHELL DEPOT'S `idllib.h`
// get a link target item without resolving it.
HRESULT GetTargetIdList(_In_ IShellItem* psiLink, _COM_Outptr_ PIDLIST_ABSOLUTE* ppidl)
{
*ppidl = nullptr;
IShellLink* psl;
HRESULT hr = psiLink->BindToHandler(NULL, BHID_SFUIObject, IID_PPV_ARGS(&psl));
if (SUCCEEDED(hr))
{
hr = psl->GetIDList(ppidl);
if (SUCCEEDED(hr) && (*ppidl == nullptr))
{
hr = E_FAIL;
}
psl->Release();
}
return hr;
}
HRESULT GetTargetItem(_In_ IShellItem* psiLink, _In_ REFIID riid, _COM_Outptr_ void** ppv)
{
*ppv = nullptr;
PIDLIST_ABSOLUTE pidl;
HRESULT hr = GetTargetIdList(psiLink, &pidl);
if (SUCCEEDED(hr))
{
hr = SHCreateItemFromIDList(pidl, riid, ppv);
ILFree(pidl);
}
return hr;
}
///////////////////////////////////////////////////////////////////////////
HRESULT _GetShellItemLinkTargetExpanded(_In_ IShellItem* pShellItem,
_Out_writes_(cchFilePathExtended) PWSTR pszFilePathExtended,
const size_t cchFilePathExtended)
{
ComPtr<IShellItem> shellItemLinkTarget;
HRESULT hr = GetTargetItem(pShellItem, IID_PPV_ARGS(&shellItemLinkTarget));
if (SUCCEEDED(hr))
{
wil::unique_cotaskmem_string linkTargetPath;
hr = shellItemLinkTarget->GetDisplayName(SIGDN_FILESYSPATH, &linkTargetPath);
if (SUCCEEDED(hr))
{
hr = StringCchCopy(pszFilePathExtended, cchFilePathExtended, linkTargetPath.get());
}
}
return hr;
}
HRESULT _ShouldAddPropertySheet(_In_ IDataObject* pdtobj,
_Out_writes_(cchLinkFileName) PWSTR pszLinkFileName,
const size_t cchLinkFileName)
{
ComPtr<IShellItemArray> shellItemArray;
HRESULT hr = SHCreateShellItemArrayFromDataObject(pdtobj, IID_PPV_ARGS(&shellItemArray));
if (SUCCEEDED(hr))
{
DWORD dwItemCount;
hr = shellItemArray->GetCount(&dwItemCount);
if (SUCCEEDED(hr))
{
// only consider being available for selections of a single file
hr = dwItemCount == 1 ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
ComPtr<IShellItem> shellItem;
hr = shellItemArray->GetItemAt(0, &shellItem);
if (SUCCEEDED(hr))
{
// First expensive portion of this method -- reads .lnk file
WCHAR szFileExpanded[MAX_PATH];
hr = _GetShellItemLinkTargetExpanded(shellItem.Get(), szFileExpanded, ARRAYSIZE(szFileExpanded));
if (SUCCEEDED(hr))
{
// Second expensive portion of this method -- cracks the PE header of the .lnk file target
// if it's an executable
SHFILEINFO sfi = { 0 };
DWORD_PTR dwFileType = SHGetFileInfo(szFileExpanded,
0 /*dwFileAttributes*/,
&sfi,
sizeof(sfi),
SHGFI_EXETYPE);
if (HIWORD(dwFileType) == 0 &&
LOWORD(dwFileType) == PEMAGIC)
{
// link target is a console application -- we should show our UI
hr = S_OK;
}
else
{
// link target is not a console application -- we should not show our UI
hr = E_FAIL;
}
}
}
if (hr == S_OK)
{
// We're going to show the UI, write out the link filename while we're here. This is needed
// so we know where changes should be written.
wil::unique_cotaskmem_string linkDisplayName;
hr = shellItem->GetDisplayName(SIGDN_FILESYSPATH, &linkDisplayName);
if (SUCCEEDED(hr))
{
hr = StringCchCopy(pszLinkFileName, cchLinkFileName, linkDisplayName.get());
}
}
}
}
}
return hr;
}
};
CoCreatableClass(ConsolePropertySheetHandler);