From c324cd59532d249a33b68aab0f1f63485a0dc6f0 Mon Sep 17 00:00:00 2001 From: yuyoyuppe Date: Thu, 21 Oct 2021 20:02:45 +0300 Subject: [PATCH] [Setup] Implement modulesRegistry API --- PowerToys.sln | 2 + src/common/utils/modulesRegistry.h | 88 ++++++++ src/common/utils/registry.h | 329 +++++++++++++++++++++++++++++ src/common/version/version.h | 9 + 4 files changed, 428 insertions(+) create mode 100644 src/common/utils/modulesRegistry.h create mode 100644 src/common/utils/registry.h diff --git a/PowerToys.sln b/PowerToys.sln index ad3f59a4e9..f054435ed7 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -276,10 +276,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643 src\common\utils\HttpClient.h = src\common\utils\HttpClient.h src\common\utils\json.h = src\common\utils\json.h src\common\utils\logger_helper.h = src\common\utils\logger_helper.h + src\common\utils\modulesRegistry.h = src\common\utils\modulesRegistry.h src\common\utils\MsiUtils.h = src\common\utils\MsiUtils.h src\common\utils\os-detect.h = src\common\utils\os-detect.h src\common\utils\process_path.h = src\common\utils\process_path.h src\common\utils\ProcessWaiter.h = src\common\utils\ProcessWaiter.h + src\common\utils\registry.h = src\common\utils\registry.h src\common\utils\resources.h = src\common\utils\resources.h src\common\utils\string_utils.h = src\common\utils\string_utils.h src\common\utils\timeutil.h = src\common\utils\timeutil.h diff --git a/src/common/utils/modulesRegistry.h b/src/common/utils/modulesRegistry.h new file mode 100644 index 0000000000..14d0c7e51e --- /dev/null +++ b/src/common/utils/modulesRegistry.h @@ -0,0 +1,88 @@ +#pragma once + +#include "registry.h" + +#include + +namespace fs = std::filesystem; + +inline registry::Changeset getSvgPreviewHandlerChangset(const std::wstring installationDir, const bool perUser) +{ + using namespace registry::shellex; + return generatePreviewHandler(PreviewHandlerType::preview, + perUser, + L"{ddee2b8a-6807-48a6-bb20-2338174ff779}", + get_std_product_version(), + (fs::path{ installationDir } / + LR"d(modules\FileExplorerPreview\SvgPreviewHandler.comhost.dll)d") + .wstring(), + registry::DOTNET_COMPONENT_CATEGORY_CLSID, + L"Microsoft.PowerToys.PreviewHandler.Svg.SvgPreviewHandler", + L"Svg Preview Handler", + L".svg"); +} + +inline registry::Changeset getMdPreviewHandlerChangset(const std::wstring installationDir, const bool perUser) +{ + using namespace registry::shellex; + return generatePreviewHandler(PreviewHandlerType::preview, + perUser, + L"{45769bcc-e8fd-42d0-947e-02beef77a1f5}", + get_std_product_version(), + (fs::path{ installationDir } / LR"d(modules\FileExplorerPreview\MarkdownPreviewHandler.comhost.dll)d").wstring(), + registry::DOTNET_COMPONENT_CATEGORY_CLSID, + L"Microsoft.PowerToys.PreviewHandler.Markdown.MarkdownPreviewHandler", + L"Markdown Preview Handler", + L".md"); +} + +inline registry::Changeset getPdfPreviewHandlerChangset(const std::wstring installationDir, const bool perUser) +{ + using namespace registry::shellex; + return generatePreviewHandler(PreviewHandlerType::preview, + perUser, + L"{07665729-6243-4746-95b7-79579308d1b2}", + get_std_product_version(), + (fs::path{ installationDir } / LR"d(modules\FileExplorerPreview\PdfPreviewHandler.comhost.dll)d").wstring(), + registry::DOTNET_COMPONENT_CATEGORY_CLSID, + L"Microsoft.PowerToys.PreviewHandler.Pdf.PdfPreviewHandler", + L"Pdf Preview Handler", + L".pdf"); +} + +inline registry::Changeset getSvgThumbnailHandlerChangset(const std::wstring installationDir, const bool perUser) +{ + using namespace registry::shellex; + return generatePreviewHandler(PreviewHandlerType::thumbnail, + perUser, + L"{36B27788-A8BB-4698-A756-DF9F11F64F84}", + get_std_product_version(), + (fs::path{ installationDir } / LR"d(modules\FileExplorerPreview\SvgThumbnailProvider.comhost.dll)d").wstring(), + registry::DOTNET_COMPONENT_CATEGORY_CLSID, + L"Microsoft.PowerToys.ThumbnailHandler.Svg.SvgThumbnailProvider", + L"Svg Thumbnail Provider", + L".svg"); +} + +inline registry::Changeset getPdfThumbnailHandlerChangset(const std::wstring installationDir, const bool perUser) +{ + using namespace registry::shellex; + return generatePreviewHandler(PreviewHandlerType::thumbnail, + perUser, + L"{BCC13D15-9720-4CC4-8371-EA74A274741E}", + get_std_product_version(), + (fs::path{ installationDir } / LR"d(modules\FileExplorerPreview\PdfThumbnailProvider.comhost.dll)d").wstring(), + registry::DOTNET_COMPONENT_CATEGORY_CLSID, + L"Microsoft.PowerToys.ThumbnailHandler.Pdf.PdfThumbnailProvider", + L"Pdf Thumbnail Provider", + L".pdf"); +} + +inline std::vector getAllModulesChangesets(const std::wstring installationDir, const bool perUser) +{ + return { getSvgPreviewHandlerChangset(installationDir, perUser), + getMdPreviewHandlerChangset(installationDir, perUser), + getPdfPreviewHandlerChangset(installationDir, perUser), + getSvgThumbnailHandlerChangset(installationDir, perUser), + getPdfThumbnailHandlerChangset(installationDir, perUser) }; +} \ No newline at end of file diff --git a/src/common/utils/registry.h b/src/common/utils/registry.h new file mode 100644 index 0000000000..88af8b83e0 --- /dev/null +++ b/src/common/utils/registry.h @@ -0,0 +1,329 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include "../version/version.h" + +namespace registry +{ + namespace detail + { + struct on_exit + { + std::function f; + + on_exit(std::function f) : + f{ std::move(f) } {} + ~on_exit() { f(); } + }; + + template + inline constexpr bool always_false_v = false; + + template + struct overloaded : Ts... + { + using Ts::operator()...; + }; + + template + overloaded(Ts...) -> overloaded; + } + struct ValueChange + { + using value_t = std::variant; + static constexpr size_t VALUE_BUFFER_SIZE = 512; + + HKEY scope{}; + std::wstring path; + std::optional name; // none == default + value_t value; + + ValueChange(const HKEY scope, std::wstring path, std::optional name, value_t value) : + scope{ scope }, path{ std::move(path) }, name{ std::move(name) }, value{ std::move(value) } + { + } + + bool isApplied() const + { + HKEY key{}; + if (RegOpenKeyExW(scope, path.c_str(), 0, KEY_READ, &key) != ERROR_SUCCESS) + { + return false; + } + detail::on_exit closeKey{ [key] { RegCloseKey(key); } }; + + const DWORD expectedType = valueTypeToWinapiType(value); + + DWORD retrievedType{}; + wchar_t buffer[VALUE_BUFFER_SIZE]; + DWORD valueSize = sizeof(buffer); + if (RegQueryValueExW(key, + name.has_value() ? name->c_str() : nullptr, + 0, + &retrievedType, + reinterpret_cast(&buffer), + &valueSize) != ERROR_SUCCESS) + { + return false; + } + + if (expectedType != retrievedType) + { + return false; + } + + if (const auto retrievedValue = bufferToValue(buffer, valueSize, retrievedType)) + { + return value == retrievedValue; + } + else + { + return false; + } + } + + bool apply() const + { + HKEY key{}; + + if (RegCreateKeyExW(scope, path.c_str(), 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_WRITE, nullptr, &key, nullptr) != + ERROR_SUCCESS) + { + return false; + } + detail::on_exit closeKey{ [key] { RegCloseKey(key); } }; + + wchar_t buffer[VALUE_BUFFER_SIZE]; + DWORD valueSize; + DWORD valueType; + + valueToBuffer(value, buffer, valueSize, valueType); + return RegSetValueExW(key, + name.has_value() ? name->c_str() : nullptr, + 0, + valueType, + reinterpret_cast(buffer), + valueSize) == ERROR_SUCCESS; + } + + bool unapply() const + { + HKEY key{}; + if (RegOpenKeyExW(scope, path.c_str(), 0, KEY_ALL_ACCESS, &key) != ERROR_SUCCESS) + { + return false; + } + detail::on_exit closeKey{ [key] { RegCloseKey(key); } }; + + // delete the value itself + if (RegDeleteKeyValueW(scope, path.c_str(), name.has_value() ? name->c_str() : nullptr) != ERROR_SUCCESS) + { + return false; + } + + // Check if the path doesn't contain anything and delete it if so + DWORD nValues = 0; + DWORD maxValueLen = 0; + const auto ok = + RegQueryInfoKeyW( + key, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &nValues, nullptr, &maxValueLen, nullptr, nullptr) == + ERROR_SUCCESS; + + if (ok && (!nValues || !maxValueLen)) + { + RegDeleteTreeW(scope, path.c_str()); + } + return true; + } + + bool requiresElevation() const { return scope == HKEY_LOCAL_MACHINE; } + + private: + static DWORD valueTypeToWinapiType(const value_t& v) + { + return std::visit( + [](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) + return REG_DWORD; + else if constexpr (std::is_same_v) + return REG_SZ; + else + static_assert(always_false_v, "support for this registry type is not implemented"); + }, + v); + } + + static void valueToBuffer(const value_t& value, wchar_t buffer[VALUE_BUFFER_SIZE], DWORD& valueSize, DWORD& type) + { + using detail::overloaded; + + std::visit(overloaded{ [&](DWORD value) { + *reinterpret_cast(buffer) = value; + type = REG_DWORD; + valueSize = sizeof(value); + }, + [&](const std::wstring& value) { + assert(value.size() < VALUE_BUFFER_SIZE); + value.copy(buffer, value.size()); + type = REG_SZ; + valueSize = static_cast(sizeof(wchar_t) * value.size()); + } }, + value); + } + + static std::optional bufferToValue(const wchar_t buffer[VALUE_BUFFER_SIZE], + const DWORD valueSize, + const DWORD type) + { + switch (type) + { + case REG_DWORD: + return *(DWORD*)buffer; + case REG_SZ: + { + if (!valueSize) + { + return std::wstring{}; + } + std::wstring result{ buffer, valueSize / sizeof(wchar_t) }; + while (result[result.size() - 1] == L'\0') + { + result.resize(result.size() - 1); + } + return result; + } + default: + return std::nullopt; + } + } + }; + + struct Changeset + { + std::vector changes; + + bool isApplied() const + { + for (const auto& c : changes) + { + if (!c.isApplied()) + { + return false; + } + } + return true; + } + + bool apply() const + { + bool ok = true; + for (const auto& c : changes) + { + ok = c.apply() && ok; + } + return ok; + } + + bool unapply() const + { + bool ok = true; + for (const auto& c : changes) + { + ok = c.unapply() && ok; + } + return ok; + } + }; + + const inline std::wstring DOTNET_COMPONENT_CATEGORY_CLSID = L"{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}"; + const inline std::wstring ITHUMBNAIL_PROVIDER_CLSID = L"{E357FCCD-A995-4576-B01F-234630154E96}"; + const inline std::wstring IPREVIEW_HANDLER_CLSID = L"{8895b1c6-b41f-4c1c-a562-0d564250836f}"; + + namespace shellex + { + enum PreviewHandlerType + { + preview, + thumbnail + }; + + inline registry::Changeset generatePreviewHandler(const PreviewHandlerType handlerType, + const bool perUser, + std::wstring handlerClsid, + std::wstring powertoysVersion, + std::wstring fullPathToHandler, + std::wstring handlerCategory, + std::wstring className, + std::wstring displayName, + std::wstring fileType) + { + const HKEY scope = perUser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE; + + std::wstring clsidPath = L"Software\\Classes\\CLSID"; + clsidPath += L'\\'; + clsidPath += handlerClsid; + + std::wstring inprocServerPath = clsidPath; + inprocServerPath += L'\\'; + inprocServerPath += L"InprocServer32"; + + std::wstring implementedCategoriesPath = clsidPath + LR"d(\Implemented Categories\)d"; + implementedCategoriesPath += handlerCategory; + + std::wstring assemblyKeyValue; + if (const auto lastDotPos = className.rfind(L'.'); lastDotPos != std::wstring::npos) + { + assemblyKeyValue = className.substr(lastDotPos + 1); + } + else + { + assemblyKeyValue = className; + } + + assemblyKeyValue += L", Version="; + assemblyKeyValue += powertoysVersion; + assemblyKeyValue += L", Culture=neutral"; + + std::wstring versionPath = inprocServerPath; + versionPath += L'\\'; + versionPath += powertoysVersion; + + std::wstring fileAssociationPath = L"Software\\Classes\\"; + fileAssociationPath += fileType; + fileAssociationPath += L"\\shellex\\"; + fileAssociationPath += handlerType == PreviewHandlerType::preview ? IPREVIEW_HANDLER_CLSID : ITHUMBNAIL_PROVIDER_CLSID; + + using vec_t = std::vector; + // TODO: verify that we actually need all of those + vec_t changes = { { scope, clsidPath, L"DisplayName", displayName }, + { scope, clsidPath, std::nullopt, className }, + { scope, implementedCategoriesPath, std::nullopt, L"" }, + { scope, inprocServerPath, std::nullopt, fullPathToHandler }, + { scope, inprocServerPath, L"Assembly", assemblyKeyValue }, + { scope, inprocServerPath, L"Class", className }, + { scope, inprocServerPath, L"ThreadingModel", L"Both" }, + { scope, versionPath, L"Assembly", assemblyKeyValue }, + { scope, versionPath, L"Class", className }, + { scope, fileAssociationPath, std::nullopt, handlerClsid } }; + if (handlerType == PreviewHandlerType::preview) + { + const std::wstring previewHostClsid = L"{6d2b5079-2f0b-48dd-ab7f-97cec514d30b}"; + const std::wstring previewHandlerListPath = LR"(Software\Microsoft\Windows\CurrentVersion\PreviewHandlers)"; + + changes.push_back({ scope, clsidPath, L"AppID", previewHostClsid }); + changes.push_back({ scope, previewHandlerListPath, handlerClsid, displayName }); + } + + return registry::Changeset{ .changes = std::move(changes) }; + } + } +} diff --git a/src/common/version/version.h b/src/common/version/version.h index 4cf88d8ac7..4a6f347849 100644 --- a/src/common/version/version.h +++ b/src/common/version/version.h @@ -33,5 +33,14 @@ inline std::wstring get_product_version() L"." + std::to_wstring(VERSION_MINOR) + L"." + std::to_wstring(VERSION_REVISION); + return version; +} + +inline std::wstring get_std_product_version() +{ + static std::wstring version = L"v" + std::to_wstring(VERSION_MAJOR) + + L"." + std::to_wstring(VERSION_MINOR) + + L"." + std::to_wstring(VERSION_REVISION) + L".0"; + return version; } \ No newline at end of file