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;