diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6fe0b24..adc9388 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,8 @@ add_library(libsnoretoast STATIC snoretoasts.cpp toasteventhandler.cpp linkhelper.cpp utils.cpp) add_executable(SnoreToast WIN32 main.cpp ${SNORE_TOAST_DEPS}) target_link_libraries(SnoreToast runtimeobject shlwapi libsnoretoast) install(TARGETS SnoreToast RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib) +install(FILES snoretoastactions.h DESTINATION includes/snoretoast) diff --git a/src/snoretoastactions.h b/src/snoretoastactions.h new file mode 100644 index 0000000..b88177d --- /dev/null +++ b/src/snoretoastactions.h @@ -0,0 +1,66 @@ +/* + SnoreToast is capable to invoke Windows 8 toast notifications. + Copyright (C) 2013-2019 Hannah von Reth + + SnoreToast is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SnoreToast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with SnoreToast. If not, see . +*/ +#pragma once + +#include +#include + + +class SnoreToastActions { + public: + enum class Actions { + Clicked, + Hidden, + Dismissed, + Timedout, + ButtonClicked, + TextEntered, + + Error = -1 + }; + + static constexpr std::wstring_view getActionString(const Actions &a) + { + return ActionStrings[static_cast(a)]; + } + + static SnoreToastActions::Actions getAction(const std::wstring_view &s) + { + int i = 0; + for (const auto &sv : ActionStrings) + { + if (sv.compare(s) == 0) + { + return static_cast(i); + } + ++i; + } + return SnoreToastActions::Actions::Error; + } + + private: + static constexpr std::wstring_view ActionStrings[] = + { + L"clicked", + L"hidden", + L"dismissed", + L"timedout", + L"buttonClicked", + L"textEntered", + }; +}; diff --git a/src/snoretoasts.cpp b/src/snoretoasts.cpp index ded5b72..5cb72a1 100644 --- a/src/snoretoasts.cpp +++ b/src/snoretoasts.cpp @@ -1,572 +1,559 @@ #include "snoretoasts.h" #include "snoretoasts.h" #include "snoretoasts.h" /* SnoreToast is capable to invoke Windows 8 toast notifications. Copyright (C) 2013-2019 Hannah von Reth SnoreToast is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SnoreToast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with SnoreToast. If not, see . */ #include "snoretoasts.h" #include "toasteventhandler.h" #include "linkhelper.h" #include "utils.h" #include #include using namespace Microsoft::WRL; using namespace ABI::Windows::UI::Notifications; using namespace ABI::Windows::Data::Xml::Dom; using namespace Windows::Foundation; SnoreToasts::SnoreToasts(const std::wstring &appID) : m_appID(appID), m_id(std::to_wstring(GetCurrentProcessId())) { Utils::registerActivator(); } SnoreToasts::~SnoreToasts() { Utils::unregisterActivator(); } void SnoreToasts::displayToast(const std::wstring &title, const std::wstring &body, const std::filesystem::path &image, bool wait) { HRESULT hr = S_OK; m_title = title; m_body = body; m_image = image; m_wait = wait; hr = GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &m_toastManager); if (SUCCEEDED(hr)) { if (!m_image.empty()) { hr = m_toastManager->GetTemplateContent(ToastTemplateType_ToastImageAndText02, &m_toastXml); } else { hr = m_toastManager->GetTemplateContent(ToastTemplateType_ToastText02, &m_toastXml); } if (SUCCEEDED(hr)) { ComPtr rootList; hr = m_toastXml->GetElementsByTagName(StringReferenceWrapper(L"toast").Get(), &rootList); if (SUCCEEDED(hr)) { ComPtr root; hr = rootList->Item(0, &root); ComPtr rootAttributes; hr = root->get_Attributes(&rootAttributes); if (SUCCEEDED(hr)) { const auto data = formatAction(SnoreToastActions::Actions::Clicked); hr = addAttribute(L"launch", rootAttributes.Get(), data); } //Adding buttons if (!m_buttons.empty()) { setButtons(root); } else if (m_textbox) { setTextBox(root); } if (SUCCEEDED(hr)) { ComPtr audioElement; hr = m_toastXml->CreateElement(StringReferenceWrapper(L"audio").Get(), &audioElement); if (SUCCEEDED(hr)) { ComPtr audioNodeTmp; hr = audioElement.As(&audioNodeTmp); if (SUCCEEDED(hr)) { ComPtr audioNode; hr = root->AppendChild(audioNodeTmp.Get(), &audioNode); if (SUCCEEDED(hr)) { ComPtr attributes; hr = audioNode->get_Attributes(&attributes); if (SUCCEEDED(hr)) { hr = addAttribute(L"src", attributes.Get()); if (SUCCEEDED(hr)) { addAttribute(L"silent", attributes.Get()); } } } } } } } } } // printXML(); if (SUCCEEDED(hr)) { if (!m_image.empty()) { hr = setImage(); } if (SUCCEEDED(hr)) { hr = setSound(); if (SUCCEEDED(hr)) { hr = setTextValues(); if (SUCCEEDED(hr)) { printXML(); hr = createToast(); } } } } m_action = SUCCEEDED(hr) ? SnoreToastActions::Actions::Clicked : SnoreToastActions::Actions::Error; } SnoreToastActions::Actions SnoreToasts::userAction() { if (m_eventHanlder.Get()) { HANDLE event = m_eventHanlder.Get()->event(); WaitForSingleObject(event, INFINITE); m_action = m_eventHanlder.Get()->userAction(); if (m_action == SnoreToastActions::Actions::Hidden) { m_notifier->Hide(m_notification.Get()); tLog << L"The application hid the toast using ToastNotifier.hide()"; } CloseHandle(event); } return m_action; } bool SnoreToasts::closeNotification() { std::wstringstream eventName; eventName << L"ToastEvent" << m_id; HANDLE event = OpenEventW(EVENT_ALL_ACCESS, FALSE, eventName.str().c_str()); if (event) { SetEvent(event); return true; } tLog << "Notification " << m_id << " does not exist"; return false; } void SnoreToasts::setSound(const std::wstring &soundFile) { m_sound = soundFile; } void SnoreToasts::setSilent(bool silent) { m_silent = silent; } void SnoreToasts::setId(const std::wstring &id) { if (!id.empty()) { m_id = id; } } std::wstring SnoreToasts::id() const { return m_id; } void SnoreToasts::setButtons(const std::wstring &buttons) { m_buttons = buttons; } void SnoreToasts::setTextBoxEnabled(bool textBoxEnabled) { m_textbox = textBoxEnabled; } // Set the value of the "src" attribute of the "image" node HRESULT SnoreToasts::setImage() { HRESULT hr = S_OK; ComPtr nodeList; hr = m_toastXml->GetElementsByTagName(StringReferenceWrapper(L"image").Get(), &nodeList); if (SUCCEEDED(hr)) { ComPtr imageNode; hr = nodeList->Item(0, &imageNode); if (SUCCEEDED(hr)) { ComPtr attributes; hr = imageNode->get_Attributes(&attributes); if (SUCCEEDED(hr)) { ComPtr srcAttribute; hr = attributes->GetNamedItem(StringReferenceWrapper(L"src").Get(), &srcAttribute); if (SUCCEEDED(hr)) { hr = setNodeValueString(StringReferenceWrapper(m_image).Get(), srcAttribute.Get()); } } } } return hr; } HRESULT SnoreToasts::setSound() { HRESULT hr = S_OK; ComPtr nodeList; hr = m_toastXml->GetElementsByTagName(StringReferenceWrapper(L"audio").Get(), &nodeList); if (SUCCEEDED(hr)) { ComPtr audioNode; hr = nodeList->Item(0, &audioNode); if (SUCCEEDED(hr)) { ComPtr attributes; hr = audioNode->get_Attributes(&attributes); if (SUCCEEDED(hr)) { ComPtr srcAttribute; hr = attributes->GetNamedItem(StringReferenceWrapper(L"src").Get(), &srcAttribute); if (SUCCEEDED(hr)) { std::wstring sound; if (m_sound.find(L"ms-winsoundevent:") == std::wstring::npos) { sound = L"ms-winsoundevent:"; sound.append(m_sound); } else { sound = m_sound; } hr = setNodeValueString(StringReferenceWrapper(sound).Get(), srcAttribute.Get()); if (SUCCEEDED(hr)) { hr = attributes->GetNamedItem(StringReferenceWrapper(L"silent").Get(), &srcAttribute); if (SUCCEEDED(hr)) { hr = setNodeValueString(StringReferenceWrapper(m_silent ? L"true" : L"false").Get(), srcAttribute.Get()); } } } } } } return hr; } // Set the values of each of the text nodes HRESULT SnoreToasts::setTextValues() { HRESULT hr = S_OK; if (SUCCEEDED(hr)) { ComPtr nodeList; hr = m_toastXml->GetElementsByTagName(StringReferenceWrapper(L"text").Get(), &nodeList); if (SUCCEEDED(hr)) { //create the title ComPtr textNode; hr = nodeList->Item(0, &textNode); if (SUCCEEDED(hr)) { hr = setNodeValueString(StringReferenceWrapper(m_title).Get(), textNode.Get()); if (SUCCEEDED(hr)) { hr = nodeList->Item(1, &textNode); if (SUCCEEDED(hr)) { hr = setNodeValueString(StringReferenceWrapper(m_body).Get(), textNode.Get()); } } } } } return hr; } HRESULT SnoreToasts::setButtons(ComPtr root) { ComPtr actionsElement; HRESULT hr = m_toastXml->CreateElement(StringReferenceWrapper(L"actions").Get(), &actionsElement); if (SUCCEEDED(hr)) { ComPtr actionsNodeTmp; hr = actionsElement.As(&actionsNodeTmp); if (SUCCEEDED(hr)) { ComPtr actionsNode; root->AppendChild(actionsNodeTmp.Get(), &actionsNode); std::wstring buttonText; std::wstringstream wss(m_buttons); while(std::getline(wss, buttonText, L';')) { hr &= createNewActionButton(actionsNode, buttonText); } } } return hr; } HRESULT SnoreToasts::setTextBox(ComPtr root) { ComPtr actionsElement; HRESULT hr = m_toastXml->CreateElement(StringReferenceWrapper(L"actions").Get(), &actionsElement); if (SUCCEEDED(hr)) { ComPtr actionsNodeTmp; hr = actionsElement.As(&actionsNodeTmp); if (SUCCEEDED(hr)) { ComPtr actionsNode; root->AppendChild(actionsNodeTmp.Get(), &actionsNode); ComPtr inputElement; HRESULT hr = m_toastXml->CreateElement(StringReferenceWrapper(L"input").Get(), &inputElement); if (SUCCEEDED(hr)) { ComPtr inputNodeTmp; hr = inputElement.As(&inputNodeTmp); if (SUCCEEDED(hr)) { ComPtr inputNode; actionsNode->AppendChild(inputNodeTmp.Get(), &inputNode); ComPtr inputAttributes; hr = inputNode->get_Attributes(&inputAttributes); if (SUCCEEDED(hr)) { hr &= addAttribute(L"id", inputAttributes.Get(), L"textBox"); hr &= addAttribute(L"type", inputAttributes.Get(), L"text"); hr &= addAttribute(L"placeHolderContent", inputAttributes.Get(), L"Type a reply"); } } ComPtr actionElement; HRESULT hr = m_toastXml->CreateElement(StringReferenceWrapper(L"action").Get(), &actionElement); if (SUCCEEDED(hr)) { ComPtr actionNodeTmp; hr = actionElement.As(&actionNodeTmp); if (SUCCEEDED(hr)) { ComPtr actionNode; actionsNode->AppendChild(actionNodeTmp.Get(), &actionNode); ComPtr actionAttributes; hr = actionNode->get_Attributes(&actionAttributes); if (SUCCEEDED(hr)) { hr &= addAttribute(L"content", actionAttributes.Get(), L"Send"); const auto data = formatAction(SnoreToastActions::Actions::ButtonClicked); hr &= addAttribute(L"arguments", actionAttributes.Get(), data); hr &= addAttribute(L"hint-inputId", actionAttributes.Get(), L"textBox"); } } } } } } return hr; } HRESULT SnoreToasts::setEventHandler(ComPtr toast) { // Register the event handlers EventRegistrationToken activatedToken, dismissedToken, failedToken; ComPtr eventHandler(new ToastEventHandler(*this)); HRESULT hr = toast->add_Activated(eventHandler.Get(), &activatedToken); if (SUCCEEDED(hr)) { hr = toast->add_Dismissed(eventHandler.Get(), &dismissedToken); if (SUCCEEDED(hr)) { hr = toast->add_Failed(eventHandler.Get(), &failedToken); if (SUCCEEDED(hr)) { m_eventHanlder = eventHandler; } } } return hr; } HRESULT SnoreToasts::setNodeValueString(const HSTRING &inputString, IXmlNode *node) { ComPtr inputText; HRESULT hr = m_toastXml->CreateTextNode(inputString, &inputText); if (SUCCEEDED(hr)) { ComPtr inputTextNode; hr = inputText.As(&inputTextNode); if (SUCCEEDED(hr)) { ComPtr pAppendedChild; hr = node->AppendChild(inputTextNode.Get(), &pAppendedChild); } } return hr; } HRESULT SnoreToasts::addAttribute(const std::wstring &name, IXmlNamedNodeMap *attributeMap) { ComPtr srcAttribute; HRESULT hr = m_toastXml->CreateAttribute(StringReferenceWrapper(name).Get(), &srcAttribute); if (SUCCEEDED(hr)) { ComPtr node; hr = srcAttribute.As(&node); if (SUCCEEDED(hr)) { ComPtr pNode; hr = attributeMap->SetNamedItem(node.Get(), &pNode); } } return hr; } HRESULT SnoreToasts::addAttribute(const std::wstring &name, IXmlNamedNodeMap *attributeMap, const std::wstring &value) { ComPtr srcAttribute; HRESULT hr = m_toastXml->CreateAttribute(StringReferenceWrapper(name).Get(), &srcAttribute); if (SUCCEEDED(hr)) { ComPtr node; hr = srcAttribute.As(&node); if (SUCCEEDED(hr)) { ComPtr pNode; hr = attributeMap->SetNamedItem(node.Get(), &pNode); hr = setNodeValueString(StringReferenceWrapper(value).Get(), node.Get()); } } return hr; } HRESULT SnoreToasts::createNewActionButton(ComPtr actionsNode, const std::wstring &value) { ComPtr actionElement; HRESULT hr = m_toastXml->CreateElement(StringReferenceWrapper(L"action").Get(), &actionElement); if (SUCCEEDED(hr)) { ComPtr actionNodeTmp; hr = actionElement.As(&actionNodeTmp); if (SUCCEEDED(hr)) { ComPtr actionNode; actionsNode->AppendChild(actionNodeTmp.Get(), &actionNode); ComPtr actionAttributes; hr = actionNode->get_Attributes(&actionAttributes); if (SUCCEEDED(hr)) { hr &= addAttribute(L"content", actionAttributes.Get(), value); const auto data = formatAction(SnoreToastActions::Actions::ButtonClicked, { {L"button", value} }); hr &= addAttribute(L"arguments", actionAttributes.Get(), data); hr &= addAttribute(L"activationType", actionAttributes.Get(), L"foreground"); } } } return hr; } void SnoreToasts::printXML() { ComPtr s; ComPtr ss(m_toastXml); ss.As(&s); HSTRING string; s->GetXml(&string); PCWSTR str = WindowsGetStringRawBuffer(string, nullptr); tLog << L"------------------------\n\t\t\t" << str << L"\n\t\t" << L"------------------------"; } std::filesystem::path SnoreToasts::pipeName() const { return m_pipeName; } void SnoreToasts::setPipeName(const std::filesystem::path &pipeName) { m_pipeName = pipeName; } std::filesystem::path SnoreToasts::application() const { return m_application; } void SnoreToasts::setApplication(const std::filesystem::path & application) { m_application = application; } -std::wstring SnoreToasts::formatAction(const SnoreToastActions::Actions &action, const std::vector > &extraData) const +std::wstring SnoreToasts::formatAction(const SnoreToastActions::Actions &action, const std::vector > &extraData) const { - const auto &actionString = SnoreToastActions::getActionString(action); - std::vector> data = { - {L"action", std::wstring(actionString.data(), actionString.size())}, - {L"notificationId", m_id}, - {L"pipe", m_pipeName}, - {L"application", m_application} + const auto pipe = m_pipeName.wstring(); + const auto application = m_application.wstring(); + std::vector> data = { + {L"action", SnoreToastActions::getActionString(action)}, + {L"notificationId", std::wstring_view(m_id)}, + {L"pipe", std::wstring_view(pipe)}, + {L"application", std::wstring_view(application)} }; data.insert(data.end(), extraData.cbegin(), extraData.cend()); return Utils::formatData(data); } // Create and display the toast HRESULT SnoreToasts::createToast() { HRESULT hr = m_toastManager->CreateToastNotifierWithId(StringReferenceWrapper(m_appID).Get(), &m_notifier); if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) { ComPtr factory; hr = GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), &factory); if (SUCCEEDED(hr)) { hr = factory->CreateToastNotification(m_toastXml.Get(), &m_notification); if (SUCCEEDED(hr)) { if (m_wait) { NotificationSetting setting = NotificationSetting_Enabled; m_notifier->get_Setting(&setting); if (setting == NotificationSetting_Enabled) { hr = setEventHandler(m_notification); } else { std::wcerr << L"Notifications are disabled" << std::endl << L"Reason: "; switch (setting) { case NotificationSetting_DisabledForApplication: std::wcerr << L"DisabledForApplication" << std::endl; break; case NotificationSetting_DisabledForUser: std::wcerr << L"DisabledForUser" << std::endl; break; case NotificationSetting_DisabledByGroupPolicy: std::wcerr << L"DisabledByGroupPolicy" << std::endl; break; case NotificationSetting_DisabledByManifest: std::wcerr << L"DisabledByManifest" << std::endl; break; } std::wcerr << L"Please make sure that the app id is set correctly." << std::endl; std::wcerr << L"Command Line: " << GetCommandLineW() << std::endl; } } if (SUCCEEDED(hr)) { hr = m_notifier->Show(m_notification.Get()); } } } } } return hr; } std::wstring SnoreToasts::version() { // if there are changes to the callback mechanism we need to change the uuid for the activator TOAST_UUID return L"0.5.99"; -} - -SnoreToastActions::Actions SnoreToastActions::getAction(const std::wstring &s) -{ - int i = 0; - for (const auto &sv : ActionStrings) - { - if (sv.compare(s.c_str()) == 0) - { - return static_cast(i); - } - ++i; - } - return SnoreToastActions::Actions::Error; -} +} \ No newline at end of file diff --git a/src/snoretoasts.h b/src/snoretoasts.h index 00a2a96..7214654 100644 --- a/src/snoretoasts.h +++ b/src/snoretoasts.h @@ -1,147 +1,115 @@ /* SnoreToast is capable to invoke Windows 8 toast notifications. Copyright (C) 2013-2019 Hannah von Reth SnoreToast is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SnoreToast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with SnoreToast. If not, see . */ #pragma once #include "stringreferencewrapper.h" +#include "snoretoastactions.h" #include // Windows Header Files: #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Microsoft::WRL; using namespace ABI::Windows::Data::Xml::Dom; class ToastEventHandler; - -class SnoreToastActions { -public: - enum class Actions { - Clicked, - Hidden, - Dismissed, - Timedout, - ButtonClicked, - TextEntered, - - Error = -1 - }; - - static constexpr std::wstring_view getActionString(const Actions &a) - { - return ActionStrings[static_cast(a)]; - } - - static SnoreToastActions::Actions getAction(const std::wstring &s); - -private: - static constexpr std::wstring_view ActionStrings[] = - { - L"clicked", - L"hidden", - L"dismissed", - L"timedout", - L"buttonClicked", - L"textEntered", - }; -}; - class SnoreToasts { public: static std::wstring version(); SnoreToasts(const std::wstring &appID); ~SnoreToasts(); void displayToast(const std::wstring &title, const std::wstring &body, const std::filesystem::path &image, bool wait); SnoreToastActions::Actions userAction(); bool closeNotification(); void setSound(const std::wstring &soundFile); void setSilent(bool silent); void setId(const std::wstring &id); std::wstring id() const; void setButtons(const std::wstring &buttons); void setTextBoxEnabled(bool textBoxEnabled); std::filesystem::path pipeName() const; void setPipeName(const std::filesystem::path &pipeName); std::filesystem::path application() const; void setApplication(const std::filesystem::path &application); - std::wstring formatAction(const SnoreToastActions::Actions &action, const std::vector > &extraData = {}) const; + std::wstring formatAction(const SnoreToastActions::Actions &action, const std::vector > &extraData = {}) const; private: HRESULT createToast(); HRESULT setImage(); HRESULT setSound(); HRESULT setTextValues(); HRESULT setButtons(ComPtr root); HRESULT setTextBox(ComPtr root); HRESULT setEventHandler(Microsoft::WRL::ComPtr toast); HRESULT setNodeValueString(const HSTRING &onputString, ABI::Windows::Data::Xml::Dom::IXmlNode *node); HRESULT addAttribute(const std::wstring &name, ABI::Windows::Data::Xml::Dom::IXmlNamedNodeMap *attributeMap); HRESULT addAttribute(const std::wstring &name, ABI::Windows::Data::Xml::Dom::IXmlNamedNodeMap *attributeMap, const std::wstring &value); HRESULT createNewActionButton(ComPtr actionsNode, const std::wstring &value); void printXML(); std::wstring m_appID; std::filesystem::path m_pipeName; std::filesystem::path m_application; std::wstring m_title; std::wstring m_body; std::filesystem::path m_image; std::wstring m_sound = L"Notification.Default"; std::wstring m_id; std::wstring m_buttons; bool m_silent; bool m_wait; bool m_textbox; SnoreToastActions::Actions m_action = SnoreToastActions::Actions::Clicked; Microsoft::WRL::ComPtr m_toastXml; Microsoft::WRL::ComPtr m_toastManager; Microsoft::WRL::ComPtr m_notifier; Microsoft::WRL::ComPtr m_notification; Microsoft::WRL::ComPtr m_eventHanlder; }; diff --git a/src/toasteventhandler.cpp b/src/toasteventhandler.cpp index 38716d7..9997d7c 100644 --- a/src/toasteventhandler.cpp +++ b/src/toasteventhandler.cpp @@ -1,210 +1,208 @@ /* SnoreToast is capable to invoke Windows 8 toast notifications. Copyright (C) 2013-2019 Hannah von Reth SnoreToast is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SnoreToast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with SnoreToast. If not, see . */ #include "snoretoasts.h" #include "toasteventhandler.h" #include "utils.h" #include #include #include #include #include using namespace ABI::Windows::UI::Notifications; ToastEventHandler::ToastEventHandler(const SnoreToasts &toast) : m_ref(1), m_userAction(SnoreToastActions::Actions::Hidden) , m_toast(toast) { std::wstringstream eventName; eventName << L"ToastEvent" << m_toast.id(); m_event = CreateEventW(nullptr, true, false, eventName.str().c_str()); } ToastEventHandler::~ToastEventHandler() { CloseHandle(m_event); } HANDLE ToastEventHandler::event() { return m_event; } SnoreToastActions::Actions &ToastEventHandler::userAction() { return m_userAction; } // DesktopToastActivatedEventHandler IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification * /*sender*/, _In_ IInspectable *args) { IToastActivatedEventArgs *buttonReply = nullptr; args->QueryInterface(&buttonReply); if (buttonReply == nullptr) { std::wcerr << L"args is not a IToastActivatedEventArgs" << std::endl; } else { HSTRING args; buttonReply->get_Arguments(&args); std::wstring data = WindowsGetStringRawBuffer(args, nullptr); tLog << data; const auto dataMap = Utils::splitData(data); const auto action = SnoreToastActions::getAction(dataMap.at(L"action")); assert(dataMap.at(L"notificationId") == m_toast.id()); if (action == SnoreToastActions::Actions::TextEntered) { // The text is only passed to the named pipe tLog << L"The user entered a text."; m_userAction = SnoreToastActions::Actions::TextEntered; } else if (action == SnoreToastActions::Actions::Clicked) { tLog << L"The user clicked on the toast."; m_userAction = SnoreToastActions::Actions::Clicked; } else { tLog << L"The user clicked on a toast button."; std::wcout << dataMap.at(L"button") << std::endl; m_userAction = SnoreToastActions::Actions::ButtonClicked; } } SetEvent(m_event); return S_OK; } // DesktopToastDismissedEventHandler IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification * /* sender */, _In_ IToastDismissedEventArgs *e) { ToastDismissalReason tdr; HRESULT hr = e->get_Reason(&tdr); if (SUCCEEDED(hr)) { switch (tdr) { case ToastDismissalReason_ApplicationHidden: tLog << L"The application hid the toast using ToastNotifier.hide()"; m_userAction = SnoreToastActions::Actions::Hidden; break; case ToastDismissalReason_UserCanceled: tLog << L"The user dismissed this toast"; m_userAction = SnoreToastActions::Actions::Dismissed; break; case ToastDismissalReason_TimedOut: tLog << L"The toast has timed out"; m_userAction = SnoreToastActions::Actions::Timedout; break; } } if (!m_toast.pipeName().empty()) { Utils::writePipe(m_toast.pipeName(), m_toast.formatAction(m_userAction)); } SetEvent(m_event); return S_OK; } // DesktopToastFailedEventHandler IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification * /* sender */, _In_ IToastFailedEventArgs * /* e */) { std::wcerr << L"The toast encountered an error." << std::endl; std::wcerr << L"Please make sure that the app id is set correctly." << std::endl; std::wcerr << L"Command Line: " << GetCommandLineW() << std::endl; m_userAction = SnoreToastActions::Actions::Error; SetEvent(m_event); return S_OK; } CToastNotificationActivationCallback::CToastNotificationActivationCallback() { } -HANDLE CToastNotificationActivationCallback::m_event = INVALID_HANDLE_VALUE; +HANDLE CToastNotificationActivationCallback::event() +{ + static HANDLE _event = []{ + std::wstringstream eventName; + eventName << L"ToastActivationEvent" << GetCurrentProcessId(); + return CreateEvent(nullptr, true, false, eventName.str().c_str()); + }(); + return _event; +} HRESULT CToastNotificationActivationCallback::Activate(LPCWSTR appUserModelId, LPCWSTR invokedArgs, const NOTIFICATION_USER_INPUT_DATA* data, ULONG count) { if (invokedArgs == nullptr) { return S_OK; } tLog << "CToastNotificationActivationCallback::Activate: " << appUserModelId << " : " << invokedArgs << " : " << data; const auto dataMap = Utils::splitData(invokedArgs); const auto action = SnoreToastActions::getAction(dataMap.at(L"action")); std::wstring dataString; if (action == SnoreToastActions::Actions::TextEntered) { assert(count); std::wstringstream sMsg; sMsg << invokedArgs << L"text="; for (ULONG i=0; isecond, dataString)) { const auto app = dataMap.find(L"application"); if (app != dataMap.cend()) { if (Utils::startProcess(app->second)) { - Utils::writePipe(pipe->second, dataString); + Utils::writePipe(pipe->second, dataString, true); } } } } tLog << dataString; - if (m_event != INVALID_HANDLE_VALUE) - { - SetEvent(m_event); - } - return S_OK; + if (!SetEvent(event())) + { + tLog << "SetEvent failed" << GetLastError(); + return S_FALSE; + } + return S_OK; } void CToastNotificationActivationCallback::waitForActivation() { - if (m_event == INVALID_HANDLE_VALUE) - { - std::wstringstream eventName; - eventName << L"ToastActivationEvent" << GetCurrentProcessId(); - m_event = CreateEventW(nullptr, true, false, eventName.str().c_str()); - } - else - { - ResetEvent(m_event); - } - WaitForSingleObject(m_event, INFINITE); + WaitForSingleObject(event(), INFINITE); } \ No newline at end of file diff --git a/src/toasteventhandler.h b/src/toasteventhandler.h index de38c67..dc96c86 100644 --- a/src/toasteventhandler.h +++ b/src/toasteventhandler.h @@ -1,125 +1,125 @@ /* SnoreToast is capable to invoke Windows 8 toast notifications. Copyright (C) 2013-2019 Hannah von Reth SnoreToast is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SnoreToast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with SnoreToast. If not, see . */ #pragma once #include "snoretoasts.h" #include "wrl.h" #define TOAST_UUID "{383803B6-AFDA-4220-BFC3-0DBF810106BF}" typedef ABI::Windows::Foundation::ITypedEventHandler DesktopToastActivatedEventHandler; typedef ABI::Windows::Foundation::ITypedEventHandler DesktopToastDismissedEventHandler; typedef ABI::Windows::Foundation::ITypedEventHandler DesktopToastFailedEventHandler; //Define INotificationActivationCallback for older versions of the Windows SDK #include typedef struct NOTIFICATION_USER_INPUT_DATA { LPCWSTR Key; LPCWSTR Value; } NOTIFICATION_USER_INPUT_DATA; MIDL_INTERFACE("53E31837-6600-4A81-9395-75CFFE746F94") INotificationActivationCallback : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE Activate(__RPC__in_string LPCWSTR appUserModelId, __RPC__in_opt_string LPCWSTR invokedArgs, __RPC__in_ecount_full_opt(count) const NOTIFICATION_USER_INPUT_DATA *data, ULONG count) = 0; }; //The COM server which implements the callback notifcation from Action Center class DECLSPEC_UUID(TOAST_UUID) CToastNotificationActivationCallback : public Microsoft::WRL::RuntimeClass, INotificationActivationCallback> { public: static void waitForActivation(); CToastNotificationActivationCallback(); virtual HRESULT STDMETHODCALLTYPE Activate(__RPC__in_string LPCWSTR appUserModelId, __RPC__in_opt_string LPCWSTR invokedArgs, __RPC__in_ecount_full_opt(count) const NOTIFICATION_USER_INPUT_DATA* data, ULONG count) override; private: - static HANDLE m_event; + static HANDLE event(); }; CoCreatableClass(CToastNotificationActivationCallback); class ToastEventHandler : public Microsoft::WRL::Implements { public: explicit ToastEventHandler::ToastEventHandler(const SnoreToasts &toast); ~ToastEventHandler(); HANDLE event(); SnoreToastActions::Actions &userAction(); // DesktopToastActivatedEventHandler IFACEMETHODIMP Invoke(_In_ ABI::Windows::UI::Notifications::IToastNotification *sender, _In_ IInspectable *args); // DesktopToastDismissedEventHandler IFACEMETHODIMP Invoke(_In_ ABI::Windows::UI::Notifications::IToastNotification *sender, _In_ ABI::Windows::UI::Notifications::IToastDismissedEventArgs *e); // DesktopToastFailedEventHandler IFACEMETHODIMP Invoke(_In_ ABI::Windows::UI::Notifications::IToastNotification *sender, _In_ ABI::Windows::UI::Notifications::IToastFailedEventArgs *e); // IUnknown IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_ref); } IFACEMETHODIMP_(ULONG) Release() { ULONG l = InterlockedDecrement(&m_ref); if (l == 0) { delete this; } return l; } IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _COM_Outptr_ void **ppv) { if (IsEqualIID(riid, IID_IUnknown)) { *ppv = static_cast(static_cast(this)); } else if (IsEqualIID(riid, __uuidof(DesktopToastActivatedEventHandler))) { *ppv = static_cast(this); } else if (IsEqualIID(riid, __uuidof(DesktopToastDismissedEventHandler))) { *ppv = static_cast(this); } else if (IsEqualIID(riid, __uuidof(DesktopToastFailedEventHandler))) { *ppv = static_cast(this); } else { *ppv = nullptr; } if (*ppv) { reinterpret_cast(*ppv)->AddRef(); return S_OK; } return E_NOINTERFACE; } private: ULONG m_ref; SnoreToastActions::Actions m_userAction; HANDLE m_event; const SnoreToasts &m_toast; }; diff --git a/src/utils.cpp b/src/utils.cpp index 7dc5252..c75500d 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,149 +1,158 @@ /* SnoreToast is capable to invoke Windows 8 toast notifications. Copyright (C) 2019 Hannah von Reth SnoreToast is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SnoreToast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with SnoreToast. If not, see . */ #include "utils.h" #include "snoretoasts.h" #include #include #include using namespace Microsoft::WRL; namespace { bool s_registered = false; } namespace Utils { bool registerActivator() { if (!s_registered) { s_registered = true; Microsoft::WRL::Module::Create([] {}); Microsoft::WRL::Module::GetModule().IncrementObjectCount(); return SUCCEEDED(Microsoft::WRL::Module::GetModule().RegisterObjects()); } return true; } void unregisterActivator() { if (s_registered) { s_registered = false; Microsoft::WRL::Module::GetModule().UnregisterObjects(); Microsoft::WRL::Module::GetModule().DecrementObjectCount(); } } -std::unordered_map splitData(const std::wstring &data) +std::unordered_map splitData(const std::wstring_view &data) { - std::unordered_map out; - std::wstring tmp; - std::wstringstream wss(data); - while(std::getline(wss, tmp, L';')) - { - const auto pos = tmp.find(L"="); - out[tmp.substr(0, pos)] = tmp.substr(pos + 1); - } + std::unordered_map out; + size_t start = 0; + for (size_t end = data.find(L";", start); end != std::wstring::npos; start=end + 1, end=data.find(L";", start)) + { + if (start == end) + { + end = data.size(); + } + const std::wstring_view tmp(data.data() + start, end - start); + const auto pos = tmp.find(L"="); + out[tmp.substr(0, pos)] = tmp.substr(pos + 1); + //tLog << L"'" << tmp.substr(0, pos) << L"' = '" << tmp.substr(pos + 1) << L"'"; + } return out; } const std::filesystem::path &selfLocate() { static const std::filesystem::path path = [] { std::wstring buf; size_t size; do { buf.resize(buf.size() + 1024); size = GetModuleFileNameW(nullptr, const_cast(buf.data()), static_cast(buf.size())); } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); buf.resize(size); return buf; }(); return path; } -bool writePipe(const std::filesystem::path &pipe, const std::wstring &data) +bool writePipe(const std::filesystem::path &pipe, const std::wstring &data, bool wait) { - HANDLE hPipe = CreateFile(pipe.wstring().c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); + HANDLE hPipe = CreateFile(pipe.wstring().c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); if (hPipe != INVALID_HANDLE_VALUE) { DWORD written; const DWORD toWrite = static_cast(data.size() * sizeof(wchar_t)); WriteFile(hPipe, data.c_str(), toWrite, &written, nullptr); const bool success = written == toWrite; tLog << (success ? L"Wrote: " : L"Failed to write: ") << data << " to " << pipe; WriteFile(hPipe, nullptr, sizeof(wchar_t), &written, nullptr); CloseHandle(hPipe); return success; - } + } + else if (wait) + { + // wait and retry + Sleep(20000); + return writePipe(pipe, data); + } tLog << L"Failed to open pipe: " << pipe << L" data: " << data; return false; } bool startProcess(const std::filesystem::path & app) { STARTUPINFO info = {}; info.cb = sizeof(info); PROCESS_INFORMATION pInfo = {}; const auto application = app.wstring(); if (!CreateProcess(const_cast(application.c_str()), const_cast(application.c_str()), nullptr, nullptr, false, DETACHED_PROCESS | INHERIT_PARENT_AFFINITY | CREATE_NO_WINDOW, nullptr, nullptr, &info, &pInfo)) { tLog << L"Failed to start: " << app; return false; } WaitForInputIdle(pInfo.hProcess, INFINITE); - // wait a second until the application is initialised - Sleep(1000); DWORD status; GetExitCodeProcess(pInfo.hProcess, &status); CloseHandle(pInfo.hProcess); CloseHandle(pInfo.hThread); tLog << L"Started: " << app << L" Status: " << (status == STILL_ACTIVE ? L"STILL_ACTIVE" : std::to_wstring(status)); return status == STILL_ACTIVE; } -std::wstring formatData(const std::vector > &data) +std::wstring formatData(const std::vector > &data) { std::wstringstream out; for (const auto &p : data) { if (!p.second.empty()) { out << p.first << L"=" << p.second << L";"; } } return out.str(); } } ToastLog::ToastLog() { *this << Utils::selfLocate() << L" v" << SnoreToasts::version() << L"\n\t"; } ToastLog::~ToastLog() { m_log << L"\n"; OutputDebugStringW(m_log.str().c_str()); } diff --git a/src/utils.h b/src/utils.h index c02b52b..8082931 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,61 +1,61 @@ /* SnoreToast is capable to invoke Windows 8 toast notifications. Copyright (C) 2019 Hannah von Reth SnoreToast is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SnoreToast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with SnoreToast. If not, see . */ #pragma once #include #include #include namespace Utils { bool registerActivator(); void unregisterActivator(); - std::unordered_map splitData(const std::wstring &data); + std::unordered_map splitData(const std::wstring_view &data); const std::filesystem::path &selfLocate(); - std::wstring formatData(const std::vector> &data); + std::wstring formatData(const std::vector> &data); - bool writePipe(const std::filesystem::path &pipe, const std::wstring &data); + bool writePipe(const std::filesystem::path &pipe, const std::wstring &data, bool wait=false); bool startProcess(const std::filesystem::path &app); }; class ToastLog { public: ToastLog(); ~ToastLog(); inline ToastLog &log() { return *this;} private: std::wstringstream m_log; template friend ToastLog & operator<<(ToastLog &, const T&); }; #define tLog ToastLog().log() << __FUNCSIG__ << L"\n\t\t" template ToastLog &operator<< (ToastLog &log, const T &t) { log.m_log << t; return log; }