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
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 @@ -220,6 +222,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
140 changes: 56 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,51 @@ 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(
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 */));

// 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 IconUri is empty, then Shell will render a default icon in AppNotifications.
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
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));
danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
}

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

danielayala94 marked this conversation as resolved.
Show resolved Hide resolved
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