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
138 changes: 95 additions & 43 deletions dev/AppNotifications/AppNotificationUtility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,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,79 +234,124 @@ 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 RetrieveAssetsFromProcess(_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;
}

HRESULT RetrieveAssetsFromWindow(_Out_ PWSTR* displayName, _Out_ PWSTR* iconFilePath)
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
{
*displayName = nullptr;
*iconFilePath = nullptr;

// Retrieve the icon
std::wstring iconFilePath{};
wil::unique_cotaskmem_string localDisplayName;
wil::unique_cotaskmem_string localIconFilePath;

wil::unique_hwnd hWindow{ GetConsoleWindow() };
HWND hWindow = GetForegroundWindow();
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved

if (hWindow)
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
{
// 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())));
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 && Microsoft::Windows::AppNotifications::Helpers::IsWideStringEmptyOrNull(propVariantDisplayName.pwszVal))
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
{
displayName = propVariantDisplayName.pwszVal;
localDisplayName = wil::make_unique_string<wil::unique_cotaskmem_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(E_UNEXPECTED, propVariantIcon.vt != VT_LPWSTR || Microsoft::Windows::AppNotifications::Helpers::IsWideStringEmptyOrNull(propVariantIcon.pwszVal));

THROW_HR_IF_MSG(E_UNEXPECTED, wcslen(propVariantIcon.pwszVal) == 0, "Icon is an empty string");
std::wstring localIconFilePathAsWstring = propVariantIcon.pwszVal;
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved

iconFilePath = propVariantIcon.pwszVal;

// Icon filepaths from Shell APIs have this format: <filepath>,-<index>,
// since .ico files can have multiple icons in the same file.
// Icon filepaths from Shell APIs usually follow this format: <filepath>,-<index>,
// 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<wil::unique_cotaskmem_string>(localIconFilePathAsWstring.erase(iteratorForCommaDelimiter).c_str());
}

auto iteratorForFileExtension{ iconFilePath.find_first_of(L".") };
*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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does WIL have registry functions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe it does. But that would be very nice to have :)

HKEY_CURRENT_USER,
subKey.c_str(),
0,
nullptr /* lpClass */,
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
nullptr /* lpSecurityAttributes */,
&hKey,
nullptr /* lpdwDisposition */));

bool useDefaultAssets{ false };
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved

wil::unique_cotaskmem_string displayName;
wil::unique_cotaskmem_string iconFilePath;
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved

// 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.
if (FAILED(RetrieveAssetsFromProcess(&displayName, &iconFilePath)) &&
FAILED(ToastNotifications_RetrieveAssets_Stub(&displayName, &iconFilePath)) &&
FAILED(RetrieveAssetsFromWindow(&displayName, &iconFilePath)))
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
{
displayName = wil::make_unique_string<wil::unique_cotaskmem_string>(SetDisplayNameBasedOnProcessName().c_str());
iconFilePath = wil::make_unique_string<wil::unique_cotaskmem_string>(defaultAppNotificationIcon);
useDefaultAssets = true;
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
}

// 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".") };
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);
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
THROW_HR_IF_MSG(E_UNEXPECTED,
iconFileExtension != L".ico" && iconFileExtension != L".png" &&
iconFileExtension != L".jpg" && iconFileExtension != L".bmp",
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
"You must provide a supported file format for the icon. Supported formats: .bmp, .ico, .jpg, .png.");
}

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"DisplayName", reinterpret_cast<const BYTE*>(displayName.get()), REG_EXPAND_SZ, wcslen(displayName.get()) * sizeof(wchar_t));
RegisterValue(hKey, L"IconUri", reinterpret_cast<const BYTE*>(iconFilePath.get()), REG_EXPAND_SZ, wcslen(iconFilePath.get()) * sizeof(wchar_t));
RegisterValue(hKey, L"CustomActivator", reinterpret_cast<const BYTE*>(clsid.c_str()), REG_SZ, clsid.size() * sizeof(wchar_t));
}

Expand Down Expand Up @@ -429,3 +476,8 @@ winrt::Microsoft::Windows::AppNotifications::AppNotification Microsoft::Windows:

return notification;
}

bool Microsoft::Windows::AppNotifications::Helpers::IsWideStringEmptyOrNull(PCWSTR wideString)
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
{
return wideString == nullptr || wcslen(wideString) > 0;
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 2 additions & 0 deletions dev/AppNotifications/AppNotificationUtility.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,6 @@ 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();

bool IsWideStringEmptyOrNull(PCWSTR wideString);
}