Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notifications assets full support #2295

Merged
merged 12 commits into from
Apr 1, 2022
99 changes: 46 additions & 53 deletions dev/AppNotifications/AppNotificationUtility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "pch.h"
#include <cwctype>
#include <filesystem>
#include "AppNotificationUtility.h"
#include <winrt/Windows.ApplicationModel.Core.h>
#include <winrt/Windows.Foundation.h>
Expand All @@ -13,6 +14,8 @@
#include "AppNotification.h"
#include "NotificationProgressData.h"

#include <Microsoft.Foundation.String.h>

#include <wil/resource.h>
#include <wil/win32_helpers.h>
#include <propkey.h> // PKEY properties
Expand All @@ -31,6 +34,8 @@ namespace ToastABI
using namespace ::ABI::Microsoft::Internal::ToastNotifications;
}

danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
constexpr PCWSTR defaultAppNotificationIcon = LR"(ms-resource://Windows.UI.ShellCommon/Files/Images/DefaultSystemNotification.png)";
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved

std::wstring Microsoft::Windows::AppNotifications::Helpers::RetrieveUnpackagedNotificationAppId()
{
wil::unique_cotaskmem_string appId;
Expand Down Expand Up @@ -232,6 +237,34 @@ std::wstring Microsoft::Windows::AppNotifications::Helpers::SetDisplayNameBasedO
return displayName;
}

// Placeholder
HRESULT RetrieveAssetsFromProcess(_Out_ Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets& /*assets*/)
{
// 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_ Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets& /*assets*/)
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
{
// THROW_HR_IF_MSG(E_UNEXPECTED, VerifyIconFileExtension(iconFilePath));

return E_NOTIMPL;
}

HRESULT VerifyIconFileExtension(std::filesystem::path& iconFilePath)
{
const auto fileExtension = iconFilePath.extension();

const bool isFileExtensionSupported =
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
fileExtension == ".ico" || fileExtension == ".bmp" || fileExtension == ".jpg" || fileExtension == ".png" ||
fileExtension == ".ICO" || fileExtension == ".BMP" || fileExtension == ".JPG" || fileExtension == ".PNG";

THROW_HR_IF(E_UNEXPECTED, isFileExtensionSupported);
return S_OK;
}

void Microsoft::Windows::AppNotifications::Helpers::RegisterAssets(std::wstring const& appId, std::wstring const& clsid)
{
wil::unique_hkey hKey;
Expand All @@ -249,63 +282,23 @@ void Microsoft::Windows::AppNotifications::Helpers::RegisterAssets(std::wstring
&hKey,
nullptr /* lpdwDisposition */));

// Retrieve the display name
std::wstring displayName{};
displayName = SetDisplayNameBasedOnProcessName();

// Retrieve the icon
std::wstring iconFilePath{};

wil::unique_hwnd hWindow{ GetConsoleWindow() };
// 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.
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
// 4. Use the default assets.
Microsoft::Windows::AppNotifications::Helpers::AppNotificationAssets assets{};

if (hWindow)
if (FAILED(RetrieveAssetsFromProcess(assets)) &&
FAILED(ToastNotifications_RetrieveAssets_Stub(assets)))
{
// 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<IPropertyStore> propertyStore;
THROW_IF_FAILED(SHGetPropertyStoreForWindow(hWindow.get(), 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)
{
displayName = 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");

iconFilePath = propVariantIcon.pwszVal;

// Icon filepaths from Shell APIs have this format: <filepath>,-<index>,
// since .ico 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",") };
if (iteratorForCommaDelimiter != std::wstring::npos) // It may or may not have an index, which is fine.
{
iconFilePath.erase(iteratorForCommaDelimiter);
}

auto iteratorForFileExtension{ iconFilePath.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).");
assets.displayName = wil::make_unique_string<wil::unique_cotaskmem_string>(SetDisplayNameBasedOnProcessName().c_str());
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
assets.iconFilePath = wil::make_unique_string<wil::unique_cotaskmem_string>(defaultAppNotificationIcon);
}

RegisterValue(hKey, L"DisplayName", reinterpret_cast<const BYTE*>(displayName.c_str()), REG_EXPAND_SZ, displayName.size() * sizeof(wchar_t));
RegisterValue(hKey, L"IconUri", reinterpret_cast<const BYTE*>(iconFilePath.c_str()), REG_EXPAND_SZ, iconFilePath.size() * sizeof(wchar_t));
RegisterValue(hKey, L"CustomActivator", reinterpret_cast<const BYTE*>(clsid.c_str()), REG_SZ, clsid.size() * sizeof(wchar_t));
RegisterValue(hKey, L"DisplayName", reinterpret_cast<const BYTE*>(assets.displayName.get()), REG_EXPAND_SZ, wcslen(assets.displayName.get()) * sizeof(wchar_t));
RegisterValue(hKey, L"IconUri", reinterpret_cast<const BYTE*>(assets.iconFilePath.get()), REG_EXPAND_SZ, wcslen(assets.iconFilePath.get()) * sizeof(wchar_t));
RegisterValue(hKey, L"CustomActivator", reinterpret_cast<const BYTE*>(clsid.c_str()), REG_SZ, wil::guid_string_length * sizeof(wchar_t));
}

winrt::guid Microsoft::Windows::AppNotifications::Helpers::RegisterComActivatorGuidAndAssets()
Expand Down
10 changes: 10 additions & 0 deletions dev/AppNotifications/AppNotificationUtility.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
wil::unique_cotaskmem_string iconFilePath;
};

inline const int GUID_LENGTH = 39; // GUID + '{' + '}' + '/0'

inline std::wstring ConvertPathToKey(std::wstring path)
Expand Down Expand Up @@ -62,4 +67,9 @@ namespace Microsoft::Windows::AppNotifications::Helpers
winrt::Microsoft::Windows::AppNotifications::AppNotification ToastNotificationFromToastProperties(ABI::Microsoft::Internal::ToastNotifications::INotificationProperties* properties);

danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
std::wstring SetDisplayNameBasedOnProcessName();

inline bool IsNullOrEmpty(PCWSTR string)
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
{
return !string || (*string == '\0');
}
}
3 changes: 2 additions & 1 deletion dev/Common/Common.vcxitems
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
<ClInclude Include="$(MSBuildThisFileDirectory)AppModel.Identity.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)AppModel.Package.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)AppModel.PackageGraph.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)Microsoft.Foundation.String.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)Microsoft.Utf8.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)Security.IntegrityLevel.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)WindowsAppRuntime.SelfContained.h" />
</ItemGroup>
</Project>
</Project>
3 changes: 3 additions & 0 deletions dev/Common/Common.vcxitems.filters
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
<ClInclude Include="$(MSBuildThisFileDirectory)Security.IntegrityLevel.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="$(MSBuildThisFileDirectory)Microsoft.Foundation.String.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(MSBuildThisFileDirectory)WindowsAppRuntime.SelfContained.cpp">
Expand Down
22 changes: 22 additions & 0 deletions dev/Common/Microsoft.Foundation.String.h
Original file line number Diff line number Diff line change
@@ -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