diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..589baff --- /dev/null +++ b/.clang-format @@ -0,0 +1,82 @@ +# Copyright (C) 2016 Olivier Goffart +# +# You may use this file under the terms of the 3-clause BSD license. +# See the file LICENSE from this package for details. + +# This is the clang-format configuration style to be used by Qt, +# based on the rules from https://wiki.qt.io/Qt_Coding_Style and +# https://wiki.qt.io/Coding_Conventions + +--- +# Webkit style was loosely based on the Qt style +BasedOnStyle: WebKit + +Standard: Cpp11 + +# Column width is limited to 100 in accordance with Qt Coding Style. +# https://wiki.qt.io/Qt_Coding_Style +# Note that this may be changed at some point in the future. +ColumnLimit: 100 +# How much weight do extra characters after the line length limit have. +# PenaltyExcessCharacter: 4 + +# Disable reflow of qdoc comments: indentation rules are different. +# Translation comments are also excluded. +CommentPragmas: "^!|^:" + +# We want a space between the type and the star for pointer types. +PointerBindsToType: false + +# We use template< without space. +SpaceAfterTemplateKeyword: false + +# We want to break before the operators, but not before a '='. +BreakBeforeBinaryOperators: NonAssignment + +# Braces are usually attached, but not after functions or class declarations. +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: true + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + +# When constructor initializers do not fit on one line, put them each on a new line. +ConstructorInitializerAllOnOneLineOrOnePerLine: true +# Indent initializers by 4 spaces +ConstructorInitializerIndentWidth: 4 + +# Indent width for line continuations. +ContinuationIndentWidth: 8 + +# No indentation for namespaces. +NamespaceIndentation: None + +# Horizontally align arguments after an open bracket. +# The coding style does not specify the following, but this is what gives +# results closest to the existing code. +AlignAfterOpenBracket: true +AlwaysBreakTemplateDeclarations: true + +# Ideally we should also allow less short function in a single line, but +# clang-format does not handle that. +AllowShortFunctionsOnASingleLine: Inline + +# The coding style specifies some include order categories, but also tells to +# separate categories with an empty line. It does not specify the order within +# the categories. Since the SortInclude feature of clang-format does not +# re-order includes separated by empty lines, the feature is not used. +SortIncludes: false + +# macros for which the opening brace stays attached. +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ] + +# Break constructor initializers before the colon and after the commas. +BreakConstructorInitializers: BeforeColon diff --git a/examples/qt/main.cpp b/examples/qt/main.cpp index 56acc45..eb47733 100644 --- a/examples/qt/main.cpp +++ b/examples/qt/main.cpp @@ -1,82 +1,74 @@ /* 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; + 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; + // TODO: parse data + }); + server->listen("foo"); + std::wcout << qPrintable(server->fullServerName()) << std::endl; - const QString appId = "SnoreToast.Qt.Example"; - QProcess proc(&a); - proc.start("SnoreToast.exe", { - "-install", - "SnoreToastTestQt", - a.applicationFilePath(), - appId - }); - proc.waitForFinished(); - std::wcout << proc.exitCode() << std::endl; - std::wcout << qPrintable(proc.readAll()) << std::endl; + const QString appId = "SnoreToast.Qt.Example"; + QProcess proc(&a); + proc.start("SnoreToast.exe", + { "-install", "SnoreToastTestQt", a.applicationFilePath(), appId }); + proc.waitForFinished(); + std::wcout << proc.exitCode() << std::endl; + std::wcout << qPrintable(proc.readAll()) << std::endl; - QTimer *timer = new QTimer(&a); - a.connect(timer, &QTimer::timeout, timer, [&]{ - static int id = 0; - if (id >= 10) - { - timer->stop(); - } - auto proc = new QProcess(&a); - proc->start("SnoreToast.exe", { - "-t", "test", - "-m", "message", - "-pipename", server->fullServerName(), - "-w", - "-id", QString::number(id++), - "-appId", appId, - "-application", a.applicationFilePath() - }); - proc->connect(proc, QOverload::of(&QProcess::finished), proc, [proc]{ - std::wcout << qPrintable(proc->errorString()) << std::endl; - std::wcout << qPrintable(proc->readAll()) << std::endl; - std::wcout << proc->exitCode() << std::endl; + QTimer *timer = new QTimer(&a); + a.connect(timer, &QTimer::timeout, timer, [&] { + static int id = 0; + if (id >= 10) { + timer->stop(); + } + auto proc = new QProcess(&a); + proc->start("SnoreToast.exe", + { "-t", "test", "-m", "message", "-pipename", server->fullServerName(), "-w", + "-id", QString::number(id++), "-appId", appId, "-application", + a.applicationFilePath() }); + proc->connect(proc, QOverload::of(&QProcess::finished), proc, [proc] { + std::wcout << qPrintable(proc->errorString()) << std::endl; + std::wcout << qPrintable(proc->readAll()) << std::endl; + std::wcout << proc->exitCode() << std::endl; + }); }); - }); - timer->start(1000); - return a.exec(); + timer->start(1000); + return a.exec(); } diff --git a/src/linkhelper.cpp b/src/linkhelper.cpp index 3093b69..7daf36f 100644 --- a/src/linkhelper.cpp +++ b/src/linkhelper.cpp @@ -1,133 +1,146 @@ /* SnoreToast is capable to invoke Windows 8 toast notifications. Copyright (C) 2013-2019 Hannah von Reth SnoreToast is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SnoreToast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with SnoreToast. If not, see . */ #include "linkhelper.h" #include "toasteventhandler.h" #include "utils.h" #include #include #include #include // compat with older sdk #ifndef INIT_PKEY_AppUserModel_ToastActivatorCLSID -EXTERN_C const PROPERTYKEY DECLSPEC_SELECTANY PKEY_AppUserModel_ToastActivatorCLSID = { { 0x9F4C2855, 0x9F79, 0x4B39,{ 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 } }, 26 }; -#define INIT_PKEY_AppUserModel_ToastActivatorCLSID { { 0x9F4C2855, 0x9F79, 0x4B39, 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 }, 26 } +EXTERN_C const PROPERTYKEY DECLSPEC_SELECTANY PKEY_AppUserModel_ToastActivatorCLSID = { + { 0x9F4C2855, 0x9F79, 0x4B39, { 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 } }, 26 +}; +#define INIT_PKEY_AppUserModel_ToastActivatorCLSID \ + { \ + { 0x9F4C2855, 0x9F79, 0x4B39, 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 }, 26 \ + } #endif //#ifndef INIT_PKEY_AppUserModel_ToastActivatorCLSID -HRESULT LinkHelper::tryCreateShortcut(const std::filesystem::path &shortcutPath, const std::filesystem::path &exePath, const std::wstring &appID) +HRESULT LinkHelper::tryCreateShortcut(const std::filesystem::path &shortcutPath, + const std::filesystem::path &exePath, + const std::wstring &appID) { - if (!std::filesystem::path(shortcutPath).is_relative()) - { + if (!std::filesystem::path(shortcutPath).is_relative()) { std::wcerr << L"The shortcut path must be relative" << std::endl; return S_FALSE; } - const std::filesystem::path path = (startmenuPath() / L"SnoreToast" / SnoreToasts::version() / shortcutPath).replace_extension(L".lnk"); + const std::filesystem::path path = + (startmenuPath() / L"SnoreToast" / SnoreToasts::version() / shortcutPath) + .replace_extension(L".lnk"); - if (std::filesystem::exists(path)) - { + 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())) - { + 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", Utils::selfLocate().c_str(), appID); } // Install the shortcut -HRESULT LinkHelper::installShortcut(const std::filesystem::path &shortcutPath, const std::filesystem::path &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; + 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 - */; - const std::wstring locPath = Utils::selfLocate().wstring(); - HRESULT hr = HRESULT_FROM_WIN32(::RegSetKeyValueW(HKEY_CURRENT_USER, L"SOFTWARE\\Classes\\CLSID\\" TOAST_UUID L"\\LocalServer32", nullptr, REG_SZ, locPath.c_str(), static_cast(locPath.size() * sizeof(wchar_t)))); + * Required to use the CToastNotificationActivationCallback for buttons and textbox + * interactions. windows.ui.notifications does not support user interaction from cpp + */ + ; + const std::wstring locPath = Utils::selfLocate().wstring(); + HRESULT hr = HRESULT_FROM_WIN32(::RegSetKeyValueW( + HKEY_CURRENT_USER, L"SOFTWARE\\Classes\\CLSID\\" TOAST_UUID L"\\LocalServer32", nullptr, + REG_SZ, locPath.c_str(), static_cast(locPath.size() * sizeof(wchar_t)))); if (SUCCEEDED(hr)) { ComPtr shellLink; - hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); + 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)); + toastActivatorPropVar.puuid = const_cast( + &__uuidof(CToastNotificationActivationCallback)); - hr = propertyStore->SetValue(PKEY_AppUserModel_ToastActivatorCLSID, toastActivatorPropVar); + 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; + std::wcerr << "Failed to install shortcut " << shortcutPath + << " error: " << _com_error(hr).ErrorMessage() << std::endl; } return hr; } std::filesystem::path LinkHelper::startmenuPath() { wchar_t buffer[MAX_PATH]; std::wstringstream path; - if (GetEnvironmentVariable(L"APPDATA", buffer , MAX_PATH) > 0) { - path << buffer - << L"\\Microsoft\\Windows\\Start Menu\\Programs\\"; - + 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 1b3de6f..991f84f 100644 --- a/src/linkhelper.h +++ b/src/linkhelper.h @@ -1,32 +1,35 @@ /* 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::filesystem::path &shortcutPath, const std::filesystem::path &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::filesystem::path &shortcutPath, const std::filesystem::path &exePath, const std::wstring &appID); + static HRESULT installShortcut(const std::filesystem::path &shortcutPath, + const std::filesystem::path &exePath, const std::wstring &appID); - static std::filesystem::path startmenuPath(); + static std::filesystem::path startmenuPath(); }; diff --git a/src/main.cpp b/src/main.cpp index 507364e..b923327 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,282 +1,324 @@ /* 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; + << L"A command line application which is capable of creating Windows Toast " + L"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"[-application] <C:\\foo.exe>\t| Provide a application that might be started if the pipe does not exist." << 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; + 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 " + L"buttons separated by ;" + << std::endl + << L"[-tb]\t\t\t| Displayed a textbox on the bottom line, only if buttons are not " + L"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 " + L"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 " + L"callbacks." + << std::endl + << L"[-application] <C:\\foo.exe>\t| Provide a application that might be started if " + L"the pipe does not exist." + << std::endl + << L"-close <id>\t\t| Closes a currently displayed notification, in order to be able " + L"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 " + L"menu which point to the executable <application>, appID used for the " + L"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"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) +SnoreToastActions::Actions parse(std::vector<wchar_t *> args) { HRESULT hr = S_OK; std::wstring appID; - std::filesystem::path pipe; - std::filesystem::path application; + std::filesystem::path pipe; + std::filesystem::path application; std::wstring title; std::wstring body; 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()) - { + auto nextArg = [&](std::vector<wchar_t *>::const_iterator &it, + const std::wstring &helpText) -> std::wstring { + if (it != args.cend()) { return *it++; - } else - { + } 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); }); + 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" + 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" + 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." + 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" + 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" + 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" + 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"-application") { - application = nextArg(it, L"Missing argument to -pipeName.\n" - L"Supply argument as -applicatzion \"C:\\foo.exe\""); + pipe = nextArg(it, + L"Missing argument to -pipeName.\n" + L"Supply argument as -pipeName \"\\.\\pipe\\foo\\\""); + } else if (arg == L"-application") { + application = nextArg(it, + L"Missing argument to -pipeName.\n" + L"Supply argument as -applicatzion \"C:\\foo.exe\""); } else if (arg == L"-b") { - buttons = nextArg(it, L"Missing argument to -b.\n" - L"Supply argument for buttons as -b \"button1;button2\""); + 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") { - 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\"")); + const std::wstring shortcut( + nextArg(it, + L"Missing argument to -install.\n" + L"Supply argument as -install \"path to your shortcut\" \"path to the " + L"application the shortcut should point to\" \"App.ID\"")); - 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\"")); + const std::wstring exe( + nextArg(it, + L"Missing argument to -install.\n" + L"Supply argument as -install \"path to your shortcut\" \"path to the " + L"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\""); + appID = nextArg(it, + L"Missing argument to -install.\n" + L"Supply argument as -install \"path to your shortcut\" \"path to the " + L"application the shortcut should point to\" \"App.ID\""); - return SUCCEEDED(LinkHelper::tryCreateShortcut(shortcut, exe, appID)) ? SnoreToastActions::Actions::Clicked : SnoreToastActions::Actions::Error; + 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" + 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; - }; - } + if (isTextBoxEnabled) { + if (pipe.empty()) { + std::wcerr << L"TextBox notifications only work if a pipe for the result " + L"was provided" + << std::endl; + return SnoreToastActions::Actions::Error; + }; + } SnoreToasts app(appID); app.setPipeName(pipe); - app.setApplication(application); + app.setApplication(application); 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) +int WINAPI wWinMain(HINSTANCE, HINSTANCE, wchar_t *, int) { - if (AttachConsole(ATTACH_PARENT_PROCESS)) - { + 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; + 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) - { + 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/snoretoastactions.h b/src/snoretoastactions.h index b88177d..ed3108d 100644 --- a/src/snoretoastactions.h +++ b/src/snoretoastactions.h @@ -1,66 +1,58 @@ /* 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 <string_view> #include <vector> - -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_view &s) - { - int i = 0; - for (const auto &sv : ActionStrings) - { - if (sv.compare(s) == 0) - { - return static_cast<SnoreToastActions::Actions>(i); - } - ++i; +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_view &s) + { + int i = 0; + for (const auto &sv : ActionStrings) { + if (sv.compare(s) == 0) { + return static_cast<SnoreToastActions::Actions>(i); } - return SnoreToastActions::Actions::Error; + ++i; } + return SnoreToastActions::Actions::Error; + } - private: - static constexpr std::wstring_view ActionStrings[] = - { - L"clicked", - L"hidden", - L"dismissed", - L"timedout", - L"buttonClicked", - L"textEntered", - }; +private: + static constexpr std::wstring_view ActionStrings[] = { + L"clicked", L"hidden", L"dismissed", L"timedout", L"buttonClicked", L"textEntered", + }; }; diff --git a/src/snoretoasts.cpp b/src/snoretoasts.cpp index 5cb72a1..853ebac 100644 --- a/src/snoretoasts.cpp +++ b/src/snoretoasts.cpp @@ -1,559 +1,578 @@ #include "snoretoasts.h" #include "snoretoasts.h" #include "snoretoasts.h" /* SnoreToast is capable to invoke Windows 8 toast notifications. Copyright (C) 2013-2019 Hannah von Reth <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())) +SnoreToasts::SnoreToasts(const std::wstring &appID) + : m_appID(appID), m_id(std::to_wstring(GetCurrentProcessId())) { Utils::registerActivator(); } SnoreToasts::~SnoreToasts() { Utils::unregisterActivator(); } -void SnoreToasts::displayToast(const std::wstring &title, const std::wstring &body, const std::filesystem::path &image, bool wait) +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); + hr = GetActivationFactory( + StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager) + .Get(), + &m_toastManager); if (SUCCEEDED(hr)) { if (!m_image.empty()) { - hr = m_toastManager->GetTemplateContent(ToastTemplateType_ToastImageAndText02, &m_toastXml); + 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); + 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()) - { + // Adding buttons + if (!m_buttons.empty()) { setButtons(root); - } - else if (m_textbox) - { + } 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); + 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.empty()) { hr = setImage(); } if (SUCCEEDED(hr)) { hr = setSound(); if (SUCCEEDED(hr)) { hr = setTextValues(); if (SUCCEEDED(hr)) { printXML(); hr = createToast(); } } } } - m_action = SUCCEEDED(hr) ? SnoreToastActions::Actions::Clicked : SnoreToastActions::Actions::Error; + 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; + 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()) - { + if (!id.empty()) { m_id = id; } } std::wstring SnoreToasts::id() const { - return m_id; + 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()); + 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()); + hr = setNodeValueString(StringReferenceWrapper(sound).Get(), + srcAttribute.Get()); if (SUCCEEDED(hr)) { - hr = attributes->GetNamedItem(StringReferenceWrapper(L"silent").Get(), &srcAttribute); + hr = attributes->GetNamedItem(StringReferenceWrapper(L"silent").Get(), + &srcAttribute); if (SUCCEEDED(hr)) { - hr = setNodeValueString(StringReferenceWrapper(m_silent ? L"true" : L"false").Get(), srcAttribute.Get()); + 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 + // 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()); + 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); + 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';')) - { + 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); + 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); + 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"); + hr &= addAttribute(L"placeHolderContent", inputAttributes.Get(), + L"Type a reply"); } } ComPtr<IXmlElement> actionElement; - HRESULT hr = m_toastXml->CreateElement(StringReferenceWrapper(L"action").Get(), &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); + 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) +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} }); + 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\t\t\t" - << str << L"\n\t\t" - << L"------------------------"; + tLog << L"------------------------\n\t\t\t" << str << L"\n\t\t" << L"------------------------"; } std::filesystem::path SnoreToasts::pipeName() const { return m_pipeName; } void SnoreToasts::setPipeName(const std::filesystem::path &pipeName) { m_pipeName = pipeName; } std::filesystem::path SnoreToasts::application() const { - return m_application; + return m_application; } -void SnoreToasts::setApplication(const std::filesystem::path & application) +void SnoreToasts::setApplication(const std::filesystem::path &application) { - m_application = application; + m_application = application; } -std::wstring SnoreToasts::formatAction(const SnoreToastActions::Actions &action, const std::vector<std::pair<std::wstring_view, std::wstring_view> > &extraData) const +std::wstring SnoreToasts::formatAction( + const SnoreToastActions::Actions &action, + const std::vector<std::pair<std::wstring_view, std::wstring_view>> &extraData) const { - const auto pipe = m_pipeName.wstring(); - const auto application = m_application.wstring(); + const auto pipe = m_pipeName.wstring(); + const auto application = m_application.wstring(); std::vector<std::pair<std::wstring_view, std::wstring_view>> data = { - {L"action", SnoreToastActions::getActionString(action)}, - {L"notificationId", std::wstring_view(m_id)}, - {L"pipe", std::wstring_view(pipe)}, - {L"application", std::wstring_view(application)} + { L"action", SnoreToastActions::getActionString(action) }, + { L"notificationId", std::wstring_view(m_id) }, + { L"pipe", std::wstring_view(pipe) }, + { L"application", std::wstring_view(application) } }; data.insert(data.end(), extraData.cbegin(), extraData.cend()); return Utils::formatData(data); } // Create and display the toast HRESULT SnoreToasts::createToast() { - HRESULT hr = m_toastManager->CreateToastNotifierWithId(StringReferenceWrapper(m_appID).Get(), &m_notifier); + 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); + 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: "; + 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"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 + // if there are changes to the callback mechanism we need to change the uuid for the activator + // TOAST_UUID return L"0.5.99"; } \ No newline at end of file diff --git a/src/snoretoasts.h b/src/snoretoasts.h index 7214654..e56bdea 100644 --- a/src/snoretoasts.h +++ b/src/snoretoasts.h @@ -1,115 +1,123 @@ /* 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 "snoretoastactions.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 SnoreToasts { public: static std::wstring version(); SnoreToasts(const std::wstring &appID); ~SnoreToasts(); - void displayToast(const std::wstring &title, const std::wstring &body, const std::filesystem::path &image, bool wait); - SnoreToastActions::Actions userAction(); + 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; + std::wstring id() const; void setButtons(const std::wstring &buttons); void setTextBoxEnabled(bool textBoxEnabled); - std::filesystem::path pipeName() const; + std::filesystem::path pipeName() const; void setPipeName(const std::filesystem::path &pipeName); - std::filesystem::path application() const; - void setApplication(const std::filesystem::path &application); + std::filesystem::path application() const; + void setApplication(const std::filesystem::path &application); - std::wstring formatAction(const SnoreToastActions::Actions &action, const std::vector<std::pair<std::wstring_view, std::wstring_view> > &extraData = {}) const; + std::wstring formatAction(const SnoreToastActions::Actions &action, + const std::vector<std::pair<std::wstring_view, std::wstring_view>> + &extraData = {}) const; - private: +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 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::filesystem::path m_pipeName; - std::filesystem::path m_application; + std::filesystem::path m_pipeName; + std::filesystem::path m_application; std::wstring m_title; std::wstring m_body; - std::filesystem::path 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; + 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::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/stringreferencewrapper.h b/src/stringreferencewrapper.h index 7c6c747..76f29cc 100644 --- a/src/stringreferencewrapper.h +++ b/src/stringreferencewrapper.h @@ -1,72 +1,69 @@ #include "snoretoasts.h" #include <string> #include <winstring.h> #include <hstring.h> class StringReferenceWrapper { public: - // Constructor which takes an existing string buffer and its length as the parameters. // It fills an HSTRING_HEADER struct with the parameter. // Warning: The caller must ensure the lifetime of the buffer outlives this // object as it does not make a copy of the wide string memory. StringReferenceWrapper(_In_reads_(length) PCWSTR stringRef, _In_ UINT32 length) throw() { HRESULT hr = WindowsCreateStringReference(stringRef, length, &_header, &_hstring); if (FAILED(hr)) { - RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); + RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, + 0, nullptr); } } - StringReferenceWrapper(const std::wstring &stringRef) throw() : - m_data(stringRef) + StringReferenceWrapper(const std::wstring &stringRef) throw() : m_data(stringRef) { - HRESULT hr = WindowsCreateStringReference(m_data.c_str(), static_cast<UINT32>(m_data.length()), &_header, &_hstring); + HRESULT hr = WindowsCreateStringReference( + m_data.c_str(), static_cast<UINT32>(m_data.length()), &_header, &_hstring); if (FAILED(hr)) { - RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); + RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, + 0, nullptr); } } - ~StringReferenceWrapper() - { - WindowsDeleteString(_hstring); - } + ~StringReferenceWrapper() { WindowsDeleteString(_hstring); } - template <size_t N> - StringReferenceWrapper(_In_reads_(N) wchar_t const(&stringRef)[N]) throw() + template<size_t N> + StringReferenceWrapper(_In_reads_(N) wchar_t const (&stringRef)[N]) throw() { UINT32 length = N - 1; HRESULT hr = WindowsCreateStringReference(stringRef, length, &_header, &_hstring); if (FAILED(hr)) { - RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); + RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, + 0, nullptr); } } - template <size_t _> + template<size_t _> StringReferenceWrapper(_In_reads_(_) wchar_t (&stringRef)[_]) throw() { UINT32 length; HRESULT hr = SizeTToUInt32(wcslen(stringRef), &length); if (FAILED(hr)) { - RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); + RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, + 0, nullptr); } WindowsCreateStringReference(stringRef, length, &_header, &_hstring); } - HSTRING Get() const throw() - { - return _hstring; - } + HSTRING Get() const throw() { return _hstring; } private: - HSTRING _hstring; - HSTRING_HEADER _header; + HSTRING _hstring; + HSTRING_HEADER _header; std::wstring m_data; }; diff --git a/src/toasteventhandler.cpp b/src/toasteventhandler.cpp index 9997d7c..0de7456 100644 --- a/src/toasteventhandler.cpp +++ b/src/toasteventhandler.cpp @@ -1,208 +1,189 @@ /* 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 SnoreToasts &toast) : - m_ref(1), - m_userAction(SnoreToastActions::Actions::Hidden) - , m_toast(toast) +ToastEventHandler::ToastEventHandler(const SnoreToasts &toast) + : m_ref(1), m_userAction(SnoreToastActions::Actions::Hidden), m_toast(toast) { std::wstringstream eventName; eventName << L"ToastEvent" << m_toast.id(); m_event = CreateEventW(nullptr, true, false, eventName.str().c_str()); } ToastEventHandler::~ToastEventHandler() { CloseHandle(m_event); } HANDLE ToastEventHandler::event() { return m_event; } SnoreToastActions::Actions &ToastEventHandler::userAction() { return m_userAction; } // DesktopToastActivatedEventHandler -IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification * /*sender*/, _In_ IInspectable *args) +IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification * /*sender*/, + _In_ IInspectable *args) { IToastActivatedEventArgs *buttonReply = nullptr; args->QueryInterface(&buttonReply); - if (buttonReply == nullptr) - { + if (buttonReply == nullptr) { std::wcerr << L"args is not a IToastActivatedEventArgs" << std::endl; - } - else - { + } else { HSTRING args; buttonReply->get_Arguments(&args); std::wstring data = WindowsGetStringRawBuffer(args, nullptr); tLog << data; const auto dataMap = Utils::splitData(data); const auto action = SnoreToastActions::getAction(dataMap.at(L"action")); assert(dataMap.at(L"notificationId") == m_toast.id()); - if (action == SnoreToastActions::Actions::TextEntered) - { - // The text is only passed to the named pipe + if (action == SnoreToastActions::Actions::TextEntered) { + // The text is only passed to the named pipe tLog << L"The user entered a text."; - m_userAction = SnoreToastActions::Actions::TextEntered; - } - else if (action == SnoreToastActions::Actions::Clicked) - { + m_userAction = SnoreToastActions::Actions::TextEntered; + } else if (action == SnoreToastActions::Actions::Clicked) { tLog << L"The user clicked on the toast."; m_userAction = SnoreToastActions::Actions::Clicked; - } - else - { + } else { tLog << L"The user clicked on a toast button."; std::wcout << dataMap.at(L"button") << std::endl; m_userAction = SnoreToastActions::Actions::ButtonClicked; } } SetEvent(m_event); return S_OK; } // DesktopToastDismissedEventHandler -IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification * /* sender */, _In_ IToastDismissedEventArgs *e) +IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification * /* sender */, + _In_ IToastDismissedEventArgs *e) { - ToastDismissalReason tdr; - HRESULT hr = e->get_Reason(&tdr); - if (SUCCEEDED(hr)) { - switch (tdr) { - case ToastDismissalReason_ApplicationHidden: - tLog << L"The application hid the toast using ToastNotifier.hide()"; - m_userAction = SnoreToastActions::Actions::Hidden; - break; - case ToastDismissalReason_UserCanceled: - tLog << L"The user dismissed this toast"; - m_userAction = SnoreToastActions::Actions::Dismissed; - break; - case ToastDismissalReason_TimedOut: - tLog << L"The toast has timed out"; - m_userAction = SnoreToastActions::Actions::Timedout; - break; - } - } - if (!m_toast.pipeName().empty()) - { - Utils::writePipe(m_toast.pipeName(), m_toast.formatAction(m_userAction)); - } + ToastDismissalReason tdr; + HRESULT hr = e->get_Reason(&tdr); + if (SUCCEEDED(hr)) { + switch (tdr) { + case ToastDismissalReason_ApplicationHidden: + tLog << L"The application hid the toast using ToastNotifier.hide()"; + m_userAction = SnoreToastActions::Actions::Hidden; + break; + case ToastDismissalReason_UserCanceled: + tLog << L"The user dismissed this toast"; + m_userAction = SnoreToastActions::Actions::Dismissed; + break; + case ToastDismissalReason_TimedOut: + tLog << L"The toast has timed out"; + m_userAction = SnoreToastActions::Actions::Timedout; + break; + } + } + if (!m_toast.pipeName().empty()) { + Utils::writePipe(m_toast.pipeName(), m_toast.formatAction(m_userAction)); + } SetEvent(m_event); return S_OK; } // DesktopToastFailedEventHandler -IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification * /* sender */, _In_ IToastFailedEventArgs * /* e */) +IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification * /* sender */, + _In_ IToastFailedEventArgs * /* e */) { std::wcerr << L"The toast encountered an error." << std::endl; std::wcerr << L"Please make sure that the app id is set correctly." << std::endl; std::wcerr << L"Command Line: " << GetCommandLineW() << std::endl; m_userAction = SnoreToastActions::Actions::Error; SetEvent(m_event); return S_OK; } -CToastNotificationActivationCallback::CToastNotificationActivationCallback() -{ -} +CToastNotificationActivationCallback::CToastNotificationActivationCallback() {} HANDLE CToastNotificationActivationCallback::event() { - static HANDLE _event = []{ - std::wstringstream eventName; - eventName << L"ToastActivationEvent" << GetCurrentProcessId(); - return CreateEvent(nullptr, true, false, eventName.str().c_str()); - }(); - return _event; + static HANDLE _event = [] { + std::wstringstream eventName; + eventName << L"ToastActivationEvent" << GetCurrentProcessId(); + return CreateEvent(nullptr, true, false, eventName.str().c_str()); + }(); + return _event; } HRESULT CToastNotificationActivationCallback::Activate(LPCWSTR appUserModelId, LPCWSTR invokedArgs, - const NOTIFICATION_USER_INPUT_DATA* data, ULONG count) + const NOTIFICATION_USER_INPUT_DATA *data, + ULONG count) { - if (invokedArgs == nullptr) - { + if (invokedArgs == nullptr) { return S_OK; } - tLog << "CToastNotificationActivationCallback::Activate: " << appUserModelId << " : " << invokedArgs << " : " << data; + tLog << "CToastNotificationActivationCallback::Activate: " << appUserModelId << " : " + << invokedArgs << " : " << data; const auto dataMap = Utils::splitData(invokedArgs); const auto action = SnoreToastActions::getAction(dataMap.at(L"action")); - std::wstring dataString; - if (action == SnoreToastActions::Actions::TextEntered) - { + std::wstring dataString; + if (action == SnoreToastActions::Actions::TextEntered) { assert(count); std::wstringstream sMsg; sMsg << invokedArgs << L"text="; - for (ULONG i=0; i<count; ++i) - { + 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; } dataString = sMsg.str(); - } - else - { + } else { dataString = invokedArgs; } const auto pipe = dataMap.find(L"pipe"); - if (pipe != dataMap.cend()) - { - if (!Utils::writePipe(pipe->second, dataString)) - { - const auto app = dataMap.find(L"application"); - if (app != dataMap.cend()) - { - if (Utils::startProcess(app->second)) - { - Utils::writePipe(pipe->second, dataString, true); - } - } - } - + if (pipe != dataMap.cend()) { + if (!Utils::writePipe(pipe->second, dataString)) { + const auto app = dataMap.find(L"application"); + if (app != dataMap.cend()) { + if (Utils::startProcess(app->second)) { + Utils::writePipe(pipe->second, dataString, true); + } + } + } } tLog << dataString; - if (!SetEvent(event())) - { - tLog << "SetEvent failed" << GetLastError(); - return S_FALSE; - } - return S_OK; + if (!SetEvent(event())) { + tLog << "SetEvent failed" << GetLastError(); + return S_FALSE; + } + return S_OK; } void CToastNotificationActivationCallback::waitForActivation() { WaitForSingleObject(event(), INFINITE); } \ No newline at end of file diff --git a/src/toasteventhandler.h b/src/toasteventhandler.h index dc96c86..28400d4 100644 --- a/src/toasteventhandler.h +++ b/src/toasteventhandler.h @@ -1,125 +1,140 @@ /* 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}" -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 +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; + 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; + 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> +// 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: static void waitForActivation(); CToastNotificationActivationCallback(); - virtual HRESULT STDMETHODCALLTYPE Activate(__RPC__in_string LPCWSTR appUserModelId, __RPC__in_opt_string LPCWSTR invokedArgs, - __RPC__in_ecount_full_opt(count) const NOTIFICATION_USER_INPUT_DATA* data, ULONG count) override; + 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 event(); - }; CoCreatableClass(CToastNotificationActivationCallback); -class ToastEventHandler : - public Microsoft::WRL::Implements<DesktopToastActivatedEventHandler, DesktopToastDismissedEventHandler, DesktopToastFailedEventHandler> +class ToastEventHandler : public Microsoft::WRL::Implements<DesktopToastActivatedEventHandler, + DesktopToastDismissedEventHandler, + DesktopToastFailedEventHandler> { public: explicit ToastEventHandler::ToastEventHandler(const SnoreToasts &toast); ~ToastEventHandler(); HANDLE event(); - SnoreToastActions::Actions &userAction(); + SnoreToastActions::Actions &userAction(); // DesktopToastActivatedEventHandler - IFACEMETHODIMP Invoke(_In_ ABI::Windows::UI::Notifications::IToastNotification *sender, _In_ IInspectable *args); + 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); + 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); + 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) 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; - SnoreToastActions::Actions m_userAction; + SnoreToastActions::Actions m_userAction; HANDLE m_event; - const SnoreToasts &m_toast; + const SnoreToasts &m_toast; }; diff --git a/src/utils.cpp b/src/utils.cpp index c75500d..1ef9e21 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,158 +1,154 @@ /* SnoreToast is capable to invoke Windows 8 toast notifications. Copyright (C) 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 "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; +bool s_registered = false; } namespace Utils { bool registerActivator() { - if (!s_registered) - { + if (!s_registered) { s_registered = true; Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::Create([] {}); Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule().IncrementObjectCount(); - return SUCCEEDED(Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule().RegisterObjects()); + return SUCCEEDED( + Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule().RegisterObjects()); } return true; } void unregisterActivator() { - if (s_registered) - { + 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_view, std::wstring_view> splitData(const std::wstring_view &data) { std::unordered_map<std::wstring_view, std::wstring_view> out; - size_t start = 0; - for (size_t end = data.find(L";", start); end != std::wstring::npos; start=end + 1, end=data.find(L";", start)) - { - if (start == end) - { - end = data.size(); - } - const std::wstring_view tmp(data.data() + start, end - start); - const auto pos = tmp.find(L"="); - out[tmp.substr(0, pos)] = tmp.substr(pos + 1); - //tLog << L"'" << tmp.substr(0, pos) << L"' = '" << tmp.substr(pos + 1) << L"'"; - } + size_t start = 0; + for (size_t end = data.find(L";", start); end != std::wstring::npos; + start = end + 1, end = data.find(L";", start)) { + if (start == end) { + end = data.size(); + } + const std::wstring_view tmp(data.data() + start, end - start); + const auto pos = tmp.find(L"="); + out[tmp.substr(0, pos)] = tmp.substr(pos + 1); + // tLog << L"'" << tmp.substr(0, pos) << L"' = '" << tmp.substr(pos + 1) << L"'"; + } return out; } const std::filesystem::path &selfLocate() { static const std::filesystem::path path = [] { - std::wstring buf; + std::wstring buf; size_t size; do { - buf.resize(buf.size() + 1024); - size = GetModuleFileNameW(nullptr, const_cast<wchar_t*>(buf.data()), static_cast<DWORD>(buf.size())); + buf.resize(buf.size() + 1024); + size = GetModuleFileNameW(nullptr, const_cast<wchar_t *>(buf.data()), + static_cast<DWORD>(buf.size())); } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); - buf.resize(size); - return buf; - }(); + buf.resize(size); + return buf; + }(); return path; } bool writePipe(const std::filesystem::path &pipe, const std::wstring &data, bool wait) { - HANDLE hPipe = CreateFile(pipe.wstring().c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); - if (hPipe != INVALID_HANDLE_VALUE) - { + HANDLE hPipe = CreateFile(pipe.wstring().c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, + nullptr); + if (hPipe != INVALID_HANDLE_VALUE) { DWORD written; const DWORD toWrite = static_cast<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; - } - else if (wait) - { - // wait and retry - Sleep(20000); - return writePipe(pipe, data); - } - tLog << L"Failed to open pipe: " << pipe << L" data: " << data; + } else if (wait) { + // wait and retry + Sleep(20000); + return writePipe(pipe, data); + } + tLog << L"Failed to open pipe: " << pipe << L" data: " << data; return false; } -bool startProcess(const std::filesystem::path & app) +bool startProcess(const std::filesystem::path &app) { - STARTUPINFO info = {}; - info.cb = sizeof(info); - PROCESS_INFORMATION pInfo = {}; - const auto application = app.wstring(); - if (!CreateProcess(const_cast<wchar_t*>(application.c_str()), const_cast<wchar_t*>(application.c_str()), nullptr, nullptr, false, DETACHED_PROCESS | INHERIT_PARENT_AFFINITY | CREATE_NO_WINDOW, nullptr, nullptr, &info, &pInfo)) - { - tLog << L"Failed to start: " << app; - return false; - } - WaitForInputIdle(pInfo.hProcess, INFINITE); - DWORD status; - GetExitCodeProcess(pInfo.hProcess, &status); - CloseHandle(pInfo.hProcess); - CloseHandle(pInfo.hThread); - tLog << L"Started: " << app << L" Status: " << (status == STILL_ACTIVE ? L"STILL_ACTIVE" : std::to_wstring(status)); - return status == STILL_ACTIVE; + STARTUPINFO info = {}; + info.cb = sizeof(info); + PROCESS_INFORMATION pInfo = {}; + const auto application = app.wstring(); + if (!CreateProcess(const_cast<wchar_t *>(application.c_str()), + const_cast<wchar_t *>(application.c_str()), nullptr, nullptr, false, + DETACHED_PROCESS | INHERIT_PARENT_AFFINITY | CREATE_NO_WINDOW, nullptr, + nullptr, &info, &pInfo)) { + tLog << L"Failed to start: " << app; + return false; + } + WaitForInputIdle(pInfo.hProcess, INFINITE); + DWORD status; + GetExitCodeProcess(pInfo.hProcess, &status); + CloseHandle(pInfo.hProcess); + CloseHandle(pInfo.hThread); + tLog << L"Started: " << app << L" Status: " + << (status == STILL_ACTIVE ? L"STILL_ACTIVE" : std::to_wstring(status)); + return status == STILL_ACTIVE; } -std::wstring formatData(const std::vector<std::pair<std::wstring_view, std::wstring_view> > &data) +std::wstring formatData(const std::vector<std::pair<std::wstring_view, std::wstring_view>> &data) { std::wstringstream out; - for (const auto &p : data) - { - if (!p.second.empty()) - { + for (const auto &p : data) { + if (!p.second.empty()) { out << p.first << L"=" << p.second << L";"; } } return out.str(); } } ToastLog::ToastLog() { - *this << Utils::selfLocate() << L" v" << SnoreToasts::version() << L"\n\t"; + *this << Utils::selfLocate() << L" v" << SnoreToasts::version() << L"\n\t"; } ToastLog::~ToastLog() { - m_log << L"\n"; + m_log << L"\n"; OutputDebugStringW(m_log.str().c_str()); } diff --git a/src/utils.h b/src/utils.h index 8082931..3dbf2f7 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,61 +1,60 @@ /* SnoreToast is capable to invoke Windows 8 toast notifications. Copyright (C) 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 <filesystem> #include <sstream> #include <unordered_map> -namespace Utils -{ - bool registerActivator(); - void unregisterActivator(); +namespace Utils { +bool registerActivator(); +void unregisterActivator(); - std::unordered_map<std::wstring_view, std::wstring_view> splitData(const std::wstring_view &data); +std::unordered_map<std::wstring_view, std::wstring_view> splitData(const std::wstring_view &data); - const std::filesystem::path &selfLocate(); +const std::filesystem::path &selfLocate(); - std::wstring formatData(const std::vector<std::pair<std::wstring_view, std::wstring_view>> &data); +std::wstring formatData(const std::vector<std::pair<std::wstring_view, std::wstring_view>> &data); - bool writePipe(const std::filesystem::path &pipe, const std::wstring &data, bool wait=false); - bool startProcess(const std::filesystem::path &app); +bool writePipe(const std::filesystem::path &pipe, const std::wstring &data, bool wait = false); +bool startProcess(const std::filesystem::path &app); }; - class ToastLog { public: ToastLog(); ~ToastLog(); - inline ToastLog &log() { return *this;} + inline ToastLog &log() { return *this; } private: std::wstringstream m_log; template<typename T> - friend ToastLog & operator<<(ToastLog &, const T&); + friend ToastLog &operator<<(ToastLog &, const T &); }; #define tLog ToastLog().log() << __FUNCSIG__ << L"\n\t\t" -template <typename T> -ToastLog &operator<< (ToastLog &log, const T &t) { +template<typename T> +ToastLog &operator<<(ToastLog &log, const T &t) +{ log.m_log << t; return log; }