Skip to content

Commit

Permalink
Assets full support V1
Browse files Browse the repository at this point in the history
  • Loading branch information
danielayala94 committed Mar 18, 2022
1 parent 0aad50b commit c67fe0c
Showing 1 changed file with 118 additions and 43 deletions.
161 changes: 118 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;
}

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;
Expand Down Expand Up @@ -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<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 && wcslen(propVariantDisplayName.pwszVal) > 0)
{
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_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: <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());
}

*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<wil::unique_cotaskmem_string>(SetDisplayNameBasedOnProcessName().c_str());

// Check if we have anything in the cache for the icon.
iconFilePath = wil::make_unique_string<wil::unique_cotaskmem_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<wil::unique_cotaskmem_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<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));
// Finally, set the assets!
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

0 comments on commit c67fe0c

Please sign in to comment.