summaryrefslogtreecommitdiffstats
path: root/third_party/WinToast
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/WinToast')
-rw-r--r--third_party/WinToast/LICENSE21
-rw-r--r--third_party/WinToast/moz-check-system-shortcut.patch81
-rw-r--r--third_party/WinToast/moz-disable-create-shortcut.patch110
-rw-r--r--third_party/WinToast/moz.yaml37
-rw-r--r--third_party/WinToast/upstream-add-toast-scenario.patch123
-rw-r--r--third_party/WinToast/wintoastlib.cpp1197
-rw-r--r--third_party/WinToast/wintoastlib.h234
7 files changed, 1803 insertions, 0 deletions
diff --git a/third_party/WinToast/LICENSE b/third_party/WinToast/LICENSE
new file mode 100644
index 0000000000..c3a4fb8868
--- /dev/null
+++ b/third_party/WinToast/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016 Mohammed Boujemaoui Boulaghmoudi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE 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 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/third_party/WinToast/moz-check-system-shortcut.patch b/third_party/WinToast/moz-check-system-shortcut.patch
new file mode 100644
index 0000000000..84411ae7bc
--- /dev/null
+++ b/third_party/WinToast/moz-check-system-shortcut.patch
@@ -0,0 +1,81 @@
+diff --git a/src/wintoastlib.cpp b/src/wintoastlib.cpp
+index 0895ff7..ac8d5cf 100644
+--- a/src/wintoastlib.cpp
++++ b/src/wintoastlib.cpp
+@@ -213,8 +213,8 @@ namespace Util {
+ }
+
+
+- inline HRESULT defaultShellLinksDirectory(_In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) {
+- DWORD written = GetEnvironmentVariableW(L"APPDATA", path, nSize);
++ inline HRESULT commonShellLinksDirectory(_In_ const WCHAR* baseEnv, _In_ WCHAR* path, _In_ DWORD nSize) {
++ DWORD written = GetEnvironmentVariableW(baseEnv, path, nSize);
+ HRESULT hr = written > 0 ? S_OK : E_INVALIDARG;
+ if (SUCCEEDED(hr)) {
+ errno_t result = wcscat_s(path, nSize, DEFAULT_SHELL_LINKS_PATH);
+@@ -224,8 +224,8 @@ namespace Util {
+ return hr;
+ }
+
+- inline HRESULT defaultShellLinkPath(const std::wstring& appname, _In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) {
+- HRESULT hr = defaultShellLinksDirectory(path, nSize);
++ inline HRESULT commonShellLinkPath(_In_ const WCHAR* baseEnv, const std::wstring& appname, _In_ WCHAR* path, _In_ DWORD nSize) {
++ HRESULT hr = commonShellLinksDirectory(baseEnv, path, nSize);
+ if (SUCCEEDED(hr)) {
+ const std::wstring appLink(appname + DEFAULT_LINK_FORMAT);
+ errno_t result = wcscat_s(path, nSize, appLink.c_str());
+@@ -235,6 +235,13 @@ namespace Util {
+ return hr;
+ }
+
++ inline HRESULT defaultUserShellLinkPath(const std::wstring& appname, _In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) {
++ return commonShellLinkPath(L"APPDATA", appname, path, nSize);
++ }
++
++ inline HRESULT defaultSystemShellLinkPath(const std::wstring& appname, _In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) {
++ return commonShellLinkPath(L"PROGRAMDATA", appname, path, nSize);
++ }
+
+ inline PCWSTR AsString(ComPtr<IXmlDocument> &xmlDocument) {
+ HSTRING xml;
+@@ -523,12 +530,19 @@ const std::wstring& WinToast::appUserModelId() const {
+
+ HRESULT WinToast::validateShellLinkHelper(_Out_ bool& wasChanged) {
+ WCHAR path[MAX_PATH] = { L'\0' };
+- Util::defaultShellLinkPath(_appName, path);
++ Util::defaultUserShellLinkPath(_appName, path);
+ // Check if the file exist
+ DWORD attr = GetFileAttributesW(path);
+ if (attr >= 0xFFFFFFF) {
+- DEBUG_MSG("Error, shell link not found. Try to create a new one in: " << path);
+- return E_FAIL;
++ // The shortcut may be in the system Start Menu.
++ WCHAR systemPath[MAX_PATH] = { L'\0' };
++ Util::defaultSystemShellLinkPath(_appName, systemPath);
++ attr = GetFileAttributesW(systemPath);
++ if (attr >= 0xFFFFFFF) {
++ DEBUG_MSG("Error, shell link not found. Try to create a new one in: " << path);
++ return E_FAIL;
++ }
++ wcscpy(path, systemPath);
+ }
+
+ // Let's load the file as shell link to validate.
+@@ -543,7 +557,7 @@ HRESULT WinToast::validateShellLinkHelper(_Out_ bool& wasChanged) {
+ ComPtr<IPersistFile> persistFile;
+ hr = shellLink.As(&persistFile);
+ if (SUCCEEDED(hr)) {
+- hr = persistFile->Load(path, STGM_READWRITE);
++ hr = persistFile->Load(path, STGM_READ);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IPropertyStore> propertyStore;
+ hr = shellLink.As(&propertyStore);
+@@ -583,7 +597,7 @@ HRESULT WinToast::validateShellLinkHelper(_Out_ bool& wasChanged) {
+ HRESULT WinToast::createShellLinkHelper() {
+ WCHAR exePath[MAX_PATH]{L'\0'};
+ WCHAR slPath[MAX_PATH]{L'\0'};
+- Util::defaultShellLinkPath(_appName, slPath);
++ Util::defaultUserShellLinkPath(_appName, slPath);
+ Util::defaultExecutablePath(exePath);
+ ComPtr<IShellLinkW> shellLink;
+ HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink));
diff --git a/third_party/WinToast/moz-disable-create-shortcut.patch b/third_party/WinToast/moz-disable-create-shortcut.patch
new file mode 100644
index 0000000000..96ccac2732
--- /dev/null
+++ b/third_party/WinToast/moz-disable-create-shortcut.patch
@@ -0,0 +1,110 @@
+diff --git a/src/wintoastlib.cpp b/src/wintoastlib.cpp
+index 0895ff7..52de554 100644
+--- a/src/wintoastlib.cpp
++++ b/src/wintoastlib.cpp
+@@ -391,6 +391,10 @@ void WinToast::setAppUserModelId(_In_ const std::wstring& aumi) {
+ DEBUG_MSG(L"Default App User Model Id: " << _aumi.c_str());
+ }
+
++void WinToast::setShortcutPolicy(_In_ ShortcutPolicy shortcutPolicy) {
++ _shortcutPolicy = shortcutPolicy;
++}
++
+ bool WinToast::isCompatible() {
+ DllImporter::initialize();
+ return !((DllImporter::SetCurrentProcessExplicitAppUserModelID == nullptr)
+@@ -492,10 +496,12 @@ bool WinToast::initialize(_Out_ WinToastError* error) {
+ return false;
+ }
+
+- if (createShortcut() < 0) {
+- setError(error, WinToastError::ShellLinkNotCreated);
+- DEBUG_MSG(L"Error while attaching the AUMI to the current proccess =(");
+- return false;
++ if (_shortcutPolicy != SHORTCUT_POLICY_IGNORE) {
++ if (createShortcut() < 0) {
++ setError(error, WinToastError::ShellLinkNotCreated);
++ DEBUG_MSG(L"Error while attaching the AUMI to the current proccess =(");
++ return false;
++ }
+ }
+
+ if (FAILED(DllImporter::SetCurrentProcessExplicitAppUserModelID(_aumi.c_str()))) {
+@@ -555,18 +561,23 @@ HRESULT WinToast::validateShellLinkHelper(_Out_ bool& wasChanged) {
+ hr = DllImporter::PropVariantToString(appIdPropVar, AUMI, MAX_PATH);
+ wasChanged = false;
+ if (FAILED(hr) || _aumi != AUMI) {
+- // AUMI Changed for the same app, let's update the current value! =)
+- wasChanged = true;
+- PropVariantClear(&appIdPropVar);
+- hr = InitPropVariantFromString(_aumi.c_str(), &appIdPropVar);
+- if (SUCCEEDED(hr)) {
+- hr = propertyStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar);
++ if (_shortcutPolicy == SHORTCUT_POLICY_REQUIRE_CREATE) {
++ // AUMI Changed for the same app, let's update the current value! =)
++ wasChanged = true;
++ PropVariantClear(&appIdPropVar);
++ hr = InitPropVariantFromString(_aumi.c_str(), &appIdPropVar);
+ if (SUCCEEDED(hr)) {
+- hr = propertyStore->Commit();
+- if (SUCCEEDED(hr) && SUCCEEDED(persistFile->IsDirty())) {
+- hr = persistFile->Save(path, TRUE);
++ hr = propertyStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar);
++ if (SUCCEEDED(hr)) {
++ hr = propertyStore->Commit();
++ if (SUCCEEDED(hr) && SUCCEEDED(persistFile->IsDirty())) {
++ hr = persistFile->Save(path, TRUE);
++ }
+ }
+ }
++ } else {
++ // Not allowed to touch the shortcut to fix the AUMI
++ hr = E_FAIL;
+ }
+ }
+ PropVariantClear(&appIdPropVar);
+@@ -581,6 +592,10 @@ HRESULT WinToast::validateShellLinkHelper(_Out_ bool& wasChanged) {
+
+
+ HRESULT WinToast::createShellLinkHelper() {
++ if (_shortcutPolicy != SHORTCUT_POLICY_REQUIRE_CREATE) {
++ return E_FAIL;
++ }
++
+ WCHAR exePath[MAX_PATH]{L'\0'};
+ WCHAR slPath[MAX_PATH]{L'\0'};
+ Util::defaultShellLinkPath(_appName, slPath);
+diff --git a/src/wintoastlib.h b/src/wintoastlib.h
+index 68b1cb1..dc8d745 100644
+--- a/src/wintoastlib.h
++++ b/src/wintoastlib.h
+@@ -173,6 +173,16 @@ namespace WinToastLib {
+ SHORTCUT_CREATE_FAILED = -4
+ };
+
++ enum ShortcutPolicy {
++ /* Don't check, create, or modify a shortcut. */
++ SHORTCUT_POLICY_IGNORE = 0,
++ /* Require a shortcut with matching AUMI, don't create or modify an existing one. */
++ SHORTCUT_POLICY_REQUIRE_NO_CREATE = 1,
++ /* Require a shortcut with matching AUMI, create if missing, modify if not matching.
++ * This is the default. */
++ SHORTCUT_POLICY_REQUIRE_CREATE = 2,
++ };
++
+ WinToast(void);
+ virtual ~WinToast();
+ static WinToast* instance();
+@@ -194,10 +204,12 @@ namespace WinToastLib {
+ const std::wstring& appUserModelId() const;
+ void setAppUserModelId(_In_ const std::wstring& aumi);
+ void setAppName(_In_ const std::wstring& appName);
++ void setShortcutPolicy(_In_ ShortcutPolicy policy);
+
+ protected:
+ bool _isInitialized{false};
+ bool _hasCoInitialized{false};
++ ShortcutPolicy _shortcutPolicy{SHORTCUT_POLICY_REQUIRE_CREATE};
+ std::wstring _appName{};
+ std::wstring _aumi{};
+ std::map<INT64, ComPtr<IToastNotification>> _buffer{};
diff --git a/third_party/WinToast/moz.yaml b/third_party/WinToast/moz.yaml
new file mode 100644
index 0000000000..7a27bf29ad
--- /dev/null
+++ b/third_party/WinToast/moz.yaml
@@ -0,0 +1,37 @@
+# Version of this schema
+schema: 1
+
+# Manual Update can be done with:
+# cd third_pary/WinToast
+# wget https://raw.githubusercontent.com/mohabouje/WinToast/master/src/wintoastlib.cpp
+# wget https://raw.githubusercontent.com/mohabouje/WinToast/master/src/wintoastlib.h
+# patch -p2 < moz-check-system-shortcut.patch
+# patch -p2 < moz-disable-create-shortcut.patch
+# patch -p2 < upstream-add-toast-scenario.patch
+
+bugzilla:
+ # Bugzilla product and component for this directory and subdirectories
+ product: Toolkit
+ component: "General"
+
+# Document the source of externally hosted code
+origin:
+
+ # Short name of the package/library
+ name: WinToast
+
+ description: WinToast is a lightly library written in C++ which brings a complete integration of the modern toast notifications of Windows 8 & Windows 10.
+
+ # Full URL for the package's homepage/etc
+ # Usually different from repository url
+ url: https://github.com/mohabouje/WinToast
+
+ # Human-readable identifier for this version/release
+ # Generally "version NNN", "tag SSS", "bookmark SSS"
+ release: commit 09227c72f16ccefc36e9d430dea3b435346dbcbc
+
+ # The package's license, where possible using the mnemonic from
+ # https://spdx.org/licenses/
+ # Multiple licenses can be specified (as a YAML list)
+ # A "LICENSE" file must exist containing the full license text
+ license: MIT
diff --git a/third_party/WinToast/upstream-add-toast-scenario.patch b/third_party/WinToast/upstream-add-toast-scenario.patch
new file mode 100644
index 0000000000..0be8bf878d
--- /dev/null
+++ b/third_party/WinToast/upstream-add-toast-scenario.patch
@@ -0,0 +1,123 @@
+diff --git a/src/wintoastlib.cpp b/src/wintoastlib.cpp
+index 3cf5f21..1adfe19 100644
+--- a/src/wintoastlib.cpp
++++ b/src/wintoastlib.cpp
+@@ -677,6 +677,10 @@ INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHan
+ (toast.duration() == WinToastTemplate::Duration::Short) ? L"short" : L"long");
+ }
+
++ if (SUCCEEDED(hr)) {
++ hr = addScenarioHelper(xmlDocument.Get(), toast.scenario());
++ }
++
+ } else {
+ DEBUG_MSG("Modern features (Actions/Sounds/Attributes) not supported in this os version");
+ }
+@@ -828,6 +832,28 @@ HRESULT WinToast::addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstr
+ return hr;
+ }
+
++HRESULT WinToast::addScenarioHelper(_In_ IXmlDocument* xml, _In_ const std::wstring& scenario) {
++ ComPtr<IXmlNodeList> nodeList;
++ HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"toast").Get(), &nodeList);
++ if (SUCCEEDED(hr)) {
++ UINT32 length;
++ hr = nodeList->get_Length(&length);
++ if (SUCCEEDED(hr)) {
++ ComPtr<IXmlNode> toastNode;
++ hr = nodeList->Item(0, &toastNode);
++ if (SUCCEEDED(hr)) {
++ ComPtr<IXmlElement> toastElement;
++ hr = toastNode.As(&toastElement);
++ if (SUCCEEDED(hr)) {
++ hr = toastElement->SetAttribute(WinToastStringWrapper(L"scenario").Get(),
++ WinToastStringWrapper(scenario).Get());
++ }
++ }
++ }
++ }
++ return hr;
++}
++
+ HRESULT WinToast::setTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text, _In_ UINT32 pos) {
+ ComPtr<IXmlNodeList> nodeList;
+ HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"text").Get(), &nodeList);
+@@ -1065,6 +1091,15 @@ void WinToastTemplate::setExpiration(_In_ INT64 millisecondsFromNow) {
+ _expiration = millisecondsFromNow;
+ }
+
++void WinToastLib::WinToastTemplate::setScenario(Scenario scenario) {
++ switch (scenario) {
++ case Scenario::Default: _scenario = L"Default"; break;
++ case Scenario::Alarm: _scenario = L"Alarm"; break;
++ case Scenario::IncomingCall: _scenario = L"IncomingCall"; break;
++ case Scenario::Reminder: _scenario = L"Reminder"; break;
++ }
++}
++
+ void WinToastTemplate::setAttributionText(_In_ const std::wstring& attributionText) {
+ _attributionText = attributionText;
+ }
+@@ -1112,6 +1147,10 @@ const std::wstring& WinToastTemplate::attributionText() const {
+ return _attributionText;
+ }
+
++const std::wstring& WinToastLib::WinToastTemplate::scenario() const {
++ return _scenario;
++}
++
+ INT64 WinToastTemplate::expiration() const {
+ return _expiration;
+ }
+diff --git a/src/wintoastlib.h b/src/wintoastlib.h
+index d028994..291e15f 100644
+--- a/src/wintoastlib.h
++++ b/src/wintoastlib.h
+@@ -63,6 +63,7 @@ namespace WinToastLib {
+
+ class WinToastTemplate {
+ public:
++ enum class Scenario { Default, Alarm, IncomingCall, Reminder };
+ enum Duration { System, Short, Long };
+ enum AudioOption { Default = 0, Silent, Loop };
+ enum TextField { FirstLine = 0, SecondLine, ThirdLine };
+@@ -114,13 +115,14 @@ namespace WinToastLib {
+ void setSecondLine(_In_ const std::wstring& text);
+ void setThirdLine(_In_ const std::wstring& text);
+ void setTextField(_In_ const std::wstring& txt, _In_ TextField pos);
+- void setAttributionText(_In_ const std::wstring & attributionText);
++ void setAttributionText(_In_ const std::wstring& attributionText);
+ void setImagePath(_In_ const std::wstring& imgPath);
+ void setAudioPath(_In_ WinToastTemplate::AudioSystemFile audio);
+ void setAudioPath(_In_ const std::wstring& audioPath);
+ void setAudioOption(_In_ WinToastTemplate::AudioOption audioOption);
+ void setDuration(_In_ Duration duration);
+ void setExpiration(_In_ INT64 millisecondsFromNow);
++ void setScenario(_In_ Scenario scenario);
+ void addAction(_In_ const std::wstring& label);
+
+ std::size_t textFieldsCount() const;
+@@ -132,6 +134,7 @@ namespace WinToastLib {
+ const std::wstring& imagePath() const;
+ const std::wstring& audioPath() const;
+ const std::wstring& attributionText() const;
++ const std::wstring& scenario() const;
+ INT64 expiration() const;
+ WinToastTemplateType type() const;
+ WinToastTemplate::AudioOption audioOption() const;
+@@ -142,6 +145,7 @@ namespace WinToastLib {
+ std::wstring _imagePath{};
+ std::wstring _audioPath{};
+ std::wstring _attributionText{};
++ std::wstring _scenario{L"Default"};
+ INT64 _expiration{0};
+ AudioOption _audioOption{WinToastTemplate::AudioOption::Default};
+ WinToastTemplateType _type{WinToastTemplateType::Text01};
+@@ -210,6 +214,7 @@ namespace WinToastLib {
+ HRESULT setAttributionTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text);
+ HRESULT addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& action, _In_ const std::wstring& arguments);
+ HRESULT addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& duration);
++ HRESULT addScenarioHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& scenario);
+ ComPtr<IToastNotifier> notifier(_In_ bool* succeded) const;
+ void setError(_Out_opt_ WinToastError* error, _In_ WinToastError value);
+ };
diff --git a/third_party/WinToast/wintoastlib.cpp b/third_party/WinToast/wintoastlib.cpp
new file mode 100644
index 0000000000..ea5648a61d
--- /dev/null
+++ b/third_party/WinToast/wintoastlib.cpp
@@ -0,0 +1,1197 @@
+/* * Copyright (C) 2016-2019 Mohammed Boujemaoui <mohabouje@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE 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 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "wintoastlib.h"
+#include <memory>
+#include <assert.h>
+#include <unordered_map>
+#include <array>
+
+#pragma comment(lib,"shlwapi")
+#pragma comment(lib,"user32")
+
+#ifdef NDEBUG
+ #define DEBUG_MSG(str) do { } while ( false )
+#else
+ #define DEBUG_MSG(str) do { std::wcout << str << std::endl; } while( false )
+#endif
+
+#define DEFAULT_SHELL_LINKS_PATH L"\\Microsoft\\Windows\\Start Menu\\Programs\\"
+#define DEFAULT_LINK_FORMAT L".lnk"
+#define STATUS_SUCCESS (0x00000000)
+
+
+// Quickstart: Handling toast activations from Win32 apps in Windows 10
+// https://blogs.msdn.microsoft.com/tiles_and_toasts/2015/10/16/quickstart-handling-toast-activations-from-win32-apps-in-windows-10/
+using namespace WinToastLib;
+namespace DllImporter {
+
+ // Function load a function from library
+ template <typename Function>
+ HRESULT loadFunctionFromLibrary(HINSTANCE library, LPCSTR name, Function &func) {
+ if (!library) {
+ return E_INVALIDARG;
+ }
+ func = reinterpret_cast<Function>(GetProcAddress(library, name));
+ return (func != nullptr) ? S_OK : E_FAIL;
+ }
+
+ typedef HRESULT(FAR STDAPICALLTYPE *f_SetCurrentProcessExplicitAppUserModelID)(__in PCWSTR AppID);
+ typedef HRESULT(FAR STDAPICALLTYPE *f_PropVariantToString)(_In_ REFPROPVARIANT propvar, _Out_writes_(cch) PWSTR psz, _In_ UINT cch);
+ typedef HRESULT(FAR STDAPICALLTYPE *f_RoGetActivationFactory)(_In_ HSTRING activatableClassId, _In_ REFIID iid, _COM_Outptr_ void ** factory);
+ typedef HRESULT(FAR STDAPICALLTYPE *f_WindowsCreateStringReference)(_In_reads_opt_(length + 1) PCWSTR sourceString, UINT32 length, _Out_ HSTRING_HEADER * hstringHeader, _Outptr_result_maybenull_ _Result_nullonfailure_ HSTRING * string);
+ typedef PCWSTR(FAR STDAPICALLTYPE *f_WindowsGetStringRawBuffer)(_In_ HSTRING string, _Out_ UINT32 *length);
+ typedef HRESULT(FAR STDAPICALLTYPE *f_WindowsDeleteString)(_In_opt_ HSTRING string);
+
+ static f_SetCurrentProcessExplicitAppUserModelID SetCurrentProcessExplicitAppUserModelID;
+ static f_PropVariantToString PropVariantToString;
+ static f_RoGetActivationFactory RoGetActivationFactory;
+ static f_WindowsCreateStringReference WindowsCreateStringReference;
+ static f_WindowsGetStringRawBuffer WindowsGetStringRawBuffer;
+ static f_WindowsDeleteString WindowsDeleteString;
+
+
+ template<class T>
+ _Check_return_ __inline HRESULT _1_GetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ T** factory) {
+ return RoGetActivationFactory(activatableClassId, IID_INS_ARGS(factory));
+ }
+
+ template<typename T>
+ inline HRESULT Wrap_GetActivationFactory(_In_ HSTRING activatableClassId, _Inout_ Details::ComPtrRef<T> factory) noexcept {
+ return _1_GetActivationFactory(activatableClassId, factory.ReleaseAndGetAddressOf());
+ }
+
+ inline HRESULT initialize() {
+ HINSTANCE LibShell32 = LoadLibraryW(L"SHELL32.DLL");
+ HRESULT hr = loadFunctionFromLibrary(LibShell32, "SetCurrentProcessExplicitAppUserModelID", SetCurrentProcessExplicitAppUserModelID);
+ if (SUCCEEDED(hr)) {
+ HINSTANCE LibPropSys = LoadLibraryW(L"PROPSYS.DLL");
+ hr = loadFunctionFromLibrary(LibPropSys, "PropVariantToString", PropVariantToString);
+ if (SUCCEEDED(hr)) {
+ HINSTANCE LibComBase = LoadLibraryW(L"COMBASE.DLL");
+ const bool succeded = SUCCEEDED(loadFunctionFromLibrary(LibComBase, "RoGetActivationFactory", RoGetActivationFactory))
+ && SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsCreateStringReference", WindowsCreateStringReference))
+ && SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsGetStringRawBuffer", WindowsGetStringRawBuffer))
+ && SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsDeleteString", WindowsDeleteString));
+ return succeded ? S_OK : E_FAIL;
+ }
+ }
+ return hr;
+ }
+}
+
+class WinToastStringWrapper {
+public:
+ WinToastStringWrapper(_In_reads_(length) PCWSTR stringRef, _In_ UINT32 length) noexcept {
+ HRESULT hr = DllImporter::WindowsCreateStringReference(stringRef, length, &_header, &_hstring);
+ if (!SUCCEEDED(hr)) {
+ RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr);
+ }
+ }
+
+ WinToastStringWrapper(_In_ const std::wstring &stringRef) noexcept {
+ HRESULT hr = DllImporter::WindowsCreateStringReference(stringRef.c_str(), static_cast<UINT32>(stringRef.length()), &_header, &_hstring);
+ if (FAILED(hr)) {
+ RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr);
+ }
+ }
+
+ ~WinToastStringWrapper() {
+ DllImporter::WindowsDeleteString(_hstring);
+ }
+
+ inline HSTRING Get() const noexcept {
+ return _hstring;
+ }
+private:
+ HSTRING _hstring;
+ HSTRING_HEADER _header;
+
+};
+
+class InternalDateTime : public IReference<DateTime> {
+public:
+ static INT64 Now() {
+ FILETIME now;
+ GetSystemTimeAsFileTime(&now);
+ return ((((INT64)now.dwHighDateTime) << 32) | now.dwLowDateTime);
+ }
+
+ InternalDateTime(DateTime dateTime) : _dateTime(dateTime) {}
+
+ InternalDateTime(INT64 millisecondsFromNow) {
+ _dateTime.UniversalTime = Now() + millisecondsFromNow * 10000;
+ }
+
+ virtual ~InternalDateTime() = default;
+
+ operator INT64() {
+ return _dateTime.UniversalTime;
+ }
+
+ HRESULT STDMETHODCALLTYPE get_Value(DateTime *dateTime) {
+ *dateTime = _dateTime;
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE QueryInterface(const IID& riid, void** ppvObject) {
+ if (!ppvObject) {
+ return E_POINTER;
+ }
+ if (riid == __uuidof(IUnknown) || riid == __uuidof(IReference<DateTime>)) {
+ *ppvObject = static_cast<IUnknown*>(static_cast<IReference<DateTime>*>(this));
+ return S_OK;
+ }
+ return E_NOINTERFACE;
+ }
+
+ ULONG STDMETHODCALLTYPE Release() {
+ return 1;
+ }
+
+ ULONG STDMETHODCALLTYPE AddRef() {
+ return 2;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetIids(ULONG*, IID**) {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetRuntimeClassName(HSTRING*) {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetTrustLevel(TrustLevel*) {
+ return E_NOTIMPL;
+ }
+
+protected:
+ DateTime _dateTime;
+};
+
+namespace Util {
+
+ typedef LONG NTSTATUS, *PNTSTATUS;
+ typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
+ inline RTL_OSVERSIONINFOW getRealOSVersion() {
+ HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll");
+ if (hMod) {
+ RtlGetVersionPtr fxPtr = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion");
+ if (fxPtr != nullptr) {
+ RTL_OSVERSIONINFOW rovi = { 0 };
+ rovi.dwOSVersionInfoSize = sizeof(rovi);
+ if (STATUS_SUCCESS == fxPtr(&rovi)) {
+ return rovi;
+ }
+ }
+ }
+ RTL_OSVERSIONINFOW rovi = { 0 };
+ return rovi;
+ }
+
+ inline HRESULT defaultExecutablePath(_In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) {
+ DWORD written = GetModuleFileNameExW(GetCurrentProcess(), nullptr, path, nSize);
+ DEBUG_MSG("Default executable path: " << path);
+ return (written > 0) ? S_OK : E_FAIL;
+ }
+
+
+ inline HRESULT commonShellLinksDirectory(_In_ const WCHAR* baseEnv, _In_ WCHAR* path, _In_ DWORD nSize) {
+ DWORD written = GetEnvironmentVariableW(baseEnv, path, nSize);
+ HRESULT hr = written > 0 ? S_OK : E_INVALIDARG;
+ if (SUCCEEDED(hr)) {
+ errno_t result = wcscat_s(path, nSize, DEFAULT_SHELL_LINKS_PATH);
+ hr = (result == 0) ? S_OK : E_INVALIDARG;
+ DEBUG_MSG("Default shell link path: " << path);
+ }
+ return hr;
+ }
+
+ inline HRESULT commonShellLinkPath(_In_ const WCHAR* baseEnv, const std::wstring& appname, _In_ WCHAR* path, _In_ DWORD nSize) {
+ HRESULT hr = commonShellLinksDirectory(baseEnv, path, nSize);
+ if (SUCCEEDED(hr)) {
+ const std::wstring appLink(appname + DEFAULT_LINK_FORMAT);
+ errno_t result = wcscat_s(path, nSize, appLink.c_str());
+ hr = (result == 0) ? S_OK : E_INVALIDARG;
+ DEBUG_MSG("Default shell link file path: " << path);
+ }
+ return hr;
+ }
+
+ inline HRESULT defaultUserShellLinkPath(const std::wstring& appname, _In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) {
+ return commonShellLinkPath(L"APPDATA", appname, path, nSize);
+ }
+
+ inline HRESULT defaultSystemShellLinkPath(const std::wstring& appname, _In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) {
+ return commonShellLinkPath(L"PROGRAMDATA", appname, path, nSize);
+ }
+
+ inline PCWSTR AsString(ComPtr<IXmlDocument> &xmlDocument) {
+ HSTRING xml;
+ ComPtr<IXmlNodeSerializer> ser;
+ HRESULT hr = xmlDocument.As<IXmlNodeSerializer>(&ser);
+ hr = ser->GetXml(&xml);
+ if (SUCCEEDED(hr))
+ return DllImporter::WindowsGetStringRawBuffer(xml, nullptr);
+ return nullptr;
+ }
+
+ inline PCWSTR AsString(HSTRING hstring) {
+ return DllImporter::WindowsGetStringRawBuffer(hstring, nullptr);
+ }
+
+ inline HRESULT setNodeStringValue(const std::wstring& string, IXmlNode *node, IXmlDocument *xml) {
+ ComPtr<IXmlText> textNode;
+ HRESULT hr = xml->CreateTextNode( WinToastStringWrapper(string).Get(), &textNode);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> stringNode;
+ hr = textNode.As(&stringNode);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> appendedChild;
+ hr = node->AppendChild(stringNode.Get(), &appendedChild);
+ }
+ }
+ return hr;
+ }
+
+ inline HRESULT setEventHandlers(_In_ IToastNotification* notification, _In_ std::shared_ptr<IWinToastHandler> eventHandler, _In_ INT64 expirationTime) {
+ EventRegistrationToken activatedToken, dismissedToken, failedToken;
+ HRESULT hr = notification->add_Activated(
+ Callback < Implements < RuntimeClassFlags<ClassicCom>,
+ ITypedEventHandler<ToastNotification*, IInspectable* >> >(
+ [eventHandler](IToastNotification*, IInspectable* inspectable)
+ {
+ IToastActivatedEventArgs *activatedEventArgs;
+ HRESULT hr = inspectable->QueryInterface(&activatedEventArgs);
+ if (SUCCEEDED(hr)) {
+ HSTRING argumentsHandle;
+ hr = activatedEventArgs->get_Arguments(&argumentsHandle);
+ if (SUCCEEDED(hr)) {
+ PCWSTR arguments = Util::AsString(argumentsHandle);
+ if (arguments && *arguments) {
+ eventHandler->toastActivated(static_cast<int>(wcstol(arguments, nullptr, 10)));
+ return S_OK;
+ }
+ }
+ }
+ eventHandler->toastActivated();
+ return S_OK;
+ }).Get(), &activatedToken);
+
+ if (SUCCEEDED(hr)) {
+ hr = notification->add_Dismissed(Callback < Implements < RuntimeClassFlags<ClassicCom>,
+ ITypedEventHandler<ToastNotification*, ToastDismissedEventArgs* >> >(
+ [eventHandler, expirationTime](IToastNotification*, IToastDismissedEventArgs* e)
+ {
+ ToastDismissalReason reason;
+ if (SUCCEEDED(e->get_Reason(&reason)))
+ {
+ if (reason == ToastDismissalReason_UserCanceled && expirationTime && InternalDateTime::Now() >= expirationTime)
+ reason = ToastDismissalReason_TimedOut;
+ eventHandler->toastDismissed(static_cast<IWinToastHandler::WinToastDismissalReason>(reason));
+ }
+ return S_OK;
+ }).Get(), &dismissedToken);
+ if (SUCCEEDED(hr)) {
+ hr = notification->add_Failed(Callback < Implements < RuntimeClassFlags<ClassicCom>,
+ ITypedEventHandler<ToastNotification*, ToastFailedEventArgs* >> >(
+ [eventHandler](IToastNotification*, IToastFailedEventArgs*)
+ {
+ eventHandler->toastFailed();
+ return S_OK;
+ }).Get(), &failedToken);
+ }
+ }
+ return hr;
+ }
+
+ inline HRESULT addAttribute(_In_ IXmlDocument *xml, const std::wstring &name, IXmlNamedNodeMap *attributeMap) {
+ ComPtr<ABI::Windows::Data::Xml::Dom::IXmlAttribute> srcAttribute;
+ HRESULT hr = xml->CreateAttribute(WinToastStringWrapper(name).Get(), &srcAttribute);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> node;
+ hr = srcAttribute.As(&node);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> pNode;
+ hr = attributeMap->SetNamedItem(node.Get(), &pNode);
+ }
+ }
+ return hr;
+ }
+
+ inline HRESULT createElement(_In_ IXmlDocument *xml, _In_ const std::wstring& root_node, _In_ const std::wstring& element_name, _In_ const std::vector<std::wstring>& attribute_names) {
+ ComPtr<IXmlNodeList> rootList;
+ HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(root_node).Get(), &rootList);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> root;
+ hr = rootList->Item(0, &root);
+ if (SUCCEEDED(hr)) {
+ ComPtr<ABI::Windows::Data::Xml::Dom::IXmlElement> audioElement;
+ hr = xml->CreateElement(WinToastStringWrapper(element_name).Get(), &audioElement);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> audioNodeTmp;
+ hr = audioElement.As(&audioNodeTmp);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> audioNode;
+ hr = root->AppendChild(audioNodeTmp.Get(), &audioNode);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNamedNodeMap> attributes;
+ hr = audioNode->get_Attributes(&attributes);
+ if (SUCCEEDED(hr)) {
+ for (const auto& it : attribute_names) {
+ hr = addAttribute(xml, it, attributes.Get());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return hr;
+ }
+}
+
+WinToast* WinToast::instance() {
+ static WinToast instance;
+ return &instance;
+}
+
+WinToast::WinToast() :
+ _isInitialized(false),
+ _hasCoInitialized(false)
+{
+ if (!isCompatible()) {
+ DEBUG_MSG(L"Warning: Your system is not compatible with this library ");
+ }
+}
+
+WinToast::~WinToast() {
+ if (_hasCoInitialized) {
+ CoUninitialize();
+ }
+}
+
+void WinToast::setAppName(_In_ const std::wstring& appName) {
+ _appName = appName;
+}
+
+
+void WinToast::setAppUserModelId(_In_ const std::wstring& aumi) {
+ _aumi = aumi;
+ DEBUG_MSG(L"Default App User Model Id: " << _aumi.c_str());
+}
+
+void WinToast::setShortcutPolicy(_In_ ShortcutPolicy shortcutPolicy) {
+ _shortcutPolicy = shortcutPolicy;
+}
+
+bool WinToast::isCompatible() {
+ DllImporter::initialize();
+ return !((DllImporter::SetCurrentProcessExplicitAppUserModelID == nullptr)
+ || (DllImporter::PropVariantToString == nullptr)
+ || (DllImporter::RoGetActivationFactory == nullptr)
+ || (DllImporter::WindowsCreateStringReference == nullptr)
+ || (DllImporter::WindowsDeleteString == nullptr));
+}
+
+bool WinToastLib::WinToast::isSupportingModernFeatures() {
+ constexpr auto MinimumSupportedVersion = 6;
+ return Util::getRealOSVersion().dwMajorVersion > MinimumSupportedVersion;
+
+}
+std::wstring WinToast::configureAUMI(_In_ const std::wstring &companyName,
+ _In_ const std::wstring &productName,
+ _In_ const std::wstring &subProduct,
+ _In_ const std::wstring &versionInformation)
+{
+ std::wstring aumi = companyName;
+ aumi += L"." + productName;
+ if (subProduct.length() > 0) {
+ aumi += L"." + subProduct;
+ if (versionInformation.length() > 0) {
+ aumi += L"." + versionInformation;
+ }
+ }
+
+ if (aumi.length() > SCHAR_MAX) {
+ DEBUG_MSG("Error: max size allowed for AUMI: 128 characters.");
+ }
+ return aumi;
+}
+
+const std::wstring& WinToast::strerror(WinToastError error) {
+ static const std::unordered_map<WinToastError, std::wstring> Labels = {
+ {WinToastError::NoError, L"No error. The process was executed correctly"},
+ {WinToastError::NotInitialized, L"The library has not been initialized"},
+ {WinToastError::SystemNotSupported, L"The OS does not support WinToast"},
+ {WinToastError::ShellLinkNotCreated, L"The library was not able to create a Shell Link for the app"},
+ {WinToastError::InvalidAppUserModelID, L"The AUMI is not a valid one"},
+ {WinToastError::InvalidParameters, L"The parameters used to configure the library are not valid normally because an invalid AUMI or App Name"},
+ {WinToastError::NotDisplayed, L"The toast was created correctly but WinToast was not able to display the toast"},
+ {WinToastError::UnknownError, L"Unknown error"}
+ };
+
+ const auto iter = Labels.find(error);
+ assert(iter != Labels.end());
+ return iter->second;
+}
+
+enum WinToast::ShortcutResult WinToast::createShortcut() {
+ if (_aumi.empty() || _appName.empty()) {
+ DEBUG_MSG(L"Error: App User Model Id or Appname is empty!");
+ return SHORTCUT_MISSING_PARAMETERS;
+ }
+
+ if (!isCompatible()) {
+ DEBUG_MSG(L"Your OS is not compatible with this library! =(");
+ return SHORTCUT_INCOMPATIBLE_OS;
+ }
+
+ if (!_hasCoInitialized) {
+ HRESULT initHr = CoInitializeEx(nullptr, COINIT::COINIT_MULTITHREADED);
+ if (initHr != RPC_E_CHANGED_MODE) {
+ if (FAILED(initHr) && initHr != S_FALSE) {
+ DEBUG_MSG(L"Error on COM library initialization!");
+ return SHORTCUT_COM_INIT_FAILURE;
+ }
+ else {
+ _hasCoInitialized = true;
+ }
+ }
+ }
+
+ bool wasChanged;
+ HRESULT hr = validateShellLinkHelper(wasChanged);
+ if (SUCCEEDED(hr))
+ return wasChanged ? SHORTCUT_WAS_CHANGED : SHORTCUT_UNCHANGED;
+
+ hr = createShellLinkHelper();
+ return SUCCEEDED(hr) ? SHORTCUT_WAS_CREATED : SHORTCUT_CREATE_FAILED;
+}
+
+bool WinToast::initialize(_Out_ WinToastError* error) {
+ _isInitialized = false;
+ setError(error, WinToastError::NoError);
+
+ if (!isCompatible()) {
+ setError(error, WinToastError::SystemNotSupported);
+ DEBUG_MSG(L"Error: system not supported.");
+ return false;
+ }
+
+
+ if (_aumi.empty() || _appName.empty()) {
+ setError(error, WinToastError::InvalidParameters);
+ DEBUG_MSG(L"Error while initializing, did you set up a valid AUMI and App name?");
+ return false;
+ }
+
+ if (_shortcutPolicy != SHORTCUT_POLICY_IGNORE) {
+ if (createShortcut() < 0) {
+ setError(error, WinToastError::ShellLinkNotCreated);
+ DEBUG_MSG(L"Error while attaching the AUMI to the current proccess =(");
+ return false;
+ }
+ }
+
+ if (FAILED(DllImporter::SetCurrentProcessExplicitAppUserModelID(_aumi.c_str()))) {
+ setError(error, WinToastError::InvalidAppUserModelID);
+ DEBUG_MSG(L"Error while attaching the AUMI to the current proccess =(");
+ return false;
+ }
+
+ _isInitialized = true;
+ return _isInitialized;
+}
+
+bool WinToast::isInitialized() const {
+ return _isInitialized;
+}
+
+const std::wstring& WinToast::appName() const {
+ return _appName;
+}
+
+const std::wstring& WinToast::appUserModelId() const {
+ return _aumi;
+}
+
+
+HRESULT WinToast::validateShellLinkHelper(_Out_ bool& wasChanged) {
+ WCHAR path[MAX_PATH] = { L'\0' };
+ Util::defaultUserShellLinkPath(_appName, path);
+ // Check if the file exist
+ DWORD attr = GetFileAttributesW(path);
+ if (attr >= 0xFFFFFFF) {
+ // The shortcut may be in the system Start Menu.
+ WCHAR systemPath[MAX_PATH] = { L'\0' };
+ Util::defaultSystemShellLinkPath(_appName, systemPath);
+ attr = GetFileAttributesW(systemPath);
+ if (attr >= 0xFFFFFFF) {
+ DEBUG_MSG("Error, shell link not found. Try to create a new one in: " << path);
+ return E_FAIL;
+ }
+ wcscpy(path, systemPath);
+ }
+
+ // Let's load the file as shell link to validate.
+ // - Create a shell link
+ // - Create a persistant file
+ // - Load the path as data for the persistant file
+ // - Read the property AUMI and validate with the current
+ // - Review if AUMI is equal.
+ ComPtr<IShellLink> shellLink;
+ HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink));
+ if (SUCCEEDED(hr)) {
+ ComPtr<IPersistFile> persistFile;
+ hr = shellLink.As(&persistFile);
+ if (SUCCEEDED(hr)) {
+ hr = persistFile->Load(path, STGM_READ);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IPropertyStore> propertyStore;
+ hr = shellLink.As(&propertyStore);
+ if (SUCCEEDED(hr)) {
+ PROPVARIANT appIdPropVar;
+ hr = propertyStore->GetValue(PKEY_AppUserModel_ID, &appIdPropVar);
+ if (SUCCEEDED(hr)) {
+ WCHAR AUMI[MAX_PATH];
+ hr = DllImporter::PropVariantToString(appIdPropVar, AUMI, MAX_PATH);
+ wasChanged = false;
+ if (FAILED(hr) || _aumi != AUMI) {
+ if (_shortcutPolicy == SHORTCUT_POLICY_REQUIRE_CREATE) {
+ // AUMI Changed for the same app, let's update the current value! =)
+ wasChanged = true;
+ PropVariantClear(&appIdPropVar);
+ hr = InitPropVariantFromString(_aumi.c_str(), &appIdPropVar);
+ if (SUCCEEDED(hr)) {
+ hr = propertyStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar);
+ if (SUCCEEDED(hr)) {
+ hr = propertyStore->Commit();
+ if (SUCCEEDED(hr) && SUCCEEDED(persistFile->IsDirty())) {
+ hr = persistFile->Save(path, TRUE);
+ }
+ }
+ }
+ } else {
+ // Not allowed to touch the shortcut to fix the AUMI
+ hr = E_FAIL;
+ }
+ }
+ PropVariantClear(&appIdPropVar);
+ }
+ }
+ }
+ }
+ }
+ return hr;
+}
+
+
+
+HRESULT WinToast::createShellLinkHelper() {
+ if (_shortcutPolicy != SHORTCUT_POLICY_REQUIRE_CREATE) {
+ return E_FAIL;
+ }
+
+ WCHAR exePath[MAX_PATH]{L'\0'};
+ WCHAR slPath[MAX_PATH]{L'\0'};
+ Util::defaultUserShellLinkPath(_appName, slPath);
+ Util::defaultExecutablePath(exePath);
+ ComPtr<IShellLinkW> shellLink;
+ HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink));
+ if (SUCCEEDED(hr)) {
+ hr = shellLink->SetPath(exePath);
+ if (SUCCEEDED(hr)) {
+ hr = shellLink->SetArguments(L"");
+ if (SUCCEEDED(hr)) {
+ hr = shellLink->SetWorkingDirectory(exePath);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IPropertyStore> propertyStore;
+ hr = shellLink.As(&propertyStore);
+ if (SUCCEEDED(hr)) {
+ PROPVARIANT appIdPropVar;
+ hr = InitPropVariantFromString(_aumi.c_str(), &appIdPropVar);
+ if (SUCCEEDED(hr)) {
+ hr = propertyStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar);
+ if (SUCCEEDED(hr)) {
+ hr = propertyStore->Commit();
+ if (SUCCEEDED(hr)) {
+ ComPtr<IPersistFile> persistFile;
+ hr = shellLink.As(&persistFile);
+ if (SUCCEEDED(hr)) {
+ hr = persistFile->Save(slPath, TRUE);
+ }
+ }
+ }
+ PropVariantClear(&appIdPropVar);
+ }
+ }
+ }
+ }
+ }
+ }
+ return hr;
+}
+
+INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHandler* handler, _Out_ WinToastError* error) {
+ setError(error, WinToastError::NoError);
+ INT64 id = -1;
+ if (!isInitialized()) {
+ setError(error, WinToastError::NotInitialized);
+ DEBUG_MSG("Error when launching the toast. WinToast is not initialized.");
+ return id;
+ }
+ if (!handler) {
+ setError(error, WinToastError::InvalidHandler);
+ DEBUG_MSG("Error when launching the toast. Handler cannot be nullptr.");
+ return id;
+ }
+
+ ComPtr<IToastNotificationManagerStatics> notificationManager;
+ HRESULT hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &notificationManager);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IToastNotifier> notifier;
+ hr = notificationManager->CreateToastNotifierWithId(WinToastStringWrapper(_aumi).Get(), &notifier);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IToastNotificationFactory> notificationFactory;
+ hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), &notificationFactory);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlDocument> xmlDocument;
+ HRESULT hr = notificationManager->GetTemplateContent(ToastTemplateType(toast.type()), &xmlDocument);
+ if (SUCCEEDED(hr)) {
+ for (std::size_t i = 0, fieldsCount = toast.textFieldsCount(); i < fieldsCount && SUCCEEDED(hr); i++) {
+ hr = setTextFieldHelper(xmlDocument.Get(), toast.textField(WinToastTemplate::TextField(i)), i);
+ }
+
+ // Modern feature are supported Windows > Windows 10
+ if (SUCCEEDED(hr) && isSupportingModernFeatures()) {
+
+ // Note that we do this *after* using toast.textFieldsCount() to
+ // iterate/fill the template's text fields, since we're adding yet another text field.
+ if (SUCCEEDED(hr)
+ && !toast.attributionText().empty()) {
+ hr = setAttributionTextFieldHelper(xmlDocument.Get(), toast.attributionText());
+ }
+
+ std::array<WCHAR, 12> buf;
+ for (std::size_t i = 0, actionsCount = toast.actionsCount(); i < actionsCount && SUCCEEDED(hr); i++) {
+ _snwprintf_s(buf.data(), buf.size(), _TRUNCATE, L"%zd", i);
+ hr = addActionHelper(xmlDocument.Get(), toast.actionLabel(i), buf.data());
+ }
+
+ if (SUCCEEDED(hr)) {
+ hr = (toast.audioPath().empty() && toast.audioOption() == WinToastTemplate::AudioOption::Default)
+ ? hr : setAudioFieldHelper(xmlDocument.Get(), toast.audioPath(), toast.audioOption());
+ }
+
+ if (SUCCEEDED(hr) && toast.duration() != WinToastTemplate::Duration::System) {
+ hr = addDurationHelper(xmlDocument.Get(),
+ (toast.duration() == WinToastTemplate::Duration::Short) ? L"short" : L"long");
+ }
+
+ if (SUCCEEDED(hr)) {
+ hr = addScenarioHelper(xmlDocument.Get(), toast.scenario());
+ }
+
+ } else {
+ DEBUG_MSG("Modern features (Actions/Sounds/Attributes) not supported in this os version");
+ }
+
+ if (SUCCEEDED(hr)) {
+ hr = toast.hasImage() ? setImageFieldHelper(xmlDocument.Get(), toast.imagePath()) : hr;
+ if (SUCCEEDED(hr)) {
+ ComPtr<IToastNotification> notification;
+ hr = notificationFactory->CreateToastNotification(xmlDocument.Get(), &notification);
+ if (SUCCEEDED(hr)) {
+ INT64 expiration = 0, relativeExpiration = toast.expiration();
+ if (relativeExpiration > 0) {
+ InternalDateTime expirationDateTime(relativeExpiration);
+ expiration = expirationDateTime;
+ hr = notification->put_ExpirationTime(&expirationDateTime);
+ }
+
+ if (SUCCEEDED(hr)) {
+ hr = Util::setEventHandlers(notification.Get(), std::shared_ptr<IWinToastHandler>(handler), expiration);
+ if (FAILED(hr)) {
+ setError(error, WinToastError::InvalidHandler);
+ }
+ }
+
+ if (SUCCEEDED(hr)) {
+ GUID guid;
+ hr = CoCreateGuid(&guid);
+ if (SUCCEEDED(hr)) {
+ id = guid.Data1;
+ _buffer[id] = notification;
+ DEBUG_MSG("xml: " << Util::AsString(xmlDocument));
+ hr = notifier->Show(notification.Get());
+ if (FAILED(hr)) {
+ setError(error, WinToastError::NotDisplayed);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return FAILED(hr) ? -1 : id;
+}
+
+ComPtr<IToastNotifier> WinToast::notifier(_In_ bool* succeded) const {
+ ComPtr<IToastNotificationManagerStatics> notificationManager;
+ ComPtr<IToastNotifier> notifier;
+ HRESULT hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &notificationManager);
+ if (SUCCEEDED(hr)) {
+ hr = notificationManager->CreateToastNotifierWithId(WinToastStringWrapper(_aumi).Get(), &notifier);
+ }
+ *succeded = SUCCEEDED(hr);
+ return notifier;
+}
+
+bool WinToast::hideToast(_In_ INT64 id) {
+ if (!isInitialized()) {
+ DEBUG_MSG("Error when hiding the toast. WinToast is not initialized.");
+ return false;
+ }
+
+ if (_buffer.find(id) != _buffer.end()) {
+ auto succeded = false;
+ auto notify = notifier(&succeded);
+ if (succeded) {
+ auto result = notify->Hide(_buffer[id].Get());
+ _buffer.erase(id);
+ return SUCCEEDED(result);
+ }
+ }
+ return false;
+}
+
+void WinToast::clear() {
+ auto succeded = false;
+ auto notify = notifier(&succeded);
+ if (succeded) {
+ auto end = _buffer.end();
+ for (auto it = _buffer.begin(); it != end; ++it) {
+ notify->Hide(it->second.Get());
+ }
+ _buffer.clear();
+ }
+}
+
+//
+// Available as of Windows 10 Anniversary Update
+// Ref: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts
+//
+// NOTE: This will add a new text field, so be aware when iterating over
+// the toast's text fields or getting a count of them.
+//
+HRESULT WinToast::setAttributionTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text) {
+ Util::createElement(xml, L"binding", L"text", { L"placement" });
+ ComPtr<IXmlNodeList> nodeList;
+ HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"text").Get(), &nodeList);
+ if (SUCCEEDED(hr)) {
+ UINT32 nodeListLength;
+ hr = nodeList->get_Length(&nodeListLength);
+ if (SUCCEEDED(hr)) {
+ for (UINT32 i = 0; i < nodeListLength; i++) {
+ ComPtr<IXmlNode> textNode;
+ hr = nodeList->Item(i, &textNode);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNamedNodeMap> attributes;
+ hr = textNode->get_Attributes(&attributes);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> editedNode;
+ if (SUCCEEDED(hr)) {
+ hr = attributes->GetNamedItem(WinToastStringWrapper(L"placement").Get(), &editedNode);
+ if (FAILED(hr) || !editedNode) {
+ continue;
+ }
+ hr = Util::setNodeStringValue(L"attribution", editedNode.Get(), xml);
+ if (SUCCEEDED(hr)) {
+ return setTextFieldHelper(xml, text, i);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return hr;
+}
+
+HRESULT WinToast::addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& duration) {
+ ComPtr<IXmlNodeList> nodeList;
+ HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"toast").Get(), &nodeList);
+ if (SUCCEEDED(hr)) {
+ UINT32 length;
+ hr = nodeList->get_Length(&length);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> toastNode;
+ hr = nodeList->Item(0, &toastNode);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlElement> toastElement;
+ hr = toastNode.As(&toastElement);
+ if (SUCCEEDED(hr)) {
+ hr = toastElement->SetAttribute(WinToastStringWrapper(L"duration").Get(),
+ WinToastStringWrapper(duration).Get());
+ }
+ }
+ }
+ }
+ return hr;
+}
+
+HRESULT WinToast::addScenarioHelper(_In_ IXmlDocument* xml, _In_ const std::wstring& scenario) {
+ ComPtr<IXmlNodeList> nodeList;
+ HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"toast").Get(), &nodeList);
+ if (SUCCEEDED(hr)) {
+ UINT32 length;
+ hr = nodeList->get_Length(&length);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> toastNode;
+ hr = nodeList->Item(0, &toastNode);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlElement> toastElement;
+ hr = toastNode.As(&toastElement);
+ if (SUCCEEDED(hr)) {
+ hr = toastElement->SetAttribute(WinToastStringWrapper(L"scenario").Get(),
+ WinToastStringWrapper(scenario).Get());
+ }
+ }
+ }
+ }
+ return hr;
+}
+
+HRESULT WinToast::setTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text, _In_ UINT32 pos) {
+ ComPtr<IXmlNodeList> nodeList;
+ HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"text").Get(), &nodeList);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> node;
+ hr = nodeList->Item(pos, &node);
+ if (SUCCEEDED(hr)) {
+ hr = Util::setNodeStringValue(text, node.Get(), xml);
+ }
+ }
+ return hr;
+}
+
+
+HRESULT WinToast::setImageFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path) {
+ assert(path.size() < MAX_PATH);
+
+ wchar_t imagePath[MAX_PATH] = L"file:///";
+ HRESULT hr = StringCchCatW(imagePath, MAX_PATH, path.c_str());
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNodeList> nodeList;
+ HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"image").Get(), &nodeList);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> node;
+ hr = nodeList->Item(0, &node);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNamedNodeMap> attributes;
+ hr = node->get_Attributes(&attributes);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> editedNode;
+ hr = attributes->GetNamedItem(WinToastStringWrapper(L"src").Get(), &editedNode);
+ if (SUCCEEDED(hr)) {
+ Util::setNodeStringValue(imagePath, editedNode.Get(), xml);
+ }
+ }
+ }
+ }
+ }
+ return hr;
+}
+
+HRESULT WinToast::setAudioFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path, _In_opt_ WinToastTemplate::AudioOption option) {
+ std::vector<std::wstring> attrs;
+ if (!path.empty()) attrs.push_back(L"src");
+ if (option == WinToastTemplate::AudioOption::Loop) attrs.push_back(L"loop");
+ if (option == WinToastTemplate::AudioOption::Silent) attrs.push_back(L"silent");
+ Util::createElement(xml, L"toast", L"audio", attrs);
+
+ ComPtr<IXmlNodeList> nodeList;
+ HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"audio").Get(), &nodeList);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> node;
+ hr = nodeList->Item(0, &node);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNamedNodeMap> attributes;
+ hr = node->get_Attributes(&attributes);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> editedNode;
+ if (!path.empty()) {
+ if (SUCCEEDED(hr)) {
+ hr = attributes->GetNamedItem(WinToastStringWrapper(L"src").Get(), &editedNode);
+ if (SUCCEEDED(hr)) {
+ hr = Util::setNodeStringValue(path, editedNode.Get(), xml);
+ }
+ }
+ }
+
+ if (SUCCEEDED(hr)) {
+ switch (option) {
+ case WinToastTemplate::AudioOption::Loop:
+ hr = attributes->GetNamedItem(WinToastStringWrapper(L"loop").Get(), &editedNode);
+ if (SUCCEEDED(hr)) {
+ hr = Util::setNodeStringValue(L"true", editedNode.Get(), xml);
+ }
+ break;
+ case WinToastTemplate::AudioOption::Silent:
+ hr = attributes->GetNamedItem(WinToastStringWrapper(L"silent").Get(), &editedNode);
+ if (SUCCEEDED(hr)) {
+ hr = Util::setNodeStringValue(L"true", editedNode.Get(), xml);
+ }
+ default:
+ break;
+ }
+ }
+ }
+ }
+ }
+ return hr;
+}
+
+HRESULT WinToast::addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& content, _In_ const std::wstring& arguments) {
+ ComPtr<IXmlNodeList> nodeList;
+ HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"actions").Get(), &nodeList);
+ if (SUCCEEDED(hr)) {
+ UINT32 length;
+ hr = nodeList->get_Length(&length);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> actionsNode;
+ if (length > 0) {
+ hr = nodeList->Item(0, &actionsNode);
+ } else {
+ hr = xml->GetElementsByTagName(WinToastStringWrapper(L"toast").Get(), &nodeList);
+ if (SUCCEEDED(hr)) {
+ hr = nodeList->get_Length(&length);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> toastNode;
+ hr = nodeList->Item(0, &toastNode);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlElement> toastElement;
+ hr = toastNode.As(&toastElement);
+ if (SUCCEEDED(hr))
+ hr = toastElement->SetAttribute(WinToastStringWrapper(L"template").Get(), WinToastStringWrapper(L"ToastGeneric").Get());
+ if (SUCCEEDED(hr))
+ hr = toastElement->SetAttribute(WinToastStringWrapper(L"duration").Get(), WinToastStringWrapper(L"long").Get());
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlElement> actionsElement;
+ hr = xml->CreateElement(WinToastStringWrapper(L"actions").Get(), &actionsElement);
+ if (SUCCEEDED(hr)) {
+ hr = actionsElement.As(&actionsNode);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> appendedChild;
+ hr = toastNode->AppendChild(actionsNode.Get(), &appendedChild);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlElement> actionElement;
+ hr = xml->CreateElement(WinToastStringWrapper(L"action").Get(), &actionElement);
+ if (SUCCEEDED(hr))
+ hr = actionElement->SetAttribute(WinToastStringWrapper(L"content").Get(), WinToastStringWrapper(content).Get());
+ if (SUCCEEDED(hr))
+ hr = actionElement->SetAttribute(WinToastStringWrapper(L"arguments").Get(), WinToastStringWrapper(arguments).Get());
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> actionNode;
+ hr = actionElement.As(&actionNode);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IXmlNode> appendedChild;
+ hr = actionsNode->AppendChild(actionNode.Get(), &appendedChild);
+ }
+ }
+ }
+ }
+ }
+ return hr;
+}
+
+void WinToast::setError(_Out_ WinToastError* error, _In_ WinToastError value) {
+ if (error) {
+ *error = value;
+ }
+}
+
+WinToastTemplate::WinToastTemplate(_In_ WinToastTemplateType type) : _type(type) {
+ static constexpr std::size_t TextFieldsCount[] = { 1, 2, 2, 3, 1, 2, 2, 3};
+ _textFields = std::vector<std::wstring>(TextFieldsCount[type], L"");
+}
+
+WinToastTemplate::~WinToastTemplate() {
+ _textFields.clear();
+}
+
+void WinToastTemplate::setTextField(_In_ const std::wstring& txt, _In_ WinToastTemplate::TextField pos) {
+ const auto position = static_cast<std::size_t>(pos);
+ assert(position < _textFields.size());
+ _textFields[position] = txt;
+}
+
+void WinToastTemplate::setImagePath(_In_ const std::wstring& imgPath) {
+ _imagePath = imgPath;
+}
+
+void WinToastTemplate::setAudioPath(_In_ const std::wstring& audioPath) {
+ _audioPath = audioPath;
+}
+
+void WinToastTemplate::setAudioPath(_In_ AudioSystemFile file) {
+ static const std::unordered_map<AudioSystemFile, std::wstring> Files = {
+ {AudioSystemFile::DefaultSound, L"ms-winsoundevent:Notification.Default"},
+ {AudioSystemFile::IM, L"ms-winsoundevent:Notification.IM"},
+ {AudioSystemFile::Mail, L"ms-winsoundevent:Notification.Mail"},
+ {AudioSystemFile::Reminder, L"ms-winsoundevent:Notification.Reminder"},
+ {AudioSystemFile::SMS, L"ms-winsoundevent:Notification.SMS"},
+ {AudioSystemFile::Alarm, L"ms-winsoundevent:Notification.Looping.Alarm"},
+ {AudioSystemFile::Alarm2, L"ms-winsoundevent:Notification.Looping.Alarm2"},
+ {AudioSystemFile::Alarm3, L"ms-winsoundevent:Notification.Looping.Alarm3"},
+ {AudioSystemFile::Alarm4, L"ms-winsoundevent:Notification.Looping.Alarm4"},
+ {AudioSystemFile::Alarm5, L"ms-winsoundevent:Notification.Looping.Alarm5"},
+ {AudioSystemFile::Alarm6, L"ms-winsoundevent:Notification.Looping.Alarm6"},
+ {AudioSystemFile::Alarm7, L"ms-winsoundevent:Notification.Looping.Alarm7"},
+ {AudioSystemFile::Alarm8, L"ms-winsoundevent:Notification.Looping.Alarm8"},
+ {AudioSystemFile::Alarm9, L"ms-winsoundevent:Notification.Looping.Alarm9"},
+ {AudioSystemFile::Alarm10, L"ms-winsoundevent:Notification.Looping.Alarm10"},
+ {AudioSystemFile::Call, L"ms-winsoundevent:Notification.Looping.Call"},
+ {AudioSystemFile::Call1, L"ms-winsoundevent:Notification.Looping.Call1"},
+ {AudioSystemFile::Call2, L"ms-winsoundevent:Notification.Looping.Call2"},
+ {AudioSystemFile::Call3, L"ms-winsoundevent:Notification.Looping.Call3"},
+ {AudioSystemFile::Call4, L"ms-winsoundevent:Notification.Looping.Call4"},
+ {AudioSystemFile::Call5, L"ms-winsoundevent:Notification.Looping.Call5"},
+ {AudioSystemFile::Call6, L"ms-winsoundevent:Notification.Looping.Call6"},
+ {AudioSystemFile::Call7, L"ms-winsoundevent:Notification.Looping.Call7"},
+ {AudioSystemFile::Call8, L"ms-winsoundevent:Notification.Looping.Call8"},
+ {AudioSystemFile::Call9, L"ms-winsoundevent:Notification.Looping.Call9"},
+ {AudioSystemFile::Call10, L"ms-winsoundevent:Notification.Looping.Call10"},
+ };
+ const auto iter = Files.find(file);
+ assert(iter != Files.end());
+ _audioPath = iter->second;
+}
+
+void WinToastTemplate::setAudioOption(_In_ WinToastTemplate::AudioOption audioOption) {
+ _audioOption = audioOption;
+}
+
+void WinToastTemplate::setFirstLine(const std::wstring &text) {
+ setTextField(text, WinToastTemplate::FirstLine);
+}
+
+void WinToastTemplate::setSecondLine(const std::wstring &text) {
+ setTextField(text, WinToastTemplate::SecondLine);
+}
+
+void WinToastTemplate::setThirdLine(const std::wstring &text) {
+ setTextField(text, WinToastTemplate::ThirdLine);
+}
+
+void WinToastTemplate::setDuration(_In_ Duration duration) {
+ _duration = duration;
+}
+
+void WinToastTemplate::setExpiration(_In_ INT64 millisecondsFromNow) {
+ _expiration = millisecondsFromNow;
+}
+
+void WinToastLib::WinToastTemplate::setScenario(Scenario scenario) {
+ switch (scenario) {
+ case Scenario::Default: _scenario = L"Default"; break;
+ case Scenario::Alarm: _scenario = L"Alarm"; break;
+ case Scenario::IncomingCall: _scenario = L"IncomingCall"; break;
+ case Scenario::Reminder: _scenario = L"Reminder"; break;
+ }
+}
+
+void WinToastTemplate::setAttributionText(_In_ const std::wstring& attributionText) {
+ _attributionText = attributionText;
+}
+
+void WinToastTemplate::addAction(_In_ const std::wstring & label) {
+ _actions.push_back(label);
+}
+
+std::size_t WinToastTemplate::textFieldsCount() const {
+ return _textFields.size();
+}
+
+std::size_t WinToastTemplate::actionsCount() const {
+ return _actions.size();
+}
+
+bool WinToastTemplate::hasImage() const {
+ return _type < WinToastTemplateType::Text01;
+}
+
+const std::vector<std::wstring>& WinToastTemplate::textFields() const {
+ return _textFields;
+}
+
+const std::wstring& WinToastTemplate::textField(_In_ TextField pos) const {
+ const auto position = static_cast<std::size_t>(pos);
+ assert(position < _textFields.size());
+ return _textFields[position];
+}
+
+const std::wstring& WinToastTemplate::actionLabel(_In_ std::size_t position) const {
+ assert(position < _actions.size());
+ return _actions[position];
+}
+
+const std::wstring& WinToastTemplate::imagePath() const {
+ return _imagePath;
+}
+
+const std::wstring& WinToastTemplate::audioPath() const {
+ return _audioPath;
+}
+
+const std::wstring& WinToastTemplate::attributionText() const {
+ return _attributionText;
+}
+
+const std::wstring& WinToastLib::WinToastTemplate::scenario() const {
+ return _scenario;
+}
+
+INT64 WinToastTemplate::expiration() const {
+ return _expiration;
+}
+
+WinToastTemplate::WinToastTemplateType WinToastTemplate::type() const {
+ return _type;
+}
+
+WinToastTemplate::AudioOption WinToastTemplate::audioOption() const {
+ return _audioOption;
+}
+
+WinToastTemplate::Duration WinToastTemplate::duration() const {
+ return _duration;
+}
diff --git a/third_party/WinToast/wintoastlib.h b/third_party/WinToast/wintoastlib.h
new file mode 100644
index 0000000000..93546dc5b8
--- /dev/null
+++ b/third_party/WinToast/wintoastlib.h
@@ -0,0 +1,234 @@
+/* * Copyright (C) 2016-2019 Mohammed Boujemaoui <mohabouje@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE 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 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef WINTOASTLIB_H
+#define WINTOASTLIB_H
+#include <Windows.h>
+#include <sdkddkver.h>
+#include <WinUser.h>
+#include <ShObjIdl.h>
+#include <wrl/implements.h>
+#include <wrl/event.h>
+#include <windows.ui.notifications.h>
+#include <strsafe.h>
+#include <Psapi.h>
+#include <ShlObj.h>
+#include <roapi.h>
+#include <propvarutil.h>
+#include <functiondiscoverykeys.h>
+#include <iostream>
+#include <winstring.h>
+#include <string.h>
+#include <vector>
+#include <map>
+using namespace Microsoft::WRL;
+using namespace ABI::Windows::Data::Xml::Dom;
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::UI::Notifications;
+using namespace Windows::Foundation;
+
+
+namespace WinToastLib {
+
+ class IWinToastHandler {
+ public:
+ enum WinToastDismissalReason {
+ UserCanceled = ToastDismissalReason::ToastDismissalReason_UserCanceled,
+ ApplicationHidden = ToastDismissalReason::ToastDismissalReason_ApplicationHidden,
+ TimedOut = ToastDismissalReason::ToastDismissalReason_TimedOut
+ };
+ virtual ~IWinToastHandler() = default;
+ virtual void toastActivated() const = 0;
+ virtual void toastActivated(int actionIndex) const = 0;
+ virtual void toastDismissed(WinToastDismissalReason state) const = 0;
+ virtual void toastFailed() const = 0;
+ };
+
+ class WinToastTemplate {
+ public:
+ enum class Scenario { Default, Alarm, IncomingCall, Reminder };
+ enum Duration { System, Short, Long };
+ enum AudioOption { Default = 0, Silent, Loop };
+ enum TextField { FirstLine = 0, SecondLine, ThirdLine };
+ enum WinToastTemplateType {
+ ImageAndText01 = ToastTemplateType::ToastTemplateType_ToastImageAndText01,
+ ImageAndText02 = ToastTemplateType::ToastTemplateType_ToastImageAndText02,
+ ImageAndText03 = ToastTemplateType::ToastTemplateType_ToastImageAndText03,
+ ImageAndText04 = ToastTemplateType::ToastTemplateType_ToastImageAndText04,
+ Text01 = ToastTemplateType::ToastTemplateType_ToastText01,
+ Text02 = ToastTemplateType::ToastTemplateType_ToastText02,
+ Text03 = ToastTemplateType::ToastTemplateType_ToastText03,
+ Text04 = ToastTemplateType::ToastTemplateType_ToastText04,
+ };
+
+ enum AudioSystemFile {
+ DefaultSound,
+ IM,
+ Mail,
+ Reminder,
+ SMS,
+ Alarm,
+ Alarm2,
+ Alarm3,
+ Alarm4,
+ Alarm5,
+ Alarm6,
+ Alarm7,
+ Alarm8,
+ Alarm9,
+ Alarm10,
+ Call,
+ Call1,
+ Call2,
+ Call3,
+ Call4,
+ Call5,
+ Call6,
+ Call7,
+ Call8,
+ Call9,
+ Call10,
+ };
+
+
+ WinToastTemplate(_In_ WinToastTemplateType type = WinToastTemplateType::ImageAndText02);
+ ~WinToastTemplate();
+
+ void setFirstLine(_In_ const std::wstring& text);
+ void setSecondLine(_In_ const std::wstring& text);
+ void setThirdLine(_In_ const std::wstring& text);
+ void setTextField(_In_ const std::wstring& txt, _In_ TextField pos);
+ void setAttributionText(_In_ const std::wstring& attributionText);
+ void setImagePath(_In_ const std::wstring& imgPath);
+ void setAudioPath(_In_ WinToastTemplate::AudioSystemFile audio);
+ void setAudioPath(_In_ const std::wstring& audioPath);
+ void setAudioOption(_In_ WinToastTemplate::AudioOption audioOption);
+ void setDuration(_In_ Duration duration);
+ void setExpiration(_In_ INT64 millisecondsFromNow);
+ void setScenario(_In_ Scenario scenario);
+ void addAction(_In_ const std::wstring& label);
+
+ std::size_t textFieldsCount() const;
+ std::size_t actionsCount() const;
+ bool hasImage() const;
+ const std::vector<std::wstring>& textFields() const;
+ const std::wstring& textField(_In_ TextField pos) const;
+ const std::wstring& actionLabel(_In_ std::size_t pos) const;
+ const std::wstring& imagePath() const;
+ const std::wstring& audioPath() const;
+ const std::wstring& attributionText() const;
+ const std::wstring& scenario() const;
+ INT64 expiration() const;
+ WinToastTemplateType type() const;
+ WinToastTemplate::AudioOption audioOption() const;
+ Duration duration() const;
+ private:
+ std::vector<std::wstring> _textFields{};
+ std::vector<std::wstring> _actions{};
+ std::wstring _imagePath{};
+ std::wstring _audioPath{};
+ std::wstring _attributionText{};
+ std::wstring _scenario{L"Default"};
+ INT64 _expiration{0};
+ AudioOption _audioOption{WinToastTemplate::AudioOption::Default};
+ WinToastTemplateType _type{WinToastTemplateType::Text01};
+ Duration _duration{Duration::System};
+ };
+
+ class WinToast {
+ public:
+ enum WinToastError {
+ NoError = 0,
+ NotInitialized,
+ SystemNotSupported,
+ ShellLinkNotCreated,
+ InvalidAppUserModelID,
+ InvalidParameters,
+ InvalidHandler,
+ NotDisplayed,
+ UnknownError
+ };
+
+ enum ShortcutResult {
+ SHORTCUT_UNCHANGED = 0,
+ SHORTCUT_WAS_CHANGED = 1,
+ SHORTCUT_WAS_CREATED = 2,
+
+ SHORTCUT_MISSING_PARAMETERS = -1,
+ SHORTCUT_INCOMPATIBLE_OS = -2,
+ SHORTCUT_COM_INIT_FAILURE = -3,
+ SHORTCUT_CREATE_FAILED = -4
+ };
+
+ enum ShortcutPolicy {
+ /* Don't check, create, or modify a shortcut. */
+ SHORTCUT_POLICY_IGNORE = 0,
+ /* Require a shortcut with matching AUMI, don't create or modify an existing one. */
+ SHORTCUT_POLICY_REQUIRE_NO_CREATE = 1,
+ /* Require a shortcut with matching AUMI, create if missing, modify if not matching.
+ * This is the default. */
+ SHORTCUT_POLICY_REQUIRE_CREATE = 2,
+ };
+
+ WinToast(void);
+ virtual ~WinToast();
+ static WinToast* instance();
+ static bool isCompatible();
+ static bool isSupportingModernFeatures();
+ static std::wstring configureAUMI(_In_ const std::wstring& companyName,
+ _In_ const std::wstring& productName,
+ _In_ const std::wstring& subProduct = std::wstring(),
+ _In_ const std::wstring& versionInformation = std::wstring());
+ static const std::wstring& strerror(_In_ WinToastError error);
+ virtual bool initialize(_Out_ WinToastError* error = nullptr);
+ virtual bool isInitialized() const;
+ virtual bool hideToast(_In_ INT64 id);
+ virtual INT64 showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHandler* handler, _Out_ WinToastError* error = nullptr);
+ virtual void clear();
+ virtual enum ShortcutResult createShortcut();
+
+ const std::wstring& appName() const;
+ const std::wstring& appUserModelId() const;
+ void setAppUserModelId(_In_ const std::wstring& aumi);
+ void setAppName(_In_ const std::wstring& appName);
+ void setShortcutPolicy(_In_ ShortcutPolicy policy);
+
+ protected:
+ bool _isInitialized{false};
+ bool _hasCoInitialized{false};
+ ShortcutPolicy _shortcutPolicy{SHORTCUT_POLICY_REQUIRE_CREATE};
+ std::wstring _appName{};
+ std::wstring _aumi{};
+ std::map<INT64, ComPtr<IToastNotification>> _buffer{};
+
+ HRESULT validateShellLinkHelper(_Out_ bool& wasChanged);
+ HRESULT createShellLinkHelper();
+ HRESULT setImageFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path);
+ HRESULT setAudioFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path, _In_opt_ WinToastTemplate::AudioOption option = WinToastTemplate::AudioOption::Default);
+ HRESULT setTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text, _In_ UINT32 pos);
+ HRESULT setAttributionTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text);
+ HRESULT addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& action, _In_ const std::wstring& arguments);
+ HRESULT addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& duration);
+ HRESULT addScenarioHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& scenario);
+ ComPtr<IToastNotifier> notifier(_In_ bool* succeded) const;
+ void setError(_Out_ WinToastError* error, _In_ WinToastError value);
+ };
+}
+#endif // WINTOASTLIB_H