diff --git a/PowerToys.sln b/PowerToys.sln index 6033ca1481..e6ae7896fe 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -262,6 +262,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643 src\common\utils\EventLocker.h = src\common\utils\EventLocker.h src\common\utils\EventWaiter.h = src\common\utils\EventWaiter.h src\common\utils\excluded_apps.h = src\common\utils\excluded_apps.h + src\common\utils\shell_ext_registration.h = src\common\utils\shell_ext_registration.h src\common\utils\exec.h = src\common\utils\exec.h src\common\utils\game_mode.h = src\common\utils\game_mode.h src\common\utils\gpo.h = src\common\utils\gpo.h diff --git a/installer/PowerToysSetup/FileLocksmith.wxs b/installer/PowerToysSetup/FileLocksmith.wxs index 085e60eaa7..5943ab4147 100644 --- a/installer/PowerToysSetup/FileLocksmith.wxs +++ b/installer/PowerToysSetup/FileLocksmith.wxs @@ -14,21 +14,6 @@ - - - - - - - - - - - - - - - @@ -38,7 +23,6 @@ - diff --git a/installer/PowerToysSetup/ImageResizer.wxs b/installer/PowerToysSetup/ImageResizer.wxs index 17da272014..67b5acf198 100644 --- a/installer/PowerToysSetup/ImageResizer.wxs +++ b/installer/PowerToysSetup/ImageResizer.wxs @@ -16,71 +16,6 @@ - - - - - - - - - - - - - - - - - - - - - - - @@ -90,7 +25,6 @@ - diff --git a/installer/PowerToysSetup/NewPlus.wxs b/installer/PowerToysSetup/NewPlus.wxs index 4dd1c67701..624c01fca2 100644 --- a/installer/PowerToysSetup/NewPlus.wxs +++ b/installer/PowerToysSetup/NewPlus.wxs @@ -18,19 +18,6 @@ - - - - - - - - - - - - - @@ -40,8 +27,7 @@ - - + @@ -81,7 +67,7 @@ - + diff --git a/installer/PowerToysSetup/PowerRename.wxs b/installer/PowerToysSetup/PowerRename.wxs index 1e722d9334..7aa357e207 100644 --- a/installer/PowerToysSetup/PowerRename.wxs +++ b/installer/PowerToysSetup/PowerRename.wxs @@ -14,22 +14,6 @@ - - - - - - - - - - - - - - - - @@ -39,7 +23,6 @@ - diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index f15b8a4714..77ffad8483 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -176,6 +176,18 @@ Installed AND (REMOVE="ALL") + + Installed AND (REMOVE="ALL") + + + Installed AND (REMOVE="ALL") + + + Installed AND (REMOVE="ALL") + + + Installed AND (REMOVE="ALL") + Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") @@ -437,6 +449,35 @@ Execute="deferred" BinaryKey="PTCustomActions" DllEntry="UnRegisterContextMenuPackagesCA" + /> + + + + + +#include +#include +#include + +#include "../logger/logger.h" + +namespace runtime_shell_ext +{ + struct Spec + { + // Mandatory + std::wstring clsid; // e.g. {GUID} + std::wstring sentinelKey; // e.g. Software\\Microsoft\\PowerToys\\ModuleName + std::wstring sentinelValue; // e.g. ContextMenuRegistered + std::vector dllFileCandidates; // relative filenames (pick first existing) + std::vector contextMenuHandlerKeyPaths; // full HKCU relative paths where default value = CLSID + + // Optional + std::wstring friendlyName; // if non-empty written as default under CLSID root + bool writeOptInEmptyValue = true; // write ContextMenuOptIn="" under CLSID root (legacy pattern) + bool writeThreadingModel = true; // write Apartment threading model + std::vector extraAssociationPaths; // additional key paths (DragDropHandlers etc.) default=CLSID + std::vector systemFileAssocExtensions; // e.g. .png -> Software\\Classes\\SystemFileAssociations\\.png\\ShellEx\\ContextMenuHandlers\\ + std::wstring systemFileAssocHandlerName; // e.g. ImageResizer + std::wstring representativeSystemExt; // used to decide if associations need repair (.png) + bool logRepairs = true; + }; + + namespace detail + { + // Minimal RAII wrapper for HKEY + struct unique_hkey + { + HKEY h{ nullptr }; + unique_hkey() = default; + explicit unique_hkey(HKEY handle) : h(handle) {} + ~unique_hkey() { if (h) RegCloseKey(h); } + unique_hkey(const unique_hkey&) = delete; + unique_hkey& operator=(const unique_hkey&) = delete; + unique_hkey(unique_hkey&& other) noexcept : h(other.h) { other.h = nullptr; } + unique_hkey& operator=(unique_hkey&& other) noexcept { if (this != &other) { if (h) RegCloseKey(h); h = other.h; other.h = nullptr; } return *this; } + HKEY get() const { return h; } + HKEY* put() { if (h) { RegCloseKey(h); h = nullptr; } return &h; } + }; + inline std::wstring base_dir_from_module(HMODULE h) + { + wchar_t buf[MAX_PATH]; + if (GetModuleFileNameW(h, buf, MAX_PATH)) + { + PathRemoveFileSpecW(buf); + return buf; + } + return L""; + } + + inline std::wstring pick_existing_dll(const std::wstring& base, const std::vector& candidates) + { + for (const auto& rel : candidates) + { + std::wstring full = base + L"\\" + rel; + if (GetFileAttributesW(full.c_str()) != INVALID_FILE_ATTRIBUTES) + { + return full; + } + } + if (!candidates.empty()) + { + return base + L"\\" + candidates.front(); + } + return L""; + } + + inline bool sentinel_exists(const Spec& spec) + { + unique_hkey key; + if (RegOpenKeyExW(HKEY_CURRENT_USER, spec.sentinelKey.c_str(), 0, KEY_READ, key.put()) != ERROR_SUCCESS) + return false; + DWORD v = 0; DWORD sz = sizeof(v); + return RegQueryValueExW(key.get(), spec.sentinelValue.c_str(), nullptr, nullptr, reinterpret_cast(&v), &sz) == ERROR_SUCCESS && v == 1; + } + + inline void write_sentinel(const Spec& spec) + { + unique_hkey key; + if (RegCreateKeyExW(HKEY_CURRENT_USER, spec.sentinelKey.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, key.put(), nullptr) == ERROR_SUCCESS) + { + DWORD one = 1; + RegSetValueExW(key.get(), spec.sentinelValue.c_str(), 0, REG_DWORD, reinterpret_cast(&one), sizeof(one)); + } + } + + inline void write_inproc_server(const Spec& spec, const std::wstring& dllPath) + { + using namespace std::string_literals; + std::wstring clsidRoot = L"Software\\Classes\\CLSID\\"s + spec.clsid; + std::wstring inprocKey = clsidRoot + L"\\InprocServer32"; + { + unique_hkey key; + if (RegCreateKeyExW(HKEY_CURRENT_USER, clsidRoot.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, key.put(), nullptr) == ERROR_SUCCESS) + { + if (!spec.friendlyName.empty()) + { + RegSetValueExW(key.get(), nullptr, 0, REG_SZ, reinterpret_cast(spec.friendlyName.c_str()), static_cast((spec.friendlyName.size() + 1) * sizeof(wchar_t))); + } + if (spec.writeOptInEmptyValue) + { + const wchar_t* optIn = L"ContextMenuOptIn"; + const wchar_t empty = L'\0'; + RegSetValueExW(key.get(), optIn, 0, REG_SZ, reinterpret_cast(&empty), sizeof(empty)); + } + } + } + unique_hkey key; + if (RegCreateKeyExW(HKEY_CURRENT_USER, inprocKey.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, key.put(), nullptr) == ERROR_SUCCESS) + { + RegSetValueExW(key.get(), nullptr, 0, REG_SZ, reinterpret_cast(dllPath.c_str()), static_cast((dllPath.size() + 1) * sizeof(wchar_t))); + if (spec.writeThreadingModel) + { + const wchar_t* tm = L"Apartment"; + RegSetValueExW(key.get(), L"ThreadingModel", 0, REG_SZ, reinterpret_cast(tm), static_cast((wcslen(tm) + 1) * sizeof(wchar_t))); + } + } + } + + inline std::wstring read_inproc_server(const Spec& spec) + { + using namespace std::string_literals; + std::wstring inprocKey = L"Software\\Classes\\CLSID\\"s + spec.clsid + L"\\InprocServer32"; + unique_hkey key; + if (RegOpenKeyExW(HKEY_CURRENT_USER, inprocKey.c_str(), 0, KEY_READ, key.put()) != ERROR_SUCCESS) + return L""; + wchar_t buf[MAX_PATH]; DWORD sz = sizeof(buf); + if (RegQueryValueExW(key.get(), nullptr, nullptr, nullptr, reinterpret_cast(buf), &sz) == ERROR_SUCCESS) + return std::wstring(buf); + return L""; + } + + inline void write_default_value_key(const std::wstring& keyPath, const std::wstring& value) + { + unique_hkey key; + if (RegCreateKeyExW(HKEY_CURRENT_USER, keyPath.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, key.put(), nullptr) == ERROR_SUCCESS) + { + RegSetValueExW(key.get(), nullptr, 0, REG_SZ, reinterpret_cast(value.c_str()), static_cast((value.size() + 1) * sizeof(wchar_t))); + } + } + + inline bool representative_association_exists(const Spec& spec) + { + using namespace std::string_literals; + if (spec.representativeSystemExt.empty() || spec.systemFileAssocHandlerName.empty()) + return true; + std::wstring keyPath = L"Software\\Classes\\SystemFileAssociations\\"s + spec.representativeSystemExt + L"\\ShellEx\\ContextMenuHandlers\\" + spec.systemFileAssocHandlerName; + unique_hkey key; + return RegOpenKeyExW(HKEY_CURRENT_USER, keyPath.c_str(), 0, KEY_READ, key.put()) == ERROR_SUCCESS; + } + } + + inline bool EnsureRegistered(const Spec& spec, HMODULE moduleInstance) + { + using namespace std::string_literals; + auto base = detail::base_dir_from_module(moduleInstance); + auto dllPath = detail::pick_existing_dll(base, spec.dllFileCandidates); + if (dllPath.empty()) + { + Logger::error(L"Runtime registration: cannot locate dll path for CLSID {}", spec.clsid); + return false; + } + bool exists = detail::sentinel_exists(spec); + bool repaired = false; + if (exists) + { + auto current = detail::read_inproc_server(spec); + if (_wcsicmp(current.c_str(), dllPath.c_str()) != 0) + { + detail::write_inproc_server(spec, dllPath); + repaired = true; + } + if (!detail::representative_association_exists(spec)) + { + repaired = true; + } + } + if (!exists) + { + detail::write_inproc_server(spec, dllPath); + } + if (!exists || repaired) + { + for (const auto& path : spec.contextMenuHandlerKeyPaths) + { + detail::write_default_value_key(path, spec.clsid); + } + for (const auto& path : spec.extraAssociationPaths) + { + detail::write_default_value_key(path, spec.clsid); + } + if (!spec.systemFileAssocExtensions.empty() && !spec.systemFileAssocHandlerName.empty()) + { + for (const auto& ext : spec.systemFileAssocExtensions) + { + std::wstring path = L"Software\\Classes\\SystemFileAssociations\\"s + ext + L"\\ShellEx\\ContextMenuHandlers\\" + spec.systemFileAssocHandlerName; + detail::write_default_value_key(path, spec.clsid); + } + } + } + if (!exists) + { + detail::write_sentinel(spec); + Logger::info(L"Runtime registration completed for CLSID {}", spec.clsid); + } + else if (repaired && spec.logRepairs) + { + Logger::info(L"Runtime registration repaired for CLSID {}", spec.clsid); + } + return true; + } + + inline bool Unregister(const Spec& spec) + { + using namespace std::string_literals; + // Remove handler key paths + for (const auto& path : spec.contextMenuHandlerKeyPaths) + { + RegDeleteTreeW(HKEY_CURRENT_USER, path.c_str()); + } + // Remove extra association paths (e.g., drag & drop handlers) + for (const auto& path : spec.extraAssociationPaths) + { + RegDeleteTreeW(HKEY_CURRENT_USER, path.c_str()); + } + // Remove per-extension system file association handler keys + if (!spec.systemFileAssocExtensions.empty() && !spec.systemFileAssocHandlerName.empty()) + { + for (const auto& ext : spec.systemFileAssocExtensions) + { + std::wstring keyPath = L"Software\\Classes\\SystemFileAssociations\\"s + ext + L"\\ShellEx\\ContextMenuHandlers\\" + spec.systemFileAssocHandlerName; + RegDeleteTreeW(HKEY_CURRENT_USER, keyPath.c_str()); + } + } + // Remove CLSID branch + if (!spec.clsid.empty()) + { + std::wstring clsidRoot = L"Software\\Classes\\CLSID\\"s + spec.clsid; + RegDeleteTreeW(HKEY_CURRENT_USER, clsidRoot.c_str()); + } + // Remove sentinel value (not deleting entire key to avoid disturbing other values) + if (!spec.sentinelKey.empty() && !spec.sentinelValue.empty()) + { + HKEY hKey{}; + if (RegOpenKeyExW(HKEY_CURRENT_USER, spec.sentinelKey.c_str(), 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS) + { + RegDeleteValueW(hKey, spec.sentinelValue.c_str()); + RegCloseKey(hKey); + } + } + Logger::info(L"Successfully unregistered CLSID {}", spec.clsid); + return true; + } +} diff --git a/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj b/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj index 0c285a8bfa..c67119808f 100644 --- a/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj +++ b/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj @@ -73,6 +73,7 @@ + diff --git a/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj.filters b/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj.filters index c3b4f47ebc..49bf0e21a9 100644 --- a/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj.filters +++ b/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj.filters @@ -27,6 +27,9 @@ Header Files + + Header Files + diff --git a/src/modules/FileLocksmith/FileLocksmithExt/PowerToysModule.cpp b/src/modules/FileLocksmith/FileLocksmithExt/PowerToysModule.cpp index ec755d99a3..7d188a2010 100644 --- a/src/modules/FileLocksmith/FileLocksmithExt/PowerToysModule.cpp +++ b/src/modules/FileLocksmith/FileLocksmithExt/PowerToysModule.cpp @@ -12,6 +12,7 @@ #include "FileLocksmithLib/Constants.h" #include "FileLocksmithLib/Settings.h" #include "FileLocksmithLib/Trace.h" +#include "RuntimeRegistration.h" #include "dllmain.h" #include "Generated Files/resource.h" @@ -82,12 +83,17 @@ public: { std::wstring path = get_module_folderpath(globals::instance); std::wstring packageUri = path + L"\\FileLocksmithContextMenuPackage.msix"; - if (!package::IsPackageRegisteredWithPowerToysVersion(constants::nonlocalizable::ContextMenuPackageName)) { package::RegisterSparsePackage(path, packageUri); } } + else + { +#if defined(ENABLE_REGISTRATION) || defined(NDEBUG) + FileLocksmithRuntimeRegistration::EnsureRegistered(); +#endif + } m_enabled = true; } @@ -95,6 +101,13 @@ public: virtual void disable() override { Logger::info(L"File Locksmith disabled"); + if (!package::IsWin11OrGreater()) + { +#if defined(ENABLE_REGISTRATION) || defined(NDEBUG) + FileLocksmithRuntimeRegistration::Unregister(); + Logger::info(L"File Locksmith context menu unregistered (Win10)"); +#endif + } m_enabled = false; } diff --git a/src/modules/FileLocksmith/FileLocksmithExt/RuntimeRegistration.h b/src/modules/FileLocksmith/FileLocksmithExt/RuntimeRegistration.h new file mode 100644 index 0000000000..4dd0d34bea --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/RuntimeRegistration.h @@ -0,0 +1,36 @@ +// Header-only runtime registration for FileLocksmith context menu extension. +#pragma once + +#include + +namespace globals { extern HMODULE instance; } + +namespace FileLocksmithRuntimeRegistration +{ + namespace + { + inline runtime_shell_ext::Spec BuildSpec() + { + runtime_shell_ext::Spec spec; + spec.clsid = L"{84D68575-E186-46AD-B0CB-BAEB45EE29C0}"; + spec.sentinelKey = L"Software\\Microsoft\\PowerToys\\FileLocksmith"; + spec.sentinelValue = L"ContextMenuRegistered"; + spec.dllFileCandidates = { L"PowerToys.FileLocksmithExt.dll" }; + spec.contextMenuHandlerKeyPaths = { + L"Software\\Classes\\AllFileSystemObjects\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt", + L"Software\\Classes\\Drive\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt" }; + spec.friendlyName = L"File Locksmith Shell Extension"; + return spec; + } + } + + inline bool EnsureRegistered() + { + return runtime_shell_ext::EnsureRegistered(BuildSpec(), globals::instance); + } + + inline void Unregister() + { + runtime_shell_ext::Unregister(BuildSpec()); + } +} diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj index 9ac251c8ec..90058a503e 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj @@ -123,6 +123,7 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv + diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj.filters b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj.filters index 7d014eb00f..d8b2eaf9ea 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj.filters +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj.filters @@ -84,6 +84,9 @@ Header Files + + Header Files + diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/RuntimeRegistration.h b/src/modules/NewPlus/NewShellExtensionContextMenu/RuntimeRegistration.h new file mode 100644 index 0000000000..91596d9e3d --- /dev/null +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/RuntimeRegistration.h @@ -0,0 +1,36 @@ +// Header-only runtime registration for New+ Win10 context menu. +#pragma once + +#include +#include +#include + +// Provided by dll_main.cpp +extern HMODULE module_instance_handle; + +namespace NewPlusRuntimeRegistration +{ + namespace { + inline runtime_shell_ext::Spec BuildSpec() + { + runtime_shell_ext::Spec spec; + spec.clsid = L"{FF90D477-E32A-4BE8-8CC5-A502A97F5401}"; + spec.sentinelKey = L"Software\\Microsoft\\PowerToys\\NewPlus"; + spec.sentinelValue = L"ContextMenuRegisteredWin10"; + spec.dllFileCandidates = { L"PowerToys.NewPlus.ShellExtension.win10.dll" }; + spec.contextMenuHandlerKeyPaths = { L"Software\\Classes\\Directory\\background\\ShellEx\\ContextMenuHandlers\\NewPlusShellExtensionWin10" }; + spec.friendlyName = L"NewPlus Shell Extension Win10"; + return spec; + } + } + + inline bool EnsureRegisteredWin10() + { + return runtime_shell_ext::EnsureRegistered(BuildSpec(), module_instance_handle); + } + + inline void Unregister() + { + runtime_shell_ext::Unregister(BuildSpec()); + } +} diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/powertoys_module.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/powertoys_module.cpp index 303f072e3b..ad94431953 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/powertoys_module.cpp +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/powertoys_module.cpp @@ -16,6 +16,7 @@ #include "trace.h" #include "new_utilities.h" #include "Generated Files/resource.h" +#include "RuntimeRegistration.h" // Note: Settings are managed via Settings and UI Settings class NewModule : public PowertoyModuleIface @@ -93,8 +94,16 @@ public: // Log telemetry Trace::EventToggleOnOff(true); - - newplus::utilities::register_msix_package(); + if (package::IsWin11OrGreater()) + { + newplus::utilities::register_msix_package(); + } + else + { +#if defined(ENABLE_REGISTRATION) || defined(NDEBUG) + NewPlusRuntimeRegistration::EnsureRegisteredWin10(); +#endif + } powertoy_new_enabled = true; } @@ -141,6 +150,13 @@ private: { Trace::EventToggleOnOff(false); } + if (!package::IsWin11OrGreater()) + { +#if defined(ENABLE_REGISTRATION) || defined(NDEBUG) + NewPlusRuntimeRegistration::Unregister(); + Logger::info(L"New+ context menu unregistered (Win10)"); +#endif + } powertoy_new_enabled = false; } diff --git a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj index c76a8c6cc2..51d3bc5522 100644 --- a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj +++ b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj @@ -100,6 +100,7 @@ + diff --git a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters index 8ec4d09d40..5d58153793 100644 --- a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters +++ b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters @@ -54,6 +54,9 @@ Generated Files + + Header Files + diff --git a/src/modules/imageresizer/dll/RuntimeRegistration.h b/src/modules/imageresizer/dll/RuntimeRegistration.h new file mode 100644 index 0000000000..f9369da1c4 --- /dev/null +++ b/src/modules/imageresizer/dll/RuntimeRegistration.h @@ -0,0 +1,38 @@ +// Header-only runtime registration for ImageResizer shell extension. +#pragma once + +#include + +extern "C" IMAGE_DOS_HEADER __ImageBase; // provided by linker + +namespace ImageResizerRuntimeRegistration +{ + namespace + { + inline runtime_shell_ext::Spec BuildSpec() + { + runtime_shell_ext::Spec spec; + spec.clsid = L"{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"; + spec.sentinelKey = L"Software\\Microsoft\\PowerToys\\ImageResizer"; + spec.sentinelValue = L"ContextMenuRegistered"; + spec.dllFileCandidates = { L"PowerToys.ImageResizerExt.dll" }; + spec.contextMenuHandlerKeyPaths = { }; + spec.systemFileAssocHandlerName = L"ImageResizer"; + spec.systemFileAssocExtensions = { L".bmp", L".dib", L".gif", L".jfif", L".jpe", L".jpeg", L".jpg", L".jxr", L".png", L".rle", L".tif", L".tiff", L".wdp" }; + spec.representativeSystemExt = L".png"; // probe for repair + spec.extraAssociationPaths = { L"Software\\Classes\\Directory\\ShellEx\\DragDropHandlers\\ImageResizer" }; + spec.friendlyName = L"ImageResizer Shell Extension"; + return spec; + } + } + + inline bool EnsureRegistered() + { + return runtime_shell_ext::EnsureRegistered(BuildSpec(), reinterpret_cast(&__ImageBase)); + } + + inline void Unregister() + { + runtime_shell_ext::Unregister(BuildSpec()); + } +} diff --git a/src/modules/imageresizer/dll/dllmain.cpp b/src/modules/imageresizer/dll/dllmain.cpp index 4786388f06..ef07566b7e 100644 --- a/src/modules/imageresizer/dll/dllmain.cpp +++ b/src/modules/imageresizer/dll/dllmain.cpp @@ -14,6 +14,7 @@ #include #include #include +#include "RuntimeRegistration.h" CImageResizerExtModule _AtlModule; HINSTANCE g_hInst_imageResizer = 0; @@ -106,12 +107,17 @@ public: { std::wstring path = get_module_folderpath(g_hInst_imageResizer); std::wstring packageUri = path + L"\\ImageResizerContextMenuPackage.msix"; - if (!package::IsPackageRegisteredWithPowerToysVersion(ImageResizerConstants::ModulePackageDisplayName)) { package::RegisterSparsePackage(path, packageUri); } } + else + { +#if defined(ENABLE_REGISTRATION) || defined(NDEBUG) + ImageResizerRuntimeRegistration::EnsureRegistered(); +#endif + } Trace::EnableImageResizer(m_enabled); } @@ -121,6 +127,13 @@ public: { m_enabled = false; Trace::EnableImageResizer(m_enabled); + if (!package::IsWin11OrGreater()) + { +#if defined(ENABLE_REGISTRATION) || defined(NDEBUG) + ImageResizerRuntimeRegistration::Unregister(); + Logger::info(L"ImageResizer context menu unregistered (Win10)"); +#endif + } } // Returns if the powertoys is enabled diff --git a/src/modules/powerrename/dll/PowerRenameExt.vcxproj b/src/modules/powerrename/dll/PowerRenameExt.vcxproj index 32484c75b6..9c612a08ff 100644 --- a/src/modules/powerrename/dll/PowerRenameExt.vcxproj +++ b/src/modules/powerrename/dll/PowerRenameExt.vcxproj @@ -34,6 +34,7 @@ + diff --git a/src/modules/powerrename/dll/PowerRenameExt.vcxproj.filters b/src/modules/powerrename/dll/PowerRenameExt.vcxproj.filters index ffd2177829..f4f0ffcbe5 100644 --- a/src/modules/powerrename/dll/PowerRenameExt.vcxproj.filters +++ b/src/modules/powerrename/dll/PowerRenameExt.vcxproj.filters @@ -36,6 +36,9 @@ Header Files + + Header Files + diff --git a/src/modules/powerrename/dll/RuntimeRegistration.h b/src/modules/powerrename/dll/RuntimeRegistration.h new file mode 100644 index 0000000000..3cb06d5876 --- /dev/null +++ b/src/modules/powerrename/dll/RuntimeRegistration.h @@ -0,0 +1,37 @@ +// Header-only runtime registration for PowerRename context menu extension. +#pragma once + +#include + +// Provided by dllmain.cpp +extern HINSTANCE g_hInst; + +namespace PowerRenameRuntimeRegistration +{ + namespace + { + inline runtime_shell_ext::Spec BuildSpec() + { + runtime_shell_ext::Spec spec; + spec.clsid = L"{0440049F-D1DC-4E46-B27B-98393D79486B}"; + spec.sentinelKey = L"Software\\Microsoft\\PowerToys\\PowerRename"; + spec.sentinelValue = L"ContextMenuRegistered"; + spec.dllFileCandidates = { L"PowerToys.PowerRenameExt.dll" }; + spec.contextMenuHandlerKeyPaths = { + L"Software\\Classes\\AllFileSystemObjects\\ShellEx\\ContextMenuHandlers\\PowerRenameExt", + L"Software\\Classes\\Directory\\background\\ShellEx\\ContextMenuHandlers\\PowerRenameExt" }; + spec.friendlyName = L"PowerRename Shell Extension"; + return spec; + } + } + + inline bool EnsureRegistered() + { + return runtime_shell_ext::EnsureRegistered(BuildSpec(), g_hInst); + } + + inline void Unregister() + { + runtime_shell_ext::Unregister(BuildSpec()); + } +} diff --git a/src/modules/powerrename/dll/dllmain.cpp b/src/modules/powerrename/dll/dllmain.cpp index 4f9c918fb6..18c612a304 100644 --- a/src/modules/powerrename/dll/dllmain.cpp +++ b/src/modules/powerrename/dll/dllmain.cpp @@ -15,6 +15,7 @@ #include #include #include +#include "RuntimeRegistration.h" #include @@ -196,12 +197,17 @@ public: { std::wstring path = get_module_folderpath(g_hInst); std::wstring packageUri = path + L"\\PowerRenameContextMenuPackage.msix"; - if (!package::IsPackageRegisteredWithPowerToysVersion(PowerRenameConstants::ModulePackageDisplayName)) { package::RegisterSparsePackage(path, packageUri); } } + else + { +#if defined(ENABLE_REGISTRATION) || defined(NDEBUG) + PowerRenameRuntimeRegistration::EnsureRegistered(); +#endif + } } // Disable the powertoy @@ -209,6 +215,13 @@ public: { m_enabled = false; Logger::info(L"PowerRename disabled"); + if (!package::IsWin11OrGreater()) + { +#if defined(ENABLE_REGISTRATION) || defined(NDEBUG) + PowerRenameRuntimeRegistration::Unregister(); + Logger::info(L"PowerRename context menu unregistered (Win10)"); +#endif + } } // Returns if the powertoy is enabled