From 46af07748b02b0a1b3bd17b5d445210345260dd8 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 16 Apr 2022 14:57:26 +0200 Subject: [PATCH] Move desktop notifications to its own header We'll need to use this from the chainloader. --- src/common/notifications.cpp | 177 +++++++++++++++++++++++++++++++++++ src/common/notifications.h | 58 ++++++++++++ src/common/utils.cpp | 109 --------------------- src/common/utils.h | 11 --- src/plugin/bridges/common.h | 4 +- src/plugin/meson.build | 2 + src/plugin/utils.cpp | 46 --------- src/plugin/utils.h | 20 ---- src/plugin/vst2-plugin.cpp | 1 + src/plugin/vst3-plugin.cpp | 9 +- src/wine-host/meson.build | 1 + src/wine-host/xdnd-proxy.cpp | 1 + 12 files changed, 246 insertions(+), 193 deletions(-) create mode 100644 src/common/notifications.cpp create mode 100644 src/common/notifications.h diff --git a/src/common/notifications.cpp b/src/common/notifications.cpp new file mode 100644 index 00000000..da27a1bf --- /dev/null +++ b/src/common/notifications.cpp @@ -0,0 +1,177 @@ +// yabridge: a Wine plugin bridge +// Copyright (C) 2020-2022 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "notifications.h" + +#include + +#include "process.h" +#include "utils.h" + +bool send_notification(const std::string& title, + const std::string body, + std::optional origin) { + // I think there's a zero chance that we're going to call this function with + // anything that even somewhat resembles HTML, but we should still do a + // basic XML escape anyways. + std::ostringstream formatted_body; + formatted_body << xml_escape(body); + + // If the path to the current library file is provided, then we'll append + // the path to that library file to the message. In earlier versions we + // would detect the library path right here, but that will not work with + // chainloaded plugins as they will load the actual plugin libraries from + // fixed locations. + if (origin) { + try { + formatted_body << "\n" + << "Source: parent_path().string()) + << "\">" << xml_escape(origin->filename().string()) + << ""; + } catch (const std::system_error&) { + // I don't think this can fail in the way we're using it, but the + // last thing we want is our notification informing the user of an + // exception to trigger another exception + } + } + + Process process("notify-send"); + process.arg("--urgency=normal"); + process.arg("--app-name=yabridge"); + process.arg(title); + process.arg(formatted_body.str()); + + // We will have printed the message to the terminal anyways, so if the user + // doesn't have libnotify installed we'll just fail silently + const auto result = process.spawn_get_status(); + return std::visit( + overload{ + [](int status) -> bool { return status == EXIT_SUCCESS; }, + [](const Process::CommandNotFound&) -> bool { return false; }, + [](const std::error_code&) -> bool { return false; }, + }, + result); +} + +std::string xml_escape(std::string string) { + // Implementation idea stolen from https://stackoverflow.com/a/5665377 + std::string escaped; + escaped.reserve( + static_cast(static_cast(string.size()) * 1.1)); + for (const char& character : string) { + switch (character) { + case '&': + escaped.append("&"); + break; + case '\"': + escaped.append("""); + break; + case '\'': + escaped.append("'"); + break; + case '<': + escaped.append("<"); + break; + case '>': + escaped.append(">"); + break; + default: + escaped.push_back(character); + break; + } + } + + return escaped; +} + +std::string url_encode_path(std::string path) { + // We only need to escape a couple of special characters here. This is used + // in the notifications as well as in the XDND proxy. We encode the reserved + // characters mentioned here, with the exception of the forward slash: + // https://en.wikipedia.org/wiki/Percent-encoding#Reserved_characters + std::string escaped; + escaped.reserve( + static_cast(static_cast(path.size()) * 1.1)); + for (const char& character : path) { + switch (character) { + // Spaces are somehow in the above list, but Bitwig Studio requires + // spaces to be escaped in the `text/uri-list` format + case ' ': + escaped.append("%20"); + break; + case '!': + escaped.append("%21"); + break; + case '#': + escaped.append("%23"); + break; + case '$': + escaped.append("%24"); + break; + case '%': + escaped.append("%25"); + break; + case '&': + escaped.append("%26"); + break; + case '\'': + escaped.append("%27"); + break; + case '(': + escaped.append("%28"); + break; + case ')': + escaped.append("%29"); + break; + case '*': + escaped.append("%2A"); + break; + case '+': + escaped.append("%2B"); + break; + case ',': + escaped.append("%2C"); + break; + case ':': + escaped.append("%3A"); + break; + case ';': + escaped.append("%3B"); + break; + case '=': + escaped.append("%3D"); + break; + case '?': + escaped.append("%3F"); + break; + case '@': + escaped.append("%40"); + break; + case '[': + escaped.append("%5B"); + break; + case ']': + escaped.append("%5D"); + break; + default: + escaped.push_back(character); + break; + } + } + + return escaped; +} diff --git a/src/common/notifications.h b/src/common/notifications.h new file mode 100644 index 00000000..188a0c61 --- /dev/null +++ b/src/common/notifications.h @@ -0,0 +1,58 @@ +// yabridge: a Wine plugin bridge +// Copyright (C) 2020-2022 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +// This header is used by the plugins and the chainloaders to send desktop +// notifications when something goes wrong + +#include +#include + +// TODO: At some point, provide an alternative to notify-send by dlopen()-ing +// libdbus instead. Some more obscure distros won't have notify-send +// available. + +/** + * Send a desktop notification using `notify-send`. Used for diagnostics when a + * plugin fails to load since the user may not be checking the output in a + * terminal. + * + * @param title The title (or technically, summary) of the notification. + * @param body The message to display. This can contain line feeds, and it any + * HTML tags and XML escape sequences will be automatically escaped. The + * message can also be empty. + * @param origin If this is set to the current plugin's path, then the + * notification will append a 'Source: ' hyperlink to the body so the + * user can more easily navigate to the plugin's path. + * + * @return Whether the notification was sent. This will be false if + * `notify-send` is not available. + */ +bool send_notification(const std::string& title, + const std::string body, + std::optional origin); + +/** + * Escape XML entities within a string. Used inside of desktop notifications. + */ +std::string xml_escape(std::string string); + +/** + * URL encode a file path. We won't escape forward slashes, and `path` should + * not yet include the `file://` prefix. + */ +std::string url_encode_path(std::string path); diff --git a/src/common/utils.cpp b/src/common/utils.cpp index 1b1f2f8c..b8938338 100644 --- a/src/common/utils.cpp +++ b/src/common/utils.cpp @@ -94,115 +94,6 @@ bool is_watchdog_timer_disabled() { return disable_watchdog_env && disable_watchdog_env == "1"sv; } -std::string url_encode_path(std::string path) { - // We only need to escape a couple of special characters here. This is used - // in the notifications as well as in the XDND proxy. We encode the reserved - // characters mentioned here, with the exception of the forward slash: - // https://en.wikipedia.org/wiki/Percent-encoding#Reserved_characters - std::string escaped; - escaped.reserve( - static_cast(static_cast(path.size()) * 1.1)); - for (const char& character : path) { - switch (character) { - // Spaces are somehow in the above list, but Bitwig Studio requires - // spaces to be escaped in the `text/uri-list` format - case ' ': - escaped.append("%20"); - break; - case '!': - escaped.append("%21"); - break; - case '#': - escaped.append("%23"); - break; - case '$': - escaped.append("%24"); - break; - case '%': - escaped.append("%25"); - break; - case '&': - escaped.append("%26"); - break; - case '\'': - escaped.append("%27"); - break; - case '(': - escaped.append("%28"); - break; - case ')': - escaped.append("%29"); - break; - case '*': - escaped.append("%2A"); - break; - case '+': - escaped.append("%2B"); - break; - case ',': - escaped.append("%2C"); - break; - case ':': - escaped.append("%3A"); - break; - case ';': - escaped.append("%3B"); - break; - case '=': - escaped.append("%3D"); - break; - case '?': - escaped.append("%3F"); - break; - case '@': - escaped.append("%40"); - break; - case '[': - escaped.append("%5B"); - break; - case ']': - escaped.append("%5D"); - break; - default: - escaped.push_back(character); - break; - } - } - - return escaped; -} - -std::string xml_escape(std::string string) { - // Implementation idea stolen from https://stackoverflow.com/a/5665377 - std::string escaped; - escaped.reserve( - static_cast(static_cast(string.size()) * 1.1)); - for (const char& character : string) { - switch (character) { - case '&': - escaped.append("&"); - break; - case '\"': - escaped.append("""); - break; - case '\'': - escaped.append("'"); - break; - case '<': - escaped.append("<"); - break; - case '>': - escaped.append(">"); - break; - default: - escaped.push_back(character); - break; - } - } - - return escaped; -} - ScopedFlushToZero::ScopedFlushToZero() noexcept { old_ftz_mode_ = _MM_GET_FLUSH_ZERO_MODE(); _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); diff --git a/src/common/utils.h b/src/common/utils.h index f01e2a30..789be1f3 100644 --- a/src/common/utils.h +++ b/src/common/utils.h @@ -140,17 +140,6 @@ std::optional get_rttime_limit() noexcept; */ bool is_watchdog_timer_disabled(); -/** - * URL encode a file path. We won't escape forward slashes, and `path` should - * not yet include the `file://` prefix. - */ -std::string url_encode_path(std::string path); - -/** - * Escape XML entities within a string. Used inside of desktop notifications. - */ -std::string xml_escape(std::string string); - /** * A RAII wrapper that will temporarily enable the FTZ flag so that denormals * are automatically flushed to zero, returning to whatever the flag was diff --git a/src/plugin/bridges/common.h b/src/plugin/bridges/common.h index 5f2610a4..b52b86ba 100644 --- a/src/plugin/bridges/common.h +++ b/src/plugin/bridges/common.h @@ -26,12 +26,10 @@ #include #include "../../common/configuration.h" +#include "../../common/notifications.h" #include "../../common/utils.h" #include "../host-process.h" -// FIXME: This should be passed as an argument instead -#include "../../common/linking.h" - /** * If the amount of lockable memory is below this, then we'll warn about it * during startup. Otherwise we may run into issues when mapping shared memory diff --git a/src/plugin/meson.build b/src/plugin/meson.build index 287ae11a..17183ed3 100644 --- a/src/plugin/meson.build +++ b/src/plugin/meson.build @@ -10,6 +10,7 @@ vst2_plugin_sources = files( '../common/logging/vst2.cpp', '../common/audio-shm.cpp', '../common/linking.cpp', + '../common/notifications.cpp', '../common/plugins.cpp', '../common/process.cpp', '../common/utils.cpp', @@ -82,6 +83,7 @@ vst3_plugin_sources = files( '../common/audio-shm.cpp', '../common/configuration.cpp', '../common/linking.cpp', + '../common/notifications.cpp', '../common/plugins.cpp', '../common/process.cpp', '../common/utils.cpp', diff --git a/src/plugin/utils.cpp b/src/plugin/utils.cpp index 887058f1..14656827 100644 --- a/src/plugin/utils.cpp +++ b/src/plugin/utils.cpp @@ -422,49 +422,3 @@ Configuration load_config_for(const fs::path& yabridge_path) { return Configuration(*config_file, yabridge_path); } - -bool send_notification(const std::string& title, - const std::string body, - std::optional origin) { - // I think there's a zero chance that we're going to call this function with - // anything that even somewhat resembles HTML, but we should still do a - // basic XML escape anyways. - std::ostringstream formatted_body; - formatted_body << xml_escape(body); - - // If the path to the current library file is provided, then we'll append - // the path to that library file to the message. In earlier versions we - // would detect the library path right here, but that will not work with - // chainloaded plugins as they will load the actual plugin libraries from - // fixed locations. - if (origin) { - try { - formatted_body << "\n" - << "Source: parent_path().string()) - << "\">" << xml_escape(origin->filename().string()) - << ""; - } catch (const std::system_error&) { - // I don't think this can fail in the way we're using it, but the - // last thing we want is our notification informing the user of an - // exception to trigger another exception - } - } - - Process process("notify-send"); - process.arg("--urgency=normal"); - process.arg("--app-name=yabridge"); - process.arg(title); - process.arg(formatted_body.str()); - - // We will have printed the message to the terminal anyways, so if the user - // doesn't have libnotify installed we'll just fail silently - const auto result = process.spawn_get_status(); - return std::visit( - overload{ - [](int status) -> bool { return status == EXIT_SUCCESS; }, - [](const Process::CommandNotFound&) -> bool { return false; }, - [](const std::error_code&) -> bool { return false; }, - }, - result); -} diff --git a/src/plugin/utils.h b/src/plugin/utils.h index ae05ee52..02babbe2 100644 --- a/src/plugin/utils.h +++ b/src/plugin/utils.h @@ -263,26 +263,6 @@ std::vector get_augmented_search_path(); */ Configuration load_config_for(const ghc::filesystem::path& yabridge_path); -/** - * Send a desktop notification using `notify-send`. Used for diagnostics when a - * plugin fails to load since the user may not be checking the output in a - * terminal. - * - * @param title The title (or technically, summary) of the notification. - * @param body The message to display. This can contain line feeds, and it any - * HTML tags and XML escape sequences will be automatically escaped. The - * message can also be empty. - * @param origin If this is set to the current plugin's path, then the - * notification will append a 'Source: ' hyperlink to the body so the - * user can more easily navigate to the plugin's path. - * - * @return Whether the notification was sent. This will be false if - * `notify-send` is not available. - */ -bool send_notification(const std::string& title, - const std::string body, - std::optional origin); - /** * Starting from the starting file or directory, go up in the directory * hierarchy until we find a file named `filename`. diff --git a/src/plugin/vst2-plugin.cpp b/src/plugin/vst2-plugin.cpp index dfb89ea8..7b80d30b 100644 --- a/src/plugin/vst2-plugin.cpp +++ b/src/plugin/vst2-plugin.cpp @@ -19,6 +19,7 @@ #include #include +#include "../common/linking.h" #include "../common/logging/common.h" #include "bridges/vst2.h" diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp index e8b2a733..3c5ec6ad 100644 --- a/src/plugin/vst3-plugin.cpp +++ b/src/plugin/vst3-plugin.cpp @@ -14,10 +14,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "bridges/vst3.h" - -using namespace std::literals::string_literals; - // FIXME: The VST3 SDK as of version 3.7.2 now includes multiple local functions // called `InitModule` and `DeinitModule`: one in the new // `public.sdk/source/main/initmodule.cpp`, and the existing ones in the @@ -29,6 +25,9 @@ using namespace std::literals::string_literals; // NOLINTNEXTLINE(bugprone-suspicious-include) #include +#include "../common/linking.h" +#include "bridges/vst3.h" + using namespace std::literals::string_literals; namespace fs = ghc::filesystem; @@ -49,6 +48,8 @@ namespace fs = ghc::filesystem; std::unique_ptr bridge; +// These functions are called by the `ModuleEntry` and `ModuleExit` functions on +// the first load and load unload bool InitModule() { assert(!bridge); diff --git a/src/wine-host/meson.build b/src/wine-host/meson.build index 519e5d7b..62c45ed0 100644 --- a/src/wine-host/meson.build +++ b/src/wine-host/meson.build @@ -61,6 +61,7 @@ host_common_sources = files( '../common/logging/common.cpp', '../common/logging/vst2.cpp', '../common/audio-shm.cpp', + '../common/notifications.cpp', '../common/plugins.cpp', '../common/process.cpp', '../common/utils.cpp', diff --git a/src/wine-host/xdnd-proxy.cpp b/src/wine-host/xdnd-proxy.cpp index 9a197003..f860c00f 100644 --- a/src/wine-host/xdnd-proxy.cpp +++ b/src/wine-host/xdnd-proxy.cpp @@ -19,6 +19,7 @@ #include #include +#include "../common/notifications.h" #include "editor.h" using namespace std::literals::chrono_literals;