diff --git a/examples/qt-lib/main.cpp b/examples/qt-lib/main.cpp index 4cda69d..61e7958 100644 --- a/examples/qt-lib/main.cpp +++ b/examples/qt-lib/main.cpp @@ -1,51 +1,50 @@ /* 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 #include #include #include #include "../../src/snoretoasts.h" #include "../../src/snoretoastactions.h" #include "../../src/linkhelper.h" namespace { const auto appId = QStringLiteral("Snore.DesktopToasts%1.QtLib").arg(SnoreToasts::version()).toStdWString(); } int main(int argc, char *argv[]) { QGuiApplication a(argc, argv); qDebug() << SUCCEEDED(LinkHelper::tryCreateShortcut( std::filesystem::path(L"SnoreToast") / SnoreToasts::version() / L"SnoreToastQtLib", appId)); QTimer::singleShot(0, &a, [&] { SnoreToasts toast(appId); // app.setPipeName(pipe); // app.setApplication(app.applicationDirPath().); toast.displayToast(L"Test", L"Message", L"", true); std::wcout << "Result" << SnoreToastActions::getActionString(toast.userAction()) << std::endl; a.quit(); }); return a.exec(); } - diff --git a/examples/qt/main.cpp b/examples/qt/main.cpp index daa05cf..c5b7072 100644 --- a/examples/qt/main.cpp +++ b/examples/qt/main.cpp @@ -1,86 +1,85 @@ /* 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 #include #include #include #include #include #include - #include "../../src/snoretoastactions.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QLocalServer *server = new QLocalServer(); QObject::connect(server, &QLocalServer::newConnection, server, [server]() { auto sock = server->nextPendingConnection(); sock->waitForReadyRead(); const QByteArray rawData = sock->readAll(); const QString data = QString::fromWCharArray(reinterpret_cast(rawData.constData()), rawData.size() / sizeof(wchar_t)); QMap map; - for (const auto &str : data.split(";")) - { - const auto index = str.indexOf("="); + for (const auto &str : data.split(";")) { + const auto index = str.indexOf("="); map[str.mid(0, index)] = str.mid(index + 1); } const auto action = map["action"]; std::wstring waction(action.size(), 0); action.toWCharArray(waction.data()); std::wcout << qPrintable(data) << std::endl; - std::wcout << "Action: " << waction << " " << static_cast(SnoreToastActions::getAction(waction)) << std::endl; + std::wcout << "Action: " << waction << " " + << static_cast(SnoreToastActions::getAction(waction)) << std::endl; // TODO: parse data }); server->listen("foo"); std::wcout << qPrintable(server->fullServerName()) << std::endl; const QString appId = "SnoreToast.Qt.Example"; QProcess proc(&a); proc.start("SnoreToast.exe", { "-install", "SnoreToastTestQt", a.applicationFilePath(), appId }); proc.waitForFinished(); std::wcout << proc.exitCode() << std::endl; std::wcout << qPrintable(proc.readAll()) << std::endl; QTimer *timer = new QTimer(&a); a.connect(timer, &QTimer::timeout, timer, [&] { static int id = 0; if (id >= 10) { timer->stop(); } auto proc = new QProcess(&a); proc->start("SnoreToast.exe", { "-t", "test", "-m", "message", "-pipename", server->fullServerName(), "-w", "-id", QString::number(id++), "-appId", appId, "-application", a.applicationFilePath() }); proc->connect(proc, QOverload::of(&QProcess::finished), proc, [proc] { std::wcout << qPrintable(proc->readAll()) << std::endl; std::wcout << proc->exitCode() << std::endl; }); }); timer->start(1000); return a.exec(); } diff --git a/src/linkhelper.cpp b/src/linkhelper.cpp index 17295c9..7eba5b6 100644 --- a/src/linkhelper.cpp +++ b/src/linkhelper.cpp @@ -1,154 +1,153 @@ /* 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 "linkhelper.h" #include "toasteventhandler.h" #include "utils.h" #include #include #include #include // compat with older sdk #ifndef INIT_PKEY_AppUserModel_ToastActivatorCLSID EXTERN_C const PROPERTYKEY DECLSPEC_SELECTANY PKEY_AppUserModel_ToastActivatorCLSID = { { 0x9F4C2855, 0x9F79, 0x4B39, { 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 } }, 26 }; #define INIT_PKEY_AppUserModel_ToastActivatorCLSID \ { \ { 0x9F4C2855, 0x9F79, 0x4B39, 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 }, 26 \ } #endif //#ifndef INIT_PKEY_AppUserModel_ToastActivatorCLSID HRESULT LinkHelper::tryCreateShortcut(const std::filesystem::path &shortcutPath, const std::filesystem::path &exePath, const std::wstring &appID, const std::wstring &callbackUUID) { if (!std::filesystem::path(shortcutPath).is_relative()) { std::wcerr << L"The shortcut path must be relative" << std::endl; return S_FALSE; } const std::filesystem::path path = (startmenuPath() / shortcutPath).replace_extension(L".lnk"); if (std::filesystem::exists(path)) { tLog << L"Path: " << path << L" already exists, skip creation of shortcut"; return S_OK; } if (!std::filesystem::exists(path.parent_path()) && !std::filesystem::create_directories(path.parent_path())) { tLog << L"Failed to create dir: " << path.parent_path(); return S_FALSE; } return installShortcut(path, exePath, appID, callbackUUID); } HRESULT LinkHelper::tryCreateShortcut(const std::filesystem::path &shortcutPath, const std::wstring &appID, const std::wstring &callbackUUID) { return tryCreateShortcut(shortcutPath, Utils::selfLocate(), appID, callbackUUID); } // Install the shortcut HRESULT LinkHelper::installShortcut(const std::filesystem::path &shortcutPath, const std::filesystem::path &exePath, const std::wstring &appID, const std::wstring &callbackUUID) { HRESULT hr = S_OK; std::wcout << L"Installing shortcut: " << shortcutPath << L" " << exePath << L" " << appID << std::endl; tLog << L"Installing shortcut: " << shortcutPath << L" " << exePath << L" " << appID << L" " << callbackUUID; if (!callbackUUID.empty()) { /** * Add CToastNotificationActivationCallback to registry * Required to use the CToastNotificationActivationCallback for buttons and textbox * interactions. windows.ui.notifications does not support user interaction from cpp */ const std::wstring locPath = Utils::selfLocate().wstring(); std::wstringstream url; url << L"SOFTWARE\\Classes\\CLSID\\" << callbackUUID << L"\\LocalServer32"; hr = HRESULT_FROM_WIN32(::RegSetKeyValueW( HKEY_CURRENT_USER, url.str().c_str(), nullptr, REG_SZ, locPath.c_str(), static_cast(locPath.size() * sizeof(wchar_t)))); } if (SUCCEEDED(hr)) { ComPtr shellLink; hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); if (SUCCEEDED(hr)) { hr = shellLink->SetPath(exePath.c_str()); if (SUCCEEDED(hr)) { hr = shellLink->SetArguments(L""); if (SUCCEEDED(hr)) { ComPtr propertyStore; hr = shellLink.As(&propertyStore); if (SUCCEEDED(hr)) { PROPVARIANT appIdPropVar; hr = InitPropVariantFromString(appID.c_str(), &appIdPropVar); if (SUCCEEDED(hr)) { hr = propertyStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar); PropVariantClear(&appIdPropVar); } } if (SUCCEEDED(hr) && !callbackUUID.empty()) { GUID guid; hr = CLSIDFromString(callbackUUID.c_str(), &guid); - if (SUCCEEDED(hr)) - { + if (SUCCEEDED(hr)) { tLog << guid.Data1; PROPVARIANT toastActivatorPropVar = {}; toastActivatorPropVar.vt = VT_CLSID; toastActivatorPropVar.puuid = &guid; hr = propertyStore->SetValue(PKEY_AppUserModel_ToastActivatorCLSID, toastActivatorPropVar); } } if (SUCCEEDED(hr)) { hr = propertyStore->Commit(); if (SUCCEEDED(hr)) { ComPtr persistFile; hr = shellLink.As(&persistFile); if (SUCCEEDED(hr)) { hr = persistFile->Save(shortcutPath.c_str(), TRUE); } } } } } } } if (FAILED(hr)) { std::wcerr << "Failed to install shortcut " << shortcutPath << " error: " << _com_error(hr).ErrorMessage() << std::endl; } return hr; } std::filesystem::path LinkHelper::startmenuPath() { wchar_t buffer[MAX_PATH]; std::wstringstream path; if (GetEnvironmentVariable(L"APPDATA", buffer, MAX_PATH) > 0) { path << buffer << L"\\Microsoft\\Windows\\Start Menu\\Programs\\"; } return path.str(); } diff --git a/src/snoretoastactions.h b/src/snoretoastactions.h index 2317f0d..885fe62 100644 --- a/src/snoretoastactions.h +++ b/src/snoretoastactions.h @@ -1,68 +1,67 @@ /* 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 #include class SnoreToastActions { public: enum class Actions { Clicked, Hidden, Dismissed, Timedout, ButtonClicked, TextEntered, Error = -1 }; - static const inline std::wstring& getActionString(const Actions &a) + static const inline std::wstring &getActionString(const Actions &a) { return actionMap().at(a); } template static inline SnoreToastActions::Actions getAction(const T &s) { for (const auto &a : actionMap()) { if (a.second.compare(s) == 0) { return a.first; } } return SnoreToastActions::Actions::Error; } - - private: - static const std::map &actionMap(){ + static const std::map &actionMap() + { static const std::map _ActionStrings = { - {Actions::Clicked, L"clicked"}, - {Actions::Hidden, L"hidden"}, - {Actions::Dismissed, L"dismissed"}, - {Actions::Timedout, L"timedout"}, - {Actions::ButtonClicked, L"buttonClicked"}, - {Actions::TextEntered, L"textEntered"} + { Actions::Clicked, L"clicked" }, + { Actions::Hidden, L"hidden" }, + { Actions::Dismissed, L"dismissed" }, + { Actions::Timedout, L"timedout" }, + { Actions::ButtonClicked, L"buttonClicked" }, + { Actions::TextEntered, L"textEntered" } }; return _ActionStrings; } }; diff --git a/src/snoretoasts.cpp b/src/snoretoasts.cpp index fd3a2ed..7e83de7 100644 --- a/src/snoretoasts.cpp +++ b/src/snoretoasts.cpp @@ -1,672 +1,673 @@ /* 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 "stringreferencewrapper.h" #include #include using namespace Microsoft::WRL; using namespace ABI::Windows::UI; using namespace ABI::Windows::UI::Notifications; using namespace ABI::Windows::Data::Xml::Dom; using namespace Windows::Foundation; class SnoreToastsPrivate { public: SnoreToastsPrivate(SnoreToasts *parent, const std::wstring &appID) : m_parent(parent), m_appID(appID), m_id(std::to_wstring(GetCurrentProcessId())) { } SnoreToasts *m_parent; 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 = false; bool m_wait = false; bool m_textbox = false; SnoreToastActions::Actions m_action = SnoreToastActions::Actions::Clicked; ComPtr m_toastXml; ComPtr m_toastManager; ComPtr m_notifier; ComPtr m_notification; ComPtr m_eventHanlder; static HANDLE ctoastEvent() { static HANDLE _event = [] { std::wstringstream eventName; eventName << L"ToastActivationEvent" << GetCurrentProcessId(); return CreateEvent(nullptr, true, false, eventName.str().c_str()); }(); return _event; } }; SnoreToasts::SnoreToasts(const std::wstring &appID) : d(new SnoreToastsPrivate(this, appID)) { Utils::registerActivator(); } SnoreToasts::~SnoreToasts() { Utils::unregisterActivator(); delete d; } void SnoreToasts::displayToast(const std::wstring &title, const std::wstring &body, const std::filesystem::path &image, bool wait) { HRESULT hr = S_OK; d->m_title = title; d->m_body = body; d->m_image = image; d->m_wait = wait; hr = GetActivationFactory( StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager) .Get(), &d->m_toastManager); if (!SUCCEEDED(hr)) { std::wcerr << L"SnoreToasts: Failed to register com Factory, please make sure you " L"correctly initialised with RO_INIT_MULTITHREADED" << std::endl; d->m_action = SnoreToastActions::Actions::Error; return; } if (!d->m_image.empty()) { hr = d->m_toastManager->GetTemplateContent(ToastTemplateType_ToastImageAndText02, &d->m_toastXml); } else { hr = d->m_toastManager->GetTemplateContent(ToastTemplateType_ToastText02, &d->m_toastXml); } if (SUCCEEDED(hr)) { ComPtr rootList; hr = d->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 (!d->m_buttons.empty()) { setButtons(root); } else if (d->m_textbox) { setTextBox(root); } if (SUCCEEDED(hr)) { ComPtr audioElement; hr = d->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 (!d->m_image.empty()) { hr = setImage(); } if (SUCCEEDED(hr)) { hr = setSound(); if (SUCCEEDED(hr)) { hr = setTextValues(); if (SUCCEEDED(hr)) { printXML(); hr = createToast(); } } } } d->m_action = SUCCEEDED(hr) ? SnoreToastActions::Actions::Clicked : SnoreToastActions::Actions::Error; } SnoreToastActions::Actions SnoreToasts::userAction() { if (d->m_eventHanlder.Get()) { HANDLE event = d->m_eventHanlder.Get()->event(); WaitForSingleObject(event, INFINITE); d->m_action = d->m_eventHanlder.Get()->userAction(); if (d->m_action == SnoreToastActions::Actions::Hidden) { d->m_notifier->Hide(d->m_notification.Get()); tLog << L"The application hid the toast using ToastNotifier.hide()"; } CloseHandle(event); } return d->m_action; } bool SnoreToasts::closeNotification() { std::wstringstream eventName; eventName << L"ToastEvent" << d->m_id; HANDLE event = OpenEventW(EVENT_ALL_ACCESS, FALSE, eventName.str().c_str()); if (event) { SetEvent(event); return true; } tLog << "Notification " << d->m_id << " does not exist"; return false; } void SnoreToasts::setSound(const std::wstring &soundFile) { d->m_sound = soundFile; } void SnoreToasts::setSilent(bool silent) { d->m_silent = silent; } void SnoreToasts::setId(const std::wstring &id) { if (!id.empty()) { d->m_id = id; } } std::wstring SnoreToasts::id() const { return d->m_id; } void SnoreToasts::setButtons(const std::wstring &buttons) { d->m_buttons = buttons; } void SnoreToasts::setTextBoxEnabled(bool textBoxEnabled) { d->m_textbox = textBoxEnabled; } // Set the value of the "src" attribute of the "image" node HRESULT SnoreToasts::setImage() { HRESULT hr = S_OK; ComPtr nodeList; hr = d->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(d->m_image).Get(), srcAttribute.Get()); } } } } return hr; } HRESULT SnoreToasts::setSound() { HRESULT hr = S_OK; ComPtr nodeList; hr = d->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 (d->m_sound.find(L"ms-winsoundevent:") == std::wstring::npos) { sound = L"ms-winsoundevent:"; sound.append(d->m_sound); } else { sound = d->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(d->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 = d->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(d->m_title).Get(), textNode.Get()); if (SUCCEEDED(hr)) { hr = nodeList->Item(1, &textNode); if (SUCCEEDED(hr)) { hr = setNodeValueString(StringReferenceWrapper(d->m_body).Get(), textNode.Get()); } } } } } return hr; } HRESULT SnoreToasts::setButtons(ComPtr root) { ComPtr actionsElement; HRESULT hr = d->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(d->m_buttons); while (std::getline(wss, buttonText, L';')) { hr &= createNewActionButton(actionsNode, buttonText); } } } return hr; } HRESULT SnoreToasts::setTextBox(ComPtr root) { ComPtr actionsElement; HRESULT hr = d->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 = d->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 = d->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)) { d->m_eventHanlder = eventHandler; } } } return hr; } HRESULT SnoreToasts::setNodeValueString(const HSTRING &inputString, IXmlNode *node) { ComPtr inputText; HRESULT hr = d->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 = d->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 = d->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 = d->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(d->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 d->m_pipeName; } void SnoreToasts::setPipeName(const std::filesystem::path &pipeName) { d->m_pipeName = pipeName; } std::filesystem::path SnoreToasts::application() const { return d->m_application; } void SnoreToasts::setApplication(const std::filesystem::path &application) { d->m_application = application; } std::wstring SnoreToasts::formatAction( const SnoreToastActions::Actions &action, const std::vector> &extraData) const { const auto pipe = d->m_pipeName.wstring(); const auto application = d->m_application.wstring(); std::vector> data = { { L"action", SnoreToastActions::getActionString(action) }, { L"notificationId", std::wstring_view(d->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 = d->m_toastManager->CreateToastNotifierWithId( StringReferenceWrapper(d->m_appID).Get(), &d->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(d->m_toastXml.Get(), &d->m_notification); if (SUCCEEDED(hr)) { if (d->m_wait) { NotificationSetting setting = NotificationSetting_Enabled; d->m_notifier->get_Setting(&setting); if (setting == NotificationSetting_Enabled) { hr = setEventHandler(d->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; case NotificationSetting_Enabled: // unreachable break; } std::wcerr << L"Please make sure that the app id is set correctly." << std::endl; std::wcerr << L"Command Line: " << GetCommandLineW() << std::endl; hr = S_FALSE; } } if (SUCCEEDED(hr)) { hr = d->m_notifier->Show(d->m_notification.Get()); } } } } } return hr; } std::wstring SnoreToasts::version() { - static std::wstring ver = []{ + static std::wstring ver = [] { std::wstringstream st; - st << SNORETOAST_VERSION_MAJOR << L"." << SNORETOAST_VERSION_MINOR << L"." << SNORETOAST_VERSION_PATCH; + st << SNORETOAST_VERSION_MAJOR << L"." << SNORETOAST_VERSION_MINOR << L"." + << SNORETOAST_VERSION_PATCH; return st.str(); }(); return ver; } HRESULT SnoreToasts::backgroundCallback(const std::wstring &appUserModelId, const std::wstring &invokedArgs, const std::wstring &msg) { tLog << "CToastNotificationActivationCallback::Activate: " << appUserModelId << " : " << invokedArgs << " : " << msg; const auto dataMap = Utils::splitData(invokedArgs); const auto action = SnoreToastActions::getAction(dataMap.at(L"action")); std::wstring dataString; if (action == SnoreToastActions::Actions::TextEntered) { std::wstringstream sMsg; sMsg << invokedArgs << L"text=" << msg; ; dataString = sMsg.str(); } else { dataString = invokedArgs; } const auto pipe = dataMap.find(L"pipe"); if (pipe != dataMap.cend()) { if (!Utils::writePipe(pipe->second, dataString)) { const auto app = dataMap.find(L"application"); if (app != dataMap.cend()) { if (Utils::startProcess(app->second)) { Utils::writePipe(pipe->second, dataString, true); } } } } tLog << dataString; if (!SetEvent(SnoreToastsPrivate::ctoastEvent())) { tLog << "SetEvent failed" << GetLastError(); return S_FALSE; } return S_OK; } void SnoreToasts::waitForCallbackActivation() { Utils::registerActivator(); WaitForSingleObject(SnoreToastsPrivate::ctoastEvent(), INFINITE); Utils::unregisterActivator(); } diff --git a/src/utils.cpp b/src/utils.cpp index 460bd72..5a07eb0 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,159 +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_view &data) { 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 wait) { 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); 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::wstringstream out; - const auto add = [&](const std::pair& p){ + const auto add = [&](const std::pair &p) { if (!p.second.empty()) { out << p.first << L"=" << p.second << L";"; } }; for (const auto &p : data) { add(p); } add({ L"version", SnoreToasts::version() }); 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()); }