From c67fe0ca72047cf235deb65a673f1f95272c46e4 Mon Sep 17 00:00:00 2001 From: Daniel Ayala <14967941+danielayala94@users.noreply.github.com> Date: Thu, 17 Mar 2022 18:12:11 -0700 Subject: [PATCH 01/11] Assets full support V1 --- .../AppNotificationUtility.cpp | 161 +++++++++++++----- 1 file changed, 118 insertions(+), 43 deletions(-) diff --git a/dev/AppNotifications/AppNotificationUtility.cpp b/dev/AppNotifications/AppNotificationUtility.cpp index b0f645b2c4..a0ae491726 100644 --- a/dev/AppNotifications/AppNotificationUtility.cpp +++ b/dev/AppNotifications/AppNotificationUtility.cpp @@ -31,6 +31,8 @@ namespace ToastABI using namespace ::ABI::Microsoft::Internal::ToastNotifications; } +constexpr PCWSTR defaultAppNotificationIcon = LR"(ms-resource://Windows.UI.ShellCommon/Files/Images/DefaultSystemNotification.png)"; + std::wstring Microsoft::Windows::AppNotifications::Helpers::RetrieveUnpackagedNotificationAppId() { wil::unique_cotaskmem_string appId; @@ -232,79 +234,152 @@ std::wstring Microsoft::Windows::AppNotifications::Helpers::SetDisplayNameBasedO return displayName; } -void Microsoft::Windows::AppNotifications::Helpers::RegisterAssets(std::wstring const& appId, std::wstring const& clsid) +// Placeholder +HRESULT ExtractIconFromCsAssets(_Out_ PWSTR* displayName, _Out_ PWSTR* iconFilePath) { - wil::unique_hkey hKey; - // subKey: \Software\Classes\AppUserModelId\{AppGUID} - std::wstring subKey{ c_appIdentifierPath + appId }; + *displayName = nullptr; + *iconFilePath = nullptr; - THROW_IF_WIN32_ERROR(RegCreateKeyEx( - HKEY_CURRENT_USER, - subKey.c_str(), - 0, - nullptr /* lpClass */, - REG_OPTION_NON_VOLATILE, - KEY_ALL_ACCESS, - nullptr /* lpSecurityAttributes */, - &hKey, - nullptr /* lpdwDisposition */)); + return E_NOTIMPL; +} - // Retrieve the display name - std::wstring displayName{}; - displayName = SetDisplayNameBasedOnProcessName(); +// Do nothing. This is just a placeholder while the UDK is ingested with the proper API. +HRESULT ToastNotifications_RetrieveAssets_Stub(_Out_ PWSTR* displayName, _Out_ PWSTR* iconFilePath) +{ + *displayName = nullptr; + *iconFilePath = nullptr; + + return E_NOTIMPL; +} - // Retrieve the icon - std::wstring iconFilePath{}; +HRESULT RetrieveAssetsFromShell(_Out_ PWSTR* displayName, _Out_ PWSTR* iconFilePath) +{ + *displayName = nullptr; + *iconFilePath = nullptr; + + wil::unique_cotaskmem_string localDisplayName; + wil::unique_cotaskmem_string localIconFilePath; - wil::unique_hwnd hWindow{ GetConsoleWindow() }; + HWND hWindow = nullptr; + GetWindow(hWindow, GW_HWNDFIRST); if (hWindow) { - // Retrieve DisplayName and IconUri - // - DisplayName: Retrieve from Shell. If not specified, fall back to filename. - // - Icon: Retrieve from Shell. If it's not the case or the file extension is unsupported, then throw. winrt::com_ptr propertyStore; - THROW_IF_FAILED(SHGetPropertyStoreForWindow(hWindow.get(), IID_PPV_ARGS(propertyStore.put()))); + THROW_IF_FAILED(SHGetPropertyStoreForWindow(hWindow, IID_PPV_ARGS(propertyStore.put()))); wil::unique_prop_variant propVariantDisplayName; - // Do not throw in case of failure, default to the filepath approach below as fallback to set a DisplayName. THROW_IF_FAILED(propertyStore->GetValue(PKEY_AppUserModel_RelaunchDisplayNameResource, &propVariantDisplayName)); - if (propVariantDisplayName.vt == VT_LPWSTR && propVariantDisplayName.pwszVal != nullptr && wcslen(propVariantDisplayName.pwszVal) > 0) + if (propVariantDisplayName.vt == VT_LPWSTR && wcslen(propVariantDisplayName.pwszVal) > 0) { - displayName = propVariantDisplayName.pwszVal; + localDisplayName = wil::make_unique_string(propVariantDisplayName.pwszVal); } wil::unique_prop_variant propVariantIcon; THROW_IF_FAILED(propertyStore->GetValue(PKEY_AppUserModel_RelaunchIconResource, &propVariantIcon)); - THROW_HR_IF_MSG(E_UNEXPECTED, (propVariantIcon.vt == VT_EMPTY || propVariantIcon.pwszVal == nullptr), "Icon is not specified"); - - THROW_HR_IF_MSG(E_UNEXPECTED, propVariantIcon.vt != VT_LPWSTR, "Icon should be a valid Unicode string"); - - THROW_HR_IF_MSG(E_UNEXPECTED, wcslen(propVariantIcon.pwszVal) == 0, "Icon is an empty string"); + THROW_HR_IF(E_UNEXPECTED, propVariantIcon.vt != VT_LPWSTR || wcslen(propVariantIcon.pwszVal) == 0); - iconFilePath = propVariantIcon.pwszVal; + std::wstring localIconFilePathAsWstring = propVariantIcon.pwszVal; - // Icon filepaths from Shell APIs have this format: ,-, - // since .ico files can have multiple icons in the same file. + // Icon filepaths from Shell APIs usually follow this format: ,-, + // since .ico or .dll files can have multiple icons in the same file. // NotificationController doesn't seem to support such format, so let it take the first icon by default. - auto iteratorForCommaDelimiter{ iconFilePath.find_first_of(L",") }; + auto iteratorForCommaDelimiter{ localIconFilePathAsWstring.find_first_of(L",") }; if (iteratorForCommaDelimiter != std::wstring::npos) // It may or may not have an index, which is fine. { - iconFilePath.erase(iteratorForCommaDelimiter); + localIconFilePath = wil::make_unique_string(localIconFilePathAsWstring.erase(iteratorForCommaDelimiter).c_str()); + } + + *displayName = localDisplayName.release(); + *iconFilePath = localIconFilePath.release(); + + return S_OK; + } + + return HRESULT_FROM_WIN32(ERROR_NOT_FOUND); +} + +void Microsoft::Windows::AppNotifications::Helpers::RegisterAssets(std::wstring const& appId, std::wstring const& clsid) +{ + wil::unique_hkey hKey; + // subKey: \Software\Classes\AppUserModelId\{AppGUID} + std::wstring subKey{ c_appIdentifierPath + appId }; + + THROW_IF_WIN32_ERROR(RegCreateKeyEx( + HKEY_CURRENT_USER, + subKey.c_str(), + 0, + nullptr /* lpClass */, + REG_OPTION_NON_VOLATILE, + KEY_ALL_ACCESS, + nullptr /* lpSecurityAttributes */, + &hKey, + nullptr /* lpdwDisposition */)); + + bool isDefaultIcon{ false }; + + wil::unique_cotaskmem_string displayName; + wil::unique_cotaskmem_string iconFilePath; + + /* + * Try the following techniques to retrieve display name and icon: + * 1. Retrieve assets from Visual Studio, in case the app is written in C#. + * 2. Call the FrameworkUdk to retrieve assets based on the best app shortcut. + * 3. Call the public Shell APIs if a window is available. + * 4. Set a display name from process file path. + * Also, verify if we have a cached icon in the Registry. If not: + * 5. Set a default icon. + */ + if (FAILED(ExtractIconFromCsAssets(&displayName, &iconFilePath)) && + FAILED(ToastNotifications_RetrieveAssets_Stub(&displayName, &iconFilePath)) && + FAILED(RetrieveAssetsFromShell(&displayName, &iconFilePath))) + { + // Set a Display Name based on process name. + displayName = wil::make_unique_string(SetDisplayNameBasedOnProcessName().c_str()); + + // Check if we have anything in the cache for the icon. + iconFilePath = wil::make_unique_string(L"", MAX_PATH); + DWORD bufferLength = sizeof(iconFilePath); + + RegGetValueW( + hKey.get(), + nullptr /* lpValue */, + L"IconUri", + RRF_RT_REG_SZ, + nullptr /* pdwType */, + &iconFilePath, + &bufferLength); + + if (wcslen(iconFilePath.get()) == 0) + { + iconFilePath = wil::make_unique_string(defaultAppNotificationIcon); + } + + if (wcscmp(iconFilePath.get(), defaultAppNotificationIcon) == 0) + { + isDefaultIcon = true; } + } - auto iteratorForFileExtension{ iconFilePath.find_first_of(L".") }; + // Verify if we support the provided file format for the icon. + if (!isDefaultIcon) + { + std::wstring iconFilePathAsWstring{ iconFilePath.get() }; + auto iteratorForFileExtension{ iconFilePathAsWstring.find_first_of(L".") }; THROW_HR_IF_MSG(E_UNEXPECTED, iteratorForFileExtension == std::wstring::npos, "You must provide a valid filepath as the app icon."); - std::wstring iconFileExtension{ iconFilePath.substr(iteratorForFileExtension) }; - THROW_HR_IF_MSG(E_UNEXPECTED, iconFileExtension != L".ico" && iconFileExtension != L".png", - "You must provide a supported file extension as the icon (.ico or .png)."); + std::wstring iconFileExtension = iconFilePathAsWstring.substr(iteratorForFileExtension); + THROW_HR_IF_MSG(E_UNEXPECTED, + iconFileExtension != L".ico" && iconFileExtension != L".png" && + iconFileExtension != L".jpg" && iconFileExtension != L".bmp", + "You must provide a supported file format for the icon. Supported formats: .bmp, .ico, .jpg, .png."); } - RegisterValue(hKey, L"DisplayName", reinterpret_cast(displayName.c_str()), REG_EXPAND_SZ, displayName.size() * sizeof(wchar_t)); - RegisterValue(hKey, L"IconUri", reinterpret_cast(iconFilePath.c_str()), REG_EXPAND_SZ, iconFilePath.size() * sizeof(wchar_t)); + // Finally, set the assets! + RegisterValue(hKey, L"DisplayName", reinterpret_cast(displayName.get()), REG_EXPAND_SZ, wcslen(displayName.get()) * sizeof(wchar_t)); + RegisterValue(hKey, L"IconUri", reinterpret_cast(iconFilePath.get()), REG_EXPAND_SZ, wcslen(iconFilePath.get()) * sizeof(wchar_t)); RegisterValue(hKey, L"CustomActivator", reinterpret_cast(clsid.c_str()), REG_SZ, clsid.size() * sizeof(wchar_t)); } From ce1563a38385b82543eba38f68462cbab8d3f6e0 Mon Sep 17 00:00:00 2001 From: Daniel Ayala <14967941+danielayala94@users.noreply.github.com> Date: Fri, 18 Mar 2022 12:12:51 -0700 Subject: [PATCH 02/11] Simplifying --- .../AppNotificationUtility.cpp | 67 ++++++------------- dev/AppNotifications/AppNotificationUtility.h | 2 + 2 files changed, 24 insertions(+), 45 deletions(-) diff --git a/dev/AppNotifications/AppNotificationUtility.cpp b/dev/AppNotifications/AppNotificationUtility.cpp index a0ae491726..1306e7e1ff 100644 --- a/dev/AppNotifications/AppNotificationUtility.cpp +++ b/dev/AppNotifications/AppNotificationUtility.cpp @@ -235,7 +235,7 @@ std::wstring Microsoft::Windows::AppNotifications::Helpers::SetDisplayNameBasedO } // Placeholder -HRESULT ExtractIconFromCsAssets(_Out_ PWSTR* displayName, _Out_ PWSTR* iconFilePath) +HRESULT RetrieveAssetsFromProcess(_Out_ PWSTR* displayName, _Out_ PWSTR* iconFilePath) { *displayName = nullptr; *iconFilePath = nullptr; @@ -252,7 +252,7 @@ HRESULT ToastNotifications_RetrieveAssets_Stub(_Out_ PWSTR* displayName, _Out_ P return E_NOTIMPL; } -HRESULT RetrieveAssetsFromShell(_Out_ PWSTR* displayName, _Out_ PWSTR* iconFilePath) +HRESULT RetrieveAssetsFromWindow(_Out_ PWSTR* displayName, _Out_ PWSTR* iconFilePath) { *displayName = nullptr; *iconFilePath = nullptr; @@ -260,8 +260,7 @@ HRESULT RetrieveAssetsFromShell(_Out_ PWSTR* displayName, _Out_ PWSTR* iconFileP wil::unique_cotaskmem_string localDisplayName; wil::unique_cotaskmem_string localIconFilePath; - HWND hWindow = nullptr; - GetWindow(hWindow, GW_HWNDFIRST); + HWND hWindow = GetForegroundWindow(); if (hWindow) { @@ -271,7 +270,7 @@ HRESULT RetrieveAssetsFromShell(_Out_ PWSTR* displayName, _Out_ PWSTR* iconFileP wil::unique_prop_variant propVariantDisplayName; THROW_IF_FAILED(propertyStore->GetValue(PKEY_AppUserModel_RelaunchDisplayNameResource, &propVariantDisplayName)); - if (propVariantDisplayName.vt == VT_LPWSTR && wcslen(propVariantDisplayName.pwszVal) > 0) + if (propVariantDisplayName.vt == VT_LPWSTR && Microsoft::Windows::AppNotifications::Helpers::IsWideStringEmptyOrNull(propVariantDisplayName.pwszVal)) { localDisplayName = wil::make_unique_string(propVariantDisplayName.pwszVal); } @@ -279,7 +278,7 @@ HRESULT RetrieveAssetsFromShell(_Out_ PWSTR* displayName, _Out_ PWSTR* iconFileP wil::unique_prop_variant propVariantIcon; THROW_IF_FAILED(propertyStore->GetValue(PKEY_AppUserModel_RelaunchIconResource, &propVariantIcon)); - THROW_HR_IF(E_UNEXPECTED, propVariantIcon.vt != VT_LPWSTR || wcslen(propVariantIcon.pwszVal) == 0); + THROW_HR_IF(E_UNEXPECTED, propVariantIcon.vt != VT_LPWSTR || Microsoft::Windows::AppNotifications::Helpers::IsWideStringEmptyOrNull(propVariantIcon.pwszVal)); std::wstring localIconFilePathAsWstring = propVariantIcon.pwszVal; @@ -318,53 +317,27 @@ void Microsoft::Windows::AppNotifications::Helpers::RegisterAssets(std::wstring &hKey, nullptr /* lpdwDisposition */)); - bool isDefaultIcon{ false }; + bool useDefaultAssets{ false }; wil::unique_cotaskmem_string displayName; wil::unique_cotaskmem_string iconFilePath; - /* - * Try the following techniques to retrieve display name and icon: - * 1. Retrieve assets from Visual Studio, in case the app is written in C#. - * 2. Call the FrameworkUdk to retrieve assets based on the best app shortcut. - * 3. Call the public Shell APIs if a window is available. - * 4. Set a display name from process file path. - * Also, verify if we have a cached icon in the Registry. If not: - * 5. Set a default icon. - */ - if (FAILED(ExtractIconFromCsAssets(&displayName, &iconFilePath)) && + // Try the following techniques to retrieve display name and icon: + // 1. From the current process. + // 2. Based on the best app shortcut, using the FrameworkUdk. + // 3. From the foreground window. + // 4. Use the default assets. + if (FAILED(RetrieveAssetsFromProcess(&displayName, &iconFilePath)) && FAILED(ToastNotifications_RetrieveAssets_Stub(&displayName, &iconFilePath)) && - FAILED(RetrieveAssetsFromShell(&displayName, &iconFilePath))) + FAILED(RetrieveAssetsFromWindow(&displayName, &iconFilePath))) { - // Set a Display Name based on process name. displayName = wil::make_unique_string(SetDisplayNameBasedOnProcessName().c_str()); - - // Check if we have anything in the cache for the icon. - iconFilePath = wil::make_unique_string(L"", MAX_PATH); - DWORD bufferLength = sizeof(iconFilePath); - - RegGetValueW( - hKey.get(), - nullptr /* lpValue */, - L"IconUri", - RRF_RT_REG_SZ, - nullptr /* pdwType */, - &iconFilePath, - &bufferLength); - - if (wcslen(iconFilePath.get()) == 0) - { - iconFilePath = wil::make_unique_string(defaultAppNotificationIcon); - } - - if (wcscmp(iconFilePath.get(), defaultAppNotificationIcon) == 0) - { - isDefaultIcon = true; - } + iconFilePath = wil::make_unique_string(defaultAppNotificationIcon); + useDefaultAssets = true; } - // Verify if we support the provided file format for the icon. - if (!isDefaultIcon) + // Verify if we support the provided file format for the icon. Do not verify for the default icon, we know it is supported. + if (!useDefaultAssets) { std::wstring iconFilePathAsWstring{ iconFilePath.get() }; auto iteratorForFileExtension{ iconFilePathAsWstring.find_first_of(L".") }; @@ -377,7 +350,6 @@ void Microsoft::Windows::AppNotifications::Helpers::RegisterAssets(std::wstring "You must provide a supported file format for the icon. Supported formats: .bmp, .ico, .jpg, .png."); } - // Finally, set the assets! RegisterValue(hKey, L"DisplayName", reinterpret_cast(displayName.get()), REG_EXPAND_SZ, wcslen(displayName.get()) * sizeof(wchar_t)); RegisterValue(hKey, L"IconUri", reinterpret_cast(iconFilePath.get()), REG_EXPAND_SZ, wcslen(iconFilePath.get()) * sizeof(wchar_t)); RegisterValue(hKey, L"CustomActivator", reinterpret_cast(clsid.c_str()), REG_SZ, clsid.size() * sizeof(wchar_t)); @@ -504,3 +476,8 @@ winrt::Microsoft::Windows::AppNotifications::AppNotification Microsoft::Windows: return notification; } + +bool Microsoft::Windows::AppNotifications::Helpers::IsWideStringEmptyOrNull(PCWSTR wideString) +{ + return wideString == nullptr || wcslen(wideString) > 0; +} diff --git a/dev/AppNotifications/AppNotificationUtility.h b/dev/AppNotifications/AppNotificationUtility.h index f865648c38..f8c314dc98 100644 --- a/dev/AppNotifications/AppNotificationUtility.h +++ b/dev/AppNotifications/AppNotificationUtility.h @@ -62,4 +62,6 @@ namespace Microsoft::Windows::AppNotifications::Helpers winrt::Microsoft::Windows::AppNotifications::AppNotification ToastNotificationFromToastProperties(ABI::Microsoft::Internal::ToastNotifications::INotificationProperties* properties); std::wstring SetDisplayNameBasedOnProcessName(); + + bool IsWideStringEmptyOrNull(PCWSTR wideString); } From c640c3a18947f44b67cec937a9c37ef421ba7032 Mon Sep 17 00:00:00 2001 From: Daniel Ayala <14967941+danielayala94@users.noreply.github.com> Date: Mon, 21 Mar 2022 18:06:43 -0700 Subject: [PATCH 03/11] Addressed most of the comments --- .../AppNotificationUtility.cpp | 103 ++++-------------- dev/AppNotifications/AppNotificationUtility.h | 10 +- dev/Common/Common.vcxitems | 3 +- dev/Common/Common.vcxitems.filters | 3 + dev/Common/Microsoft.Foundation.String.h | 22 ++++ 5 files changed, 58 insertions(+), 83 deletions(-) create mode 100644 dev/Common/Microsoft.Foundation.String.h diff --git a/dev/AppNotifications/AppNotificationUtility.cpp b/dev/AppNotifications/AppNotificationUtility.cpp index 1306e7e1ff..2b52951511 100644 --- a/dev/AppNotifications/AppNotificationUtility.cpp +++ b/dev/AppNotifications/AppNotificationUtility.cpp @@ -4,6 +4,7 @@ #include "pch.h" #include +#include #include "AppNotificationUtility.h" #include #include @@ -13,6 +14,8 @@ #include "AppNotification.h" #include "NotificationProgressData.h" +#include + #include #include #include // PKEY properties @@ -235,69 +238,31 @@ std::wstring Microsoft::Windows::AppNotifications::Helpers::SetDisplayNameBasedO } // Placeholder -HRESULT RetrieveAssetsFromProcess(_Out_ PWSTR* displayName, _Out_ PWSTR* iconFilePath) +HRESULT RetrieveAssetsFromProcess(_Out_ Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets& /*assets*/) { - *displayName = nullptr; - *iconFilePath = nullptr; + // THROW_HR_IF_MSG(E_UNEXPECTED, VerifyIconFileExtension(iconFilePath)); return E_NOTIMPL; } // Do nothing. This is just a placeholder while the UDK is ingested with the proper API. -HRESULT ToastNotifications_RetrieveAssets_Stub(_Out_ PWSTR* displayName, _Out_ PWSTR* iconFilePath) +HRESULT ToastNotifications_RetrieveAssets_Stub(_Out_ Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets& /*assets*/) { - *displayName = nullptr; - *iconFilePath = nullptr; + // THROW_HR_IF_MSG(E_UNEXPECTED, VerifyIconFileExtension(iconFilePath)); return E_NOTIMPL; } -HRESULT RetrieveAssetsFromWindow(_Out_ PWSTR* displayName, _Out_ PWSTR* iconFilePath) +HRESULT VerifyIconFileExtension(std::filesystem::path& iconFilePath) { - *displayName = nullptr; - *iconFilePath = nullptr; - - wil::unique_cotaskmem_string localDisplayName; - wil::unique_cotaskmem_string localIconFilePath; + const auto fileExtension = iconFilePath.extension(); - HWND hWindow = GetForegroundWindow(); - - if (hWindow) - { - winrt::com_ptr propertyStore; - THROW_IF_FAILED(SHGetPropertyStoreForWindow(hWindow, IID_PPV_ARGS(propertyStore.put()))); + const bool isFileExtensionSupported = + fileExtension == ".ico" || fileExtension == ".bmp" || fileExtension == ".jpg" || fileExtension == ".png" || + fileExtension == ".ICO" || fileExtension == ".BMP" || fileExtension == ".JPG" || fileExtension == ".PNG"; - wil::unique_prop_variant propVariantDisplayName; - THROW_IF_FAILED(propertyStore->GetValue(PKEY_AppUserModel_RelaunchDisplayNameResource, &propVariantDisplayName)); - - if (propVariantDisplayName.vt == VT_LPWSTR && Microsoft::Windows::AppNotifications::Helpers::IsWideStringEmptyOrNull(propVariantDisplayName.pwszVal)) - { - localDisplayName = wil::make_unique_string(propVariantDisplayName.pwszVal); - } - - wil::unique_prop_variant propVariantIcon; - THROW_IF_FAILED(propertyStore->GetValue(PKEY_AppUserModel_RelaunchIconResource, &propVariantIcon)); - - THROW_HR_IF(E_UNEXPECTED, propVariantIcon.vt != VT_LPWSTR || Microsoft::Windows::AppNotifications::Helpers::IsWideStringEmptyOrNull(propVariantIcon.pwszVal)); - - std::wstring localIconFilePathAsWstring = propVariantIcon.pwszVal; - - // Icon filepaths from Shell APIs usually follow this format: ,-, - // since .ico or .dll files can have multiple icons in the same file. - // NotificationController doesn't seem to support such format, so let it take the first icon by default. - auto iteratorForCommaDelimiter{ localIconFilePathAsWstring.find_first_of(L",") }; - if (iteratorForCommaDelimiter != std::wstring::npos) // It may or may not have an index, which is fine. - { - localIconFilePath = wil::make_unique_string(localIconFilePathAsWstring.erase(iteratorForCommaDelimiter).c_str()); - } - - *displayName = localDisplayName.release(); - *iconFilePath = localIconFilePath.release(); - - return S_OK; - } - - return HRESULT_FROM_WIN32(ERROR_NOT_FOUND); + THROW_HR_IF(E_UNEXPECTED, isFileExtensionSupported); + return S_OK; } void Microsoft::Windows::AppNotifications::Helpers::RegisterAssets(std::wstring const& appId, std::wstring const& clsid) @@ -317,42 +282,23 @@ void Microsoft::Windows::AppNotifications::Helpers::RegisterAssets(std::wstring &hKey, nullptr /* lpdwDisposition */)); - bool useDefaultAssets{ false }; - - wil::unique_cotaskmem_string displayName; - wil::unique_cotaskmem_string iconFilePath; - // Try the following techniques to retrieve display name and icon: // 1. From the current process. // 2. Based on the best app shortcut, using the FrameworkUdk. // 3. From the foreground window. // 4. Use the default assets. - if (FAILED(RetrieveAssetsFromProcess(&displayName, &iconFilePath)) && - FAILED(ToastNotifications_RetrieveAssets_Stub(&displayName, &iconFilePath)) && - FAILED(RetrieveAssetsFromWindow(&displayName, &iconFilePath))) - { - displayName = wil::make_unique_string(SetDisplayNameBasedOnProcessName().c_str()); - iconFilePath = wil::make_unique_string(defaultAppNotificationIcon); - useDefaultAssets = true; - } + Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets assets{}; - // Verify if we support the provided file format for the icon. Do not verify for the default icon, we know it is supported. - if (!useDefaultAssets) + if (FAILED(RetrieveAssetsFromProcess(assets)) && + FAILED(ToastNotifications_RetrieveAssets_Stub(assets))) { - std::wstring iconFilePathAsWstring{ iconFilePath.get() }; - auto iteratorForFileExtension{ iconFilePathAsWstring.find_first_of(L".") }; - THROW_HR_IF_MSG(E_UNEXPECTED, iteratorForFileExtension == std::wstring::npos, "You must provide a valid filepath as the app icon."); - - std::wstring iconFileExtension = iconFilePathAsWstring.substr(iteratorForFileExtension); - THROW_HR_IF_MSG(E_UNEXPECTED, - iconFileExtension != L".ico" && iconFileExtension != L".png" && - iconFileExtension != L".jpg" && iconFileExtension != L".bmp", - "You must provide a supported file format for the icon. Supported formats: .bmp, .ico, .jpg, .png."); + assets.displayName = wil::make_unique_string(SetDisplayNameBasedOnProcessName().c_str()); + assets.iconFilePath = wil::make_unique_string(defaultAppNotificationIcon); } - RegisterValue(hKey, L"DisplayName", reinterpret_cast(displayName.get()), REG_EXPAND_SZ, wcslen(displayName.get()) * sizeof(wchar_t)); - RegisterValue(hKey, L"IconUri", reinterpret_cast(iconFilePath.get()), REG_EXPAND_SZ, wcslen(iconFilePath.get()) * sizeof(wchar_t)); - RegisterValue(hKey, L"CustomActivator", reinterpret_cast(clsid.c_str()), REG_SZ, clsid.size() * sizeof(wchar_t)); + RegisterValue(hKey, L"DisplayName", reinterpret_cast(assets.displayName.get()), REG_EXPAND_SZ, wcslen(assets.displayName.get()) * sizeof(wchar_t)); + RegisterValue(hKey, L"IconUri", reinterpret_cast(assets.iconFilePath.get()), REG_EXPAND_SZ, wcslen(assets.iconFilePath.get()) * sizeof(wchar_t)); + RegisterValue(hKey, L"CustomActivator", reinterpret_cast(clsid.c_str()), REG_SZ, wil::guid_string_length * sizeof(wchar_t)); } winrt::guid Microsoft::Windows::AppNotifications::Helpers::RegisterComActivatorGuidAndAssets() @@ -476,8 +422,3 @@ winrt::Microsoft::Windows::AppNotifications::AppNotification Microsoft::Windows: return notification; } - -bool Microsoft::Windows::AppNotifications::Helpers::IsWideStringEmptyOrNull(PCWSTR wideString) -{ - return wideString == nullptr || wcslen(wideString) > 0; -} diff --git a/dev/AppNotifications/AppNotificationUtility.h b/dev/AppNotifications/AppNotificationUtility.h index f8c314dc98..0bab5ec933 100644 --- a/dev/AppNotifications/AppNotificationUtility.h +++ b/dev/AppNotifications/AppNotificationUtility.h @@ -16,6 +16,11 @@ namespace Microsoft::Windows::AppNotifications::Helpers const std::wstring c_quote{ LR"(")" }; const std::wstring c_notificationActivatedArgument{ L" ----AppNotificationActivated:" }; + struct AppNotificationAssets { + wil::unique_cotaskmem_string displayName; + wil::unique_cotaskmem_string iconFilePath; + }; + inline const int GUID_LENGTH = 39; // GUID + '{' + '}' + '/0' inline std::wstring ConvertPathToKey(std::wstring path) @@ -63,5 +68,8 @@ namespace Microsoft::Windows::AppNotifications::Helpers std::wstring SetDisplayNameBasedOnProcessName(); - bool IsWideStringEmptyOrNull(PCWSTR wideString); + inline bool IsNullOrEmpty(PCWSTR string) + { + return !string || (*string == '\0'); + } } diff --git a/dev/Common/Common.vcxitems b/dev/Common/Common.vcxitems index 2f1c94fdc4..46f65e5fb0 100644 --- a/dev/Common/Common.vcxitems +++ b/dev/Common/Common.vcxitems @@ -21,8 +21,9 @@ + - + \ No newline at end of file diff --git a/dev/Common/Common.vcxitems.filters b/dev/Common/Common.vcxitems.filters index 5ed4b7cd27..1a5018a3a7 100644 --- a/dev/Common/Common.vcxitems.filters +++ b/dev/Common/Common.vcxitems.filters @@ -29,6 +29,9 @@ Header Files + + Header Files + diff --git a/dev/Common/Microsoft.Foundation.String.h b/dev/Common/Microsoft.Foundation.String.h new file mode 100644 index 0000000000..dae9165ae1 --- /dev/null +++ b/dev/Common/Microsoft.Foundation.String.h @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#ifndef __MICROSOFT_FOUNDATION_STRING_H +#define __MICROSOFT_FOUNDATION_STRING_H + +namespace Microsoft::Foundation::String +{ + /// Return true if the string is nullptr or an empty string (""). + inline bool IsNullOrEmpty(_In_ PCSTR string) + { + return !string || (*string == '\0'); + } + + /// Return true if the string is nullptr or an empty string (""). + inline bool IsNullOrEmpty(_In_ PCWSTR string) + { + return !string || (*string == L'\0'); + } +} + +#endif // __MICROSOFT_FOUNDATION_STRING_H From 5fc95e3cbd614a67647ee221979dda7c991c62e7 Mon Sep 17 00:00:00 2001 From: Daniel Ayala <14967941+danielayala94@users.noreply.github.com> Date: Tue, 22 Mar 2022 10:30:16 -0700 Subject: [PATCH 04/11] Further simplification :) --- .../AppNotificationUtility.cpp | 30 ++++++++++++------- dev/AppNotifications/AppNotificationUtility.h | 11 ++----- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/dev/AppNotifications/AppNotificationUtility.cpp b/dev/AppNotifications/AppNotificationUtility.cpp index 2b52951511..206d59ece8 100644 --- a/dev/AppNotifications/AppNotificationUtility.cpp +++ b/dev/AppNotifications/AppNotificationUtility.cpp @@ -221,7 +221,7 @@ HRESULT Microsoft::Windows::AppNotifications::Helpers::GetActivatorGuid(std::wst } CATCH_RETURN() -std::wstring Microsoft::Windows::AppNotifications::Helpers::SetDisplayNameBasedOnProcessName() +std::wstring Microsoft::Windows::AppNotifications::Helpers::GetDisplayNameBasedOnProcessName() { std::wstring displayName{}; THROW_IF_FAILED(wil::GetModuleFileNameExW(GetCurrentProcess(), nullptr, displayName)); @@ -253,13 +253,25 @@ HRESULT ToastNotifications_RetrieveAssets_Stub(_Out_ Microsoft::Windows::AppNoti return E_NOTIMPL; } -HRESULT VerifyIconFileExtension(std::filesystem::path& iconFilePath) +HRESULT RetrieveDefaultAssets(_Out_ Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets& assets) +{ + assets.displayName = Microsoft::Windows::AppNotifications::Helpers::GetDisplayNameBasedOnProcessName(); + assets.iconFilePath = defaultAppNotificationIcon; + + return S_OK; +} + +HRESULT VerifyIconFileExtension(std::filesystem::path const& iconFilePath) { const auto fileExtension = iconFilePath.extension(); + std::string lowercaseFileExtension{ fileExtension.u8string() }; + + std::transform(lowercaseFileExtension.begin(), lowercaseFileExtension.end(), lowercaseFileExtension.begin(), + [](unsigned char c) { return std::tolower(c); }); + const bool isFileExtensionSupported = - fileExtension == ".ico" || fileExtension == ".bmp" || fileExtension == ".jpg" || fileExtension == ".png" || - fileExtension == ".ICO" || fileExtension == ".BMP" || fileExtension == ".JPG" || fileExtension == ".PNG"; + lowercaseFileExtension == ".ico" || lowercaseFileExtension == ".bmp" || lowercaseFileExtension == ".jpg" || lowercaseFileExtension == ".png"; THROW_HR_IF(E_UNEXPECTED, isFileExtensionSupported); return S_OK; @@ -285,19 +297,17 @@ void Microsoft::Windows::AppNotifications::Helpers::RegisterAssets(std::wstring // Try the following techniques to retrieve display name and icon: // 1. From the current process. // 2. Based on the best app shortcut, using the FrameworkUdk. - // 3. From the foreground window. - // 4. Use the default assets. + // 3. Use the default assets. Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets assets{}; if (FAILED(RetrieveAssetsFromProcess(assets)) && FAILED(ToastNotifications_RetrieveAssets_Stub(assets))) { - assets.displayName = wil::make_unique_string(SetDisplayNameBasedOnProcessName().c_str()); - assets.iconFilePath = wil::make_unique_string(defaultAppNotificationIcon); + THROW_IF_FAILED(RetrieveDefaultAssets(assets)); } - RegisterValue(hKey, L"DisplayName", reinterpret_cast(assets.displayName.get()), REG_EXPAND_SZ, wcslen(assets.displayName.get()) * sizeof(wchar_t)); - RegisterValue(hKey, L"IconUri", reinterpret_cast(assets.iconFilePath.get()), REG_EXPAND_SZ, wcslen(assets.iconFilePath.get()) * sizeof(wchar_t)); + RegisterValue(hKey, L"DisplayName", reinterpret_cast(assets.displayName.c_str()), REG_EXPAND_SZ, assets.displayName.size() * sizeof(wchar_t)); + RegisterValue(hKey, L"IconUri", reinterpret_cast(assets.iconFilePath.c_str()), REG_EXPAND_SZ, assets.iconFilePath.size() * sizeof(wchar_t)); RegisterValue(hKey, L"CustomActivator", reinterpret_cast(clsid.c_str()), REG_SZ, wil::guid_string_length * sizeof(wchar_t)); } diff --git a/dev/AppNotifications/AppNotificationUtility.h b/dev/AppNotifications/AppNotificationUtility.h index 0bab5ec933..f955290fb2 100644 --- a/dev/AppNotifications/AppNotificationUtility.h +++ b/dev/AppNotifications/AppNotificationUtility.h @@ -17,8 +17,8 @@ namespace Microsoft::Windows::AppNotifications::Helpers const std::wstring c_notificationActivatedArgument{ L" ----AppNotificationActivated:" }; struct AppNotificationAssets { - wil::unique_cotaskmem_string displayName; - wil::unique_cotaskmem_string iconFilePath; + std::wstring displayName; + std::wstring iconFilePath; }; inline const int GUID_LENGTH = 39; // GUID + '{' + '}' + '/0' @@ -66,10 +66,5 @@ namespace Microsoft::Windows::AppNotifications::Helpers winrt::Microsoft::Windows::AppNotifications::AppNotification ToastNotificationFromToastProperties(ABI::Microsoft::Internal::ToastNotifications::INotificationProperties* properties); - std::wstring SetDisplayNameBasedOnProcessName(); - - inline bool IsNullOrEmpty(PCWSTR string) - { - return !string || (*string == '\0'); - } + std::wstring GetDisplayNameBasedOnProcessName(); } From 062c2b879f72e8fa3e83adb3cbbf1f188dce5250 Mon Sep 17 00:00:00 2001 From: Sharath Manchala <10109130+sharath2727@users.noreply.github.com> Date: Wed, 30 Mar 2022 17:11:52 -0700 Subject: [PATCH 05/11] Get Icon from process (#2330) * Get Icon from process * Address comments --- WindowsAppRuntime.sln | 14 +- .../AppNotificationUtility.cpp | 70 ++++++-- dev/AppNotifications/AppNotificationUtility.h | 6 + .../AppNotifications.vcxitems | 2 + dev/AppNotifications/WICUtility.cpp | 165 ++++++++++++++++++ dev/AppNotifications/WICUtility.h | 12 ++ .../ToastNotificationsDemoApp.rc | 71 ++++++++ .../ToastNotificationsDemoApp.vcxproj | 9 +- .../ToastNotificationsDemoApp.vcxproj.filters | 29 +++ .../ToastNotificationsDemoApp/icon1.ico | Bin 0 -> 45451 bytes .../ToastNotificationsDemoApp/main.cpp | 30 +--- .../ToastNotificationsDemoApp/resource.h | 16 ++ 12 files changed, 377 insertions(+), 47 deletions(-) create mode 100644 dev/AppNotifications/WICUtility.cpp create mode 100644 dev/AppNotifications/WICUtility.h create mode 100644 test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.rc create mode 100644 test/TestApps/ToastNotificationsDemoApp/icon1.ico create mode 100644 test/TestApps/ToastNotificationsDemoApp/resource.h diff --git a/WindowsAppRuntime.sln b/WindowsAppRuntime.sln index 995bff0f60..095ea6a6a1 100644 --- a/WindowsAppRuntime.sln +++ b/WindowsAppRuntime.sln @@ -293,6 +293,13 @@ EndProject Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "AccessControlTestAppPackage", "test\TestApps\AccessControlTestAppPackage\AccessControlTestAppPackage.wapproj", "{03EBF097-66C6-4996-95A3-28F6F5999E27}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ToastNotificationsDemoApp", "test\TestApps\ToastNotificationsDemoApp\ToastNotificationsDemoApp.vcxproj", "{412D023E-8635-4AD2-A0EA-E19E08D36915}" + ProjectSection(ProjectDependencies) = postProject + {B73AD907-6164-4294-88FB-F3C9C10DA1F1} = {B73AD907-6164-4294-88FB-F3C9C10DA1F1} + {A7391725-4EF5-438F-8DE1-645423E46955} = {A7391725-4EF5-438F-8DE1-645423E46955} + {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} = {9C1A6C58-52D6-4514-9120-5C339C5DF4BE} + {B71E818A-882E-456A-87E5-4DE4A6602B99} = {B71E818A-882E-456A-87E5-4DE4A6602B99} + {C422B090-F501-4204-8068-1084B0799405} = {C422B090-F501-4204-8068-1084B0799405} + EndProjectSection EndProject Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "ToastNotificationsDemoAppPackage", "test\TestApps\ToastNotificationsDemoAppPackage\ToastNotificationsDemoAppPackage.wapproj", "{E695A08E-8735-41CD-AE55-A5B589BA297F}" EndProject @@ -301,6 +308,7 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppNotifications", "dev\AppNotifications\AppNotifications.vcxitems", "{B4824897-88E0-4927-8FB9-E60106F01ED9}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Windows.AppNotifications.Projection", "dev\Projections\CS\Microsoft.Windows.AppNotifications.Projection\Microsoft.Windows.AppNotifications.Projection.csproj", "{2385A443-A1C2-4562-8D28-D0AD239EE5E7}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{82A73181-EA4A-431A-B82B-BE6734604CC9}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test_Common", "test\Common\Common.vcxproj", "{50451390-66E7-4465-8804-427560625794}" @@ -1270,10 +1278,6 @@ Global {BC5E5A3E-E733-4388-8B00-F8495DA7C778} = {3DE93B2F-F887-437D-B512-6B1024ABA290} {34671779-4A4D-4D0E-B259-CD0F14D4F6D4} = {448ED2E5-0B37-4D97-9E6B-8C10A507976A} {885A43FA-052D-4B0D-A2DC-13EE15796435} = {34671779-4A4D-4D0E-B259-CD0F14D4F6D4} - {BC5E5A3E-E733-4388-8B00-F8495DA7C778} = {3DE93B2F-F887-437D-B512-6B1024ABA290} - {BC5E5A3E-E733-4388-8B00-F8495DA7C778} = {3DE93B2F-F887-437D-B512-6B1024ABA290} - {34671779-4A4D-4D0E-B259-CD0F14D4F6D4} = {448ED2E5-0B37-4D97-9E6B-8C10A507976A} - {885A43FA-052D-4B0D-A2DC-13EE15796435} = {34671779-4A4D-4D0E-B259-CD0F14D4F6D4} {A06B7FE8-D201-4EA2-BB10-61B4595EC377} = {448ED2E5-0B37-4D97-9E6B-8C10A507976A} {C91BCB93-9ED1-4ACD-85F3-26F9F6AC52E3} = {A06B7FE8-D201-4EA2-BB10-61B4595EC377} {08BC78E0-63C6-49A7-81B3-6AFC3DEAC4DE} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} @@ -1284,8 +1288,6 @@ Global {1C9A0791-2BAA-420B-84B6-C0721F22A6E8} = {448ED2E5-0B37-4D97-9E6B-8C10A507976A} {B4824897-88E0-4927-8FB9-E60106F01ED9} = {1C9A0791-2BAA-420B-84B6-C0721F22A6E8} {2385A443-A1C2-4562-8D28-D0AD239EE5E7} = {716C26A0-E6B0-4981-8412-D14A4D410531} - {34671779-4A4D-4D0E-B259-CD0F14D4F6D4} = {448ED2E5-0B37-4D97-9E6B-8C10A507976A} - {885A43FA-052D-4B0D-A2DC-13EE15796435} = {34671779-4A4D-4D0E-B259-CD0F14D4F6D4} {82A73181-EA4A-431A-B82B-BE6734604CC9} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} {50451390-66E7-4465-8804-427560625794} = {82A73181-EA4A-431A-B82B-BE6734604CC9} {0419CA2B-5ED1-49F0-B70B-5F470A15D3D0} = {448ED2E5-0B37-4D97-9E6B-8C10A507976A} diff --git a/dev/AppNotifications/AppNotificationUtility.cpp b/dev/AppNotifications/AppNotificationUtility.cpp index 206d59ece8..054d40cf05 100644 --- a/dev/AppNotifications/AppNotificationUtility.cpp +++ b/dev/AppNotifications/AppNotificationUtility.cpp @@ -4,7 +4,7 @@ #include "pch.h" #include -#include + #include "AppNotificationUtility.h" #include #include @@ -15,12 +15,15 @@ #include "NotificationProgressData.h" #include - -#include -#include -#include // PKEY properties -#include // IPropertyStore #include +#include +#include +#include + +namespace std +{ + using namespace std::filesystem; +} namespace winrt { @@ -237,14 +240,57 @@ std::wstring Microsoft::Windows::AppNotifications::Helpers::GetDisplayNameBasedO return displayName; } -// Placeholder -HRESULT RetrieveAssetsFromProcess(_Out_ Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets& /*assets*/) +inline wil::unique_hicon RetrieveIconFromProcess() { - // THROW_HR_IF_MSG(E_UNEXPECTED, VerifyIconFileExtension(iconFilePath)); + std::wstring processPath{}; + THROW_IF_FAILED(wil::GetModuleFileNameExW(GetCurrentProcess(), nullptr, processPath)); - return E_NOTIMPL; + // Extract the small icon from the first .ico, if failed extract the large icon. + wil::unique_hicon hIcon{}; + if (!ExtractIconExW(processPath.c_str(), 0 /* index */, nullptr /* Large icon */, &hIcon, 1)) + { + THROW_HR_IF(E_FAIL, ExtractIconExW(processPath.c_str(), 0, &hIcon, nullptr /* Small Icon */, 1) == 0); + } + + return hIcon; } +inline std::wstring RetrieveLocalFolderPath() +{ + wil::unique_cotaskmem_string localAppDataPath; + THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0 /* flags */, nullptr /* access token handle */, &localAppDataPath)); + + // path: C:\Users\\AppData\Local\Microsoft + std::path localFolderPath{ std::wstring(localAppDataPath.get()) + Microsoft::Windows::AppNotifications::Helpers::c_localMicrosoftFolder }; + THROW_HR_IF(ERROR_FILE_NOT_FOUND, !std::exists(localFolderPath)); + + // path: C:\Users\\AppData\Local\Microsoft\WindowsAppSDK + localFolderPath.append(Microsoft::Windows::AppNotifications::Helpers::c_localWindowsAppSDKFolder); + if (!std::exists(localFolderPath)) + { + std::create_directory(localFolderPath); + } + + return std::wstring(localFolderPath.c_str()); +} + +HRESULT Microsoft::Windows::AppNotifications::Helpers::RetrieveAssetsFromProcess(_Out_ Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets& assets) noexcept try +{ + wil::unique_hicon hIcon{ RetrieveIconFromProcess() }; + + std::wstring notificationAppId{ RetrieveNotificationAppId() }; + + // path: C:\Users\\AppData\Local\Microsoft\WindowsAppSDK\{AppGUID}.png + std::wstring writeToFile{ RetrieveLocalFolderPath().c_str() + c_backSlash + notificationAppId + c_pngExtension }; + Microsoft::Windows::AppNotifications::WICHelpers::WriteHIconToPngFile(hIcon, writeToFile.c_str()); + + assets.displayName = Microsoft::Windows::AppNotifications::Helpers::GetDisplayNameBasedOnProcessName(); + assets.iconFilePath = writeToFile; + + return S_OK; +} +CATCH_RETURN() + // Do nothing. This is just a placeholder while the UDK is ingested with the proper API. HRESULT ToastNotifications_RetrieveAssets_Stub(_Out_ Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets& /*assets*/) { @@ -300,8 +346,8 @@ void Microsoft::Windows::AppNotifications::Helpers::RegisterAssets(std::wstring // 3. Use the default assets. Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets assets{}; - if (FAILED(RetrieveAssetsFromProcess(assets)) && - FAILED(ToastNotifications_RetrieveAssets_Stub(assets))) + if (FAILED(ToastNotifications_RetrieveAssets_Stub(assets)) && + FAILED(RetrieveAssetsFromProcess(assets))) { THROW_IF_FAILED(RetrieveDefaultAssets(assets)); } diff --git a/dev/AppNotifications/AppNotificationUtility.h b/dev/AppNotifications/AppNotificationUtility.h index f955290fb2..c3671f9e0b 100644 --- a/dev/AppNotifications/AppNotificationUtility.h +++ b/dev/AppNotifications/AppNotificationUtility.h @@ -14,7 +14,11 @@ namespace Microsoft::Windows::AppNotifications::Helpers const std::wstring c_appIdentifierPath{ LR"(Software\Classes\AppUserModelId\)" }; const std::wstring c_clsIdPath{ LR"(Software\Classes\CLSID\)" }; const std::wstring c_quote{ LR"(")" }; + const std::wstring c_backSlash{ LR"(\)" }; const std::wstring c_notificationActivatedArgument{ L" ----AppNotificationActivated:" }; + const std::wstring c_localMicrosoftFolder{ LR"(\Microsoft\)" }; + const std::wstring c_localWindowsAppSDKFolder{ LR"(WindowsAppSDK)" }; + const std::wstring c_pngExtension{ LR"(.png)" }; struct AppNotificationAssets { std::wstring displayName; @@ -67,4 +71,6 @@ namespace Microsoft::Windows::AppNotifications::Helpers winrt::Microsoft::Windows::AppNotifications::AppNotification ToastNotificationFromToastProperties(ABI::Microsoft::Internal::ToastNotifications::INotificationProperties* properties); std::wstring GetDisplayNameBasedOnProcessName(); + + HRESULT RetrieveAssetsFromProcess(_Out_ Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets& assets) noexcept; } diff --git a/dev/AppNotifications/AppNotifications.vcxitems b/dev/AppNotifications/AppNotifications.vcxitems index cf8a923928..d3f1e495de 100644 --- a/dev/AppNotifications/AppNotifications.vcxitems +++ b/dev/AppNotifications/AppNotifications.vcxitems @@ -23,6 +23,7 @@ + @@ -32,6 +33,7 @@ + diff --git a/dev/AppNotifications/WICUtility.cpp b/dev/AppNotifications/WICUtility.cpp new file mode 100644 index 0000000000..267fed6754 --- /dev/null +++ b/dev/AppNotifications/WICUtility.cpp @@ -0,0 +1,165 @@ + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include "pch.h" +#include "WICUtility.h" +#include +#include +#include +#include + +#include +#include +#include +#include + +typedef enum +{ + BMPV_1, + BMPV_5, +} BITMAP_VERSION; + +winrt::com_ptr ConvertWICBitmapPixelFormat( + winrt::com_ptr const& wicImagingFactory, + winrt::com_ptr const& wicBitmapSource, + WICPixelFormatGUID guidPixelFormatSource, + WICBitmapDitherType bitmapDitherType) +{ + winrt::com_ptr wicFormatConverter; + THROW_IF_FAILED(wicImagingFactory->CreateFormatConverter(wicFormatConverter.put())); + + THROW_IF_FAILED(wicFormatConverter->Initialize(wicBitmapSource.get(), guidPixelFormatSource, bitmapDitherType, nullptr, 0.f, WICBitmapPaletteTypeCustom)); + + // Store the converted bitmap as ppToRenderBitmapSource + winrt::com_ptr wicBitmapSourceConverted; + THROW_IF_FAILED(wicFormatConverter->QueryInterface(_uuidof(IWICBitmapSource), wicBitmapSourceConverted.put_void())); + + return wicBitmapSourceConverted; +} + +void AddFrameToWICBitmap( + winrt::com_ptr const& wicImagingFactory, + winrt::com_ptr const& wicEncoder, + winrt::com_ptr const& wicBitmapSource, + BITMAP_VERSION bmpv) +{ + winrt::com_ptr wicFrameEncoder; + winrt::com_ptr wicEncoderOptions; + + THROW_IF_FAILED(wicEncoder->CreateNewFrame(wicFrameEncoder.put(), wicEncoderOptions.put())); + + GUID containerGuid; + THROW_IF_FAILED(wicEncoder->GetContainerFormat(&containerGuid)); + + if ((containerGuid == GUID_ContainerFormatBmp) && (bmpv == BMPV_5)) + { + // Write the encoder option to the property bag instance. + VARIANT varValue{}; + + varValue.vt = VT_BOOL; + varValue.boolVal = VARIANT_TRUE; + + // Options to enable the v5 header support for 32bppBGRA. + PROPBAG2 v5HeaderOption{}; + + std::wstring str = L"EnableV5Header32bppBGRA"; + v5HeaderOption.pstrName = (LPOLESTR)str.c_str(); + + THROW_IF_FAILED(wicEncoderOptions->Write(1, &v5HeaderOption, &varValue)); + } + + THROW_IF_FAILED(wicFrameEncoder->Initialize(wicEncoderOptions.get())); + + UINT uWidth, uHeight; + THROW_IF_FAILED(wicBitmapSource->GetSize(&uWidth, &uHeight)); + + THROW_IF_FAILED(wicFrameEncoder->SetSize(uWidth, uHeight)); + + winrt::com_ptr wicbitmapSourceConverted; + wicbitmapSourceConverted = ConvertWICBitmapPixelFormat(wicImagingFactory, wicBitmapSource, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone); + + WICRect rect { 0, 0, static_cast(uWidth), static_cast(uHeight) }; + + // Write the image data and commit + THROW_IF_FAILED(wicFrameEncoder->WriteSource(wicbitmapSourceConverted.get(), &rect)); + + THROW_IF_FAILED(wicFrameEncoder->Commit()); +} + +winrt::com_ptr GetStreamOfWICBitmapSource( + winrt::com_ptr const& wicImagingFactory, + winrt::com_ptr const& wicBitmapSource, + _In_ REFGUID guidContainerFormat, + _In_ BITMAP_VERSION bmpv) +{ + winrt::com_ptr spImageStream; + THROW_IF_FAILED(CreateStreamOnHGlobal(nullptr /* handle */, true /* delete on release */, spImageStream.put())); + + // Create encoder and initialize it + winrt::com_ptr wicEncoder; + THROW_IF_FAILED(wicImagingFactory->CreateEncoder(guidContainerFormat, nullptr, wicEncoder.put())); + + THROW_IF_FAILED(wicEncoder->Initialize(spImageStream.get(), WICBitmapEncoderCacheOption::WICBitmapEncoderNoCache)); + + // Add a single frame to the encoder with the Bitmap + AddFrameToWICBitmap(wicImagingFactory, wicEncoder, wicBitmapSource, bmpv); + + THROW_IF_FAILED(wicEncoder->Commit()); + + // Seek the stream to the beginning and transfer + static const LARGE_INTEGER lnBeginning = {}; + THROW_IF_FAILED(spImageStream->Seek(lnBeginning, STREAM_SEEK_SET, nullptr /* new seek pointer */)); + + return spImageStream; +} + +void SaveImageWithWIC( + winrt::com_ptr const& wicImagingFactory, + winrt::com_ptr const& wicBitmapSource, + _In_ REFGUID guidContainerFormat, + winrt::com_ptr& pStream) +{ + winrt::com_ptr spImageStream; + spImageStream = GetStreamOfWICBitmapSource(wicImagingFactory, wicBitmapSource, guidContainerFormat, BMPV_1); + + // Seek the stream to the beginning and transfer + static LARGE_INTEGER const lnBeginning{ 0 }; + THROW_IF_FAILED(spImageStream->Seek(lnBeginning, STREAM_SEEK_SET, nullptr /* new seek pointer */)); + + static ULARGE_INTEGER lnbuffer{ INT_MAX }; + THROW_IF_FAILED(spImageStream->CopyTo(pStream.get(), lnbuffer, nullptr /* pointer to number of bytes read */, nullptr /* pointer to number of bytes written */)); +} + +void Microsoft::Windows::AppNotifications::WICHelpers::WriteHIconToPngFile(wil::unique_hicon const& hIcon, _In_ PCWSTR pszFileName) +{ + auto wicImagingFactory{ winrt::create_instance(CLSID_WICImagingFactory, CLSCTX_INPROC_SERVER) }; + + winrt::com_ptr wicBitmap; + THROW_IF_FAILED(wicImagingFactory->CreateBitmapFromHICON(hIcon.get(), wicBitmap.put())); + + // Create stream to save the HICON to. + winrt::com_ptr spStream; + THROW_IF_FAILED(CreateStreamOnHGlobal(nullptr /* handle */, TRUE, spStream.put())); + + winrt::com_ptr wicBitmapSource; + wicBitmapSource = wicBitmap; + + SaveImageWithWIC(wicImagingFactory, wicBitmapSource, GUID_ContainerFormatPng, spStream); + + // Write out the stream to a file. + winrt::com_ptr spStreamOut; + THROW_IF_FAILED(SHCreateStreamOnFileEx(pszFileName, STGM_CREATE | STGM_WRITE | STGM_SHARE_DENY_WRITE, FILE_ATTRIBUTE_NORMAL, TRUE, nullptr, spStreamOut.put())); + + STATSTG statstg; + THROW_IF_FAILED(spStream->Stat(&statstg, STATFLAG_NONAME)); + + THROW_IF_FAILED(IStream_Reset(spStream.get())); + + THROW_IF_FAILED(spStreamOut->SetSize(statstg.cbSize)); + + // TODO: Comments need to be added + THROW_IF_FAILED(spStream->CopyTo(spStreamOut.get(), statstg.cbSize, nullptr /* pointer to number of bytes read */, nullptr /* pointer to number of bytes written */)); + + THROW_IF_FAILED(spStreamOut->Commit(STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE)); +} diff --git a/dev/AppNotifications/WICUtility.h b/dev/AppNotifications/WICUtility.h new file mode 100644 index 0000000000..89877cfe47 --- /dev/null +++ b/dev/AppNotifications/WICUtility.h @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#pragma once + +#include "pch.h" + +namespace Microsoft::Windows::AppNotifications::WICHelpers +{ + void WriteHIconToPngFile(wil::unique_hicon const& hIcon, _In_ PCWSTR pszFileName); +} + diff --git a/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.rc b/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.rc new file mode 100644 index 0000000000..49e9d07abb --- /dev/null +++ b/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.rc @@ -0,0 +1,71 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "icon1.ico" + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.vcxproj b/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.vcxproj index a823e15979..a503352b2b 100644 --- a/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.vcxproj +++ b/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.vcxproj @@ -241,6 +241,7 @@ + @@ -267,6 +268,12 @@ {f76b776e-86f5-48c5-8fc7-d2795ecc9746} + + + + + + @@ -285,4 +292,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.vcxproj.filters b/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.vcxproj.filters index a8a65633bf..f8de0a3b86 100644 --- a/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.vcxproj.filters +++ b/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.vcxproj.filters @@ -14,4 +14,33 @@ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + + + + + + Resource Files + + + + + Resource Files + + \ No newline at end of file diff --git a/test/TestApps/ToastNotificationsDemoApp/icon1.ico b/test/TestApps/ToastNotificationsDemoApp/icon1.ico new file mode 100644 index 0000000000000000000000000000000000000000..5d06b9f2857b39f0b5d3395e3e7999ba6bcc3a38 GIT binary patch literal 45451 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 literal 0 HcmV?d00001 diff --git a/test/TestApps/ToastNotificationsDemoApp/main.cpp b/test/TestApps/ToastNotificationsDemoApp/main.cpp index b04cd19451..b44277eef9 100644 --- a/test/TestApps/ToastNotificationsDemoApp/main.cpp +++ b/test/TestApps/ToastNotificationsDemoApp/main.cpp @@ -133,33 +133,6 @@ bool PostToastHelper(std::wstring const& tag, std::wstring const& group) return true; } -// This function is intended to be called in the unpackaged scenario. -void SetDisplayNameAndIcon() noexcept try -{ - // Not mandatory, but it's highly recommended to specify AppUserModelId - THROW_IF_FAILED(SetCurrentProcessExplicitAppUserModelID(L"TestAppId")); - - // Icon is mandatory - winrt::com_ptr propertyStore; - wil::unique_hwnd hWindow{ GetConsoleWindow() }; - - THROW_IF_FAILED(SHGetPropertyStoreForWindow(hWindow.get(), IID_PPV_ARGS(&propertyStore))); - - wil::unique_prop_variant propVariantIcon; - wil::unique_cotaskmem_string sth = wil::make_unique_string(LR"(%SystemRoot%\system32\@WLOGO_96x96.png)"); - propVariantIcon.pwszVal = sth.release(); - propVariantIcon.vt = VT_LPWSTR; - THROW_IF_FAILED(propertyStore->SetValue(PKEY_AppUserModel_RelaunchIconResource, propVariantIcon)); - - // App name is not mandatory, but it's highly recommended to specify it - wil::unique_prop_variant propVariantAppName; - wil::unique_cotaskmem_string prodName = wil::make_unique_string(L"The Toast Demo App"); - propVariantAppName.pwszVal = prodName.release(); - propVariantAppName.vt = VT_LPWSTR; - THROW_IF_FAILED(propertyStore->SetValue(PKEY_AppUserModel_RelaunchDisplayNameResource, propVariantAppName)); -} -CATCH_LOG() - int main() { // Retrieve the app scenario. @@ -175,7 +148,8 @@ int main() const PACKAGE_VERSION minVersion{}; RETURN_IF_FAILED(MddBootstrapInitialize(c_Version_MajorMinor, nullptr, minVersion)); - SetDisplayNameAndIcon(); + // Not mandatory, but it's highly recommended to specify AppUserModelId + THROW_IF_FAILED(SetCurrentProcessExplicitAppUserModelID(L"TestAppId")); } std::wcout << L"--------------------------------\n"; diff --git a/test/TestApps/ToastNotificationsDemoApp/resource.h b/test/TestApps/ToastNotificationsDemoApp/resource.h new file mode 100644 index 0000000000..16f2e21364 --- /dev/null +++ b/test/TestApps/ToastNotificationsDemoApp/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ToastNotificationsDemoApp.rc +// +#define IDI_ICON1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif From 394a3f363374e7c3590c3a706ba6803b62a7457f Mon Sep 17 00:00:00 2001 From: Daniel Ayala <14967941+danielayala94@users.noreply.github.com> Date: Thu, 31 Mar 2022 16:38:55 -0700 Subject: [PATCH 06/11] Moved Shell stuff to its own file, and other amendments --- .../AppNotificationManager.cpp | 5 + .../AppNotificationUtility.cpp | 134 +++-------------- dev/AppNotifications/AppNotificationUtility.h | 11 +- .../AppNotifications.vcxitems | 4 +- .../{WICUtility.cpp => ShellLocalization.cpp} | 142 +++++++++++++++--- dev/AppNotifications/ShellLocalization.h | 31 ++++ dev/AppNotifications/WICUtility.h | 12 -- 7 files changed, 187 insertions(+), 152 deletions(-) rename dev/AppNotifications/{WICUtility.cpp => ShellLocalization.cpp} (50%) create mode 100644 dev/AppNotifications/ShellLocalization.h delete mode 100644 dev/AppNotifications/WICUtility.h diff --git a/dev/AppNotifications/AppNotificationManager.cpp b/dev/AppNotifications/AppNotificationManager.cpp index c5099d0b3d..c367e4c623 100644 --- a/dev/AppNotifications/AppNotificationManager.cpp +++ b/dev/AppNotifications/AppNotificationManager.cpp @@ -20,6 +20,7 @@ #include #include #include +#include using namespace std::literals; @@ -46,6 +47,7 @@ namespace PushNotificationHelpers } using namespace Microsoft::Windows::AppNotifications::Helpers; +using namespace Microsoft::Windows::AppNotifications::ShellLocalization; namespace winrt::Microsoft::Windows::AppNotifications::implementation { @@ -226,6 +228,9 @@ namespace winrt::Microsoft::Windows::AppNotifications::implementation UnRegisterNotificationAppIdentifierFromRegistry(); THROW_IF_FAILED(PushNotifications_UnregisterFullTrustApplication(m_appId.c_str())); + + // If the app icon was inferred from process, then we should clean it up. + DeleteIconFromCache(); } } catch (...) diff --git a/dev/AppNotifications/AppNotificationUtility.cpp b/dev/AppNotifications/AppNotificationUtility.cpp index 054d40cf05..f9577ca6b5 100644 --- a/dev/AppNotifications/AppNotificationUtility.cpp +++ b/dev/AppNotifications/AppNotificationUtility.cpp @@ -16,10 +16,11 @@ #include #include -#include #include #include +#include + namespace std { using namespace std::filesystem; @@ -37,8 +38,6 @@ namespace ToastABI using namespace ::ABI::Microsoft::Internal::ToastNotifications; } -constexpr PCWSTR defaultAppNotificationIcon = LR"(ms-resource://Windows.UI.ShellCommon/Files/Images/DefaultSystemNotification.png)"; - std::wstring Microsoft::Windows::AppNotifications::Helpers::RetrieveUnpackagedNotificationAppId() { wil::unique_cotaskmem_string appId; @@ -240,87 +239,35 @@ std::wstring Microsoft::Windows::AppNotifications::Helpers::GetDisplayNameBasedO return displayName; } -inline wil::unique_hicon RetrieveIconFromProcess() +winrt::guid Microsoft::Windows::AppNotifications::Helpers::RegisterComActivatorGuidAndAssets() { - std::wstring processPath{}; - THROW_IF_FAILED(wil::GetModuleFileNameExW(GetCurrentProcess(), nullptr, processPath)); + std::wstring registeredGuid; + auto hr = GetActivatorGuid(registeredGuid); - // Extract the small icon from the first .ico, if failed extract the large icon. wil::unique_hicon hIcon{}; - if (!ExtractIconExW(processPath.c_str(), 0 /* index */, nullptr /* Large icon */, &hIcon, 1)) + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { - THROW_HR_IF(E_FAIL, ExtractIconExW(processPath.c_str(), 0, &hIcon, nullptr /* Small Icon */, 1) == 0); - } - - return hIcon; -} - -inline std::wstring RetrieveLocalFolderPath() -{ - wil::unique_cotaskmem_string localAppDataPath; - THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0 /* flags */, nullptr /* access token handle */, &localAppDataPath)); + // Create a GUID for the COM Activator + GUID comActivatorGuid = GUID_NULL; + THROW_IF_FAILED(CoCreateGuid(&comActivatorGuid)); - // path: C:\Users\\AppData\Local\Microsoft - std::path localFolderPath{ std::wstring(localAppDataPath.get()) + Microsoft::Windows::AppNotifications::Helpers::c_localMicrosoftFolder }; - THROW_HR_IF(ERROR_FILE_NOT_FOUND, !std::exists(localFolderPath)); + // StringFromCLSID returns GUID String with braces + wil::unique_cotaskmem_string comActivatorGuidString; + THROW_IF_FAILED(StringFromCLSID(comActivatorGuid, &comActivatorGuidString)); + RegisterComServer(comActivatorGuidString); - // path: C:\Users\\AppData\Local\Microsoft\WindowsAppSDK - localFolderPath.append(Microsoft::Windows::AppNotifications::Helpers::c_localWindowsAppSDKFolder); - if (!std::exists(localFolderPath)) + registeredGuid = comActivatorGuidString.get(); + } + else { - std::create_directory(localFolderPath); + THROW_IF_FAILED(hr); } - return std::wstring(localFolderPath.c_str()); -} - -HRESULT Microsoft::Windows::AppNotifications::Helpers::RetrieveAssetsFromProcess(_Out_ Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets& assets) noexcept try -{ - wil::unique_hicon hIcon{ RetrieveIconFromProcess() }; - - std::wstring notificationAppId{ RetrieveNotificationAppId() }; - - // path: C:\Users\\AppData\Local\Microsoft\WindowsAppSDK\{AppGUID}.png - std::wstring writeToFile{ RetrieveLocalFolderPath().c_str() + c_backSlash + notificationAppId + c_pngExtension }; - Microsoft::Windows::AppNotifications::WICHelpers::WriteHIconToPngFile(hIcon, writeToFile.c_str()); - - assets.displayName = Microsoft::Windows::AppNotifications::Helpers::GetDisplayNameBasedOnProcessName(); - assets.iconFilePath = writeToFile; - - return S_OK; -} -CATCH_RETURN() - -// Do nothing. This is just a placeholder while the UDK is ingested with the proper API. -HRESULT ToastNotifications_RetrieveAssets_Stub(_Out_ Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets& /*assets*/) -{ - // THROW_HR_IF_MSG(E_UNEXPECTED, VerifyIconFileExtension(iconFilePath)); - - return E_NOTIMPL; -} - -HRESULT RetrieveDefaultAssets(_Out_ Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets& assets) -{ - assets.displayName = Microsoft::Windows::AppNotifications::Helpers::GetDisplayNameBasedOnProcessName(); - assets.iconFilePath = defaultAppNotificationIcon; - - return S_OK; -} - -HRESULT VerifyIconFileExtension(std::filesystem::path const& iconFilePath) -{ - const auto fileExtension = iconFilePath.extension(); - - std::string lowercaseFileExtension{ fileExtension.u8string() }; - - std::transform(lowercaseFileExtension.begin(), lowercaseFileExtension.end(), lowercaseFileExtension.begin(), - [](unsigned char c) { return std::tolower(c); }); - - const bool isFileExtensionSupported = - lowercaseFileExtension == ".ico" || lowercaseFileExtension == ".bmp" || lowercaseFileExtension == ".jpg" || lowercaseFileExtension == ".png"; + std::wstring notificationAppId{ Microsoft::Windows::AppNotifications::Helpers::RetrieveNotificationAppId() }; + RegisterAssets(notificationAppId, registeredGuid); - THROW_HR_IF(E_UNEXPECTED, isFileExtensionSupported); - return S_OK; + // Remove braces around the guid string + return winrt::guid(registeredGuid.substr(1, registeredGuid.size() - 2)); } void Microsoft::Windows::AppNotifications::Helpers::RegisterAssets(std::wstring const& appId, std::wstring const& clsid) @@ -344,12 +291,12 @@ void Microsoft::Windows::AppNotifications::Helpers::RegisterAssets(std::wstring // 1. From the current process. // 2. Based on the best app shortcut, using the FrameworkUdk. // 3. Use the default assets. - Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets assets{}; + Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets assets{}; - if (FAILED(ToastNotifications_RetrieveAssets_Stub(assets)) && - FAILED(RetrieveAssetsFromProcess(assets))) + if (FAILED(Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveAssetsFromShortcut(assets)) && + FAILED(Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveAssetsFromProcess(assets))) { - THROW_IF_FAILED(RetrieveDefaultAssets(assets)); + THROW_IF_FAILED(Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveDefaultAssets(assets)); } RegisterValue(hKey, L"DisplayName", reinterpret_cast(assets.displayName.c_str()), REG_EXPAND_SZ, assets.displayName.size() * sizeof(wchar_t)); @@ -357,37 +304,6 @@ void Microsoft::Windows::AppNotifications::Helpers::RegisterAssets(std::wstring RegisterValue(hKey, L"CustomActivator", reinterpret_cast(clsid.c_str()), REG_SZ, wil::guid_string_length * sizeof(wchar_t)); } -winrt::guid Microsoft::Windows::AppNotifications::Helpers::RegisterComActivatorGuidAndAssets() -{ - std::wstring registeredGuid; - auto hr = GetActivatorGuid(registeredGuid); - - if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) - { - // Create a GUID for the COM Activator - GUID comActivatorGuid = GUID_NULL; - THROW_IF_FAILED(CoCreateGuid(&comActivatorGuid)); - - // StringFromCLSID returns GUID String with braces - wil::unique_cotaskmem_string comActivatorGuidString; - THROW_IF_FAILED(StringFromCLSID(comActivatorGuid, &comActivatorGuidString)); - RegisterComServer(comActivatorGuidString); - - registeredGuid = comActivatorGuidString.get(); - } - else - { - THROW_IF_FAILED(hr); - } - - std::wstring notificationAppId{ RetrieveNotificationAppId() }; - RegisterAssets(notificationAppId, registeredGuid); - - // Remove braces around the guid string - return winrt::guid(registeredGuid.substr(1, registeredGuid.size() - 2)); - -} - wil::unique_cotaskmem_string Microsoft::Windows::AppNotifications::Helpers::ConvertUtf8StringToWideString(unsigned long length, const byte* utf8String) { int size{ MultiByteToWideChar( diff --git a/dev/AppNotifications/AppNotificationUtility.h b/dev/AppNotifications/AppNotificationUtility.h index c3671f9e0b..b00e48989d 100644 --- a/dev/AppNotifications/AppNotificationUtility.h +++ b/dev/AppNotifications/AppNotificationUtility.h @@ -16,15 +16,10 @@ namespace Microsoft::Windows::AppNotifications::Helpers const std::wstring c_quote{ LR"(")" }; const std::wstring c_backSlash{ LR"(\)" }; const std::wstring c_notificationActivatedArgument{ L" ----AppNotificationActivated:" }; - const std::wstring c_localMicrosoftFolder{ LR"(\Microsoft\)" }; - const std::wstring c_localWindowsAppSDKFolder{ LR"(WindowsAppSDK)" }; + const std::wstring c_localMicrosoftFolder{ LR"(\Microsoft)" }; + const std::wstring c_localWindowsAppSDKFolder{ LR"(\WindowsAppSDK)" }; const std::wstring c_pngExtension{ LR"(.png)" }; - struct AppNotificationAssets { - std::wstring displayName; - std::wstring iconFilePath; - }; - inline const int GUID_LENGTH = 39; // GUID + '{' + '}' + '/0' inline std::wstring ConvertPathToKey(std::wstring path) @@ -71,6 +66,4 @@ namespace Microsoft::Windows::AppNotifications::Helpers winrt::Microsoft::Windows::AppNotifications::AppNotification ToastNotificationFromToastProperties(ABI::Microsoft::Internal::ToastNotifications::INotificationProperties* properties); std::wstring GetDisplayNameBasedOnProcessName(); - - HRESULT RetrieveAssetsFromProcess(_Out_ Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets& assets) noexcept; } diff --git a/dev/AppNotifications/AppNotifications.vcxitems b/dev/AppNotifications/AppNotifications.vcxitems index d3f1e495de..63b054a559 100644 --- a/dev/AppNotifications/AppNotifications.vcxitems +++ b/dev/AppNotifications/AppNotifications.vcxitems @@ -23,7 +23,7 @@ - + @@ -33,7 +33,7 @@ - + diff --git a/dev/AppNotifications/WICUtility.cpp b/dev/AppNotifications/ShellLocalization.cpp similarity index 50% rename from dev/AppNotifications/WICUtility.cpp rename to dev/AppNotifications/ShellLocalization.cpp index 267fed6754..4f1ec89f2a 100644 --- a/dev/AppNotifications/WICUtility.cpp +++ b/dev/AppNotifications/ShellLocalization.cpp @@ -1,10 +1,13 @@ - -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" -#include "WICUtility.h" + +#include +#include + #include +#include #include #include #include @@ -14,10 +17,15 @@ #include #include +namespace std +{ + using namespace std::filesystem; +} + typedef enum { - BMPV_1, - BMPV_5, + VERSION1, + VERSION5, } BITMAP_VERSION; winrt::com_ptr ConvertWICBitmapPixelFormat( @@ -42,7 +50,7 @@ void AddFrameToWICBitmap( winrt::com_ptr const& wicImagingFactory, winrt::com_ptr const& wicEncoder, winrt::com_ptr const& wicBitmapSource, - BITMAP_VERSION bmpv) + BITMAP_VERSION bitmapVersion) { winrt::com_ptr wicFrameEncoder; winrt::com_ptr wicEncoderOptions; @@ -52,7 +60,7 @@ void AddFrameToWICBitmap( GUID containerGuid; THROW_IF_FAILED(wicEncoder->GetContainerFormat(&containerGuid)); - if ((containerGuid == GUID_ContainerFormatBmp) && (bmpv == BMPV_5)) + if ((containerGuid == GUID_ContainerFormatBmp) && (bitmapVersion == BITMAP_VERSION::VERSION5)) { // Write the encoder option to the property bag instance. VARIANT varValue{}; @@ -76,10 +84,9 @@ void AddFrameToWICBitmap( THROW_IF_FAILED(wicFrameEncoder->SetSize(uWidth, uHeight)); - winrt::com_ptr wicbitmapSourceConverted; - wicbitmapSourceConverted = ConvertWICBitmapPixelFormat(wicImagingFactory, wicBitmapSource, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone); + winrt::com_ptr wicbitmapSourceConverted = ConvertWICBitmapPixelFormat(wicImagingFactory, wicBitmapSource, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone); - WICRect rect { 0, 0, static_cast(uWidth), static_cast(uHeight) }; + WICRect rect{ 0 /* x-coordinate */, 0 /* y-coordinate */, static_cast(uWidth), static_cast(uHeight) }; // Write the image data and commit THROW_IF_FAILED(wicFrameEncoder->WriteSource(wicbitmapSourceConverted.get(), &rect)); @@ -90,8 +97,8 @@ void AddFrameToWICBitmap( winrt::com_ptr GetStreamOfWICBitmapSource( winrt::com_ptr const& wicImagingFactory, winrt::com_ptr const& wicBitmapSource, - _In_ REFGUID guidContainerFormat, - _In_ BITMAP_VERSION bmpv) + REFGUID guidContainerFormat, + BITMAP_VERSION bitmapVersion) { winrt::com_ptr spImageStream; THROW_IF_FAILED(CreateStreamOnHGlobal(nullptr /* handle */, true /* delete on release */, spImageStream.put())); @@ -103,7 +110,7 @@ winrt::com_ptr GetStreamOfWICBitmapSource( THROW_IF_FAILED(wicEncoder->Initialize(spImageStream.get(), WICBitmapEncoderCacheOption::WICBitmapEncoderNoCache)); // Add a single frame to the encoder with the Bitmap - AddFrameToWICBitmap(wicImagingFactory, wicEncoder, wicBitmapSource, bmpv); + AddFrameToWICBitmap(wicImagingFactory, wicEncoder, wicBitmapSource, bitmapVersion); THROW_IF_FAILED(wicEncoder->Commit()); @@ -120,21 +127,20 @@ void SaveImageWithWIC( _In_ REFGUID guidContainerFormat, winrt::com_ptr& pStream) { - winrt::com_ptr spImageStream; - spImageStream = GetStreamOfWICBitmapSource(wicImagingFactory, wicBitmapSource, guidContainerFormat, BMPV_1); + winrt::com_ptr spImageStream = GetStreamOfWICBitmapSource(wicImagingFactory, wicBitmapSource, guidContainerFormat, BITMAP_VERSION::VERSION1); // Seek the stream to the beginning and transfer - static LARGE_INTEGER const lnBeginning{ 0 }; + LARGE_INTEGER const lnBeginning{ 0 }; THROW_IF_FAILED(spImageStream->Seek(lnBeginning, STREAM_SEEK_SET, nullptr /* new seek pointer */)); - static ULARGE_INTEGER lnbuffer{ INT_MAX }; + ULARGE_INTEGER lnbuffer{ INT_MAX }; THROW_IF_FAILED(spImageStream->CopyTo(pStream.get(), lnbuffer, nullptr /* pointer to number of bytes read */, nullptr /* pointer to number of bytes written */)); } -void Microsoft::Windows::AppNotifications::WICHelpers::WriteHIconToPngFile(wil::unique_hicon const& hIcon, _In_ PCWSTR pszFileName) +void WriteHIconToPngFile(wil::unique_hicon const& hIcon, _In_ PCWSTR pszFileName) { auto wicImagingFactory{ winrt::create_instance(CLSID_WICImagingFactory, CLSCTX_INPROC_SERVER) }; - + winrt::com_ptr wicBitmap; THROW_IF_FAILED(wicImagingFactory->CreateBitmapFromHICON(hIcon.get(), wicBitmap.put())); @@ -158,8 +164,104 @@ void Microsoft::Windows::AppNotifications::WICHelpers::WriteHIconToPngFile(wil:: THROW_IF_FAILED(spStreamOut->SetSize(statstg.cbSize)); - // TODO: Comments need to be added THROW_IF_FAILED(spStream->CopyTo(spStreamOut.get(), statstg.cbSize, nullptr /* pointer to number of bytes read */, nullptr /* pointer to number of bytes written */)); THROW_IF_FAILED(spStreamOut->Commit(STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE)); } + +HRESULT IsIconFileExtensionSupported(std::filesystem::path const& iconFilePath) +{ + const auto fileExtension = iconFilePath.extension(); + + std::string lowercaseFileExtension{ fileExtension.u8string() }; + + std::transform(lowercaseFileExtension.begin(), lowercaseFileExtension.end(), lowercaseFileExtension.begin(), + [](unsigned char c) { return std::tolower(c); }); + + const bool isFileExtensionSupported = + lowercaseFileExtension == ".ico" || lowercaseFileExtension == ".bmp" || lowercaseFileExtension == ".jpg" || lowercaseFileExtension == ".png"; + + THROW_HR_IF(E_UNEXPECTED, isFileExtensionSupported); + return S_OK; +} + +inline std::wstring RetrieveLocalFolderPath() +{ + wil::unique_cotaskmem_string localAppDataPath; + THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0 /* flags */, nullptr /* access token handle */, &localAppDataPath)); + + // path: C:\Users\\AppData\Local\Microsoft\WindowsAppSDK + std::path localFolderPath{ std::wstring(localAppDataPath.get()) + + Microsoft::Windows::AppNotifications::Helpers::c_localMicrosoftFolder + + Microsoft::Windows::AppNotifications::Helpers::c_localWindowsAppSDKFolder }; + + if (!std::exists(localFolderPath)) + { + std::create_directory(localFolderPath); + } + + return std::wstring(localFolderPath.c_str()); +} + +inline wil::unique_hicon RetrieveIconFromProcess() +{ + std::wstring processPath{}; + THROW_IF_FAILED(wil::GetModuleFileNameExW(GetCurrentProcess(), nullptr, processPath)); + + // Extract the small icon from the first .ico, if failed extract the large icon. + // Small icon is good enough for an App Notification icon since higher quality doesn't really impact in a substantial way. + wil::unique_hicon hIcon{}; + if (!ExtractIconExW(processPath.c_str(), 0 /* index */, nullptr /* Large icon */, &hIcon, 1)) + { + THROW_HR_IF(E_FAIL, ExtractIconExW(processPath.c_str(), 0, &hIcon, nullptr /* Small Icon */, 1) == 0); + } + + return hIcon; +} + +HRESULT Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveAssetsFromProcess(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& assets) noexcept try +{ + wil::unique_hicon hIcon{ RetrieveIconFromProcess() }; + + std::wstring notificationAppId{ Microsoft::Windows::AppNotifications::Helpers::RetrieveNotificationAppId() }; + + // path: C:\Users\\AppData\Local\Microsoft\WindowsAppSDK\{AppGUID}.png + std::wstring iconFilePath{ RetrieveLocalFolderPath().c_str() + c_backSlash + notificationAppId + c_pngExtension }; + WriteHIconToPngFile(hIcon, iconFilePath.c_str()); + + assets.displayName = Microsoft::Windows::AppNotifications::Helpers::GetDisplayNameBasedOnProcessName(); + assets.iconFilePath = iconFilePath; + + // TODO: Clean up the icon file in case UnregisterAll wasn't called. + + return S_OK; +} +CATCH_RETURN() + +// Do nothing. This is just a placeholder while the UDK is ingested with the proper API. +HRESULT Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveAssetsFromShortcut(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& /*assets*/) +{ + // THROW_HR_IF_MSG(E_UNEXPECTED, IsIconFileExtensionSupported(iconFilePath)); + + return E_NOTIMPL; +} + +HRESULT Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveDefaultAssets(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& assets) noexcept try +{ + assets.displayName = Microsoft::Windows::AppNotifications::Helpers::GetDisplayNameBasedOnProcessName(); + assets.iconFilePath = L""; // No icon filepath for now. This is coming in a future FrameworkUdk ingestion. + + return S_OK; +} +CATCH_RETURN() + +void Microsoft::Windows::AppNotifications::ShellLocalization::DeleteIconFromCache() noexcept try +{ + std::wstring notificationAppId{ Microsoft::Windows::AppNotifications::Helpers::RetrieveNotificationAppId() }; + + // path: C:\Users\\AppData\Local\Microsoft\WindowsAppSDK\{AppGUID}.png + std::wstring iconFilePath{ RetrieveLocalFolderPath().c_str() + c_backSlash + notificationAppId + c_pngExtension }; + + THROW_IF_WIN32_BOOL_FALSE(DeleteFile(iconFilePath.c_str())); +} +CATCH_LOG() diff --git a/dev/AppNotifications/ShellLocalization.h b/dev/AppNotifications/ShellLocalization.h new file mode 100644 index 0000000000..562b7d5a60 --- /dev/null +++ b/dev/AppNotifications/ShellLocalization.h @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#pragma once + +#include "pch.h" + +// In this file we define methods that write/return Shell assets, like DisplayName and icon URI. + +namespace Microsoft::Windows::AppNotifications::ShellLocalization +{ + const std::wstring c_appIdentifierPath{ LR"(Software\Classes\AppUserModelId\)" }; + const std::wstring c_quote{ LR"(")" }; + const std::wstring c_backSlash{ LR"(\)" }; + const std::wstring c_localMicrosoftFolder{ LR"(\Microsoft)" }; + const std::wstring c_localWindowsAppSDKFolder{ LR"(\WindowsAppSDK)" }; + const std::wstring c_pngExtension{ LR"(.png)" }; + + struct AppNotificationAssets { + std::wstring displayName; + std::wstring iconFilePath; + }; + + HRESULT RetrieveAssetsFromProcess(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& assets) noexcept; + + HRESULT RetrieveAssetsFromShortcut(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& /*assets*/); + + HRESULT RetrieveDefaultAssets(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& assets) noexcept; + + void DeleteIconFromCache() noexcept; +} diff --git a/dev/AppNotifications/WICUtility.h b/dev/AppNotifications/WICUtility.h deleted file mode 100644 index 89877cfe47..0000000000 --- a/dev/AppNotifications/WICUtility.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -#pragma once - -#include "pch.h" - -namespace Microsoft::Windows::AppNotifications::WICHelpers -{ - void WriteHIconToPngFile(wil::unique_hicon const& hIcon, _In_ PCWSTR pszFileName); -} - From d0e86566fa05da0e9aa2a76e1bd5c17cf895ac6d Mon Sep 17 00:00:00 2001 From: Daniel Ayala <14967941+danielayala94@users.noreply.github.com> Date: Thu, 31 Mar 2022 16:57:17 -0700 Subject: [PATCH 07/11] Addressed new round of comments --- .../AppNotificationUtility.cpp | 1 - dev/AppNotifications/ShellLocalization.cpp | 22 +++++++++---------- .../ToastNotificationsDemoApp.vcxproj | 2 +- .../ToastNotificationsDemoApp.vcxproj.filters | 2 +- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/dev/AppNotifications/AppNotificationUtility.cpp b/dev/AppNotifications/AppNotificationUtility.cpp index f9577ca6b5..103d17ecc2 100644 --- a/dev/AppNotifications/AppNotificationUtility.cpp +++ b/dev/AppNotifications/AppNotificationUtility.cpp @@ -244,7 +244,6 @@ winrt::guid Microsoft::Windows::AppNotifications::Helpers::RegisterComActivatorG std::wstring registeredGuid; auto hr = GetActivatorGuid(registeredGuid); - wil::unique_hicon hIcon{}; if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { // Create a GUID for the COM Activator diff --git a/dev/AppNotifications/ShellLocalization.cpp b/dev/AppNotifications/ShellLocalization.cpp index 4f1ec89f2a..6dfdf727fb 100644 --- a/dev/AppNotifications/ShellLocalization.cpp +++ b/dev/AppNotifications/ShellLocalization.cpp @@ -64,33 +64,31 @@ void AddFrameToWICBitmap( { // Write the encoder option to the property bag instance. VARIANT varValue{}; - varValue.vt = VT_BOOL; varValue.boolVal = VARIANT_TRUE; // Options to enable the v5 header support for 32bppBGRA. PROPBAG2 v5HeaderOption{}; - - std::wstring str = L"EnableV5Header32bppBGRA"; - v5HeaderOption.pstrName = (LPOLESTR)str.c_str(); + v5HeaderOption.pstrName = (LPOLESTR) L"EnableV5Header32bppBGRA"; THROW_IF_FAILED(wicEncoderOptions->Write(1, &v5HeaderOption, &varValue)); } THROW_IF_FAILED(wicFrameEncoder->Initialize(wicEncoderOptions.get())); - UINT uWidth, uHeight; - THROW_IF_FAILED(wicBitmapSource->GetSize(&uWidth, &uHeight)); + UINT width{}; + UINT height{}; + THROW_IF_FAILED(wicBitmapSource->GetSize(&width, &height)); - THROW_IF_FAILED(wicFrameEncoder->SetSize(uWidth, uHeight)); + THROW_IF_FAILED(wicFrameEncoder->SetSize(width, height)); - winrt::com_ptr wicbitmapSourceConverted = ConvertWICBitmapPixelFormat(wicImagingFactory, wicBitmapSource, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone); + winrt::com_ptr wicbitmapSourceConverted + { ConvertWICBitmapPixelFormat(wicImagingFactory, wicBitmapSource, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone) }; - WICRect rect{ 0 /* x-coordinate */, 0 /* y-coordinate */, static_cast(uWidth), static_cast(uHeight) }; + WICRect rect{ 0 /* x-coordinate */, 0 /* y-coordinate */, static_cast(width), static_cast(height) }; // Write the image data and commit THROW_IF_FAILED(wicFrameEncoder->WriteSource(wicbitmapSourceConverted.get(), &rect)); - THROW_IF_FAILED(wicFrameEncoder->Commit()); } @@ -124,7 +122,7 @@ winrt::com_ptr GetStreamOfWICBitmapSource( void SaveImageWithWIC( winrt::com_ptr const& wicImagingFactory, winrt::com_ptr const& wicBitmapSource, - _In_ REFGUID guidContainerFormat, + GUID const& guidContainerFormat, winrt::com_ptr& pStream) { winrt::com_ptr spImageStream = GetStreamOfWICBitmapSource(wicImagingFactory, wicBitmapSource, guidContainerFormat, BITMAP_VERSION::VERSION1); @@ -157,7 +155,7 @@ void WriteHIconToPngFile(wil::unique_hicon const& hIcon, _In_ PCWSTR pszFileName winrt::com_ptr spStreamOut; THROW_IF_FAILED(SHCreateStreamOnFileEx(pszFileName, STGM_CREATE | STGM_WRITE | STGM_SHARE_DENY_WRITE, FILE_ATTRIBUTE_NORMAL, TRUE, nullptr, spStreamOut.put())); - STATSTG statstg; + STATSTG statstg{}; THROW_IF_FAILED(spStream->Stat(&statstg, STATFLAG_NONAME)); THROW_IF_FAILED(IStream_Reset(spStream.get())); diff --git a/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.vcxproj b/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.vcxproj index a503352b2b..978fb56c26 100644 --- a/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.vcxproj +++ b/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.vcxproj @@ -292,4 +292,4 @@ - \ No newline at end of file + diff --git a/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.vcxproj.filters b/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.vcxproj.filters index f8de0a3b86..38477c94c8 100644 --- a/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.vcxproj.filters +++ b/test/TestApps/ToastNotificationsDemoApp/ToastNotificationsDemoApp.vcxproj.filters @@ -43,4 +43,4 @@ Resource Files - \ No newline at end of file + From 2538356656b1cff1d90c6ccccb2c868d0ab33709 Mon Sep 17 00:00:00 2001 From: Daniel Ayala <14967941+danielayala94@users.noreply.github.com> Date: Fri, 1 Apr 2022 10:11:18 -0700 Subject: [PATCH 08/11] Fixes related to DeleteIconFromCache --- .../AppNotificationManager.cpp | 8 +++++--- dev/AppNotifications/ShellLocalization.cpp | 19 +++++++++++++------ dev/AppNotifications/ShellLocalization.h | 4 ++-- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/dev/AppNotifications/AppNotificationManager.cpp b/dev/AppNotifications/AppNotificationManager.cpp index c367e4c623..5f4430b534 100644 --- a/dev/AppNotifications/AppNotificationManager.cpp +++ b/dev/AppNotifications/AppNotificationManager.cpp @@ -222,15 +222,17 @@ namespace winrt::Microsoft::Windows::AppNotifications::implementation if (!AppModel::Identity::IsPackagedProcess()) { + // If the app icon was inferred from process, then we should clean it up. + // Do not fail this function if such a file doesn't exist. + winrt::hresult deleteIconResult{ DeleteIconFromCache() }; + THROW_HR_IF(deleteIconResult, FAILED(deleteIconResult) && deleteIconResult != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); + std::wstring storedComActivatorString; THROW_IF_FAILED(GetActivatorGuid(storedComActivatorString)); UnRegisterComServer(storedComActivatorString); UnRegisterNotificationAppIdentifierFromRegistry(); THROW_IF_FAILED(PushNotifications_UnregisterFullTrustApplication(m_appId.c_str())); - - // If the app icon was inferred from process, then we should clean it up. - DeleteIconFromCache(); } } catch (...) diff --git a/dev/AppNotifications/ShellLocalization.cpp b/dev/AppNotifications/ShellLocalization.cpp index 6dfdf727fb..bc4e6543ab 100644 --- a/dev/AppNotifications/ShellLocalization.cpp +++ b/dev/AppNotifications/ShellLocalization.cpp @@ -174,7 +174,7 @@ HRESULT IsIconFileExtensionSupported(std::filesystem::path const& iconFilePath) std::string lowercaseFileExtension{ fileExtension.u8string() }; std::transform(lowercaseFileExtension.begin(), lowercaseFileExtension.end(), lowercaseFileExtension.begin(), - [](unsigned char c) { return std::tolower(c); }); + [](unsigned char c) { return static_cast(std::tolower(c)); }); const bool isFileExtensionSupported = lowercaseFileExtension == ".ico" || lowercaseFileExtension == ".bmp" || lowercaseFileExtension == ".jpg" || lowercaseFileExtension == ".png"; @@ -230,14 +230,15 @@ HRESULT Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveAssetsF assets.displayName = Microsoft::Windows::AppNotifications::Helpers::GetDisplayNameBasedOnProcessName(); assets.iconFilePath = iconFilePath; - // TODO: Clean up the icon file in case UnregisterAll wasn't called. + // TODO: In case we are caching icons from uninstalled apps who didn't call UnregisterAll, + // we should make an effort to clean them up in an opportunistic way. return S_OK; } CATCH_RETURN() // Do nothing. This is just a placeholder while the UDK is ingested with the proper API. -HRESULT Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveAssetsFromShortcut(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& /*assets*/) +HRESULT Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveAssetsFromShortcut(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& /*assets*/) noexcept { // THROW_HR_IF_MSG(E_UNEXPECTED, IsIconFileExtensionSupported(iconFilePath)); @@ -253,13 +254,19 @@ HRESULT Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveDefault } CATCH_RETURN() -void Microsoft::Windows::AppNotifications::ShellLocalization::DeleteIconFromCache() noexcept try +HRESULT Microsoft::Windows::AppNotifications::ShellLocalization::DeleteIconFromCache() noexcept try { std::wstring notificationAppId{ Microsoft::Windows::AppNotifications::Helpers::RetrieveNotificationAppId() }; // path: C:\Users\\AppData\Local\Microsoft\WindowsAppSDK\{AppGUID}.png std::wstring iconFilePath{ RetrieveLocalFolderPath().c_str() + c_backSlash + notificationAppId + c_pngExtension }; - THROW_IF_WIN32_BOOL_FALSE(DeleteFile(iconFilePath.c_str())); + // If DeleteFile returned FALSE, then deletion failed and we should return the corresponding error code. + if (DeleteFile(iconFilePath.c_str()) == FALSE) + { + THROW_HR(HRESULT_FROM_WIN32(GetLastError())); + } + + return S_OK; } -CATCH_LOG() +CATCH_RETURN() diff --git a/dev/AppNotifications/ShellLocalization.h b/dev/AppNotifications/ShellLocalization.h index 562b7d5a60..5819f820c4 100644 --- a/dev/AppNotifications/ShellLocalization.h +++ b/dev/AppNotifications/ShellLocalization.h @@ -23,9 +23,9 @@ namespace Microsoft::Windows::AppNotifications::ShellLocalization HRESULT RetrieveAssetsFromProcess(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& assets) noexcept; - HRESULT RetrieveAssetsFromShortcut(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& /*assets*/); + HRESULT RetrieveAssetsFromShortcut(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& assets) noexcept; HRESULT RetrieveDefaultAssets(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& assets) noexcept; - void DeleteIconFromCache() noexcept; + HRESULT DeleteIconFromCache() noexcept; } From 821b5795227f586cffedc6f6c9705663f8132217 Mon Sep 17 00:00:00 2001 From: Sharath Manchala <10109130+sharath2727@users.noreply.github.com> Date: Fri, 1 Apr 2022 12:37:05 -0700 Subject: [PATCH 09/11] Address comments --- dev/AppNotifications/ShellLocalization.cpp | 10 +++------- test/TestApps/ToastNotificationsDemoApp/main.cpp | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/dev/AppNotifications/ShellLocalization.cpp b/dev/AppNotifications/ShellLocalization.cpp index bc4e6543ab..57c7fa4f5d 100644 --- a/dev/AppNotifications/ShellLocalization.cpp +++ b/dev/AppNotifications/ShellLocalization.cpp @@ -113,7 +113,7 @@ winrt::com_ptr GetStreamOfWICBitmapSource( THROW_IF_FAILED(wicEncoder->Commit()); // Seek the stream to the beginning and transfer - static const LARGE_INTEGER lnBeginning = {}; + const LARGE_INTEGER lnBeginning{}; THROW_IF_FAILED(spImageStream->Seek(lnBeginning, STREAM_SEEK_SET, nullptr /* new seek pointer */)); return spImageStream; @@ -206,13 +206,9 @@ inline wil::unique_hicon RetrieveIconFromProcess() std::wstring processPath{}; THROW_IF_FAILED(wil::GetModuleFileNameExW(GetCurrentProcess(), nullptr, processPath)); - // Extract the small icon from the first .ico, if failed extract the large icon. - // Small icon is good enough for an App Notification icon since higher quality doesn't really impact in a substantial way. + // Extract the Small icon as it is good enough for an App Notification icon since higher quality doesn't really impact in a substantial way. wil::unique_hicon hIcon{}; - if (!ExtractIconExW(processPath.c_str(), 0 /* index */, nullptr /* Large icon */, &hIcon, 1)) - { - THROW_HR_IF(E_FAIL, ExtractIconExW(processPath.c_str(), 0, &hIcon, nullptr /* Small Icon */, 1) == 0); - } + THROW_HR_IF(E_FAIL, ExtractIconExW(processPath.c_str(), 0 /* index for first resource */, nullptr /* Large Icon */, &hIcon, 1) == 0); return hIcon; } diff --git a/test/TestApps/ToastNotificationsDemoApp/main.cpp b/test/TestApps/ToastNotificationsDemoApp/main.cpp index b44277eef9..bbe966454a 100644 --- a/test/TestApps/ToastNotificationsDemoApp/main.cpp +++ b/test/TestApps/ToastNotificationsDemoApp/main.cpp @@ -219,7 +219,7 @@ int main() std::cin.ignore(); // Call Unregister so that COM can launch a new process for ToastInvokes after we terminate this process. - appNotificationManager.Unregister(); + appNotificationManager.UnregisterAll(); if (!isPackaged) { MddBootstrapShutdown(); From 121c4e8f3ce6f03acabbe7bb089d30dbdce0984b Mon Sep 17 00:00:00 2001 From: Daniel Ayala <14967941+danielayala94@users.noreply.github.com> Date: Fri, 1 Apr 2022 13:21:10 -0700 Subject: [PATCH 10/11] Optimizations and other good practices --- .../AppNotificationManager.cpp | 3 +- .../AppNotificationUtility.cpp | 20 +++++--- dev/AppNotifications/AppNotificationUtility.h | 12 ++--- dev/AppNotifications/ShellLocalization.cpp | 49 ++++++++----------- dev/AppNotifications/ShellLocalization.h | 11 ++--- .../ToastNotificationsDemoApp/main.cpp | 2 +- 6 files changed, 44 insertions(+), 53 deletions(-) diff --git a/dev/AppNotifications/AppNotificationManager.cpp b/dev/AppNotifications/AppNotificationManager.cpp index 5f4430b534..5915e63c28 100644 --- a/dev/AppNotifications/AppNotificationManager.cpp +++ b/dev/AppNotifications/AppNotificationManager.cpp @@ -223,7 +223,8 @@ namespace winrt::Microsoft::Windows::AppNotifications::implementation if (!AppModel::Identity::IsPackagedProcess()) { // If the app icon was inferred from process, then we should clean it up. - // Do not fail this function if such a file doesn't exist. + // Do not fail this function if such a file doesn't exist, + // which is the case if the icon was retrieved from shortcut or there is no IconUri in registry. winrt::hresult deleteIconResult{ DeleteIconFromCache() }; THROW_HR_IF(deleteIconResult, FAILED(deleteIconResult) && deleteIconResult != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); diff --git a/dev/AppNotifications/AppNotificationUtility.cpp b/dev/AppNotifications/AppNotificationUtility.cpp index 103d17ecc2..05fbd0b8e6 100644 --- a/dev/AppNotifications/AppNotificationUtility.cpp +++ b/dev/AppNotifications/AppNotificationUtility.cpp @@ -113,7 +113,9 @@ void Microsoft::Windows::AppNotifications::Helpers::RegisterComServer(wil::uniqu { wil::unique_hkey hKey; //subKey: Software\Classes\CLSID\{comActivatorGuidString}\LocalServer32 - std::wstring subKey{ c_clsIdPath + clsid.get() + LR"(\LocalServer32)" }; + std::wstring subKey{ c_clsIdPath }; + subKey.append(clsid.get()); + subKey.append(LR"(\LocalServer32)"); THROW_IF_WIN32_ERROR(RegCreateKeyEx( HKEY_CURRENT_USER, @@ -287,19 +289,25 @@ void Microsoft::Windows::AppNotifications::Helpers::RegisterAssets(std::wstring nullptr /* lpdwDisposition */)); // Try the following techniques to retrieve display name and icon: - // 1. From the current process. - // 2. Based on the best app shortcut, using the FrameworkUdk. - // 3. Use the default assets. + // 1. Based on the best app shortcut, using the FrameworkUdk. + // 2. From the current process. + // 3. Set a default DisplayName, but leave empty the icon file path so Shell can set a default icon. Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets assets{}; if (FAILED(Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveAssetsFromShortcut(assets)) && FAILED(Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveAssetsFromProcess(assets))) { - THROW_IF_FAILED(Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveDefaultAssets(assets)); + assets.displayName = GetDisplayNameBasedOnProcessName(); } RegisterValue(hKey, L"DisplayName", reinterpret_cast(assets.displayName.c_str()), REG_EXPAND_SZ, assets.displayName.size() * sizeof(wchar_t)); - RegisterValue(hKey, L"IconUri", reinterpret_cast(assets.iconFilePath.c_str()), REG_EXPAND_SZ, assets.iconFilePath.size() * sizeof(wchar_t)); + + // If IconUri is empty, then Shell will render a default icon in AppNotifications. + if (!assets.iconFilePath.empty()) + { + RegisterValue(hKey, L"IconUri", reinterpret_cast(assets.iconFilePath.c_str()), REG_EXPAND_SZ, assets.iconFilePath.size() * sizeof(wchar_t)); + } + RegisterValue(hKey, L"CustomActivator", reinterpret_cast(clsid.c_str()), REG_SZ, wil::guid_string_length * sizeof(wchar_t)); } diff --git a/dev/AppNotifications/AppNotificationUtility.h b/dev/AppNotifications/AppNotificationUtility.h index b00e48989d..e5a3537506 100644 --- a/dev/AppNotifications/AppNotificationUtility.h +++ b/dev/AppNotifications/AppNotificationUtility.h @@ -11,14 +11,10 @@ namespace Microsoft::Windows::AppNotifications::Helpers { - const std::wstring c_appIdentifierPath{ LR"(Software\Classes\AppUserModelId\)" }; - const std::wstring c_clsIdPath{ LR"(Software\Classes\CLSID\)" }; - const std::wstring c_quote{ LR"(")" }; - const std::wstring c_backSlash{ LR"(\)" }; - const std::wstring c_notificationActivatedArgument{ L" ----AppNotificationActivated:" }; - const std::wstring c_localMicrosoftFolder{ LR"(\Microsoft)" }; - const std::wstring c_localWindowsAppSDKFolder{ LR"(\WindowsAppSDK)" }; - const std::wstring c_pngExtension{ LR"(.png)" }; + const PCWSTR c_appIdentifierPath{ LR"(Software\Classes\AppUserModelId\)" }; + const PCWSTR c_clsIdPath{ LR"(Software\Classes\CLSID\)" }; + const PCWSTR c_quote{ LR"(")" }; + const PCWSTR c_notificationActivatedArgument{ L" ----AppNotificationActivated:" }; inline const int GUID_LENGTH = 39; // GUID + '{' + '}' + '/0' diff --git a/dev/AppNotifications/ShellLocalization.cpp b/dev/AppNotifications/ShellLocalization.cpp index 57c7fa4f5d..0bf96eccec 100644 --- a/dev/AppNotifications/ShellLocalization.cpp +++ b/dev/AppNotifications/ShellLocalization.cpp @@ -104,12 +104,10 @@ winrt::com_ptr GetStreamOfWICBitmapSource( // Create encoder and initialize it winrt::com_ptr wicEncoder; THROW_IF_FAILED(wicImagingFactory->CreateEncoder(guidContainerFormat, nullptr, wicEncoder.put())); - THROW_IF_FAILED(wicEncoder->Initialize(spImageStream.get(), WICBitmapEncoderCacheOption::WICBitmapEncoderNoCache)); // Add a single frame to the encoder with the Bitmap AddFrameToWICBitmap(wicImagingFactory, wicEncoder, wicBitmapSource, bitmapVersion); - THROW_IF_FAILED(wicEncoder->Commit()); // Seek the stream to the beginning and transfer @@ -167,38 +165,39 @@ void WriteHIconToPngFile(wil::unique_hicon const& hIcon, _In_ PCWSTR pszFileName THROW_IF_FAILED(spStreamOut->Commit(STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE)); } -HRESULT IsIconFileExtensionSupported(std::filesystem::path const& iconFilePath) +bool IsIconFileExtensionSupported(std::filesystem::path const& iconFilePath) { - const auto fileExtension = iconFilePath.extension(); - - std::string lowercaseFileExtension{ fileExtension.u8string() }; - - std::transform(lowercaseFileExtension.begin(), lowercaseFileExtension.end(), lowercaseFileExtension.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); + static PCWSTR c_supportedExtensions[]{ L".bmp", L".ico", L".jpg", L".png" }; - const bool isFileExtensionSupported = - lowercaseFileExtension == ".ico" || lowercaseFileExtension == ".bmp" || lowercaseFileExtension == ".jpg" || lowercaseFileExtension == ".png"; + const auto fileExtension{ iconFilePath.extension() }; + const auto extension{ fileExtension.c_str() }; + for (auto supportedExtension : c_supportedExtensions) + { + if (CompareStringOrdinal(extension, -1, supportedExtension, -1, TRUE) == CSTR_EQUAL) + { + return true; + } + } - THROW_HR_IF(E_UNEXPECTED, isFileExtensionSupported); - return S_OK; + return false; } -inline std::wstring RetrieveLocalFolderPath() +inline std::path RetrieveLocalFolderPath() { wil::unique_cotaskmem_string localAppDataPath; THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0 /* flags */, nullptr /* access token handle */, &localAppDataPath)); // path: C:\Users\\AppData\Local\Microsoft\WindowsAppSDK std::path localFolderPath{ std::wstring(localAppDataPath.get()) + - Microsoft::Windows::AppNotifications::Helpers::c_localMicrosoftFolder + - Microsoft::Windows::AppNotifications::Helpers::c_localWindowsAppSDKFolder }; + Microsoft::Windows::AppNotifications::ShellLocalization::c_localMicrosoftFolder + + Microsoft::Windows::AppNotifications::ShellLocalization::c_localWindowsAppSDKFolder }; if (!std::exists(localFolderPath)) { std::create_directory(localFolderPath); } - return std::wstring(localFolderPath.c_str()); + return localFolderPath; } inline wil::unique_hicon RetrieveIconFromProcess() @@ -220,7 +219,7 @@ HRESULT Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveAssetsF std::wstring notificationAppId{ Microsoft::Windows::AppNotifications::Helpers::RetrieveNotificationAppId() }; // path: C:\Users\\AppData\Local\Microsoft\WindowsAppSDK\{AppGUID}.png - std::wstring iconFilePath{ RetrieveLocalFolderPath().c_str() + c_backSlash + notificationAppId + c_pngExtension }; + auto iconFilePath{ RetrieveLocalFolderPath() / (notificationAppId + c_pngExtension) }; WriteHIconToPngFile(hIcon, iconFilePath.c_str()); assets.displayName = Microsoft::Windows::AppNotifications::Helpers::GetDisplayNameBasedOnProcessName(); @@ -233,29 +232,21 @@ HRESULT Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveAssetsF } CATCH_RETURN() -// Do nothing. This is just a placeholder while the UDK is ingested with the proper API. HRESULT Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveAssetsFromShortcut(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& /*assets*/) noexcept { + // Do nothing for now. This is just a placeholder while we wait for the FrameworkUdk API + // to get icon file path from shortcut. This API is already implemented but not ready for consumption. // THROW_HR_IF_MSG(E_UNEXPECTED, IsIconFileExtensionSupported(iconFilePath)); return E_NOTIMPL; } -HRESULT Microsoft::Windows::AppNotifications::ShellLocalization::RetrieveDefaultAssets(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& assets) noexcept try -{ - assets.displayName = Microsoft::Windows::AppNotifications::Helpers::GetDisplayNameBasedOnProcessName(); - assets.iconFilePath = L""; // No icon filepath for now. This is coming in a future FrameworkUdk ingestion. - - return S_OK; -} -CATCH_RETURN() - HRESULT Microsoft::Windows::AppNotifications::ShellLocalization::DeleteIconFromCache() noexcept try { std::wstring notificationAppId{ Microsoft::Windows::AppNotifications::Helpers::RetrieveNotificationAppId() }; // path: C:\Users\\AppData\Local\Microsoft\WindowsAppSDK\{AppGUID}.png - std::wstring iconFilePath{ RetrieveLocalFolderPath().c_str() + c_backSlash + notificationAppId + c_pngExtension }; + std::path iconFilePath{ RetrieveLocalFolderPath() / (notificationAppId + c_pngExtension) }; // If DeleteFile returned FALSE, then deletion failed and we should return the corresponding error code. if (DeleteFile(iconFilePath.c_str()) == FALSE) diff --git a/dev/AppNotifications/ShellLocalization.h b/dev/AppNotifications/ShellLocalization.h index 5819f820c4..39add29dbc 100644 --- a/dev/AppNotifications/ShellLocalization.h +++ b/dev/AppNotifications/ShellLocalization.h @@ -9,12 +9,9 @@ namespace Microsoft::Windows::AppNotifications::ShellLocalization { - const std::wstring c_appIdentifierPath{ LR"(Software\Classes\AppUserModelId\)" }; - const std::wstring c_quote{ LR"(")" }; - const std::wstring c_backSlash{ LR"(\)" }; - const std::wstring c_localMicrosoftFolder{ LR"(\Microsoft)" }; - const std::wstring c_localWindowsAppSDKFolder{ LR"(\WindowsAppSDK)" }; - const std::wstring c_pngExtension{ LR"(.png)" }; + const PCWSTR c_localMicrosoftFolder{ LR"(Microsoft)" }; + const PCWSTR c_localWindowsAppSDKFolder{ LR"(WindowsAppSDK)" }; + const PCWSTR c_pngExtension{ LR"(.png)" }; struct AppNotificationAssets { std::wstring displayName; @@ -25,7 +22,5 @@ namespace Microsoft::Windows::AppNotifications::ShellLocalization HRESULT RetrieveAssetsFromShortcut(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& assets) noexcept; - HRESULT RetrieveDefaultAssets(_Out_ Microsoft::Windows::AppNotifications::ShellLocalization::AppNotificationAssets& assets) noexcept; - HRESULT DeleteIconFromCache() noexcept; } diff --git a/test/TestApps/ToastNotificationsDemoApp/main.cpp b/test/TestApps/ToastNotificationsDemoApp/main.cpp index bbe966454a..b44277eef9 100644 --- a/test/TestApps/ToastNotificationsDemoApp/main.cpp +++ b/test/TestApps/ToastNotificationsDemoApp/main.cpp @@ -219,7 +219,7 @@ int main() std::cin.ignore(); // Call Unregister so that COM can launch a new process for ToastInvokes after we terminate this process. - appNotificationManager.UnregisterAll(); + appNotificationManager.Unregister(); if (!isPackaged) { MddBootstrapShutdown(); From 01a17808d49e7917d43220ec187e424c82bf8b97 Mon Sep 17 00:00:00 2001 From: Daniel Ayala <14967941+danielayala94@users.noreply.github.com> Date: Fri, 1 Apr 2022 15:22:54 -0700 Subject: [PATCH 11/11] Addressed leaked icons from prior registrations --- dev/AppNotifications/AppNotificationUtility.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dev/AppNotifications/AppNotificationUtility.cpp b/dev/AppNotifications/AppNotificationUtility.cpp index 05fbd0b8e6..6d2bc0c942 100644 --- a/dev/AppNotifications/AppNotificationUtility.cpp +++ b/dev/AppNotifications/AppNotificationUtility.cpp @@ -302,11 +302,15 @@ void Microsoft::Windows::AppNotifications::Helpers::RegisterAssets(std::wstring RegisterValue(hKey, L"DisplayName", reinterpret_cast(assets.displayName.c_str()), REG_EXPAND_SZ, assets.displayName.size() * sizeof(wchar_t)); - // If IconUri is empty, then Shell will render a default icon in AppNotifications. + // If no icon is specified in the Registry, the OS will render a default icon for App Notifications. if (!assets.iconFilePath.empty()) { RegisterValue(hKey, L"IconUri", reinterpret_cast(assets.iconFilePath.c_str()), REG_EXPAND_SZ, assets.iconFilePath.size() * sizeof(wchar_t)); } + else // clean any Icon URI from previous registrations + { + LOG_IF_WIN32_ERROR(RegDeleteValue(hKey.get(), L"IconUri")); // Do not throw, since this is a best effort action. + } RegisterValue(hKey, L"CustomActivator", reinterpret_cast(clsid.c_str()), REG_SZ, wil::guid_string_length * sizeof(wchar_t)); }