diff --git a/examples/qt/main.cpp b/examples/qt/main.cpp index 630f74f..1f38f90 100644 --- a/examples/qt/main.cpp +++ b/examples/qt/main.cpp @@ -1,81 +1,81 @@ /* 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 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(); qDebug() << sock->bytesAvailable(); const QByteArray rawData = sock->readAll(); const QString data = QString::fromWCharArray(reinterpret_cast(rawData.constData()), rawData.size() / sizeof(wchar_t)); std::wcout << qPrintable(data) << 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", - "SnoreToast/SnoreToastTestQt", + "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 }); proc->connect(proc, QOverload::of(&QProcess::finished), proc, [proc]{ std::wcout << qPrintable(proc->errorString()) << std::endl; 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 31e48c0..5640a5b 100644 --- a/src/linkhelper.cpp +++ b/src/linkhelper.cpp @@ -1,140 +1,137 @@ /* 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 -#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::wstring &shortcutPath, const std::wstring &exePath, const std::wstring &appID) +HRESULT LinkHelper::tryCreateShortcut(const std::filesystem::path &shortcutPath, const std::filesystem::path &exePath, const std::wstring &appID) { - std::wstringstream lnkName; if (!std::filesystem::path(shortcutPath).is_relative()) { std::wcerr << L"The shortcut path must be relative" << std::endl; return S_FALSE; } - lnkName << startmenuPath() << L"SnoreToast/v" << SnoreToasts::version() << L"/" << shortcutPath; + std::filesystem::path path = startmenuPath(); + path += L"SnoreToast/v" + SnoreToasts::version() + L"/" + shortcutPath.wstring(); - if (shortcutPath.rfind(L".lnk") == std::wstring::npos) { - lnkName << L".lnk"; + if (shortcutPath.extension() != L".lnk") { + path += L".lnk"; } - const std::filesystem::path path(lnkName.str()); 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); } HRESULT LinkHelper::tryCreateShortcut(const std::wstring &appID) { - return tryCreateShortcut(L"SnoreToast.lnk", Utils::selfLocate().c_str(), appID); + return tryCreateShortcut(L"SnoreToast", Utils::selfLocate().c_str(), appID); } // Install the shortcut -HRESULT LinkHelper::installShortcut(const std::wstring &shortcutPath, const std::wstring &exePath, const std::wstring &appID) +HRESULT LinkHelper::installShortcut(const std::filesystem::path &shortcutPath, const std::filesystem::path &exePath, const std::wstring &appID) { std::wcout << L"Installing shortcut: " << shortcutPath << L" " << exePath << L" " << appID << std::endl; tLog << L"Installing shortcut: " << shortcutPath << L" " << exePath << L" " << appID; /** * Add CToastNotificationActivationCallback to registry * Required to use the CToastNotificationActivationCallback for buttons and textbox interactions. * windows.ui.notifications does not support user interaction from cpp */; HRESULT hr = HRESULT_FROM_WIN32(::RegSetKeyValueW(HKEY_CURRENT_USER, L"SOFTWARE\\Classes\\CLSID\\" TOAST_UUID L"\\LocalServer32", nullptr, REG_SZ, Utils::selfLocate().c_str(), static_cast(Utils::selfLocate().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); if (SUCCEEDED(hr)) { PropVariantClear(&appIdPropVar); PROPVARIANT toastActivatorPropVar; toastActivatorPropVar.vt = VT_CLSID; toastActivatorPropVar.puuid = const_cast(&__uuidof(CToastNotificationActivationCallback)); 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::wstring LinkHelper::startmenuPath() +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/linkhelper.h b/src/linkhelper.h index 82536fa..1b3de6f 100644 --- a/src/linkhelper.h +++ b/src/linkhelper.h @@ -1,33 +1,32 @@ /* 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" class LinkHelper { public: - static HRESULT tryCreateShortcut(const std::wstring &shortcutPath, const std::wstring &exePath, const std::wstring &appID); + static HRESULT tryCreateShortcut(const std::filesystem::path &shortcutPath, const std::filesystem::path &exePath, const std::wstring &appID); static HRESULT tryCreateShortcut(const std::wstring &appID); private: - static HRESULT installShortcut(const std::wstring &shortcutPath, const std::wstring &exePath, const std::wstring &appID); - - static std::wstring startmenuPath(); + static HRESULT installShortcut(const std::filesystem::path &shortcutPath, const std::filesystem::path &exePath, const std::wstring &appID); + static std::filesystem::path startmenuPath(); }; diff --git a/src/main.cpp b/src/main.cpp index cbcc700..244a43b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,278 +1,276 @@ /* 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 "linkhelper.h" #include #include #include #include #include #include #include #include using namespace Windows::Foundation; void help(const std::wstring &error) { if (!error.empty()) { std::wcerr << error << std::endl; } else { std::wcerr << L"Welcome to SnoreToast " << SnoreToasts::version() << "." << std::endl << L"A command line application which is capable of creating Windows Toast notifications." << std::endl; } - std::wcerr << std::endl - << L"---- Usage ----" << std::endl - << L"SnoreToast [Options]" << std::endl - << std::endl - << L"---- Options ----" << std::endl - << L"[-t] \t| Displayed on the first line of the toast." << std::endl - << L"[-m] <message string>\t| Displayed on the remaining lines, wrapped." << std::endl - << L"[-b] <button1;button2 string>| Displayed on the bottom line, can list multiple buttons separated by ;" << std::endl - << L"[-tb]\t\t\t| Displayed a textbox on the bottom line, only if buttons are not presented." << std::endl - << L"[-p] <image URI>\t| Display toast with an image, local files only." << std::endl - << L"[-w] \t\t\t| Wait for toast to expire or activate." << std::endl - << L"[-id] <id>\t\t| sets the id for a notification to be able to close it later." << std::endl - << L"[-s] <sound URI> \t| Sets the sound of the notifications, for possible values see http://msdn.microsoft.com/en-us/library/windows/apps/hh761492.aspx." << std::endl - << L"[-silent] \t\t| Don't play a sound file when showing the notifications." << std::endl - << L"[-appID] <App.ID>\t| Don't create a shortcut but use the provided app id." << std::endl - << L"[-pipeName] <\\.\\pipe\\pipeName\\>\t| Provide a name pipe which is used for callbacks." << std::endl - << L"-close <id>\t\t| Closes a currently displayed notification, in order to be able to close a notification the parameter -w must be used to create the notification." << std::endl - << std::endl - << L"-install <path> <application> <appID>| Creates a shortcut <path> in the start menu which point to the executable <application>, appID used for the notifications." << std::endl - << std::endl - << L"-v \t\t\t| Print the version and copying information." << std::endl - << L"-h\t\t\t| Print these instructions. Same as no args." << std::endl - << L"Exit Status\t: Exit Code" << std::endl - << L"Failed\t\t: -1" - << std::endl << std::endl - << "Success\t\t: 0" << std::endl - << "Hidden\t\t: 1" << std::endl - << "Dismissed\t: 2" << std::endl - << "TimedOut\t: 3" << std::endl - << "ButtonPressed\t: 4" << std::endl - << "TextEntered\t: 5" << std::endl - << std::endl - << L"---- Image Notes ----" << std::endl - << L"Images must be .png with:" << std::endl - << L"\tmaximum dimensions of 1024x1024" << std::endl - << L"\tsize <= 200kb" << std::endl - << L"These limitations are due to the Toast notification system." << std::endl - << L"This should go without saying, but windows style paths are required." << std::endl; + std::wcerr << std::endl + << L"---- Usage ----" << std::endl + << L"SnoreToast [Options]" << std::endl + << std::endl + << L"---- Options ----" << std::endl + << L"[-t] <title string>\t| Displayed on the first line of the toast." << std::endl + << L"[-m] <message string>\t| Displayed on the remaining lines, wrapped." << std::endl + << L"[-b] <button1;button2 string>| Displayed on the bottom line, can list multiple buttons separated by ;" << std::endl + << L"[-tb]\t\t\t| Displayed a textbox on the bottom line, only if buttons are not presented." << std::endl + << L"[-p] <image URI>\t| Display toast with an image, local files only." << std::endl + << L"[-w] \t\t\t| Wait for toast to expire or activate." << std::endl + << L"[-id] <id>\t\t| sets the id for a notification to be able to close it later." << std::endl + << L"[-s] <sound URI> \t| Sets the sound of the notifications, for possible values see http://msdn.microsoft.com/en-us/library/windows/apps/hh761492.aspx." << std::endl + << L"[-silent] \t\t| Don't play a sound file when showing the notifications." << std::endl + << L"[-appID] <App.ID>\t| Don't create a shortcut but use the provided app id." << std::endl + << L"[-pipeName] <\\.\\pipe\\pipeName\\>\t| Provide a name pipe which is used for callbacks." << std::endl + << L"-close <id>\t\t| Closes a currently displayed notification, in order to be able to close a notification the parameter -w must be used to create the notification." << std::endl + << std::endl + << L"-install <name> <application> <appID>| Creates a shortcut <name> in the start menu which point to the executable <application>, appID used for the notifications." << std::endl + << std::endl + << L"-v \t\t\t| Print the version and copying information." << std::endl + << L"-h\t\t\t| Print these instructions. Same as no args." << std::endl + << L"Exit Status\t: Exit Code" << std::endl + << L"Failed\t\t: " << static_cast<int>(SnoreToastActions::Actions::Error) + << std::endl << std::endl + << "Success\t\t: " << static_cast<int>(SnoreToastActions::Actions::Clicked) << std::endl + << "Hidden\t\t: " << static_cast<int>(SnoreToastActions::Actions::Hidden) << std::endl + << "Dismissed\t: " << static_cast<int>(SnoreToastActions::Actions::Dismissed) << std::endl + << "TimedOut\t: " << static_cast<int>(SnoreToastActions::Actions::Timedout) << std::endl + << "ButtonPressed\t: " << static_cast<int>(SnoreToastActions::Actions::ButtonClicked) << std::endl + << "TextEntered\t: " << static_cast<int>(SnoreToastActions::Actions::TextEntered) << std::endl + << std::endl + << L"---- Image Notes ----" << std::endl + << L"Images must be .png with:" << std::endl + << L"\tmaximum dimensions of 1024x1024" << std::endl + << L"\tsize <= 200kb" << std::endl + << L"These limitations are due to the Toast notification system." << std::endl; } void version() { std::wcerr << L"SnoreToast version " << SnoreToasts::version() << std::endl << L"Copyright (C) 2019 Hannah von Reth <vonreth@kde.org>" << std::endl << L"SnoreToast is free software: you can redistribute it and/or modify" << std::endl << L"it under the terms of the GNU Lesser General Public License as published by" << std::endl << L"the Free Software Foundation, either version 3 of the License, or" << std::endl << L"(at your option) any later version." << std::endl; } SnoreToastActions::Actions parse(std::vector<wchar_t*> args) { HRESULT hr = S_OK; std::wstring appID; - std::wstring pipe; + std::filesystem::path pipe; std::wstring title; std::wstring body; - std::wstring image; + std::filesystem::path image; std::wstring id; std::wstring sound(L"Notification.Default"); std::wstring buttons; bool silent = false; bool wait = false; bool closeNotify = false; bool isTextBoxEnabled = false; auto nextArg = [&](std::vector<wchar_t *>::const_iterator & it, const std::wstring & helpText)-> std::wstring { if (it != args.cend()) { return *it++; } else { help(helpText); exit(static_cast<int>(SnoreToastActions::Actions::Error)); } }; auto it = args.begin() + 1; while (it != args.end()) { std::wstring arg(nextArg(it, L"")); std::transform(arg.begin(), arg.end(), arg.begin(), [](int i) -> int { return ::tolower(i); }); if (arg == L"-m") { - body = nextArg(it, L"Missing argument to -m.\n" L"Supply argument as -m \"message string\""); } else if (arg == L"-t") { title = nextArg(it, L"Missing argument to -t.\n" L"Supply argument as -t \"bold title string\""); } else if (arg == L"-p") { std::wstring path = nextArg(it, L"Missing argument to -p." L"Supply argument as -p \"image path\""); if (path.substr(0, 8) != L"file:///") { image = L"file:///"; path = _wfullpath(nullptr, path.c_str(), MAX_PATH); } image.append(path); } else if (arg == L"-w") { wait = true; } else if (arg == L"-s") { sound = nextArg(it, L"Missing argument to -s.\n" L"Supply argument as -s \"sound name\""); } else if (arg == L"-id") { id = nextArg(it, L"Missing argument to -id.\n" L"Supply argument as -id \"id\""); } else if (arg == L"-silent") { silent = true; } else if (arg == L"-appid") { appID = nextArg(it, L"Missing argument to -appID.\n" L"Supply argument as -appID \"Your.APP.ID\""); } else if (arg == L"-pipename") { pipe = nextArg(it, L"Missing argument to -pipeName.\n" L"Supply argument as -pipeName \"\\.\\pipe\\foo\\\""); } else if (arg == L"-b") { buttons = nextArg(it, L"Missing argument to -b.\n" L"Supply argument for buttons as -b \"button1;button2\""); } else if (arg == L"-tb") { isTextBoxEnabled = true; } else if (arg == L"-install") { - std::wstring shortcut(nextArg(it, L"Missing argument to -install.\n" + const std::wstring shortcut(nextArg(it, L"Missing argument to -install.\n" L"Supply argument as -install \"path to your shortcut\" \"path to the application the shortcut should point to\" \"App.ID\"")); - std::wstring exe(nextArg(it, L"Missing argument to -install.\n" + const std::wstring exe(nextArg(it, L"Missing argument to -install.\n" L"Supply argument as -install \"path to your shortcut\" \"path to the application the shortcut should point to\" \"App.ID\"")); appID = nextArg(it, L"Missing argument to -install.\n" L"Supply argument as -install \"path to your shortcut\" \"path to the application the shortcut should point to\" \"App.ID\""); return SUCCEEDED(LinkHelper::tryCreateShortcut(shortcut, exe, appID)) ? SnoreToastActions::Actions::Clicked : SnoreToastActions::Actions::Error; } else if (arg == L"-close") { id = nextArg(it, L"Missing agument to -close" L"Supply argument as -close \"id\""); closeNotify = true; } else if (arg == L"-v") { version(); return SnoreToastActions::Actions::Clicked; } else if (arg == L"-h") { help(L""); return SnoreToastActions::Actions::Clicked; } else { std::wstringstream ws; ws << L"Unknown argument: " << arg << std::endl; help(ws.str()); return SnoreToastActions::Actions::Error; } } if (closeNotify) { if (!id.empty()) { SnoreToasts app(appID); app.setId(id); if (app.closeNotification()) { return SnoreToastActions::Actions::Clicked; } } else { help(L"Close only works if an -id id was provided."); } } else { hr = (title.length() > 0 && body.length() > 0) ? S_OK : E_FAIL; if (SUCCEEDED(hr)) { if (appID.empty()) { std::wstringstream _appID; _appID << L"Snore.DesktopToasts." << SnoreToasts::version(); appID = _appID.str(); hr = LinkHelper::tryCreateShortcut(appID); } if (SUCCEEDED(hr)) { if (isTextBoxEnabled) { if (pipe.empty()) { std::wcerr << L"TextBox notifications only work if a pipe for the result was provided" << std::endl; return SnoreToastActions::Actions::Error; }; } SnoreToasts app(appID); app.setPipeName(pipe); app.setSilent(silent); app.setSound(sound); app.setId(id); app.setButtons(buttons); app.setTextBoxEnabled(isTextBoxEnabled); app.displayToast(title, body, image, wait); return app.userAction(); } } else { help(L""); return SnoreToastActions::Actions::Clicked; } } return SnoreToastActions::Actions::Error; } SnoreToastActions::Actions handleEmbedded() { Utils::registerActivator(); CToastNotificationActivationCallback::waitForActivation(); Utils::unregisterActivator(); return SnoreToastActions::Actions::Clicked; } int WINAPI wWinMain(HINSTANCE, HINSTANCE, wchar_t*, int) { if (AttachConsole(ATTACH_PARENT_PROCESS)) { FILE *dummy; _wfreopen_s(&dummy, L"CONOUT$", L"w", stdout); setvbuf(stdout, nullptr, _IONBF, 0); _wfreopen_s(&dummy, L"CONOUT$", L"w", stderr); setvbuf(stderr, nullptr, _IONBF, 0); std::ios::sync_with_stdio(); } const auto commandLine = GetCommandLineW(); tLog << commandLine; int argc; wchar_t **argv = CommandLineToArgvW(commandLine, &argc); SnoreToastActions::Actions action = SnoreToastActions::Actions::Clicked; HRESULT hr = Initialize(RO_INIT_MULTITHREADED); if (SUCCEEDED(hr)) { if (std::wstring(commandLine).find(L"-Embedding") != std::wstring::npos) { action = handleEmbedded(); } else { action = parse(std::vector<wchar_t *>(argv, argv + argc)); } Uninitialize(); } return static_cast<int>(action); } diff --git a/src/snoretoasts.cpp b/src/snoretoasts.cpp index 549fb42..65dff58 100644 --- a/src/snoretoasts.cpp +++ b/src/snoretoasts.cpp @@ -1,562 +1,562 @@ #include "snoretoasts.h" /* SnoreToast is capable to invoke Windows 8 toast notifications. Copyright (C) 2013-2019 Hannah von Reth <vonreth@kde.org> 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 <http://www.gnu.org/licenses/>. */ #include "snoretoasts.h" #include "toasteventhandler.h" #include "linkhelper.h" #include "utils.h" #include <sstream> #include <iostream> 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::wstring &image, bool wait) +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.length() > 0) { + 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<ABI::Windows::Data::Xml::Dom::IXmlNodeList> rootList; hr = m_toastXml->GetElementsByTagName(StringReferenceWrapper(L"toast").Get(), &rootList); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> root; hr = rootList->Item(0, &root); ComPtr<IXmlNamedNodeMap> 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<ABI::Windows::Data::Xml::Dom::IXmlElement> audioElement; hr = m_toastXml->CreateElement(StringReferenceWrapper(L"audio").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)) { hr = addAttribute(L"src", attributes.Get()); if (SUCCEEDED(hr)) { addAttribute(L"silent", attributes.Get()); } } } } } } } } } // printXML(); if (SUCCEEDED(hr)) { - if (m_image.length() > 0) { + if (!m_image.empty()) { hr = setImage(); } if (SUCCEEDED(hr)) { hr = setSound(); if (SUCCEEDED(hr)) { hr = setTextValues(); if (SUCCEEDED(hr)) { //printXML(); hr = createToast(); } } } } //printXML(); 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<IXmlNodeList> nodeList; hr = m_toastXml->GetElementsByTagName(StringReferenceWrapper(L"image").Get(), &nodeList); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> imageNode; hr = nodeList->Item(0, &imageNode); if (SUCCEEDED(hr)) { ComPtr<IXmlNamedNodeMap> attributes; hr = imageNode->get_Attributes(&attributes); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> 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<IXmlNodeList> nodeList; hr = m_toastXml->GetElementsByTagName(StringReferenceWrapper(L"audio").Get(), &nodeList); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> audioNode; hr = nodeList->Item(0, &audioNode); if (SUCCEEDED(hr)) { ComPtr<IXmlNamedNodeMap> attributes; hr = audioNode->get_Attributes(&attributes); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> 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<IXmlNodeList> nodeList; hr = m_toastXml->GetElementsByTagName(StringReferenceWrapper(L"text").Get(), &nodeList); if (SUCCEEDED(hr)) { //create the title ComPtr<IXmlNode> 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<IXmlNode> root) { ComPtr<ABI::Windows::Data::Xml::Dom::IXmlElement> actionsElement; HRESULT hr = m_toastXml->CreateElement(StringReferenceWrapper(L"actions").Get(), &actionsElement); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> actionsNodeTmp; hr = actionsElement.As(&actionsNodeTmp); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> 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<IXmlNode> root) { ComPtr<ABI::Windows::Data::Xml::Dom::IXmlElement> actionsElement; HRESULT hr = m_toastXml->CreateElement(StringReferenceWrapper(L"actions").Get(), &actionsElement); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> actionsNodeTmp; hr = actionsElement.As(&actionsNodeTmp); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> actionsNode; root->AppendChild(actionsNodeTmp.Get(), &actionsNode); ComPtr<ABI::Windows::Data::Xml::Dom::IXmlElement> inputElement; HRESULT hr = m_toastXml->CreateElement(StringReferenceWrapper(L"input").Get(), &inputElement); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> inputNodeTmp; hr = inputElement.As(&inputNodeTmp); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> inputNode; actionsNode->AppendChild(inputNodeTmp.Get(), &inputNode); ComPtr<IXmlNamedNodeMap> 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<IXmlElement> actionElement; HRESULT hr = m_toastXml->CreateElement(StringReferenceWrapper(L"action").Get(), &actionElement); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> actionNodeTmp; hr = actionElement.As(&actionNodeTmp); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> actionNode; actionsNode->AppendChild(actionNodeTmp.Get(), &actionNode); ComPtr<IXmlNamedNodeMap> 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<IToastNotification> toast) { // Register the event handlers EventRegistrationToken activatedToken, dismissedToken, failedToken; ComPtr<ToastEventHandler> 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<IXmlText> inputText; HRESULT hr = m_toastXml->CreateTextNode(inputString, &inputText); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> inputTextNode; hr = inputText.As(&inputTextNode); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> pAppendedChild; hr = node->AppendChild(inputTextNode.Get(), &pAppendedChild); } } return hr; } HRESULT SnoreToasts::addAttribute(const std::wstring &name, IXmlNamedNodeMap *attributeMap) { ComPtr<ABI::Windows::Data::Xml::Dom::IXmlAttribute> srcAttribute; HRESULT hr = m_toastXml->CreateAttribute(StringReferenceWrapper(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; } HRESULT SnoreToasts::addAttribute(const std::wstring &name, IXmlNamedNodeMap *attributeMap, const std::wstring &value) { ComPtr<ABI::Windows::Data::Xml::Dom::IXmlAttribute> srcAttribute; HRESULT hr = m_toastXml->CreateAttribute(StringReferenceWrapper(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); hr = setNodeValueString(StringReferenceWrapper(value).Get(), node.Get()); } } return hr; } HRESULT SnoreToasts::createNewActionButton(ComPtr<IXmlNode> actionsNode, const std::wstring &value) { ComPtr<ABI::Windows::Data::Xml::Dom::IXmlElement> actionElement; HRESULT hr = m_toastXml->CreateElement(StringReferenceWrapper(L"action").Get(), &actionElement); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> actionNodeTmp; hr = actionElement.As(&actionNodeTmp); if (SUCCEEDED(hr)) { ComPtr<IXmlNode> actionNode; actionsNode->AppendChild(actionNodeTmp.Get(), &actionNode); ComPtr<IXmlNamedNodeMap> 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<ABI::Windows::Data::Xml::Dom::IXmlNodeSerializer> s; ComPtr<ABI::Windows::Data::Xml::Dom::IXmlDocument> ss(m_toastXml); ss.As(&s); HSTRING string; s->GetXml(&string); PCWSTR str = WindowsGetStringRawBuffer(string, nullptr); tLog << L"------------------------\n" << m_appID << L"\n" << L"------------------------\n" << str << L"\n" << L"------------------------\n"; } -std::wstring SnoreToasts::pipeName() const +std::filesystem::path SnoreToasts::pipeName() const { return m_pipeName; } -void SnoreToasts::setPipeName(const std::wstring &pipeName) +void SnoreToasts::setPipeName(const std::filesystem::path &pipeName) { m_pipeName = pipeName; } std::wstring SnoreToasts::formatAction(const SnoreToastActions::Actions &action, const std::vector<std::pair<std::wstring, std::wstring> > &extraData) const { const auto &actionString = SnoreToastActions::getActionString(action); std::vector<std::pair<std::wstring, std::wstring>> data = { {L"action", std::wstring(actionString.data(), actionString.size())}, {L"notificationId", m_id}, {L"pipe", m_pipeName} }; 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<IToastNotificationFactory> 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<SnoreToastActions::Actions>(i); } ++i; } return SnoreToastActions::Actions::Error; } diff --git a/src/snoretoasts.h b/src/snoretoasts.h index 023bddb..bb4180b 100644 --- a/src/snoretoasts.h +++ b/src/snoretoasts.h @@ -1,142 +1,143 @@ /* SnoreToast is capable to invoke Windows 8 toast notifications. Copyright (C) 2013-2019 Hannah von Reth <vonreth@kde.org> 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 <http://www.gnu.org/licenses/>. */ #pragma once #include "stringreferencewrapper.h" #include <sdkddkver.h> // Windows Header Files: #include <windows.h> #include <sal.h> #include <psapi.h> #include <strsafe.h> #include <objbase.h> #include <shobjidl.h> #include <functiondiscoverykeys.h> #include <guiddef.h> #include <shlguid.h> #include <wrl/client.h> #include <wrl/implements.h> #include <windows.ui.notifications.h> +#include <filesystem> #include <string> #include <vector> 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<int>(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::wstring &image, bool wait); + 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::wstring pipeName() const; - void setPipeName(const std::wstring &pipeName); + std::filesystem::path pipeName() const; + void setPipeName(const std::filesystem::path &pipeName); std::wstring formatAction(const SnoreToastActions::Actions &action, const std::vector<std::pair<std::wstring, std::wstring> > &extraData = {}) const; private: HRESULT createToast(); HRESULT setImage(); HRESULT setSound(); HRESULT setTextValues(); HRESULT setButtons(ComPtr<IXmlNode> root); HRESULT setTextBox(ComPtr<IXmlNode> root); HRESULT setEventHandler(Microsoft::WRL::ComPtr<ABI::Windows::UI::Notifications::IToastNotification> 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<IXmlNode> actionsNode, const std::wstring &value); void printXML(); std::wstring m_appID; - std::wstring m_pipeName; + std::filesystem::path m_pipeName; std::wstring m_title; std::wstring m_body; - std::wstring m_image; + 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<ABI::Windows::Data::Xml::Dom::IXmlDocument> m_toastXml; Microsoft::WRL::ComPtr<ABI::Windows::UI::Notifications::IToastNotificationManagerStatics> m_toastManager; Microsoft::WRL::ComPtr<ABI::Windows::UI::Notifications::IToastNotifier> m_notifier; Microsoft::WRL::ComPtr<ABI::Windows::UI::Notifications::IToastNotification> m_notification; Microsoft::WRL::ComPtr<ToastEventHandler> m_eventHanlder; };