From 225d0ba8f9e3096871f91b4cbab0e8ed43ff9e93 Mon Sep 17 00:00:00 2001 From: ge0rdi Date: Wed, 9 Sep 2020 19:31:33 +0200 Subject: [PATCH] Update: Add library for Desktop Toast notifications Based on sample code in: https://github.com/WindowsNotifications/desktop-toasts More info: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/send-local-toast-desktop-cpp-wrl --- Src/OpenShell.sln | 14 + .../DesktopNotificationManagerCompat.cpp | 291 ++++++++++++++++++ .../DesktopNotificationManagerCompat.h | 108 +++++++ Src/Update/DesktopToasts/DesktopToasts.def | 5 + Src/Update/DesktopToasts/DesktopToasts.h | 59 ++++ Src/Update/DesktopToasts/DesktopToasts.rc | 61 ++++ .../DesktopToasts/DesktopToasts.vcxproj | 110 +++++++ .../DesktopToasts.vcxproj.filters | 43 +++ Src/Update/DesktopToasts/dllmain.cpp | 131 ++++++++ 9 files changed, 822 insertions(+) create mode 100644 Src/Update/DesktopToasts/DesktopNotificationManagerCompat.cpp create mode 100644 Src/Update/DesktopToasts/DesktopNotificationManagerCompat.h create mode 100644 Src/Update/DesktopToasts/DesktopToasts.def create mode 100644 Src/Update/DesktopToasts/DesktopToasts.h create mode 100644 Src/Update/DesktopToasts/DesktopToasts.rc create mode 100644 Src/Update/DesktopToasts/DesktopToasts.vcxproj create mode 100644 Src/Update/DesktopToasts/DesktopToasts.vcxproj.filters create mode 100644 Src/Update/DesktopToasts/dllmain.cpp diff --git a/Src/OpenShell.sln b/Src/OpenShell.sln index 600c9f7..3209039 100644 --- a/Src/OpenShell.sln +++ b/Src/OpenShell.sln @@ -40,6 +40,9 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ClassicIEDLL", "ClassicIE\ClassicIEDLL\ClassicIEDLL.vcxproj", "{BC0E6E7C-08C1-4F12-A754-4608E5A22FA8}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Update", "Update\Update.vcxproj", "{171B46B0-6083-4D9E-BD33-946EA3BD76FA}" + ProjectSection(ProjectDependencies) = postProject + {D94BD2A6-1872-4F01-B911-F406603AA2E1} = {D94BD2A6-1872-4F01-B911-F406603AA2E1} + EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Win7Aero7", "Skins\Win7Aero7\Win7Aero7.vcxproj", "{A2CCDE9F-17CE-461E-8BD9-00261B8855A6}" EndProject @@ -63,6 +66,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Metro", "Skins\Metro\Metro. EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Metallic7", "Skins\Metallic7\Metallic7.vcxproj", "{CA5BFC96-428D-42F5-9F7D-CDDE048A357C}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DesktopToasts", "Update\DesktopToasts\DesktopToasts.vcxproj", "{D94BD2A6-1872-4F01-B911-F406603AA2E1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -374,6 +379,15 @@ Global {CA5BFC96-428D-42F5-9F7D-CDDE048A357C}.Setup|Win32.ActiveCfg = Resource|Win32 {CA5BFC96-428D-42F5-9F7D-CDDE048A357C}.Setup|Win32.Build.0 = Resource|Win32 {CA5BFC96-428D-42F5-9F7D-CDDE048A357C}.Setup|x64.ActiveCfg = Resource|Win32 + {D94BD2A6-1872-4F01-B911-F406603AA2E1}.Debug|Win32.ActiveCfg = Debug|Win32 + {D94BD2A6-1872-4F01-B911-F406603AA2E1}.Debug|Win32.Build.0 = Debug|Win32 + {D94BD2A6-1872-4F01-B911-F406603AA2E1}.Debug|x64.ActiveCfg = Debug|Win32 + {D94BD2A6-1872-4F01-B911-F406603AA2E1}.Release|Win32.ActiveCfg = Release|Win32 + {D94BD2A6-1872-4F01-B911-F406603AA2E1}.Release|Win32.Build.0 = Release|Win32 + {D94BD2A6-1872-4F01-B911-F406603AA2E1}.Release|x64.ActiveCfg = Release|Win32 + {D94BD2A6-1872-4F01-B911-F406603AA2E1}.Setup|Win32.ActiveCfg = Release|Win32 + {D94BD2A6-1872-4F01-B911-F406603AA2E1}.Setup|Win32.Build.0 = Release|Win32 + {D94BD2A6-1872-4F01-B911-F406603AA2E1}.Setup|x64.ActiveCfg = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Src/Update/DesktopToasts/DesktopNotificationManagerCompat.cpp b/Src/Update/DesktopToasts/DesktopNotificationManagerCompat.cpp new file mode 100644 index 0000000..24d3088 --- /dev/null +++ b/Src/Update/DesktopToasts/DesktopNotificationManagerCompat.cpp @@ -0,0 +1,291 @@ +// ****************************************************************** +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH +// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE. +// ****************************************************************** + +#include "DesktopNotificationManagerCompat.h" +#include +#include + +#define RETURN_IF_FAILED(hr) do { HRESULT _hrTemp = hr; if (FAILED(_hrTemp)) { return _hrTemp; } } while (false) + +using namespace ABI::Windows::Data::Xml::Dom; +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; + +namespace DesktopNotificationManagerCompat +{ + HRESULT RegisterComServer(GUID clsid, const wchar_t exePath[]); + HRESULT EnsureRegistered(); + bool IsRunningAsUwp(); + + bool s_registeredAumidAndComServer = false; + std::wstring s_aumid; + bool s_registeredActivator = false; + bool s_hasCheckedIsRunningAsUwp = false; + bool s_isRunningAsUwp = false; + + HRESULT RegisterAumidAndComServer(const wchar_t *aumid, GUID clsid) + { +/* + // If running as Desktop Bridge + if (IsRunningAsUwp()) + { + // Clear the AUMID since Desktop Bridge doesn't use it, and then we're done. + // Desktop Bridge apps are registered with platform through their manifest. + // Their LocalServer32 key is also registered through their manifest. + s_aumid = L""; + s_registeredAumidAndComServer = true; + return S_OK; + } +*/ + // Copy the aumid + s_aumid = std::wstring(aumid); +/* + // Get the EXE path + wchar_t exePath[MAX_PATH]; + DWORD charWritten = ::GetModuleFileName(nullptr, exePath, ARRAYSIZE(exePath)); + RETURN_IF_FAILED(charWritten > 0 ? S_OK : HRESULT_FROM_WIN32(::GetLastError())); + + // Register the COM server + RETURN_IF_FAILED(RegisterComServer(clsid, exePath)); +*/ + s_registeredAumidAndComServer = true; + return S_OK; + } + + HRESULT RegisterActivator() + { + // Module needs a callback registered before it can be used. + // Since we don't care about when it shuts down, we'll pass an empty lambda here. + Module::Create([] {}); + + // If a local server process only hosts the COM object then COM expects + // the COM server host to shutdown when the references drop to zero. + // Since the user might still be using the program after activating the notification, + // we don't want to shutdown immediately. Incrementing the object count tells COM that + // we aren't done yet. + Module::GetModule().IncrementObjectCount(); + + RETURN_IF_FAILED(Module::GetModule().RegisterObjects()); + + s_registeredActivator = true; + return S_OK; + } + + HRESULT RegisterComServer(GUID clsid, const wchar_t exePath[]) + { + // Turn the GUID into a string + OLECHAR* clsidOlechar; + StringFromCLSID(clsid, &clsidOlechar); + std::wstring clsidStr(clsidOlechar); + ::CoTaskMemFree(clsidOlechar); + + // Create the subkey + // Something like SOFTWARE\Classes\CLSID\{23A5B06E-20BB-4E7E-A0AC-6982ED6A6041}\LocalServer32 + std::wstring subKey = LR"(SOFTWARE\Classes\CLSID\)" + clsidStr + LR"(\LocalServer32)"; + + // Include -ToastActivated launch args on the exe + std::wstring exePathStr(exePath); + exePathStr = L"\"" + exePathStr + L"\" " + TOAST_ACTIVATED_LAUNCH_ARG; + + // We don't need to worry about overflow here as ::GetModuleFileName won't + // return anything bigger than the max file system path (much fewer than max of DWORD). + DWORD dataSize = static_cast((exePathStr.length() + 1) * sizeof(WCHAR)); + + // Register the EXE for the COM server + return HRESULT_FROM_WIN32(::RegSetKeyValue( + HKEY_CURRENT_USER, + subKey.c_str(), + nullptr, + REG_SZ, + reinterpret_cast(exePathStr.c_str()), + dataSize)); + } + + HRESULT CreateToastNotifier(IToastNotifier **notifier) + { + RETURN_IF_FAILED(EnsureRegistered()); + + ComPtr toastStatics; + RETURN_IF_FAILED(Windows::Foundation::GetActivationFactory( + HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), + &toastStatics)); + + if (s_aumid.empty()) + { + return toastStatics->CreateToastNotifier(notifier); + } + else + { + return toastStatics->CreateToastNotifierWithId(HStringReference(s_aumid.c_str()).Get(), notifier); + } + } + + HRESULT CreateXmlDocumentFromString(const wchar_t *xmlString, IXmlDocument **doc) + { + ComPtr answer; + RETURN_IF_FAILED(Windows::Foundation::ActivateInstance(HStringReference(RuntimeClass_Windows_Data_Xml_Dom_XmlDocument).Get(), &answer)); + + ComPtr docIO; + RETURN_IF_FAILED(answer.As(&docIO)); + + // Load the XML string + RETURN_IF_FAILED(docIO->LoadXml(HStringReference(xmlString).Get())); + + return answer.CopyTo(doc); + } + + HRESULT CreateToastNotification(IXmlDocument *content, IToastNotification **notification) + { + ComPtr factory; + RETURN_IF_FAILED(Windows::Foundation::GetActivationFactory( + HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), + &factory)); + + return factory->CreateToastNotification(content, notification); + } + + HRESULT get_History(std::unique_ptr* history) + { + RETURN_IF_FAILED(EnsureRegistered()); + + ComPtr toastStatics; + RETURN_IF_FAILED(Windows::Foundation::GetActivationFactory( + HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), + &toastStatics)); + + ComPtr toastStatics2; + RETURN_IF_FAILED(toastStatics.As(&toastStatics2)); + + ComPtr nativeHistory; + RETURN_IF_FAILED(toastStatics2->get_History(&nativeHistory)); + + *history = std::unique_ptr(new DesktopNotificationHistoryCompat(s_aumid.c_str(), nativeHistory)); + return S_OK; + } + + bool CanUseHttpImages() + { + return IsRunningAsUwp(); + } + + HRESULT EnsureRegistered() + { + // If not registered AUMID yet + if (!s_registeredAumidAndComServer) + { + // Check if Desktop Bridge + if (IsRunningAsUwp()) + { + // Implicitly registered, all good! + s_registeredAumidAndComServer = true; + } + else + { + // Otherwise, incorrect usage, must call RegisterAumidAndComServer first + return E_ILLEGAL_METHOD_CALL; + } + } + + // If not registered activator yet + if (!s_registeredActivator) + { + // Incorrect usage, must call RegisterActivator first + return E_ILLEGAL_METHOD_CALL; + } + + return S_OK; + } + + bool IsRunningAsUwp() + { + if (!s_hasCheckedIsRunningAsUwp) + { + // https://stackoverflow.com/questions/39609643/determine-if-c-application-is-running-as-a-uwp-app-in-desktop-bridge-project + UINT32 length; + wchar_t packageFamilyName[PACKAGE_FAMILY_NAME_MAX_LENGTH + 1]; + LONG result = GetPackageFamilyName(GetCurrentProcess(), &length, packageFamilyName); + s_isRunningAsUwp = result == ERROR_SUCCESS; + s_hasCheckedIsRunningAsUwp = true; + } + + return s_isRunningAsUwp; + } +} + +DesktopNotificationHistoryCompat::DesktopNotificationHistoryCompat(const wchar_t *aumid, ComPtr history) +{ + m_aumid = std::wstring(aumid); + m_history = history; +} + +HRESULT DesktopNotificationHistoryCompat::Clear() +{ + if (m_aumid.empty()) + { + return m_history->Clear(); + } + else + { + return m_history->ClearWithId(HStringReference(m_aumid.c_str()).Get()); + } +} + +HRESULT DesktopNotificationHistoryCompat::GetHistory(ABI::Windows::Foundation::Collections::IVectorView **toasts) +{ + ComPtr history2; + RETURN_IF_FAILED(m_history.As(&history2)); + + if (m_aumid.empty()) + { + return history2->GetHistory(toasts); + } + else + { + return history2->GetHistoryWithId(HStringReference(m_aumid.c_str()).Get(), toasts); + } +} + +HRESULT DesktopNotificationHistoryCompat::Remove(const wchar_t *tag) +{ + if (m_aumid.empty()) + { + return m_history->Remove(HStringReference(tag).Get()); + } + else + { + return m_history->RemoveGroupedTagWithId(HStringReference(tag).Get(), HStringReference(L"").Get(), HStringReference(m_aumid.c_str()).Get()); + } +} + +HRESULT DesktopNotificationHistoryCompat::RemoveGroupedTag(const wchar_t *tag, const wchar_t *group) +{ + if (m_aumid.empty()) + { + return m_history->RemoveGroupedTag(HStringReference(tag).Get(), HStringReference(group).Get()); + } + else + { + return m_history->RemoveGroupedTagWithId(HStringReference(tag).Get(), HStringReference(group).Get(), HStringReference(m_aumid.c_str()).Get()); + } +} + +HRESULT DesktopNotificationHistoryCompat::RemoveGroup(const wchar_t *group) +{ + if (m_aumid.empty()) + { + return m_history->RemoveGroup(HStringReference(group).Get()); + } + else + { + return m_history->RemoveGroupWithId(HStringReference(group).Get(), HStringReference(m_aumid.c_str()).Get()); + } +} diff --git a/Src/Update/DesktopToasts/DesktopNotificationManagerCompat.h b/Src/Update/DesktopToasts/DesktopNotificationManagerCompat.h new file mode 100644 index 0000000..2d63fe8 --- /dev/null +++ b/Src/Update/DesktopToasts/DesktopNotificationManagerCompat.h @@ -0,0 +1,108 @@ +// ****************************************************************** +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH +// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE. +// ****************************************************************** + +#pragma once +#include +#include +#include +#include +#include +#define TOAST_ACTIVATED_LAUNCH_ARG L"-ToastActivated" + +using namespace ABI::Windows::UI::Notifications; + +class DesktopNotificationHistoryCompat; + +namespace DesktopNotificationManagerCompat +{ + /// + /// If not running under the Desktop Bridge, you must call this method to register your AUMID with the Compat library and to + /// register your COM CLSID and EXE in LocalServer32 registry. Feel free to call this regardless, and we will no-op if running + /// under Desktop Bridge. Call this upon application startup, before calling any other APIs. + /// + /// An AUMID that uniquely identifies your application. + /// The CLSID of your NotificationActivator class. + HRESULT RegisterAumidAndComServer(const wchar_t *aumid, GUID clsid); + + /// + /// Registers your module to handle COM activations. Call this upon application startup. + /// + HRESULT RegisterActivator(); + + /// + /// Creates a toast notifier. You must have called RegisterActivator first (and also RegisterAumidAndComServer if you're a classic Win32 app), or this will throw an exception. + /// + HRESULT CreateToastNotifier(IToastNotifier** notifier); + + /// + /// Creates an XmlDocument initialized with the specified string. This is simply a convenience helper method. + /// + HRESULT CreateXmlDocumentFromString(const wchar_t *xmlString, ABI::Windows::Data::Xml::Dom::IXmlDocument** doc); + + /// + /// Creates a toast notification. This is simply a convenience helper method. + /// + HRESULT CreateToastNotification(ABI::Windows::Data::Xml::Dom::IXmlDocument* content, IToastNotification** notification); + + /// + /// Gets the DesktopNotificationHistoryCompat object. You must have called RegisterActivator first (and also RegisterAumidAndComServer if you're a classic Win32 app), or this will throw an exception. + /// + HRESULT get_History(std::unique_ptr* history); + + /// + /// Gets a boolean representing whether http images can be used within toasts. This is true if running under Desktop Bridge. + /// + bool CanUseHttpImages(); +} + +class DesktopNotificationHistoryCompat +{ +public: + + /// + /// Removes all notifications sent by this app from action center. + /// + HRESULT Clear(); + + /// + /// Gets all notifications sent by this app that are currently still in Action Center. + /// + HRESULT GetHistory(ABI::Windows::Foundation::Collections::IVectorView** history); + + /// + /// Removes an individual toast, with the specified tag label, from action center. + /// + /// The tag label of the toast notification to be removed. + HRESULT Remove(const wchar_t *tag); + + /// + /// Removes a toast notification from the action using the notification's tag and group labels. + /// + /// The tag label of the toast notification to be removed. + /// The group label of the toast notification to be removed. + HRESULT RemoveGroupedTag(const wchar_t *tag, const wchar_t *group); + + /// + /// Removes a group of toast notifications, identified by the specified group label, from action center. + /// + /// The group label of the toast notifications to be removed. + HRESULT RemoveGroup(const wchar_t *group); + + /// + /// Do not call this. Instead, call DesktopNotificationManagerCompat.get_History() to obtain an instance. + /// + DesktopNotificationHistoryCompat(const wchar_t *aumid, Microsoft::WRL::ComPtr history); + +private: + std::wstring m_aumid; + Microsoft::WRL::ComPtr m_history = nullptr; +}; \ No newline at end of file diff --git a/Src/Update/DesktopToasts/DesktopToasts.def b/Src/Update/DesktopToasts/DesktopToasts.def new file mode 100644 index 0000000..45eb4a7 --- /dev/null +++ b/Src/Update/DesktopToasts/DesktopToasts.def @@ -0,0 +1,5 @@ +LIBRARY "DesktopToasts.dll" + +EXPORTS + Initialize + DisplaySimpleToast diff --git a/Src/Update/DesktopToasts/DesktopToasts.h b/Src/Update/DesktopToasts/DesktopToasts.h new file mode 100644 index 0000000..a1ac9ce --- /dev/null +++ b/Src/Update/DesktopToasts/DesktopToasts.h @@ -0,0 +1,59 @@ +#include + +using DesktopToastActivateHandler = void(__cdecl*)(void* context, LPCWSTR invokedArgs); + +HRESULT Initialize(LPCWSTR appUserModelId, DesktopToastActivateHandler handler, void* handlerContext); +HRESULT DisplaySimpleToast(LPCWSTR title, LPCWSTR text); + +class DesktopToasts +{ +public: + explicit DesktopToasts(LPCWSTR appUserModelId) + { + if (::IsWindows10OrGreater()) + { + auto m_lib = ::LoadLibrary(L"DesktopToasts.dll"); + if (m_lib) + { + m_pInitialize = (decltype(m_pInitialize))::GetProcAddress(m_lib, "Initialize"); + m_pDisplayToast = (decltype(m_pDisplayToast))::GetProcAddress(m_lib, "DisplaySimpleToast"); + + if (m_pInitialize && m_pDisplayToast) + { + if (m_pInitialize(appUserModelId, ToastActivate, this) == S_OK) + m_initialized = true; + } + } + } + } + + ~DesktopToasts() + { + if (m_lib) + ::FreeLibrary(m_lib); + } + + explicit operator bool() const + { + return m_initialized; + } + + HRESULT DisplaySimpleToast(LPCWSTR title, LPCWSTR text) + { + return m_pDisplayToast(title, text); + } + +private: + virtual void OnToastActivate(LPCWSTR invokedArgs) {} + + static void __cdecl ToastActivate(void* context, LPCWSTR invokedArgs) + { + static_cast(context)->OnToastActivate(invokedArgs); + } + + bool m_initialized = false; + + HMODULE m_lib = nullptr; + decltype(&::Initialize) m_pInitialize = nullptr; + decltype(&::DisplaySimpleToast) m_pDisplayToast = nullptr; +}; diff --git a/Src/Update/DesktopToasts/DesktopToasts.rc b/Src/Update/DesktopToasts/DesktopToasts.rc new file mode 100644 index 0000000..3d25206 --- /dev/null +++ b/Src/Update/DesktopToasts/DesktopToasts.rc @@ -0,0 +1,61 @@ +// Microsoft Visual C++ generated resource script. +// + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION _PRODUCT_VERSION + PRODUCTVERSION _PRODUCT_VERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "Open-Shell" + VALUE "FileDescription", "Desktop toast notifications support" + VALUE "FileVersion", _PRODUCT_VERSION_STR + VALUE "InternalName", "DesktopToasts.dll" + VALUE "LegalCopyright", "Copyright (C) 2020, The Open-Shell Team" + VALUE "OriginalFilename", "DesktopToasts.dll" + VALUE "ProductName", "Open-Shell" + VALUE "ProductVersion", _PRODUCT_VERSION_STR + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// diff --git a/Src/Update/DesktopToasts/DesktopToasts.vcxproj b/Src/Update/DesktopToasts/DesktopToasts.vcxproj new file mode 100644 index 0000000..5d5a775 --- /dev/null +++ b/Src/Update/DesktopToasts/DesktopToasts.vcxproj @@ -0,0 +1,110 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + 16.0 + Win32Proj + {d94bd2a6-1872-4f01-b911-f406603aa2e1} + DesktopToasts + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + true + ..\$(Configuration)\ + + + false + $(Configuration)\ + + + + Level3 + true + WIN32;_DEBUG;DESKTOPTOASTS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Windows + true + false + runtimeobject.lib;%(AdditionalDependencies) + DesktopToasts.def + + + + + Level3 + true + true + true + WIN32;NDEBUG;DESKTOPTOASTS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + stdcpp17 + MultiThreaded + + + Windows + true + true + true + false + runtimeobject.lib;%(AdditionalDependencies) + DesktopToasts.def + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Src/Update/DesktopToasts/DesktopToasts.vcxproj.filters b/Src/Update/DesktopToasts/DesktopToasts.vcxproj.filters new file mode 100644 index 0000000..e13aeaa --- /dev/null +++ b/Src/Update/DesktopToasts/DesktopToasts.vcxproj.filters @@ -0,0 +1,43 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + + + Source Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/Src/Update/DesktopToasts/dllmain.cpp b/Src/Update/DesktopToasts/dllmain.cpp new file mode 100644 index 0000000..cccd933 --- /dev/null +++ b/Src/Update/DesktopToasts/dllmain.cpp @@ -0,0 +1,131 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#include +#include +#include +#include + +#include "DesktopToasts.h" +#include "DesktopNotificationManagerCompat.h" + +#define RETURN_IF_FAILED(hr) do { HRESULT _hrTemp = hr; if (FAILED(_hrTemp)) { return _hrTemp; } } while (false) + +using namespace ABI::Windows::Data::Xml::Dom; +using namespace ABI::Windows::UI::Notifications; +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; + +DesktopToastActivateHandler g_handler = nullptr; +void* g_handlerContext = nullptr; + +class DECLSPEC_UUID("E407B70A-1FBD-4D5E-8822-231C69102472") NotificationActivator WrlSealed WrlFinal + : public RuntimeClass, INotificationActivationCallback> +{ +public: + virtual HRESULT STDMETHODCALLTYPE Activate( + _In_ LPCWSTR appUserModelId, + _In_ LPCWSTR invokedArgs, + _In_reads_(dataCount) const NOTIFICATION_USER_INPUT_DATA * data, + ULONG dataCount) override + { + if (g_handler) + g_handler(g_handlerContext, invokedArgs); + + return S_OK; + } +}; + +// Flag class as COM creatable +CoCreatableClass(NotificationActivator); + +HRESULT Initialize(LPCWSTR appUserModelId, DesktopToastActivateHandler handler, void* handlerContext) +{ + RETURN_IF_FAILED(DesktopNotificationManagerCompat::RegisterAumidAndComServer(appUserModelId, __uuidof(NotificationActivator))); + RETURN_IF_FAILED(DesktopNotificationManagerCompat::RegisterActivator()); + g_handler = handler; + g_handlerContext = handlerContext; + + return S_OK; +} + +HRESULT SetNodeValueString(HSTRING inputString, IXmlNode* node, IXmlDocument* xml) +{ + ComPtr inputText; + RETURN_IF_FAILED(xml->CreateTextNode(inputString, &inputText)); + + ComPtr inputTextNode; + RETURN_IF_FAILED(inputText.As(&inputTextNode)); + + ComPtr appendedChild; + return node->AppendChild(inputTextNode.Get(), &appendedChild); +} + +_Use_decl_annotations_ +HRESULT SetTextValues(const PCWSTR* textValues, UINT32 textValuesCount, IXmlDocument* toastXml) +{ + ComPtr nodeList; + RETURN_IF_FAILED(toastXml->GetElementsByTagName(HStringReference(L"text").Get(), &nodeList)); + + UINT32 nodeListLength; + RETURN_IF_FAILED(nodeList->get_Length(&nodeListLength)); + + // If a template was chosen with fewer text elements, also change the amount of strings + // passed to this method. + RETURN_IF_FAILED(textValuesCount <= nodeListLength ? S_OK : E_INVALIDARG); + + for (UINT32 i = 0; i < textValuesCount; i++) + { + ComPtr textNode; + RETURN_IF_FAILED(nodeList->Item(i, &textNode)); + + RETURN_IF_FAILED(SetNodeValueString(HStringReference(textValues[i]).Get(), textNode.Get(), toastXml)); + } + + return S_OK; +} + +HRESULT DisplaySimpleToast(LPCWSTR title, LPCWSTR text) +{ + // Construct XML + ComPtr doc; + HRESULT hr = DesktopNotificationManagerCompat::CreateXmlDocumentFromString(L"", &doc); + if (SUCCEEDED(hr)) + { + PCWSTR textValues[] = { title, text }; + SetTextValues(textValues, ARRAYSIZE(textValues), doc.Get()); + + // Create the notifier + // Classic Win32 apps MUST use the compat method to create the notifier + ComPtr notifier; + hr = DesktopNotificationManagerCompat::CreateToastNotifier(¬ifier); + if (SUCCEEDED(hr)) + { + // Create the notification itself (using helper method from compat library) + ComPtr toast; + hr = DesktopNotificationManagerCompat::CreateToastNotification(doc.Get(), &toast); + if (SUCCEEDED(hr)) + { + // And show it! + hr = notifier->Show(toast.Get()); + } + } + } + + return hr; +} + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +}