Skip to content

Commit

Permalink
Notifications assets full support (#2295)
Browse files Browse the repository at this point in the history
Implemented app notification icon support, extracting icon from exe and setting default icon in case no icon can be retrieved.

Co-authored-by: Venkata Sharath Chandra Manchala <[email protected]>
Co-authored-by: Sharath Manchala <[email protected]>
  • Loading branch information
3 people authored Apr 1, 2022
1 parent 2e209e7 commit 1006b2a
Show file tree
Hide file tree
Showing 16 changed files with 522 additions and 126 deletions.
14 changes: 8 additions & 6 deletions WindowsAppRuntime.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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}"
Expand Down Expand Up @@ -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}
Expand All @@ -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}
Expand Down
8 changes: 8 additions & 0 deletions dev/AppNotifications/AppNotificationManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <string_view>
#include <winrt/Windows.Foundation.Collections.h>
#include <WindowsAppRuntime.SelfContained.h>
#include <ShellLocalization.h>

using namespace std::literals;

Expand All @@ -46,6 +47,7 @@ namespace PushNotificationHelpers
}

using namespace Microsoft::Windows::AppNotifications::Helpers;
using namespace Microsoft::Windows::AppNotifications::ShellLocalization;

namespace winrt::Microsoft::Windows::AppNotifications::implementation
{
Expand Down Expand Up @@ -231,6 +233,12 @@ 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,
// 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));

std::wstring storedComActivatorString;
THROW_IF_FAILED(GetActivatorGuid(storedComActivatorString));
UnRegisterComServer(storedComActivatorString);
Expand Down
144 changes: 60 additions & 84 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 "AppNotificationUtility.h"
#include <winrt/Windows.ApplicationModel.Core.h>
#include <winrt/Windows.Foundation.h>
Expand All @@ -13,11 +14,17 @@
#include "AppNotification.h"
#include "NotificationProgressData.h"

#include <wil/resource.h>
#include <wil/win32_helpers.h>
#include <propkey.h> // PKEY properties
#include <propsys.h> // IPropertyStore
#include <Microsoft.Foundation.String.h>
#include <ShObjIdl_core.h>
#include <shlobj_core.h>
#include <filesystem>

#include <ShellLocalization.h>

namespace std
{
using namespace std::filesystem;
}

namespace winrt
{
Expand Down Expand Up @@ -106,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,
Expand Down Expand Up @@ -216,7 +225,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));
Expand All @@ -232,82 +241,6 @@ std::wstring Microsoft::Windows::AppNotifications::Helpers::SetDisplayNameBasedO
return displayName;
}

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 */));

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

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

wil::unique_hwnd hWindow{ GetConsoleWindow() };

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())));

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).");
}

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));
}

winrt::guid Microsoft::Windows::AppNotifications::Helpers::RegisterComActivatorGuidAndAssets()
{
std::wstring registeredGuid;
Expand All @@ -331,12 +264,55 @@ winrt::guid Microsoft::Windows::AppNotifications::Helpers::RegisterComActivatorG
THROW_IF_FAILED(hr);
}

std::wstring notificationAppId{ RetrieveNotificationAppId() };
std::wstring notificationAppId{ Microsoft::Windows::AppNotifications::Helpers::RetrieveNotificationAppId() };
RegisterAssets(notificationAppId, registeredGuid);

// 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)
{
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 */));

// Try the following techniques to retrieve display name and icon:
// 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)))
{
assets.displayName = GetDisplayNameBasedOnProcessName();
}

RegisterValue(hKey, L"DisplayName", reinterpret_cast<const BYTE*>(assets.displayName.c_str()), REG_EXPAND_SZ, assets.displayName.size() * sizeof(wchar_t));

// 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<const BYTE*>(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<const BYTE*>(clsid.c_str()), REG_SZ, wil::guid_string_length * sizeof(wchar_t));
}

wil::unique_cotaskmem_string Microsoft::Windows::AppNotifications::Helpers::ConvertUtf8StringToWideString(unsigned long length, const byte* utf8String)
Expand Down
10 changes: 5 additions & 5 deletions dev/AppNotifications/AppNotificationUtility.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +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_notificationActivatedArgument{ L" ----AppNotificationActivated:" };
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'

Expand Down Expand Up @@ -61,5 +61,5 @@ namespace Microsoft::Windows::AppNotifications::Helpers

winrt::Microsoft::Windows::AppNotifications::AppNotification ToastNotificationFromToastProperties(ABI::Microsoft::Internal::ToastNotifications::INotificationProperties* properties);

std::wstring SetDisplayNameBasedOnProcessName();
std::wstring GetDisplayNameBasedOnProcessName();
}
2 changes: 2 additions & 0 deletions dev/AppNotifications/AppNotifications.vcxitems
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<ClInclude Include="$(MSBuildThisFileDirectory)NotificationProperties.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)NotificationTransientProperties.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)NotificationProgressData.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)ShellLocalization.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(MSBuildThisFileDirectory)AppNotificationManager.cpp" />
Expand All @@ -32,6 +33,7 @@
<ClCompile Include="$(MSBuildThisFileDirectory)NotificationProperties.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)NotificationTransientProperties.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)NotificationProgressData.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)ShellLocalization.cpp" />
</ItemGroup>
<ItemGroup>
<Midl Include="$(MSBuildThisFileDirectory)AppNotifications.idl" />
Expand Down
Loading

0 comments on commit 1006b2a

Please sign in to comment.