mirror of
https://github.com/Open-Shell/Open-Shell-Menu.git
synced 2026-04-11 17:37:22 +10:00
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.
This commit is contained in:
434
Src/StartMenu/StartMenuHelper/ModernSettings.cpp
Normal file
434
Src/StartMenu/StartMenuHelper/ModernSettings.cpp
Normal file
@@ -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 <Shlobj.h>
|
||||
#include <Shlwapi.h>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <mutex>
|
||||
|
||||
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<uint8_t> 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<uint8_t> m_buffer;
|
||||
};
|
||||
|
||||
static void ProcessAttributes(const void* buffer, size_t size, std::function<void(const ItemHdr&)> 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<IXMLDOMNode>& parent, const WCHAR* name)
|
||||
{
|
||||
CComPtr<IXMLDOMNode> 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<IXMLDOMNode>& parent, AttributeWriter& writer)
|
||||
{
|
||||
writer.addString(Id::FileName, GetTranslatedString(parent, L"Filename"));
|
||||
}
|
||||
|
||||
static void ParseApplicationInformation(CComPtr<IXMLDOMNode>& parent, AttributeWriter& writer)
|
||||
{
|
||||
CComPtr<IXMLDOMNode> 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<IXMLDOMNode>& parent, AttributeWriter& writer)
|
||||
{
|
||||
CComPtr<IXMLDOMNode> 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<IXMLDOMNode>& parent, AttributeWriter& writer)
|
||||
{
|
||||
CComPtr<IXMLDOMNode> 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<uint8_t> ParseSetting(CComPtr<IXMLDOMNode>& parent)
|
||||
{
|
||||
AttributeWriter writer;
|
||||
|
||||
ParseFileName(parent, writer);
|
||||
ParseApplicationInformation(parent, writer);
|
||||
ParseSettingIdentity(parent, writer);
|
||||
ParseSettingInformation(parent, writer);
|
||||
|
||||
return writer.buffer();
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> ParseModernSettings()
|
||||
{
|
||||
AttributeWriter writer;
|
||||
|
||||
CComPtr<IXMLDOMDocument> 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<IXMLDOMNode> root;
|
||||
if (doc->selectSingleNode(CComBSTR(L"PCSettings"), &root) == S_OK)
|
||||
{
|
||||
FileHdr hdr{};
|
||||
writer.addBlob(Id::Header, &hdr, sizeof(hdr));
|
||||
|
||||
CComPtr<IXMLDOMNode> node;
|
||||
root->get_firstChild(&node);
|
||||
while (node)
|
||||
{
|
||||
auto buffer = ParseSetting(node);
|
||||
if (!buffer.empty())
|
||||
writer.addBlob(Id::Blob, buffer.data(), buffer.size());
|
||||
|
||||
CComPtr<IXMLDOMNode> 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::Setting> ModernSettings::enumerate() const
|
||||
{
|
||||
std::vector<ModernSettings::Setting> 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<ModernSettings> 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<ModernSettings> 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<ModernSettings>(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<ModernSettings>(path.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s_settings;
|
||||
}
|
||||
139
Src/StartMenu/StartMenuHelper/ModernSettings.h
Normal file
139
Src/StartMenu/StartMenuHelper/ModernSettings.h
Normal file
@@ -0,0 +1,139 @@
|
||||
// Modern settings helper
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<Setting> enumerate() const;
|
||||
Setting get(const std::wstring_view& name) const;
|
||||
|
||||
private:
|
||||
MappedFile m_storage;
|
||||
std::map<std::wstring_view, Blob> m_settings;
|
||||
};
|
||||
|
||||
// retrieve actual instance of ModernSettings
|
||||
std::shared_ptr<ModernSettings> GetModernSettings();
|
||||
169
Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.cpp
Normal file
169
Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.cpp
Normal file
@@ -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<IApplicationActivationManager> 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;
|
||||
}
|
||||
60
Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.h
Normal file
60
Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.h
Normal file
@@ -0,0 +1,60 @@
|
||||
// Context menu handler for Open-Shell Modern Settings shell folder
|
||||
|
||||
#pragma once
|
||||
#include "resource.h"
|
||||
#include "StartMenuHelper_i.h"
|
||||
#include <shlobj.h>
|
||||
|
||||
// CModernSettingsContextMenu
|
||||
|
||||
class ATL_NO_VTABLE CModernSettingsContextMenu :
|
||||
public CComObjectRootEx<CComSingleThreadModel>,
|
||||
public CComCoClass<CModernSettingsContextMenu, &CLSID_ModernSettingsContextMenu>,
|
||||
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<IDataObject> m_pdtobj;
|
||||
CComPtr<IUnknown> m_punkSite;
|
||||
};
|
||||
|
||||
OBJECT_ENTRY_AUTO(__uuidof(ModernSettingsContextMenu), CModernSettingsContextMenu)
|
||||
19
Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.rgs
Normal file
19
Src/StartMenu/StartMenuHelper/ModernSettingsContextMenu.rgs
Normal file
@@ -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 ''
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
603
Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.cpp
Normal file
603
Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.cpp
Normal file
@@ -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 <propkey.h>
|
||||
#include <strsafe.h>
|
||||
#include <Uxtheme.h>
|
||||
|
||||
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<CModernSettingsShellFolder> m_parent;
|
||||
std::shared_ptr<ModernSettings> m_settings;
|
||||
std::vector<ModernSettings::Setting> 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<CModernSettingsShellFolderEnumIDList>* enumIdList;
|
||||
auto hr = CComObject<CModernSettingsShellFolderEnumIDList>::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<IShellFolder2*>(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<GlyphExtractIcon>* extract;
|
||||
hr = CComObject<GlyphExtractIcon>::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<short>(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 {};
|
||||
}
|
||||
84
Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.h
Normal file
84
Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.h
Normal file
@@ -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 <shlobj.h>
|
||||
#include <string>
|
||||
|
||||
// CModernSettingsShellFolder
|
||||
|
||||
class ATL_NO_VTABLE CModernSettingsShellFolder :
|
||||
public CComObjectRootEx<CComSingleThreadModel>,
|
||||
public CComCoClass<CModernSettingsShellFolder, &CLSID_ModernSettingsShellFolder>,
|
||||
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)
|
||||
26
Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.rgs
Normal file
26
Src/StartMenu/StartMenuHelper/ModernSettingsShellFolder.rgs
Normal file
@@ -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}'
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -358,6 +358,9 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="ModernSettings.cpp" />
|
||||
<ClCompile Include="ModernSettingsContextMenu.cpp" />
|
||||
<ClCompile Include="ModernSettingsShellFolder.cpp" />
|
||||
<ClCompile Include="StartMenuExt.cpp" />
|
||||
<ClCompile Include="StartMenuHelper.cpp" />
|
||||
<ClCompile Include="StartMenuHelper_i.c">
|
||||
@@ -371,6 +374,8 @@
|
||||
<Midl Include="StartMenuHelper.idl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="ModernSettingsContextMenu.rgs" />
|
||||
<None Include="ModernSettingsShellFolder.rgs" />
|
||||
<None Include="StartMenuExt.rgs" />
|
||||
<None Include="StartMenuHelper.rgs" />
|
||||
<None Include="StartMenuHelper32.def" />
|
||||
@@ -379,6 +384,9 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="dllmain.h" />
|
||||
<ClInclude Include="ModernSettings.h" />
|
||||
<ClInclude Include="ModernSettingsContextMenu.h" />
|
||||
<ClInclude Include="ModernSettingsShellFolder.h" />
|
||||
<ClInclude Include="Resource.h" />
|
||||
<ClInclude Include="StartMenuExt.h" />
|
||||
<ClInclude Include="StartMenuHelper_i.h" />
|
||||
|
||||
@@ -34,6 +34,15 @@
|
||||
<ClCompile Include="StartMenuHelper_i.c">
|
||||
<Filter>Generated Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ModernSettingsShellFolder.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ModernSettings.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ModernSettingsContextMenu.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="StartMenuHelper.idl">
|
||||
@@ -56,6 +65,12 @@
|
||||
<None Include="StartMenuHelperL10N.ini">
|
||||
<Filter>Resource Files</Filter>
|
||||
</None>
|
||||
<None Include="ModernSettingsShellFolder.rgs">
|
||||
<Filter>Resource Files</Filter>
|
||||
</None>
|
||||
<None Include="ModernSettingsContextMenu.rgs">
|
||||
<Filter>Resource Files</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="dllmain.h">
|
||||
@@ -76,6 +91,15 @@
|
||||
<ClInclude Include="StartMenuHelper_i.h">
|
||||
<Filter>Generated Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ModernSettingsShellFolder.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ModernSettings.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ModernSettingsContextMenu.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="StartMenuHelper.rc">
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user