From 1f57c782e5e071a6474388405ecc5edb04219b31 Mon Sep 17 00:00:00 2001 From: ge0rdi Date: Sun, 23 Aug 2020 16:20:54 +0200 Subject: [PATCH] Modern settings shell folder Adds virtual shell folder that contains items representing modern settings parsed from `%windir%\ImmersiveControlPanel\Settings\AllSystemSettings_{253E530E-387D-4BC2-959D-E6F86122E5F2}.xml`. It can be accessed via `shell:::{82E749ED-B971-4550-BAF7-06AA2BF7E836}` (in explorer). Item in folder will open given setting page in `Settings` application. --- .../StartMenuHelper/ModernSettings.cpp | 434 +++++++++++++ .../StartMenuHelper/ModernSettings.h | 139 ++++ .../ModernSettingsContextMenu.cpp | 169 +++++ .../ModernSettingsContextMenu.h | 60 ++ .../ModernSettingsContextMenu.rgs | 19 + .../ModernSettingsShellFolder.cpp | 603 ++++++++++++++++++ .../ModernSettingsShellFolder.h | 84 +++ .../ModernSettingsShellFolder.rgs | 26 + .../StartMenuHelper/StartMenuHelper.idl | 18 + .../StartMenuHelper/StartMenuHelper.rc | 2 + .../StartMenuHelper/StartMenuHelper.vcxproj | 8 + .../StartMenuHelper.vcxproj.filters | 24 + Src/StartMenu/StartMenuHelper/resource.h | 4 +- 13 files changed, 1589 insertions(+), 1 deletion(-) create mode 100644 Src/StartMenu/StartMenuHelper/ModernSettings.cpp create mode 100644 Src/StartMenu/StartMenuHelper/ModernSettings.h create mode 100644 Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.cpp create mode 100644 Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.h create mode 100644 Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.rgs create mode 100644 Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.cpp create mode 100644 Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.h create mode 100644 Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.rgs diff --git a/Src/StartMenu/StartMenuHelper/ModernSettings.cpp b/Src/StartMenu/StartMenuHelper/ModernSettings.cpp new file mode 100644 index 0000000..1ef7bfd --- /dev/null +++ b/Src/StartMenu/StartMenuHelper/ModernSettings.cpp @@ -0,0 +1,434 @@ +// Modern settings helper + +// - parse modern settings definitions from %windir%\ImmersiveControlPanel\Settings\AllSystemSettings_{253E530E-387D-4BC2-959D-E6F86122E5F2}.xml +// - store cached data (parsed settings, localized strings) in %LOCALAPPDATA%\OpenShell\ModernSettings.dat +// - provide mapped view over cached data + +#include "stdafx.h" +#include "ModernSettings.h" +#include "ResourceHelper.h" +#include +#include +#include +#include +#include + +enum class Id : uint32_t +{ + Header = 'SMSO', + Undef = 0, + Blob, + FileName, + DeepLink, + Icon, + Glyph, + PageId, + HostId, + GroupId, + SettingId, + Description, + Keywords, +}; + +#pragma pack(1) +struct FileHdr +{ + uint32_t openShellVersion = GetVersionEx(g_Instance); + uint32_t windowsVersion = GetVersionEx(GetModuleHandle(L"user32.dll")); + uint32_t userLanguageId = GetUserDefaultUILanguage(); + + bool operator==(const FileHdr& other) const + { + return (windowsVersion == other.windowsVersion) && + (openShellVersion == other.openShellVersion) && + (userLanguageId == other.userLanguageId); + } +}; + +struct ItemHdr +{ + Id id; + uint32_t size; + + const uint8_t* data() const + { + return (const uint8_t*)this + sizeof(*this); + } + + const ItemHdr* next() const + { + return (const ItemHdr*)(data() + size); + } + + std::wstring_view asString() const + { + std::wstring_view retval((const wchar_t*)data(), size / sizeof(wchar_t)); + if (!retval.empty() && retval.back() == 0) + { + retval.remove_suffix(1); + return retval; + } + + return {}; + } +}; +#pragma pack() + +class AttributeWriter +{ +public: + std::vector buffer() + { + return std::move(m_buffer); + } + + void addBlob(Id id, const void* data, size_t size) + { + ItemHdr hdr{ id, (uint32_t)size }; + append(&hdr, sizeof(hdr)); + append(data, size); + } + + void addString(Id id, const std::wstring& str) + { + if (!str.empty()) + addBlob(id, str.data(), (str.size() + 1) * sizeof(str[0])); + } + +private: + void append(const void* data, size_t size) + { + m_buffer.insert(m_buffer.end(), (const uint8_t*)data, (const uint8_t*)data + size); + } + + std::vector m_buffer; +}; + +static void ProcessAttributes(const void* buffer, size_t size, std::function callback) +{ + if (size < sizeof(ItemHdr)) + return; + + auto item = (const ItemHdr*)buffer; + auto last = (const ItemHdr*)((const uint8_t*)buffer + size); + + while (item < last) + { + auto next = item->next(); + if (next <= item || next > last) + break; + + callback(*item); + + item = next; + } +} + +/// + +static std::wstring TranslateIndirectString(const WCHAR* string) +{ + std::wstring retval; + retval.resize(1024); + + if (SUCCEEDED(::SHLoadIndirectString(string, retval.data(), (UINT)retval.size(), nullptr))) + { + retval.resize(wcslen(retval.data())); + return retval; + } + + return {}; +} + +static std::wstring TranslateIndirectMultiString(const WCHAR* string) +{ + std::wstring retval; + std::wstring_view str(string); + + // remove '@' + str.remove_prefix(1); + + while (!str.empty()) + { + auto len = str.find(L'@', 1); + if (len == std::wstring::npos) + len = str.length(); + + std::wstring tmp(str.substr(0, len)); + retval += TranslateIndirectString(tmp.c_str()); + + str.remove_prefix(len); + } + + return retval; +} + +static std::wstring GetTranslatedString(CComPtr& parent, const WCHAR* name) +{ + CComPtr node; + if (parent->selectSingleNode(CComBSTR(name), &node) == S_OK) + { + CComBSTR value; + if (node->get_text(&value) == S_OK) + { + if (value[0] == L'@') + { + if (value[1] == L'@') + return TranslateIndirectMultiString(value); + else + return TranslateIndirectString(value); + } + else + { + return (LPWSTR)value; + } + } + } + + return {}; +} + +static void ParseFileName(CComPtr& parent, AttributeWriter& writer) +{ + writer.addString(Id::FileName, GetTranslatedString(parent, L"Filename")); +} + +static void ParseApplicationInformation(CComPtr& parent, AttributeWriter& writer) +{ + CComPtr node; + if (parent->selectSingleNode(CComBSTR(L"ApplicationInformation"), &node) == S_OK) + { + writer.addString(Id::DeepLink, GetTranslatedString(node, L"DeepLink")); + writer.addString(Id::Icon, GetTranslatedString(node, L"Icon")); + writer.addString(Id::Glyph, GetTranslatedString(node, L"Glyph")); + } +} + +static void ParseSettingIdentity(CComPtr& parent, AttributeWriter& writer) +{ + CComPtr node; + if (parent->selectSingleNode(CComBSTR(L"SettingIdentity"), &node) == S_OK) + { + writer.addString(Id::PageId, GetTranslatedString(node, L"PageID")); + writer.addString(Id::HostId, GetTranslatedString(node, L"HostID")); + writer.addString(Id::GroupId, GetTranslatedString(node, L"GroupID")); + writer.addString(Id::SettingId, GetTranslatedString(node, L"SettingID")); + } +} + +static void ParseSettingInformation(CComPtr& parent, AttributeWriter& writer) +{ + CComPtr node; + if (parent->selectSingleNode(CComBSTR(L"SettingInformation"), &node) == S_OK) + { + auto description = GetTranslatedString(node, L"Description"); + if (description.empty()) + description = GetTranslatedString(node, L"Name"); + + writer.addString(Id::Description, description); + + auto keywords = GetTranslatedString(node, L"HighKeywords"); + keywords += GetTranslatedString(node, L"LowKeywords"); + keywords += GetTranslatedString(node, L"Keywords"); + + writer.addString(Id::Keywords, keywords); + } +} + +static std::vector ParseSetting(CComPtr& parent) +{ + AttributeWriter writer; + + ParseFileName(parent, writer); + ParseApplicationInformation(parent, writer); + ParseSettingIdentity(parent, writer); + ParseSettingInformation(parent, writer); + + return writer.buffer(); +} + +static std::vector ParseModernSettings() +{ + AttributeWriter writer; + + CComPtr doc; + if (SUCCEEDED(doc.CoCreateInstance(L"Msxml2.FreeThreadedDOMDocument"))) + { + doc->put_async(VARIANT_FALSE); + + wchar_t path[MAX_PATH]{}; + wcscpy_s(path, LR"(%windir%\ImmersiveControlPanel\Settings\AllSystemSettings_{253E530E-387D-4BC2-959D-E6F86122E5F2}.xml)"); + DoEnvironmentSubst(path, _countof(path)); + + VARIANT_BOOL loaded; + if (SUCCEEDED(doc->load(CComVariant(path), &loaded)) && loaded) + { + CComPtr root; + if (doc->selectSingleNode(CComBSTR(L"PCSettings"), &root) == S_OK) + { + FileHdr hdr{}; + writer.addBlob(Id::Header, &hdr, sizeof(hdr)); + + CComPtr node; + root->get_firstChild(&node); + while (node) + { + auto buffer = ParseSetting(node); + if (!buffer.empty()) + writer.addBlob(Id::Blob, buffer.data(), buffer.size()); + + CComPtr next; + if (FAILED(node->get_nextSibling(&next))) + break; + node = next; + } + } + } + } + + return writer.buffer(); +} + +ModernSettings::ModernSettings(const wchar_t* fname) : m_storage(fname) +{ + if (m_storage) + { + bool valid = false; + auto s = m_storage.get(); + ProcessAttributes(s.data, s.size, [&](const ItemHdr& item) { + switch (item.id) + { + case Id::Header: + if (item.size >= sizeof(FileHdr)) + { + const auto hdr = (const FileHdr*)item.data(); + if (FileHdr() == *hdr) + valid = true; + } + break; + case Id::Blob: + if (valid) + { + const Blob blob = { item.data(), item.size }; + ModernSettings::Setting s(blob); + if (s) + m_settings.emplace(s.fileName, blob); + } + break; + } + }); + } +} + +ModernSettings::Setting::Setting(const Blob& blob) +{ + ProcessAttributes(blob.data, blob.size, [&](const ItemHdr& item) { + switch (item.id) + { + case Id::FileName: + fileName = item.asString(); + break; + case Id::DeepLink: + deepLink = item.asString(); + break; + case Id::Glyph: + glyph = item.asString(); + break; + case Id::Icon: + icon = item.asString(); + break; + case Id::PageId: + pageId = item.asString(); + break; + case Id::HostId: + hostId = item.asString(); + break; + case Id::GroupId: + groupId = item.asString(); + break; + case Id::SettingId: + settingId = item.asString(); + break; + case Id::Description: + description = item.asString();; + break; + case Id::Keywords: + keywords = item.asString(); + break; + } + }); +} + +std::vector ModernSettings::enumerate() const +{ + std::vector retval; + retval.reserve(m_settings.size()); + + for (const auto& i : m_settings) + retval.emplace_back(i.second); + + return retval; +} + +ModernSettings::Setting ModernSettings::get(const std::wstring_view& name) const +{ + auto it = m_settings.find(name); + if (it != m_settings.end()) + return { (*it).second }; + + return {}; +} + +static std::mutex s_lock; +static std::shared_ptr s_settings; + +std::wstring GetLocalAppData() +{ + WCHAR path[MAX_PATH]{}; + wcscpy_s(path, L"%LOCALAPPDATA%\\OpenShell"); + DoEnvironmentSubst(path, _countof(path)); + + // make sure directory exists + SHCreateDirectory(nullptr, path); + + return { path }; +} + +std::shared_ptr GetModernSettings() +{ + std::unique_lock l(s_lock); + + if (!s_settings) + { + auto path = GetLocalAppData(); + path += L"\\ModernSettings.dat"; + + // try to open cached settings + s_settings = std::make_shared(path.c_str()); + if (s_settings->size() == 0) + { + // file doesn't exist or wrong format + s_settings.reset(); + + // re-parse settings + auto buffer = ParseModernSettings(); + if (!buffer.empty()) + { + // store to file + { + File f(path.c_str(), GENERIC_WRITE, 0, CREATE_ALWAYS); + if (f) + { + DWORD written; + ::WriteFile(f, buffer.data(), (DWORD)buffer.size(), &written, nullptr); + } + } + + // and try again + s_settings = std::make_shared(path.c_str()); + } + } + } + + return s_settings; +} diff --git a/Src/StartMenu/StartMenuHelper/ModernSettings.h b/Src/StartMenu/StartMenuHelper/ModernSettings.h new file mode 100644 index 0000000..1795a33 --- /dev/null +++ b/Src/StartMenu/StartMenuHelper/ModernSettings.h @@ -0,0 +1,139 @@ +// Modern settings helper + +#pragma once + +#include +#include +#include +#include + +struct Blob +{ + const void* data = nullptr; + size_t size = 0; +}; + +class File +{ +public: + File(const WCHAR* fileName, DWORD desiredAccess, DWORD shareMode, DWORD creationDisposition = OPEN_EXISTING, DWORD flagsAndAttributes = FILE_ATTRIBUTE_NORMAL) + { + m_handle = ::CreateFile(fileName, desiredAccess, shareMode, nullptr, creationDisposition, flagsAndAttributes, nullptr); + } + + ~File() + { + if (m_handle != INVALID_HANDLE_VALUE) + ::CloseHandle(m_handle); + } + + File(const File&) = delete; + File& operator=(const File&) = delete; + + explicit operator bool() const + { + return (m_handle != INVALID_HANDLE_VALUE); + } + + operator HANDLE() const + { + return m_handle; + } + + uint64_t size() const + { + LARGE_INTEGER li = {}; + return ::GetFileSizeEx(m_handle, &li) ? li.QuadPart : (uint64_t)-1; + } + +private: + HANDLE m_handle; +}; + +class MappedFile +{ +public: + MappedFile(const WCHAR* fileName) : m_file(fileName, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_DELETE) + { + if (m_file) + { + auto mapping = ::CreateFileMapping(m_file, nullptr, PAGE_READONLY, 0, 0, nullptr); + if (mapping) + { + m_view.data = ::MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0); + if (m_view.data) + m_view.size = (size_t)m_file.size(); + + ::CloseHandle(mapping); + } + } + } + + ~MappedFile() + { + if (m_view.data) + ::UnmapViewOfFile(m_view.data); + } + + MappedFile(const MappedFile&) = delete; + MappedFile& operator=(const MappedFile&) = delete; + + explicit operator bool() const + { + return (m_view.data != nullptr); + } + + Blob get() const + { + return m_view; + } + +private: + File m_file; + Blob m_view; +}; + +class ModernSettings +{ +public: + ModernSettings(const wchar_t* fname); + + size_t size() const + { + return m_settings.size(); + } + + struct Setting + { + std::wstring_view fileName; + + std::wstring_view deepLink; + std::wstring_view icon; + std::wstring_view glyph; + + std::wstring_view pageId; + std::wstring_view hostId; + std::wstring_view groupId; + std::wstring_view settingId; + std::wstring_view description; + std::wstring_view keywords; + + Setting() = default; + Setting(const Blob& blob); + + explicit operator bool() const + { + return !fileName.empty(); + } + }; + + std::vector enumerate() const; + Setting get(const std::wstring_view& name) const; + +private: + MappedFile m_storage; + std::map m_settings; +}; + +// retrieve actual instance of ModernSettings +std::shared_ptr GetModernSettings(); diff --git a/Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.cpp b/Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.cpp new file mode 100644 index 0000000..549b774 --- /dev/null +++ b/Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.cpp @@ -0,0 +1,169 @@ +// Context menu handler for Open-Shell Modern Settings shell folder + +// Based on Explorer Data Provider Sample (https://docs.microsoft.com/en-us/windows/win32/shell/samples-explorerdataprovider) + +#include "stdafx.h" +#include "ModernSettings.h" +#include "ModernSettingsContextMenu.h" + +#define MENUVERB_OPEN 0 + +struct ICIVERBTOIDMAP +{ + LPCWSTR pszCmd; // verbW + LPCSTR pszCmdA; // verbA + UINT idCmd; // hmenu id +}; + +static const ICIVERBTOIDMAP g_ContextMenuIDMap[] = +{ + { L"open", "open", MENUVERB_OPEN }, + { NULL, NULL, (UINT)-1 } +}; + +HRESULT _MapICIVerbToCmdID(LPCMINVOKECOMMANDINFO pici, UINT* pid) +{ + if (IS_INTRESOURCE(pici->lpVerb)) + { + *pid = LOWORD((UINT_PTR)pici->lpVerb); + return S_OK; + } + + if (pici->fMask & CMIC_MASK_UNICODE) + { + for (const auto& i : g_ContextMenuIDMap) + { + if (StrCmpIC(((LPCMINVOKECOMMANDINFOEX)pici)->lpVerbW, i.pszCmd) == 0) + { + *pid = i.idCmd; + return S_OK; + } + } + } + else + { + for (const auto& i : g_ContextMenuIDMap) + { + if (StrCmpICA(pici->lpVerb, i.pszCmdA) == 0) + { + *pid = i.idCmd; + return S_OK; + } + } + } + + return E_FAIL; +} + +static bool ActivateModernSettingPage(const WCHAR* page) +{ + CComPtr mgr; + mgr.CoCreateInstance(CLSID_ApplicationActivationManager); + if (mgr) + { + DWORD pid = 0; + return SUCCEEDED(mgr->ActivateApplication(L"windows.immersivecontrolpanel_cw5n1h2txyewy!microsoft.windows.immersivecontrolpanel", page, AO_NONE, &pid)); + } + + return false; +} + +extern ModernSettings::Setting GetModernSetting(LPCITEMIDLIST pidl); + +static HRESULT OpenItemByPidl(LPCITEMIDLIST pidl) +{ + auto child = ILFindLastID(pidl); + auto setting = GetModernSetting(child); + + if (!setting) + return E_INVALIDARG; + + if (setting.hostId == L"{6E6DDBCB-9C89-434B-A994-D5F22239523B}") + { + std::wstring cmd(L"windowsdefender://"); + cmd += setting.deepLink; + + return (intptr_t)::ShellExecute(nullptr, L"open", cmd.c_str(), nullptr, nullptr, SW_SHOWNORMAL) > 32 ? S_OK : E_FAIL; + } + + if (setting.pageId.empty()) + return E_INVALIDARG; + + std::wstring page; + + page += L"page="; + page += setting.pageId; + + if (!setting.settingId.empty()) + { + page += L"&target="; + page += setting.settingId; + } + else if (!setting.groupId.empty()) + { + page += L"&group="; + page += setting.groupId; + } + + page += L"&ActivationType=Search"; + + ActivateModernSettingPage(page.c_str()); + + return S_OK; +} + + +// CModernSettingsContextMenu + +HRESULT CModernSettingsContextMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT /* idCmdLast */, UINT /* uFlags */) +{ + InsertMenu(hmenu, indexMenu++, MF_BYPOSITION, idCmdFirst + MENUVERB_OPEN, L"Open"); + // other verbs could go here... + + // indicate that we added one verb. + return MAKE_HRESULT(SEVERITY_SUCCESS, 0, (USHORT)(1)); +} + +HRESULT CModernSettingsContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici) +{ + HRESULT hr = E_INVALIDARG; + UINT uID; + // Is this command for us? + if (SUCCEEDED(_MapICIVerbToCmdID(pici, &uID))) + { + if (uID == MENUVERB_OPEN && m_pdtobj) + { + LPITEMIDLIST pidl; + hr = SHGetIDListFromObject(m_pdtobj, &pidl); + if (SUCCEEDED(hr)) + { + hr = OpenItemByPidl(pidl); + ILFree(pidl); + } + } + } + + return hr; +} + +HRESULT CModernSettingsContextMenu::GetCommandString(UINT_PTR /* idCmd */, UINT /* uType */, UINT* /* pRes */, LPSTR /* pszName */, UINT /* cchMax */) +{ + return E_NOTIMPL; +} + +HRESULT CModernSettingsContextMenu::Initialize(PCIDLIST_ABSOLUTE /* pidlFolder */, IDataObject* pdtobj, HKEY /* hkeyProgID */) +{ + m_pdtobj = pdtobj; + return S_OK; +} + +HRESULT CModernSettingsContextMenu::SetSite(IUnknown* punkSite) +{ + m_punkSite = punkSite; + return S_OK; +} + +HRESULT CModernSettingsContextMenu::GetSite(REFIID riid, void** ppvSite) +{ + return m_punkSite ? m_punkSite->QueryInterface(riid, ppvSite) : E_FAIL; +} diff --git a/Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.h b/Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.h new file mode 100644 index 0000000..78fe1f1 --- /dev/null +++ b/Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.h @@ -0,0 +1,60 @@ +// Context menu handler for Open-Shell Modern Settings shell folder + +#pragma once +#include "resource.h" +#include "StartMenuHelper_i.h" +#include + +// CModernSettingsContextMenu + +class ATL_NO_VTABLE CModernSettingsContextMenu : + public CComObjectRootEx, + public CComCoClass, + public IContextMenu, + public IShellExtInit, + public IObjectWithSite +{ +public: + CModernSettingsContextMenu() + { + } + +DECLARE_REGISTRY_RESOURCEID(IDR_MODERNSETTINGSCONTEXTMENU) + +DECLARE_NOT_AGGREGATABLE(CModernSettingsContextMenu) + +BEGIN_COM_MAP(CModernSettingsContextMenu) + COM_INTERFACE_ENTRY(IContextMenu) + COM_INTERFACE_ENTRY(IShellExtInit) + COM_INTERFACE_ENTRY(IObjectWithSite) +END_COM_MAP() + + DECLARE_PROTECT_FINAL_CONSTRUCT() + + HRESULT FinalConstruct() + { + return S_OK; + } + + void FinalRelease() + { + } + + // IContextMenu + IFACEMETHODIMP QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags); + IFACEMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO lpici); + IFACEMETHODIMP GetCommandString(UINT_PTR idCmd, UINT uType, UINT* pRes, LPSTR pszName, UINT cchMax); + + // IShellExtInit + IFACEMETHODIMP Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID); + + // IObjectWithSite + IFACEMETHODIMP SetSite(IUnknown* punkSite); + IFACEMETHODIMP GetSite(REFIID riid, void** ppvSite); + +private: + CComPtr m_pdtobj; + CComPtr m_punkSite; +}; + +OBJECT_ENTRY_AUTO(__uuidof(ModernSettingsContextMenu), CModernSettingsContextMenu) diff --git a/Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.rgs b/Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.rgs new file mode 100644 index 0000000..ad8bce5 --- /dev/null +++ b/Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.rgs @@ -0,0 +1,19 @@ +HKCR +{ + NoRemove CLSID + { + ForceRemove {5ab14324-c087-42c1-b905-a0bfdb4e9532} = s 'Open-Shell Modern Settings Context Menu' + { + InprocServer32 = s '%MODULE%' + { + val ThreadingModel = s 'Apartment' + } + ShellEx + { + MayChangeDefaultMenu = s '' + { + } + } + } + } +} diff --git a/Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.cpp b/Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.cpp new file mode 100644 index 0000000..21f9443 --- /dev/null +++ b/Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.cpp @@ -0,0 +1,603 @@ +// Open-Shell Modern Settings shell folder +// Provides folder that contains all modern settings +// +// To open the folder press Win+R and type: +// shell:::{82E749ED-B971-4550-BAF7-06AA2BF7E836} + +// Based on Explorer Data Provider Sample (https://docs.microsoft.com/en-us/windows/win32/shell/samples-explorerdataprovider) + +#include "stdafx.h" +#include "ModernSettings.h" +#include "ModernSettingsShellFolder.h" +#include +#include +#include + +struct ColumnDescription +{ + const wchar_t* name; + PROPERTYKEY key; +}; + +static const ColumnDescription g_columnDescriptions[] = +{ + {L"Name", PKEY_ItemNameDisplay}, + {L"Keywords", PKEY_Keywords}, + {L"Filename", PKEY_FileName}, +}; + +#define MAGIC 'SMSO' + +#pragma pack(1) +struct FVITEMID +{ + USHORT cb; + DWORD magic; + WORD size; + wchar_t data[1]; +}; +#pragma pack() + +static const FVITEMID* PidlToItem(LPCITEMIDLIST pidl) +{ + if (pidl) + { + auto item = (const FVITEMID*)pidl; + if (item->cb && item->magic == MAGIC) + return item; + } + + return nullptr; +} + +ModernSettings::Setting GetModernSetting(LPCITEMIDLIST pidl) +{ + auto item = PidlToItem(pidl); + if (item) + { + auto settings = GetModernSettings(); + if (settings) + return settings->get({ item->data, item->size / sizeof(wchar_t) }); + } + + return {}; +} + +STDAPI StringToStrRet(PCWSTR pszName, STRRET* pStrRet) +{ + pStrRet->uType = STRRET_WSTR; + return SHStrDup(pszName, &pStrRet->pOleStr); +} + +// CModernSettingsShellFolderEnumIDList + +class ATL_NO_VTABLE CModernSettingsShellFolderEnumIDList : + public CComObjectRoot, + public IEnumIDList +{ +public: + BEGIN_COM_MAP(CModernSettingsShellFolderEnumIDList) + COM_INTERFACE_ENTRY(IEnumIDList) + END_COM_MAP() + + // IEnumIDList + IFACEMETHODIMP Next(ULONG celt, PITEMID_CHILD* rgelt, ULONG* pceltFetched) + { + ULONG celtFetched = 0; + + HRESULT hr = (pceltFetched || celt <= 1) ? S_OK : E_INVALIDARG; + if (SUCCEEDED(hr)) + { + ULONG i = 0; + while (SUCCEEDED(hr) && i < celt && m_item < m_items.size()) + { + const auto& s = m_items[m_item]; + if ((s.hostId.empty() && s.deepLink.empty()) || + (s.hostId == L"{6E6DDBCB-9C89-434B-A994-D5F22239523B}" && !s.deepLink.empty())) + { + hr = m_parent->CreateChildID(s.fileName, &rgelt[i]); + if (SUCCEEDED(hr)) + { + celtFetched++; + i++; + } + } + + m_item++; + } + } + + if (pceltFetched) + *pceltFetched = celtFetched; + + return (celtFetched == celt) ? S_OK : S_FALSE; + } + IFACEMETHODIMP Skip(DWORD celt) + { + m_item += celt; + return S_OK; + } + IFACEMETHODIMP Reset() + { + m_item = 0; + return S_OK; + } + IFACEMETHODIMP Clone(IEnumIDList** ppenum) + { + // this method is rarely used and it's acceptable to not implement it. + *ppenum = NULL; + return E_NOTIMPL; + } + + void Initialize(CModernSettingsShellFolder* parent) + { + m_parent = parent; + + m_settings = GetModernSettings(); + if (m_settings) + m_items = m_settings->enumerate(); + } + +private: + CComPtr m_parent; + std::shared_ptr m_settings; + std::vector m_items; + DWORD m_item = 0; +}; + +// Extract icon + +static void BitmapDataToStraightAlpha(void* bits, UINT width, UINT height) +{ + RGBQUAD* data = (RGBQUAD*)bits; + for (UINT y = 0; y < height; y++) + { + for (UINT x = 0; x < width; x++) + { + auto alpha = data->rgbReserved; + if (alpha) + { + data->rgbBlue = (BYTE)((DWORD)data->rgbBlue * 255 / alpha); + data->rgbGreen = (BYTE)((DWORD)data->rgbGreen * 255 / alpha); + data->rgbRed = (BYTE)((DWORD)data->rgbRed * 255 / alpha); + } + data++; + } + } +} + +HICON IconFromGlyph(UINT glyph, UINT size) +{ + ICONINFO info{}; + + info.fIcon = TRUE; + info.hbmMask = CreateBitmap(size, size, 1, 1, nullptr); + + BITMAPINFO bi{}; + bi.bmiHeader.biSize = sizeof(bi.bmiHeader); + bi.bmiHeader.biWidth = size; + bi.bmiHeader.biHeight = -((LONG)size); + bi.bmiHeader.biPlanes = 1; + bi.bmiHeader.biBitCount = 32; + + void* bits = nullptr; + info.hbmColor = CreateDIBSection(nullptr, &bi, 0, &bits, nullptr, 0); + + HDC dc = CreateCompatibleDC(nullptr); + SelectObject(dc, info.hbmColor); + + HFONT font = CreateFontW(size, 0, 0, 0, 400, 0, 0, 0, 1, 0, 0, 0, 0, L"Segoe MDL2 Assets"); + SelectObject(dc, font); + + RECT rc{}; + rc.right = size; + rc.bottom = size; + + auto theme = OpenThemeData(nullptr, L"CompositedWindow::Window"); + DTTOPTS opts{}; + opts.dwSize = sizeof(opts); + opts.dwFlags = DTT_TEXTCOLOR | DTT_COMPOSITED; + opts.crText = 0x00FFFFFF; + DrawThemeTextEx(theme, dc, 0, 0, (LPCWSTR)&glyph, 1, DT_CENTER | DT_VCENTER | DT_SINGLELINE, &rc, &opts); + CloseThemeData(theme); + + DeleteObject(font); + DeleteDC(dc); + + BitmapDataToStraightAlpha(bits, size, size); + + HICON retval = CreateIconIndirect(&info); + + DeleteObject(info.hbmColor); + DeleteObject(info.hbmMask); + + return retval; +} + +class ATL_NO_VTABLE GlyphExtractIcon : + public CComObjectRoot, + public IExtractIconW +{ +public: + + BEGIN_COM_MAP(GlyphExtractIcon) + COM_INTERFACE_ENTRY(IExtractIconW) + END_COM_MAP() + + void SetGlyph(USHORT glyph) + { + m_glyph = glyph; + } + + // IExtractIconW methods + IFACEMETHODIMP GetIconLocation(UINT uFlags, _Out_writes_(cchMax) PWSTR pszIconFile, UINT cchMax, _Out_ int* piIndex, _Out_ UINT* pwFlags) + { + StringCchCopy(pszIconFile, cchMax, L"OpenShell-ModernSettingIcon"); + *piIndex = m_glyph; + *pwFlags = GIL_NOTFILENAME; + return S_OK; + } + IFACEMETHODIMP Extract(_In_ PCWSTR pszFile, UINT nIconIndex, _Out_opt_ HICON* phiconLarge, _Out_opt_ HICON* phiconSmall, UINT nIconSize) + { + if (phiconLarge) + *phiconLarge = IconFromGlyph(nIconIndex, LOWORD(nIconSize)); + if (phiconSmall) + *phiconSmall = IconFromGlyph(nIconIndex, HIWORD(nIconSize)); + return S_OK; + } + +private: + USHORT m_glyph = 0; +}; + + +// CModernSettingsShellFolder + +// IShellFolder methods + +// Translates a display name into an item identifier list. +HRESULT CModernSettingsShellFolder::ParseDisplayName(HWND hwnd, IBindCtx* pbc, PWSTR pszName, ULONG* pchEaten, PIDLIST_RELATIVE* ppidl, ULONG* pdwAttributes) +{ + return E_INVALIDARG; +} + +// Allows a client to determine the contents of a folder by +// creating an item identifier enumeration object and returning +// its IEnumIDList interface. The methods supported by that +// interface can then be used to enumerate the folder's contents. +HRESULT CModernSettingsShellFolder::EnumObjects(HWND /* hwnd */, DWORD grfFlags, IEnumIDList** ppenumIDList) +{ + CComObject* enumIdList; + auto hr = CComObject::CreateInstance(&enumIdList); + if (SUCCEEDED(hr)) + { + enumIdList->Initialize(this); + hr = enumIdList->QueryInterface(IID_PPV_ARGS(ppenumIDList)); + } + + return hr; +} + +// Factory for handlers for the specified item. +HRESULT CModernSettingsShellFolder::BindToObject(PCUIDLIST_RELATIVE pidl, IBindCtx* pbc, REFIID riid, void** ppv) +{ + return E_NOINTERFACE; +} + +HRESULT CModernSettingsShellFolder::BindToStorage(PCUIDLIST_RELATIVE pidl, IBindCtx* pbc, REFIID riid, void** ppv) +{ + return BindToObject(pidl, pbc, riid, ppv); +} + +// Called to determine the equivalence and/or sort order of two idlists. +HRESULT CModernSettingsShellFolder::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2) +{ + UINT column = LOWORD(lParam); + return MAKE_HRESULT(SEVERITY_SUCCESS, 0, (USHORT)(StrCmp(GetColumnDisplayName(pidl1, column).data(), GetColumnDisplayName(pidl2, column).data()))); +} + +// Called by the Shell to create the View Object and return it. +HRESULT CModernSettingsShellFolder::CreateViewObject(HWND hwnd, REFIID riid, void** ppv) +{ + HRESULT hr = E_NOINTERFACE; + *ppv = NULL; + + if (riid == IID_IShellView) + { + SFV_CREATE csfv = { sizeof(csfv), 0 }; + hr = QueryInterface(IID_PPV_ARGS(&csfv.pshf)); + if (SUCCEEDED(hr)) + { + hr = SHCreateShellFolderView(&csfv, (IShellView**)ppv); + csfv.pshf->Release(); + } + } + + return hr; +} + +// Retrieves the attributes of one or more file objects or subfolders. +HRESULT CModernSettingsShellFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, ULONG* rgfInOut) +{ + *rgfInOut &= SFGAO_CANLINK; + return S_OK; +} + +// Retrieves an OLE interface that can be used to carry out +// actions on the specified file objects or folders. +HRESULT CModernSettingsShellFolder::GetUIObjectOf(HWND hwnd, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT* /* prgfInOut */, void** ppv) +{ + HRESULT hr = E_NOINTERFACE; + *ppv = nullptr; + + if (riid == IID_IContextMenu) + { + // The default context menu will call back for IQueryAssociations to determine the + // file associations with which to populate the menu. + const DEFCONTEXTMENU dcm = { hwnd, nullptr, m_pidl, static_cast(this), cidl, apidl, nullptr, 0, nullptr }; + hr = SHCreateDefaultContextMenu(&dcm, riid, ppv); + } + else if (riid == IID_IExtractIconW) + { + hr = E_INVALIDARG; + + auto s = GetModernSetting(*apidl); + if (s) + { + if (!s.icon.empty()) + { + IDefaultExtractIconInit* pdxi; + hr = SHCreateDefaultExtractIcon(IID_PPV_ARGS(&pdxi)); + if (SUCCEEDED(hr)) + { + WCHAR icon_path[MAX_PATH]; + + StringCchCopy(icon_path, _countof(icon_path), s.icon.data()); + auto location = PathParseIconLocation(icon_path); + + hr = pdxi->SetNormalIcon(icon_path, location); + if (SUCCEEDED(hr)) + hr = pdxi->QueryInterface(riid, ppv); + + pdxi->Release(); + } + } + else + { + auto glyph = !s.glyph.empty() ? s.glyph.front() : 0xe115; + + CComObject* extract; + hr = CComObject::CreateInstance(&extract); + if (SUCCEEDED(hr)) + { + extract->SetGlyph(glyph); + hr = extract->QueryInterface(riid, ppv); + } + + } + } + } + else if (riid == IID_IDataObject) + { + hr = SHCreateDataObject(m_pidl, cidl, apidl, nullptr, riid, ppv); + } + else if (riid == IID_IQueryAssociations) + { + WCHAR szFolderViewImplClassID[64]; + hr = StringFromGUID2(CLSID_ModernSettingsShellFolder, szFolderViewImplClassID, ARRAYSIZE(szFolderViewImplClassID)); + if (SUCCEEDED(hr)) + { + const ASSOCIATIONELEMENT assocItem = { ASSOCCLASS_CLSID_STR, nullptr, szFolderViewImplClassID }; + hr = AssocCreateForClasses(&assocItem, 1, riid, ppv); + } + } + + return hr; +} + +// Retrieves the display name for the specified file object or subfolder. +HRESULT CModernSettingsShellFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, SHGDNF shgdnFlags, STRRET* pName) +{ + auto setting = GetModernSetting(pidl); + if (!setting) + return E_INVALIDARG; + + HRESULT hr = S_OK; + + if (shgdnFlags & SHGDN_FORPARSING) + { + if (shgdnFlags & SHGDN_INFOLDER) + { + // This form of the display name needs to be handled by ParseDisplayName. + hr = StringToStrRet(setting.fileName.data(), pName); + } + else + { + WCHAR szDisplayName[MAX_PATH]; + PWSTR pszThisFolder; + hr = SHGetNameFromIDList(m_pidl, (shgdnFlags & SHGDN_FORADDRESSBAR) ? SIGDN_DESKTOPABSOLUTEEDITING : SIGDN_DESKTOPABSOLUTEPARSING, &pszThisFolder); + if (SUCCEEDED(hr)) + { + StringCchCopy(szDisplayName, ARRAYSIZE(szDisplayName), pszThisFolder); + StringCchCat(szDisplayName, ARRAYSIZE(szDisplayName), L"\\"); + StringCchCat(szDisplayName, ARRAYSIZE(szDisplayName), setting.fileName.data()); + + CoTaskMemFree(pszThisFolder); + + hr = StringToStrRet(szDisplayName, pName); + } + } + } + else + { + hr = StringToStrRet(setting.description.data(), pName); + } + + return hr; +} + +// Sets the display name of a file object or subfolder, changing the item identifier in the process. +HRESULT CModernSettingsShellFolder::SetNameOf(HWND /* hwnd */, PCUITEMID_CHILD /* pidl */, PCWSTR /* pszName */, DWORD /* uFlags */, PITEMID_CHILD* ppidlOut) +{ + *ppidlOut = NULL; + return E_NOTIMPL; +} + +// IShellFolder2 methods + +// Requests the GUID of the default search object for the folder. +HRESULT CModernSettingsShellFolder::GetDefaultSearchGUID(GUID* /* pguid */) +{ + return E_NOTIMPL; +} + +HRESULT CModernSettingsShellFolder::EnumSearches(IEnumExtraSearch** ppEnum) +{ + *ppEnum = NULL; + return E_NOINTERFACE; +} + +// Retrieves the default sorting and display column (indices from GetDetailsOf). +HRESULT CModernSettingsShellFolder::GetDefaultColumn(DWORD /* dwRes */, ULONG* pSort, ULONG* pDisplay) +{ + *pSort = 0; + *pDisplay = 0; + return S_OK; +} + +// Retrieves the default state for a specified column. +HRESULT CModernSettingsShellFolder::GetDefaultColumnState(UINT iColumn, SHCOLSTATEF* pcsFlags) +{ + if (iColumn < _countof(g_columnDescriptions)) + { + *pcsFlags = SHCOLSTATE_ONBYDEFAULT | SHCOLSTATE_TYPE_STR; + return S_OK; + } + + return E_INVALIDARG; +} + +// Retrieves detailed information, identified by a property set ID (FMTID) and property ID (PID), on an item in a Shell folder. +HRESULT CModernSettingsShellFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const PROPERTYKEY* pkey, VARIANT* pv) +{ + for (const auto& desc : g_columnDescriptions) + { + if (IsEqualPropertyKey(*pkey, desc.key)) + { + auto str = GetColumnDisplayName(pidl, (UINT)std::distance(g_columnDescriptions, &desc)); + + pv->vt = VT_BSTR; + pv->bstrVal = SysAllocString(str.data()); + return pv->bstrVal ? S_OK : E_OUTOFMEMORY; + } + } + + return S_OK; +} + +// Retrieves detailed information, identified by a column index, on an item in a Shell folder. +HRESULT CModernSettingsShellFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS* pDetails) +{ + pDetails->cxChar = 24; + + if (!pidl) + { + // No item means we're returning information about the column itself. + + if (iColumn >= _countof(g_columnDescriptions)) + { + // GetDetailsOf is called with increasing column indices until failure. + return E_FAIL; + } + + pDetails->fmt = LVCFMT_LEFT; + return StringToStrRet(g_columnDescriptions[iColumn].name, &pDetails->str); + } + + auto str = GetColumnDisplayName(pidl, iColumn); + return StringToStrRet(str.data(), &pDetails->str); +} + +// Converts a column name to the appropriate property set ID (FMTID) and property ID (PID). +HRESULT CModernSettingsShellFolder::MapColumnToSCID(UINT iColumn, PROPERTYKEY* pkey) +{ + if (iColumn < _countof(g_columnDescriptions)) + { + *pkey = g_columnDescriptions[iColumn].key; + return S_OK; + } + + return E_FAIL; +} + +// IPersist method +HRESULT CModernSettingsShellFolder::GetClassID(CLSID* pClassID) +{ + *pClassID = CLSID_ModernSettingsShellFolder; + return S_OK; +} + +// IPersistFolder method +HRESULT CModernSettingsShellFolder::Initialize(PCIDLIST_ABSOLUTE pidl) +{ + m_pidl = ILCloneFull(pidl); + return m_pidl ? S_OK : E_FAIL; +} + +// IPersistFolder2 methods +// Retrieves the PIDLIST_ABSOLUTE for the folder object. +HRESULT CModernSettingsShellFolder::GetCurFolder(PIDLIST_ABSOLUTE* ppidl) +{ + *ppidl = NULL; + HRESULT hr = m_pidl ? S_OK : E_FAIL; + if (SUCCEEDED(hr)) + { + *ppidl = ILCloneFull(m_pidl); + hr = *ppidl ? S_OK : E_OUTOFMEMORY; + } + return hr; +} + +HRESULT CModernSettingsShellFolder::CreateChildID(const std::wstring_view& fileName, PITEMID_CHILD* ppidl) +{ + auto size = fileName.size() * sizeof(wchar_t); + + // Sizeof an object plus the next cb plus the characters in the string. + UINT nIDSize = sizeof(FVITEMID) + sizeof(USHORT) + (WORD)size; + + // Allocate and zero the memory. + FVITEMID* lpMyObj = (FVITEMID*)CoTaskMemAlloc(nIDSize); + + HRESULT hr = lpMyObj ? S_OK : E_OUTOFMEMORY; + if (SUCCEEDED(hr)) + { + ZeroMemory(lpMyObj, nIDSize); + lpMyObj->cb = static_cast(nIDSize - sizeof(lpMyObj->cb)); + lpMyObj->magic = MAGIC; + lpMyObj->size = (WORD)size; + memcpy(lpMyObj->data, fileName.data(), size); + + *ppidl = (PITEMID_CHILD)lpMyObj; + } + + return hr; +} + +std::wstring_view CModernSettingsShellFolder::GetColumnDisplayName(PCUITEMID_CHILD pidl, UINT iColumn) +{ + auto setting = GetModernSetting(pidl); + if (setting) + { + switch (iColumn) + { + case 0: + return setting.description; + case 1: + return setting.keywords; + case 2: + return setting.fileName; + } + } + + return {}; +} diff --git a/Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.h b/Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.h new file mode 100644 index 0000000..a6d84b4 --- /dev/null +++ b/Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.h @@ -0,0 +1,84 @@ +// Open-Shell Modern Settings shell folder +// Provides folder that contains all modern settings + +#pragma once +#include "resource.h" +#include "StartMenuHelper_i.h" +#include +#include + +// CModernSettingsShellFolder + +class ATL_NO_VTABLE CModernSettingsShellFolder : + public CComObjectRootEx, + public CComCoClass, + public IShellFolder2, + public IPersistFolder2 +{ +public: + CModernSettingsShellFolder() + { + } + +DECLARE_REGISTRY_RESOURCEID(IDR_MODERNSETTINGSSHELLFOLDER) + +DECLARE_NOT_AGGREGATABLE(CModernSettingsShellFolder) + +BEGIN_COM_MAP(CModernSettingsShellFolder) + COM_INTERFACE_ENTRY(IShellFolder) + COM_INTERFACE_ENTRY(IShellFolder2) + COM_INTERFACE_ENTRY(IPersist) + COM_INTERFACE_ENTRY(IPersistFolder) + COM_INTERFACE_ENTRY(IPersistFolder2) +END_COM_MAP() + + DECLARE_PROTECT_FINAL_CONSTRUCT() + + HRESULT FinalConstruct() + { + return S_OK; + } + + void FinalRelease() + { + } + + // IShellFolder + IFACEMETHODIMP ParseDisplayName(HWND hwnd, IBindCtx* pbc, PWSTR pszName, ULONG* pchEaten, PIDLIST_RELATIVE* ppidl, ULONG* pdwAttributes); + IFACEMETHODIMP EnumObjects(HWND hwnd, DWORD grfFlags, IEnumIDList** ppenumIDList); + IFACEMETHODIMP BindToObject(PCUIDLIST_RELATIVE pidl, IBindCtx* pbc, REFIID riid, void** ppv); + IFACEMETHODIMP BindToStorage(PCUIDLIST_RELATIVE pidl, IBindCtx* pbc, REFIID riid, void** ppv); + IFACEMETHODIMP CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2); + IFACEMETHODIMP CreateViewObject(HWND hwnd, REFIID riid, void** ppv); + IFACEMETHODIMP GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, ULONG* rgfInOut); + IFACEMETHODIMP GetUIObjectOf(HWND hwnd, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT* prgfInOut, void** ppv); + IFACEMETHODIMP GetDisplayNameOf(PCUITEMID_CHILD pidl, SHGDNF shgdnFlags, STRRET* pName); + IFACEMETHODIMP SetNameOf(HWND hwnd, PCUITEMID_CHILD pidl, PCWSTR pszName, DWORD uFlags, PITEMID_CHILD* ppidlOut); + + // IShellFolder2 + IFACEMETHODIMP GetDefaultSearchGUID(GUID* pGuid); + IFACEMETHODIMP EnumSearches(IEnumExtraSearch** ppenum); + IFACEMETHODIMP GetDefaultColumn(DWORD dwRes, ULONG* pSort, ULONG* pDisplay); + IFACEMETHODIMP GetDefaultColumnState(UINT iColumn, SHCOLSTATEF* pbState); + IFACEMETHODIMP GetDetailsEx(PCUITEMID_CHILD pidl, const PROPERTYKEY* pkey, VARIANT* pv); + IFACEMETHODIMP GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS* pDetails); + IFACEMETHODIMP MapColumnToSCID(UINT iColumn, PROPERTYKEY* pkey); + + // IPersist + IFACEMETHODIMP GetClassID(CLSID* pClassID); + + // IPersistFolder + IFACEMETHODIMP Initialize(PCIDLIST_ABSOLUTE pidl); + + // IPersistFolder2 + IFACEMETHODIMP GetCurFolder(PIDLIST_ABSOLUTE* ppidl); + + HRESULT CreateChildID(const std::wstring_view& fileName, PITEMID_CHILD* ppidl); + +private: + std::wstring_view GetColumnDisplayName(PCUITEMID_CHILD pidl, UINT iColumn); + + PIDLIST_ABSOLUTE m_pidl = nullptr; // where this folder is in the name space +}; + +OBJECT_ENTRY_AUTO(__uuidof(ModernSettingsShellFolder), CModernSettingsShellFolder) diff --git a/Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.rgs b/Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.rgs new file mode 100644 index 0000000..d730872 --- /dev/null +++ b/Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.rgs @@ -0,0 +1,26 @@ +HKCR +{ + NoRemove CLSID + { + ForceRemove {82e749ed-b971-4550-baf7-06aa2bf7e836} = s 'Open-Shell Modern Settings' + { + InprocServer32 = s '%MODULE%' + { + val ThreadingModel = s 'Apartment' + } + ShellFolder + { + val Attributes = d '&HA0000000' + } + ShellEx + { + ContextMenuHandlers + { + Default = s '{5ab14324-c087-42c1-b905-a0bfdb4e9532}' + { + } + } + } + } + } +} diff --git a/Src/StartMenu/StartMenuHelper/StartMenuHelper.idl b/Src/StartMenu/StartMenuHelper/StartMenuHelper.idl index 34030f7..96c2cd8 100644 --- a/Src/StartMenu/StartMenuHelper/StartMenuHelper.idl +++ b/Src/StartMenu/StartMenuHelper/StartMenuHelper.idl @@ -6,6 +6,7 @@ import "oaidl.idl"; import "ocidl.idl"; +import "shobjidl.idl"; [ object, @@ -31,4 +32,21 @@ library StartMenuHelperLib { [default] interface IStartMenuExt; }; + [ + uuid(82e749ed-b971-4550-baf7-06aa2bf7e836) + ] + coclass ModernSettingsShellFolder + { + interface IShellFolder2; + interface IPersistFolder2; + }; + [ + uuid(5ab14324-c087-42c1-b905-a0bfdb4e9532) + ] + coclass ModernSettingsContextMenu + { + interface IContextMenu; + interface IShellExtInit; + interface IObjectWithSite; + }; }; diff --git a/Src/StartMenu/StartMenuHelper/StartMenuHelper.rc b/Src/StartMenu/StartMenuHelper/StartMenuHelper.rc index c15df9d..825ffc0 100644 --- a/Src/StartMenu/StartMenuHelper/StartMenuHelper.rc +++ b/Src/StartMenu/StartMenuHelper/StartMenuHelper.rc @@ -98,6 +98,8 @@ END IDR_STARTMENUHELPER REGISTRY "StartMenuHelper.rgs" IDR_STARTMENUEXT REGISTRY "StartMenuExt.rgs" +IDR_MODERNSETTINGSSHELLFOLDER REGISTRY "ModernSettingsShellFolder.rgs" +IDR_MODERNSETTINGSCONTEXTMENU REGISTRY "ModernSettingsContextMenu.rgs" #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj b/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj index 31ac2de..5e9cd31 100644 --- a/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj +++ b/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj @@ -358,6 +358,9 @@ + + + @@ -371,6 +374,8 @@ + + @@ -379,6 +384,9 @@ + + + diff --git a/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj.filters b/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj.filters index 496c1d9..942b4e4 100644 --- a/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj.filters +++ b/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj.filters @@ -34,6 +34,15 @@ Generated Files + + Source Files + + + Source Files + + + Source Files + @@ -56,6 +65,12 @@ Resource Files + + Resource Files + + + Resource Files + @@ -76,6 +91,15 @@ Generated Files + + Header Files + + + Header Files + + + Header Files + diff --git a/Src/StartMenu/StartMenuHelper/resource.h b/Src/StartMenu/StartMenuHelper/resource.h index 1f9e57e..d9ab48d 100644 --- a/Src/StartMenu/StartMenuHelper/resource.h +++ b/Src/StartMenu/StartMenuHelper/resource.h @@ -4,6 +4,8 @@ // #define IDR_STARTMENUHELPER 101 #define IDR_STARTMENUEXT 102 +#define IDR_MODERNSETTINGSSHELLFOLDER 103 +#define IDR_MODERNSETTINGSCONTEXTMENU 104 // Next default values for new objects // @@ -12,6 +14,6 @@ #define _APS_NEXT_RESOURCE_VALUE 201 #define _APS_NEXT_COMMAND_VALUE 32768 #define _APS_NEXT_CONTROL_VALUE 201 -#define _APS_NEXT_SYMED_VALUE 103 +#define _APS_NEXT_SYMED_VALUE 105 #endif #endif