diff --git a/CMakeLists.txt b/CMakeLists.txt index fcb0b21..8716c05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,17 @@ project( snoretoast ) cmake_minimum_required(VERSION 2.8) add_definitions(-DUNICODE -D_UNICODE -D__WRL_CLASSIC_COM_STRICT__ -DWIN32_LEAN_AND_MEAN) +set(CMAKE_CXX_STANDARD 11) + #link runtime static if(MSVC) foreach(_bt DEBUG RELEASE RELWITHDEBINFO) string(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_${_bt} ${CMAKE_CXX_FLAGS_${_bt}}) endforeach(_bt DEBUG RELEASE RELWITHDEBINFO) endif(MSVC) add_subdirectory(data) add_subdirectory(src) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 98dd45f..6fe0b24 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,15 +1,7 @@ -add_library(libsnoretoast STATIC snoretoasts.cpp toasteventhandler.cpp linkhelper.cpp) +add_library(libsnoretoast STATIC snoretoasts.cpp toasteventhandler.cpp linkhelper.cpp utils.cpp) -add_executable(SnoreToast main.cpp ${SNORE_TOAST_DEPS}) +add_executable(SnoreToast WIN32 main.cpp ${SNORE_TOAST_DEPS}) target_link_libraries(SnoreToast runtimeobject shlwapi libsnoretoast) -add_executable(SnoreToastGui WIN32 main.cpp ${SNORE_TOAST_DEPS}) -target_link_libraries(SnoreToastGui runtimeobject shlwapi libsnoretoast) -set_property( TARGET SnoreToastGui - APPEND - PROPERTY COMPILE_DEFINITIONS BUILD_GUI) - -install(TARGETS SnoreToast SnoreToastGui RUNTIME DESTINATION bin - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib) +install(TARGETS SnoreToast RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib) diff --git a/src/linkhelper.cpp b/src/linkhelper.cpp index 81e59f3..a5a239a 100644 --- a/src/linkhelper.cpp +++ b/src/linkhelper.cpp @@ -1,173 +1,155 @@ /* 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 -using namespace Microsoft::WRL; - HRESULT LinkHelper::tryCreateShortcut(const std::wstring &shortcutPath, const std::wstring &exePath, const std::wstring &appID) { HRESULT hr = S_OK; std::wstringstream lnkName; if (PathIsRelative(shortcutPath.c_str()) == TRUE) { lnkName << startmenuPath(); } lnkName << shortcutPath; if (shortcutPath.rfind(L".lnk") == std::wstring::npos) { lnkName << L".lnk"; } hr = mkdirs(lnkName.str()); if (SUCCEEDED(hr)) { DWORD attributes = GetFileAttributes(lnkName.str().c_str()); bool fileExists = attributes < 0xFFFFFFF; if (!fileExists) { - /** - * Required to use the CToastNotificationActivationCallback for buttons and textbox interactions. - * windows.ui.notifications does not support user interaction from cpp - */ hr = installShortcut(lnkName.str(), exePath, appID); } else { hr = S_FALSE; } } - registerActivator(); + Utils::registerActivator(); return hr; } HRESULT LinkHelper::tryCreateShortcut(const std::wstring &appID) { - wchar_t buffer[MAX_PATH]; - if (GetModuleFileNameEx(GetCurrentProcess(), nullptr, buffer, MAX_PATH) > 0) { - return tryCreateShortcut(L"SnoreToast.lnk", buffer, appID); - } - return E_FAIL; -} - -HRESULT LinkHelper::registerActivator() -{ - Microsoft::WRL::Module::Create([] {}); - Microsoft::WRL::Module::GetModule().IncrementObjectCount(); - return Microsoft::WRL::Module::GetModule().RegisterObjects(); -} - -void LinkHelper::unregisterActivator() -{ - Microsoft::WRL::Module::GetModule().UnregisterObjects(); - Microsoft::WRL::Module::GetModule().DecrementObjectCount(); + std::wstringstream name; + name << L"SnoreToast v" << SnoreToasts::version() << L".lnk"; + return tryCreateShortcut(name.str(), Utils::selfLocate().c_str(), appID); } // Install the shortcut HRESULT LinkHelper::installShortcut(const std::wstring &shortcutPath, const std::wstring &exePath, const std::wstring &appID) { PCWSTR pszExePath = exePath.c_str(); - std::wcerr << L"Installing shortcut: " << shortcutPath << L" " << exePath << L" " << appID << std::endl; - //Add CToastNotificationActivationCallback to registry - std::wstringstream regKey; - regKey << L"SOFTWARE\\Classes\\CLSID\\{" << TOAST_UUID << L"}\\LocalServer32"; - HRESULT hr = HRESULT_FROM_WIN32(::RegSetKeyValueW(HKEY_CURRENT_USER, regKey.str().c_str(), nullptr, REG_SZ, pszExePath, static_cast(wcslen(pszExePath)*sizeof(wchar_t)))); + std::wcout << L"Installing shortcut: " << shortcutPath << L" " << exePath << L" " << appID << std::endl; + /** + * 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, pszExePath, static_cast(wcslen(pszExePath)*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; } HRESULT LinkHelper::mkdirs(const std::wstring &dirs) { HRESULT hr = S_OK; static std::wregex seperator(L"\\\\|/"); for (std::wsregex_iterator i = std::wsregex_iterator(dirs.begin(), dirs.end(), seperator); SUCCEEDED(hr) && i != std::wsregex_iterator(); ++i) { hr = _wmkdir(dirs.substr(0, i->position()).c_str()) != ENOENT ? S_OK : E_FAIL; } return hr; } std::wstring 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 42acbe0..eb018c6 100644 --- a/src/linkhelper.h +++ b/src/linkhelper.h @@ -1,36 +1,34 @@ /* 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::wstring &appID); - static HRESULT registerActivator(); - static void unregisterActivator(); private: static HRESULT installShortcut(const std::wstring &shortcutPath, const std::wstring &exePath, const std::wstring &appID); static HRESULT mkdirs(const std::wstring &dirs); static std::wstring startmenuPath(); }; diff --git a/src/main.cpp b/src/main.cpp index 417e82b..10ab719 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,244 +1,270 @@ /* 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; } 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; } SnoreToasts::USER_ACTION parse(std::vector<wchar_t*> args) { HRESULT hr = S_OK; std::wstring appID; + std::wstring pipe; std::wstring title; std::wstring body; std::wstring 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(SnoreToasts::Failed); - return L""; } }; 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") { + } else if (arg == L"-w") { wait = true; - } else if (arg == L"-s") { + } 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") { + } else if (arg == L"-silent") { silent = true; - } else if (arg == L"-appid") { + } 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"-b") { + } 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") { + } else if (arg == L"-tb") { isTextBoxEnabled = true; - } else if (arg == L"-install") { + } else if (arg == L"-install") { 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" 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)) ? SnoreToasts::Success : SnoreToasts::Failed; } 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") { + } else if (arg == L"-v") { version(); return SnoreToasts::Success; } else if (arg == L"-h") { help(L""); return SnoreToasts::Success; } else { std::wstringstream ws; ws << L"Unknown argument: " << arg << std::endl; help(ws.str()); return SnoreToasts::Failed; } } if (closeNotify) { if (!id.empty()) { SnoreToasts app(appID); app.setId(id); if (app.closeNotification()) { return SnoreToasts::Success; } } 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.length() == 0) { - appID = L"Snore.DesktopToasts"; + if (appID.empty()) { + std::wstringstream _appID; + _appID << L"Snore.DesktopToasts." << SnoreToasts::version(); + appID = _appID.str(); hr = LinkHelper::tryCreateShortcut(appID); } if (SUCCEEDED(hr)) { 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 SnoreToasts::Success; } } return SnoreToasts::Failed; } -#ifdef BUILD_GUI + +SnoreToasts::USER_ACTION handleEmbedded() +{ + Utils::registerActivator(); + CToastNotificationActivationCallback::waitForActivation(); + Utils::unregisterActivator(); + return SnoreToasts::Success; +} + int WINAPI wWinMain(HINSTANCE, HINSTANCE, wchar_t*, int) { - if (AttachConsole(ATTACH_PARENT_PROCESS)) { - FILE *stream; - _wfreopen_s(&stream, L"CONOUT$", L"w", stdout); - _wfreopen_s(&stream, L"CONOUT$", L"w", stderr); + 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(); } -#else -int wmain() -{ -#endif - int argc; - wchar_t **argv = CommandLineToArgvW(GetCommandLineW(), &argc); + const auto commandLine = GetCommandLineW(); + tLog << commandLine; + int argc; + wchar_t **argv = CommandLineToArgvW(commandLine, &argc); SnoreToasts::USER_ACTION action = SnoreToasts::Success; HRESULT hr = Initialize(RO_INIT_MULTITHREADED); if (SUCCEEDED(hr)) { - action = parse(std::vector<wchar_t *>(argv, argv + argc)); + if (std::wstring(commandLine).find(L"-Embedding") != std::wstring::npos) + { + action = handleEmbedded(); + } else { + action = parse(std::vector<wchar_t *>(argv, argv + argc)); + } Uninitialize(); } return action; } diff --git a/src/snoretoasts.cpp b/src/snoretoasts.cpp index a651d19..31c1106 100644 --- a/src/snoretoasts.cpp +++ b/src/snoretoasts.cpp @@ -1,517 +1,543 @@ /* 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_action(Success), - m_sound(L"Notification.Default") + m_id(std::to_wstring(GetCurrentProcessId())) { + Utils::registerActivator(); } SnoreToasts::~SnoreToasts() { - LinkHelper::unregisterActivator(); + Utils::unregisterActivator(); } void SnoreToasts::displayToast(const std::wstring &title, const std::wstring &body, const std::wstring &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) { 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); - if (m_textbox){ - ComPtr<IXmlNamedNodeMap> rootAttributes; - hr = root->get_Attributes(&rootAttributes); - if (SUCCEEDED(hr)) { - hr = addAttribute(L"launch", rootAttributes.Get(), L"action=openThread&threadId=92185"); - } + ComPtr<IXmlNamedNodeMap> rootAttributes; + hr = root->get_Attributes(&rootAttributes); + if (SUCCEEDED(hr)) { + const auto data = Utils::formatData({ + {L"action", Actions::Clicked}, + {L"notificationId", m_id}, + {L"pipe", m_pipeName}, + }); + 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) { hr = setImage(); } if (SUCCEEDED(hr)) { hr = setSound(); if (SUCCEEDED(hr)) { hr = setTextValues(); if (SUCCEEDED(hr)) { //printXML(); hr = createToast(); } } } } //printXML(); m_action = SUCCEEDED(hr) ? Success : Failed; } SnoreToasts::USER_ACTION SnoreToasts::userAction() { - if (m_eventHanlder.Get() != NULL) { + if (m_eventHanlder.Get()) { HANDLE event = m_eventHanlder.Get()->event(); WaitForSingleObject(event, INFINITE); m_action = m_eventHanlder.Get()->userAction(); if (m_action == SnoreToasts::Hidden) { m_notifier->Hide(m_notification.Get()); - std::wcerr << L"The application hid the toast using ToastNotifier.hide()" << std::endl; + 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; } - std::wcerr << "Notification " << m_id << " does not exist" << std::endl; + 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) { - m_id = id; + if (!id.empty()) + { + m_id = 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"); - std::wstringstream id; - id << "action=reply&processId=" << GetCurrentProcessId(); - hr &= addAttribute(L"arguments", actionAttributes.Get(), id.str()); + const auto data = Utils::formatData({ + {L"action", Actions::Reply}, + {L"notificationId", m_id}, + {L"pipe", m_pipeName}, + }); + 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(m_id)); 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); - hr &= addAttribute(L"arguments", actionAttributes.Get(), value); + + const auto data = Utils::formatData({ + {L"action", Actions::Button}, + {L"notificationId", m_id}, + {L"button", value}, + {L"pipe", m_pipeName}, + }); + 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, NULL); - std::wcerr << L"------------------------" << std::endl - << L"SnoreToast " << version() << std::endl - << L"------------------------" << std::endl - << m_appID << std::endl - << L"------------------------" << std::endl - << str << std::endl - << L"------------------------" << std::endl; + PCWSTR str = WindowsGetStringRawBuffer(string, nullptr); + tLog << L"------------------------\n" + << m_appID << L"\n" + << L"------------------------\n" + << str << L"\n" + << L"------------------------\n"; +} + +std::wstring SnoreToasts::pipeName() const +{ + return m_pipeName; +} + +void SnoreToasts::setPipeName(const std::wstring &pipeName) +{ + m_pipeName = pipeName; } // 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"; } diff --git a/src/snoretoasts.h b/src/snoretoasts.h index 2eeba57..ba84edc 100644 --- a/src/snoretoasts.h +++ b/src/snoretoasts.h @@ -1,111 +1,115 @@ /* 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 <string> using namespace Microsoft::WRL; using namespace ABI::Windows::Data::Xml::Dom; class ToastEventHandler; class SnoreToasts { public: enum USER_ACTION { Failed = -1, Success, Hidden, Dismissed, TimedOut, ButtonPressed, TextEntered }; 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); USER_ACTION userAction(); bool closeNotification(); void setSound(const std::wstring &soundFile); void setSilent(bool silent); void setId(const std::wstring &id); void setButtons(const std::wstring &buttons); void setTextBoxEnabled(bool textBoxEnabled); -private: + std::wstring pipeName() const; + void setPipeName(const std::wstring &pipeName); + + 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::wstring m_title; std::wstring m_body; std::wstring m_image; - std::wstring m_sound; + std::wstring m_sound = L"Notification.Default"; std::wstring m_id; std::wstring m_buttons; bool m_silent; bool m_wait; bool m_textbox; - SnoreToasts::USER_ACTION m_action; + SnoreToasts::USER_ACTION m_action = SnoreToasts::Success; 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; }; diff --git a/src/toasteventhandler.cpp b/src/toasteventhandler.cpp index 1768ec6..f4336ca 100644 --- a/src/toasteventhandler.cpp +++ b/src/toasteventhandler.cpp @@ -1,151 +1,200 @@ /* 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 "utils.h" #include <sstream> #include <iostream> #include <wchar.h> #include <algorithm> +#include <assert.h> using namespace ABI::Windows::UI::Notifications; ToastEventHandler::ToastEventHandler(const std::wstring &id) : m_ref(1), m_userAction(SnoreToasts::Hidden) { std::wstringstream eventName; - eventName << L"ToastEvent"; - if (!id.empty()) { - eventName << id; - } else { - eventName << GetCurrentProcessId(); - } - m_event = CreateEventW(NULL, TRUE, FALSE, eventName.str().c_str()); + eventName << L"ToastEvent" << id; + m_event = CreateEventW(nullptr, true, false, eventName.str().c_str()); } ToastEventHandler::~ToastEventHandler() { CloseHandle(m_event); } HANDLE ToastEventHandler::event() { return m_event; } SnoreToasts::USER_ACTION &ToastEventHandler::userAction() { return m_userAction; } // DesktopToastActivatedEventHandler -IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification * sender, _In_ IInspectable * args) +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; - std::wcerr << L"The user clicked on the toast." << std::endl; } else { HSTRING args; buttonReply->get_Arguments(&args); - PCWSTR str = WindowsGetStringRawBuffer(args, NULL); + PCWSTR _str = WindowsGetStringRawBuffer(args, nullptr); + const std::wstring str = _str; + + const auto data = Utils::splitData(CToastNotificationActivationCallback::data()); + const auto action = data.at(L"action"); - std::wcerr << L"The user clicked on a toast button." << std::endl; - if (wcscmp(str, L"action=reply&processId=") < 0) + if (action == Actions::Reply) { - std::wcout << str << std::endl; + tLog << L"The user entered a text."; + std::wcout << data.at(L"text") << std::endl; m_userAction = SnoreToasts::TextEntered; } + else if (action == Actions::Clicked) + { + tLog << L"The user clicked on the toast."; + m_userAction = SnoreToasts::Success; + } else { - std::wcerr << str << std::endl; + tLog << L"The user clicked on a toast button."; + std::wcout << str << std::endl; m_userAction = SnoreToasts::ButtonPressed; } } 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: - std::wcerr << L"The application hid the toast using ToastNotifier.hide()" << std::endl; + tLog << L"The application hid the toast using ToastNotifier.hide()"; m_userAction = SnoreToasts::Hidden; break; case ToastDismissalReason_UserCanceled: - std::wcerr << L"The user dismissed this toast" << std::endl; + tLog << L"The user dismissed this toast"; m_userAction = SnoreToasts::Dismissed; break; case ToastDismissalReason_TimedOut: - std::wcerr << L"The toast has timed out" << std::endl; + tLog << L"The toast has timed out"; m_userAction = SnoreToasts::TimedOut; break; - default: - std::wcerr << L"Toast not activated" << std::endl; - m_userAction = SnoreToasts::Failed; - break; } } 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 = SnoreToasts::Failed; SetEvent(m_event); return S_OK; } +CToastNotificationActivationCallback::CToastNotificationActivationCallback() +{ +} + +HANDLE CToastNotificationActivationCallback::m_event = INVALID_HANDLE_VALUE; +std::wstring CToastNotificationActivationCallback::m_data = {}; + HRESULT CToastNotificationActivationCallback::Activate(LPCWSTR appUserModelId, LPCWSTR invokedArgs, const NOTIFICATION_USER_INPUT_DATA* data, ULONG count) { if (invokedArgs == nullptr) { return S_OK; } - std::wstringstream sMsg; - std::wcerr << "CToastNotificationActivationCallback::Activate: " << appUserModelId << " : " << invokedArgs << " : " << data << std::endl; - std::wcerr << "CurrentProcess: " << GetCurrentProcessId() << std::endl; - if (count) + tLog << "CToastNotificationActivationCallback::Activate: " << appUserModelId << " : " << invokedArgs << " : " << data; + const auto dataMap = Utils::splitData(invokedArgs); + if (dataMap.at(L"action") == Actions::Reply) + { + assert(count); + std::wstringstream sMsg; + sMsg << invokedArgs << L"text="; + for (ULONG i=0; i<count; ++i) + { + std::wstring tmp = data[i].Value; + // printing \r to stdcout is kind of problematic :D + std::replace(tmp.begin(), tmp.end(), L'\r', L'\n'); + sMsg << tmp; + } + m_data = sMsg.str(); + } + else + { + m_data = invokedArgs; + } + + const auto pipe = dataMap.find(L"pipe"); + if (pipe != dataMap.cend()) + { + Utils::writePipe(pipe->second, m_data); + } + + tLog << m_data; + if (m_event != INVALID_HANDLE_VALUE) { - for (ULONG i=0; i<count; ++i) - { - std::wstring tmp = data[i].Value; - // printing \r to stdcout is kind of problematic :D - std::replace(tmp.begin(), tmp.end(), '\r', '\n'); - sMsg << tmp; - } - std::wcout << sMsg.str() << std::endl; + SetEvent(m_event); } return S_OK; } + +std::wstring 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); + return m_data; +} + + +std::wstring CToastNotificationActivationCallback::data() +{ + return m_data; +} diff --git a/src/toasteventhandler.h b/src/toasteventhandler.h index 890d2d9..9e8f58b 100644 --- a/src/toasteventhandler.h +++ b/src/toasteventhandler.h @@ -1,123 +1,132 @@ /* 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 "snoretoasts.h" #include "wrl.h" -#define TOAST_UUID "383803B6-AFDA-4220-BFC3-0DBF810106BF" +#define TOAST_UUID "{383803B6-AFDA-4220-BFC3-0DBF810106BF}" + +namespace Actions { + static const std::wstring Reply = L"reply"; + static const std::wstring Clicked = L"clicked"; + static const std::wstring Button = L"button"; +} typedef ABI::Windows::Foundation::ITypedEventHandler<ABI::Windows::UI::Notifications::ToastNotification *, ::IInspectable *> DesktopToastActivatedEventHandler; typedef ABI::Windows::Foundation::ITypedEventHandler<ABI::Windows::UI::Notifications::ToastNotification *, ABI::Windows::UI::Notifications::ToastDismissedEventArgs *> DesktopToastDismissedEventHandler; typedef ABI::Windows::Foundation::ITypedEventHandler<ABI::Windows::UI::Notifications::ToastNotification *, ABI::Windows::UI::Notifications::ToastFailedEventArgs *> DesktopToastFailedEventHandler; //Define INotificationActivationCallback for older versions of the Windows SDK #include <ntverp.h> 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<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, INotificationActivationCallback> { public: -//Constructors / Destructors - CToastNotificationActivationCallback() - { - } + static std::wstring waitForActivation(); + static std::wstring data(); - virtual HRESULT STDMETHODCALLTYPE Activate(__RPC__in_string LPCWSTR appUserModelId, __RPC__in_opt_string LPCWSTR invokedArgs, + 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 std::wstring m_data; + }; CoCreatableClass(CToastNotificationActivationCallback); class ToastEventHandler : public Microsoft::WRL::Implements<DesktopToastActivatedEventHandler, DesktopToastDismissedEventHandler, DesktopToastFailedEventHandler> { public: ToastEventHandler::ToastEventHandler(const std::wstring &id); ~ToastEventHandler(); HANDLE event(); SnoreToasts::USER_ACTION &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<IUnknown *>(static_cast<DesktopToastActivatedEventHandler *>(this)); } else if (IsEqualIID(riid, __uuidof(DesktopToastActivatedEventHandler))) { *ppv = static_cast<DesktopToastActivatedEventHandler *>(this); } else if (IsEqualIID(riid, __uuidof(DesktopToastDismissedEventHandler))) { *ppv = static_cast<DesktopToastDismissedEventHandler *>(this); } else if (IsEqualIID(riid, __uuidof(DesktopToastFailedEventHandler))) { *ppv = static_cast<DesktopToastFailedEventHandler *>(this); } else { *ppv = nullptr; } if (*ppv) { reinterpret_cast<IUnknown *>(*ppv)->AddRef(); return S_OK; } return E_NOINTERFACE; } private: ULONG m_ref; SnoreToasts::USER_ACTION m_userAction; HANDLE m_event; }; diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..bf9bbb9 --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,107 @@ +#include "utils.h" +#include "snoretoasts.h" + +#include <wrl/client.h> +#include <wrl/implements.h> +#include <wrl/module.h> + + + +using namespace Microsoft::WRL; + +namespace { + bool s_registered = false; +} +namespace Utils { + +HRESULT registerActivator() +{ + if (!s_registered) + { + s_registered = true; + Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::Create([] {}); + Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule().IncrementObjectCount(); + return Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule().RegisterObjects(); + } + return S_OK; +} + +void unregisterActivator() +{ + if (s_registered) + { + s_registered = false; + Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule().UnregisterObjects(); + Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule().DecrementObjectCount(); + } +} + +std::unordered_map<std::wstring, std::wstring> splitData(const std::wstring &data) +{ + std::unordered_map<std::wstring, std::wstring> 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); + } + return out; +} + +std::wstring selfLocate() +{ + static std::wstring path; + if (path.empty()) + { + size_t size; + do { + path.resize(path.size() + 1024); + size = GetModuleFileNameW(nullptr, const_cast<wchar_t*>(path.data()), static_cast<DWORD>(path.size())); + } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); + path.resize(size); + } + return path; +} + +bool writePipe(const std::wstring &pipe, const std::wstring &data) +{ + HANDLE hPipe = CreateFile(pipe.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); + if (hPipe != INVALID_HANDLE_VALUE) + { + DWORD written; + const DWORD toWrite = static_cast<DWORD>(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; + } + return false; +} + +std::wstring formatData(const std::vector<std::pair<std::wstring, std::wstring> > &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": "; +} + +ToastLog::~ToastLog() +{ + OutputDebugStringW(m_log.str().c_str()); +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..d0c4425 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,43 @@ +#pragma once + +#include <windows.h> + +#include <sstream> +#include <unordered_map> + +namespace Utils +{ + HRESULT registerActivator(); + void unregisterActivator(); + + std::unordered_map<std::wstring, std::wstring> splitData(const std::wstring &data); + + std::wstring selfLocate(); + + std::wstring formatData(const std::vector<std::pair<std::wstring, std::wstring>> &data); + + bool writePipe(const std::wstring &pipe, const std::wstring &data); +}; + + +class ToastLog +{ +public: + ToastLog(); + ~ToastLog(); + + inline ToastLog &log() { return *this;} + +private: + std::wstringstream m_log; + template<typename T> + friend ToastLog & operator<<(ToastLog &, const T&); +}; + +#define tLog ToastLog().log() << __FUNCSIG__ << ": " + +template <typename T> +ToastLog &operator<< (ToastLog &log, const T &t) { + log.m_log << t; + return log; +}