From 3b76aebfc9152770cacf246f70bea96547f2c08c Mon Sep 17 00:00:00 2001 From: malja Date: Wed, 13 Jun 2018 22:06:27 +0200 Subject: [PATCH] Working on issuse 35 https://github.com/mohabouje/WinToast/issues/35 --- WinToast.sln | 31 +++++++ WinToast/WinToast.vcxproj | 135 ++++++++++++++++++++++++++++++ WinToast/WinToast.vcxproj.filters | 39 +++++++++ WinToast/WinToast.vcxproj.user | 4 + WinToast/activator_helper.h | 81 ++++++++++++++++++ WinToast/event_handler.h | 41 +++++++++ WinToast/iactivator.h | 50 +++++++++++ WinToast/main.cpp | 59 +++++++++++++ src/wintoastlib.cpp | 43 +++++++++- src/wintoastlib.h | 3 + 10 files changed, 483 insertions(+), 3 deletions(-) create mode 100644 WinToast.sln create mode 100644 WinToast/WinToast.vcxproj create mode 100644 WinToast/WinToast.vcxproj.filters create mode 100644 WinToast/WinToast.vcxproj.user create mode 100644 WinToast/activator_helper.h create mode 100644 WinToast/event_handler.h create mode 100644 WinToast/iactivator.h create mode 100644 WinToast/main.cpp diff --git a/WinToast.sln b/WinToast.sln new file mode 100644 index 0000000..5768fd6 --- /dev/null +++ b/WinToast.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27428.2002 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinToast", "WinToast\WinToast.vcxproj", "{B83F8D9B-7888-437D-A8C6-52F4D2A0179A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B83F8D9B-7888-437D-A8C6-52F4D2A0179A}.Debug|x64.ActiveCfg = Debug|x64 + {B83F8D9B-7888-437D-A8C6-52F4D2A0179A}.Debug|x64.Build.0 = Debug|x64 + {B83F8D9B-7888-437D-A8C6-52F4D2A0179A}.Debug|x86.ActiveCfg = Debug|Win32 + {B83F8D9B-7888-437D-A8C6-52F4D2A0179A}.Debug|x86.Build.0 = Debug|Win32 + {B83F8D9B-7888-437D-A8C6-52F4D2A0179A}.Release|x64.ActiveCfg = Release|x64 + {B83F8D9B-7888-437D-A8C6-52F4D2A0179A}.Release|x64.Build.0 = Release|x64 + {B83F8D9B-7888-437D-A8C6-52F4D2A0179A}.Release|x86.ActiveCfg = Release|Win32 + {B83F8D9B-7888-437D-A8C6-52F4D2A0179A}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4A73BDC8-677E-4994-AD40-D19AC5F7A245} + EndGlobalSection +EndGlobal diff --git a/WinToast/WinToast.vcxproj b/WinToast/WinToast.vcxproj new file mode 100644 index 0000000..b04bd79 --- /dev/null +++ b/WinToast/WinToast.vcxproj @@ -0,0 +1,135 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {B83F8D9B-7888-437D-A8C6-52F4D2A0179A} + WinToast + 10.0.16299.0 + + + + Application + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + Application + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + C:\Users\janma\Documents\GitHub\WinToast\src;C:\Users\janma\Documents\GitHub\WinToast\WinToast;$(IncludePath) + + + + Level3 + Disabled + true + true + + + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;runtimeobject.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WinToast/WinToast.vcxproj.filters b/WinToast/WinToast.vcxproj.filters new file mode 100644 index 0000000..37a320c --- /dev/null +++ b/WinToast/WinToast.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;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 + + + + + Hlavičkové soubory + + + Hlavičkové soubory + + + Hlavičkové soubory + + + Hlavičkové soubory + + + + + Zdrojové soubory + + + Zdrojové soubory + + + \ No newline at end of file diff --git a/WinToast/WinToast.vcxproj.user b/WinToast/WinToast.vcxproj.user new file mode 100644 index 0000000..be25078 --- /dev/null +++ b/WinToast/WinToast.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/WinToast/activator_helper.h b/WinToast/activator_helper.h new file mode 100644 index 0000000..3dd8788 --- /dev/null +++ b/WinToast/activator_helper.h @@ -0,0 +1,81 @@ +#define UNICODE //< This define is required for GetModuleFileName to work with wchar_t +#include +#include +#include + +/*! + * A parameter application is run with when activated from notification + */ +#define TOAST_ACTIVATED_LAUNCH_ARG L"-ToastActivated" + +/*! + * Macro for checking return value of HRESULT type for failure. + */ +#define RETURN_IF_FAILED(hr) do { HRESULT _hrTemp = hr; if (FAILED(_hrTemp)) { return _hrTemp; } } while (false) + +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; + +// Following functions were taken from https://raw.githubusercontent.com/WindowsNotifications/desktop-toasts/master/CPP-WRL/DesktopToastsCppWrlApp/DesktopNotificationManagerCompat.cpp +// mostly without any change. +// In the future, they should be embeded into WinToastLib::Util namespace + +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 RegisterAumidAndComServer(const wchar_t *aumid, GUID clsid) { + + // 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)); + 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()); + + return S_OK; +} + diff --git a/WinToast/event_handler.h b/WinToast/event_handler.h new file mode 100644 index 0000000..2517591 --- /dev/null +++ b/WinToast/event_handler.h @@ -0,0 +1,41 @@ +#include "wintoastlib.h" + +#include + +class EventHandler : public WinToastLib::IWinToastHandler { + + public: + + void toastActivated() const override { + std::cout << "Toast Clicked" << std::endl; + } + + void toastActivated(int actionIndex) const override { + std::cout << "Action Clicked #" << actionIndex << std::endl; + } + + void toastDismissed(WinToastDismissalReason state) const { + switch (state) { + + case UserCanceled: + std::cout << "Dissmissed" << std::endl; + break; + + case TimedOut: + std::cout << "Timed out" << std::endl; + break; + + case ApplicationHidden: + std::cout << "Hidden from app" << std::endl; + break; + + default: + std::cout << "Toast not activated" << std::endl; + break; + } + } + + void toastFailed() const { + std::cout << "Failed" << std::endl; + } +}; diff --git a/WinToast/iactivator.h b/WinToast/iactivator.h new file mode 100644 index 0000000..efeb32f --- /dev/null +++ b/WinToast/iactivator.h @@ -0,0 +1,50 @@ +#include +#include + +#include +#include +#include + +using namespace Microsoft::WRL; + +// Following code is an interface for future Activator class. + +/*class IWinToastActivator : public RuntimeClass, INotificationActivationCallback> { + public: + HRESULT STDMETHODCALLTYPE Activate(_In_ LPCWSTR appUserModelId, _In_ LPCWSTR invokedArgs, _In_reads_(dataCount) const NOTIFICATION_USER_INPUT_DATA* data, ULONG dataCount) override { + + std::map params; + + for (ULONG i = 0; i < dataCount; i++ ) { + params.insert(std::pair( (data[i]).Key, (data[i]).Value )); + } + + // Call user handler + notificationActivated(std::wstring(appUserModelId), std::wstring(invokedArgs), params); + } + + virtual void notificationActivated(const std::wstring &appUserModelId, const std::wstring ¶meters, std::map &user_input) const = 0; +};*/ + +// This class is just for testing. + +class DECLSPEC_UUID("8aa59b6c-1401-42a5-b1cf-3ccb3b360e77") Activator WrlSealed WrlFinal : public RuntimeClass, INotificationActivationCallback> { + public: + + HRESULT STDMETHODCALLTYPE Activate(_In_ LPCWSTR appUserModelId, _In_ LPCWSTR invokedArgs, _In_reads_(dataCount) const NOTIFICATION_USER_INPUT_DATA* data, ULONG dataCount) override { + + // Change username + std::wofstream file("C:\\Users\\\\Desktop"); + + file << appUserModelId << std::endl; + file << invokedArgs << std::endl; + + for (ULONG i = 0; i < dataCount; i++) { + file << (data[i]).Key << " " << (data[i]).Value << std::endl; + } + + return S_OK; + } +}; + +CoCreatableClass(Activator); \ No newline at end of file diff --git a/WinToast/main.cpp b/WinToast/main.cpp new file mode 100644 index 0000000..a4d08a7 --- /dev/null +++ b/WinToast/main.cpp @@ -0,0 +1,59 @@ +#define UNICODE + +#include "wintoastlib.h" +#include "event_handler.h" +#include +#include + +#include "iactivator.h" + +using namespace WinToastLib; + +int main(int argc, char *argv[]) { + + // Check if application was executed with parameter -ToastActivated + for (int i = 0; i < argc; i++) { + if (strcmp(argv[i], "-ToastActivated") == 0) { + std::cout << "Activated from notification" << std::endl; + std::cout << "Param: " << argv[1] << std::endl; + return 0; + } + } + + // Parameters for notification + std::wstring appName(L"Console WinToast Example"); + std::wstring aumi(L"company.product"); + std::wstring text(L"Text"); + + // Register Activator class + RegisterAumidAndComServer(aumi.c_str(), __uuidof(Activator)); + RegisterActivator(); + + if (!WinToast::isCompatible()) { + std::cerr << "Error, your system in not supported!" << std::endl; + return -1; + } + + WinToast::instance()->setAppName(appName); + WinToast::instance()->setAppUserModelId(aumi); + + if (!WinToast::instance()->initialize()) { + std::cerr << "Error, your system in not compatible!" << std::endl; + return -2; + } + + // Use ::Generic for new TemplateType which should support Activator + // Or any of other types. + WinToastTemplate templ(WinToastTemplate::Generic); + + templ.setTextField(text, WinToastTemplate::FirstLine); + + if (WinToast::instance()->showToast(templ, new EventHandler()) < 0) { + std::cerr << "Could not launch your toast notification!"; + return -3; + } + + Sleep(15000); + + return 0; +} diff --git a/src/wintoastlib.cpp b/src/wintoastlib.cpp index 1efe392..d03e3bd 100644 --- a/src/wintoastlib.cpp +++ b/src/wintoastlib.cpp @@ -72,7 +72,7 @@ namespace DllImporter { } template - inline HRESULT Wrap_GetActivationFactory(_In_ HSTRING activatableClassId, _Inout_ Details::ComPtrRef factory) throw() { + inline HRESULT Wrap_GetActivationFactory(_In_ HSTRING activatableClassId, _Inout_ Microsoft::WRL::Details::ComPtrRef factory) throw() { return _1_GetActivationFactory(activatableClassId, factory.ReleaseAndGetAddressOf()); } @@ -332,6 +332,34 @@ namespace Util { } return hr; } + + /*! + * Create an XML Document from string. + * + * @param[in] xmlString Input string with XML code. + * @param[out] doc Pointer to IXmlDocument which will be filled with xmlString. + * + * @return A [HRESULT](https://msdn.microsoft.com/en-us/library/windows/desktop/aa378137(v=vs.85).aspx) + * with result value of the function. Use + * [SUCCEEDED()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms687197(v=vs.85).aspx) and + * [FAILED()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms693474(v=vs.85).aspx) macros. + */ + inline HRESULT createDocumentFromString(const wchar_t *xmlString, IXmlDocument **doc) { + ComPtr answer; + + HRESULT hr = Windows::Foundation::ActivateInstance(HStringReference(RuntimeClass_Windows_Data_Xml_Dom_XmlDocument).Get(), &answer); + if (SUCCEEDED(hr)) { + ComPtr docIO; + hr = answer.As(&docIO); + if (SUCCEEDED(hr)) { + hr = docIO->LoadXml(HStringReference(xmlString).Get()); + if (SUCCEEDED(hr)) { + return answer.CopyTo(doc); + } + } + } + return E_FAIL; + } } WinToast* WinToast::instance() { @@ -602,8 +630,17 @@ INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHan hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), ¬ificationFactory); if (SUCCEEDED(hr)) { ComPtr xmlDocument; - HRESULT hr = notificationManager->GetTemplateContent(ToastTemplateType(toast.type()), &xmlDocument); + + HRESULT hr = notificationManager->GetTemplateContent(ToastTemplateType(toast.type()), &xmlDocument); + + // https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/send-local-toast-desktop-cpp-wrl#step-7-send-a-notification + if (toast.type() == WinToastTemplate::WinToastTemplateType::Generic) { + hr = Util::createDocumentFromString(L"", &xmlDocument); + DEBUG_MSG("GenericXml: " << Util::AsString(xmlDocument)); + } + if (SUCCEEDED(hr)) { + const int fieldsCount = toast.textFieldsCount(); for (int i = 0; i < fieldsCount && SUCCEEDED(hr); i++) { hr = setTextFieldHelper(xmlDocument.Get(), toast.textField(WinToastTemplate::TextField(i)), i); @@ -941,7 +978,7 @@ void WinToast::setError(_Out_ WinToastError* error, _In_ WinToastError value) { } WinToastTemplate::WinToastTemplate(_In_ WinToastTemplateType type) : _type(type) { - static const std::size_t TextFieldsCount[] = { 1, 2, 2, 3, 1, 2, 2, 3}; + static const std::size_t TextFieldsCount[] = { 1, 2, 2, 3, 1, 2, 2, 3, 3}; _textFields = std::vector(TextFieldsCount[type], L""); } diff --git a/src/wintoastlib.h b/src/wintoastlib.h index 46eab51..f75296d 100644 --- a/src/wintoastlib.h +++ b/src/wintoastlib.h @@ -18,7 +18,9 @@ #include #include #include + using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; // For HStringReference using namespace ABI::Windows::Data::Xml::Dom; using namespace ABI::Windows::Foundation; using namespace ABI::Windows::UI::Notifications; @@ -55,6 +57,7 @@ namespace WinToastLib { Text02 = ToastTemplateType::ToastTemplateType_ToastText02, Text03 = ToastTemplateType::ToastTemplateType_ToastText03, Text04 = ToastTemplateType::ToastTemplateType_ToastText04, + Generic, WinToastTemplateTypeCount };