From cd609fa90f1f87eb86511f017acbccbd8504dd18 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 29 Sep 2022 19:26:49 +0200 Subject: [PATCH] Implement the plugin side of the GUI functions --- src/common/serialization/clap.h | 12 ++ src/common/serialization/clap/plugin.cpp | 4 +- src/common/serialization/clap/plugin.h | 4 +- src/wine-host/bridges/clap.cpp | 223 +++++++++++++++++++++++ src/wine-host/bridges/clap.h | 1 + 5 files changed, 242 insertions(+), 2 deletions(-) diff --git a/src/common/serialization/clap.h b/src/common/serialization/clap.h index 10c8eb54..32047ca4 100644 --- a/src/common/serialization/clap.h +++ b/src/common/serialization/clap.h @@ -56,6 +56,18 @@ using ClapMainThreadControlRequest = clap::plugin::Deactivate, clap::ext::audio_ports::plugin::Count, clap::ext::audio_ports::plugin::Get, + clap::ext::gui::plugin::IsApiSupported, + clap::ext::gui::plugin::Create, + clap::ext::gui::plugin::Destroy, + clap::ext::gui::plugin::SetScale, + clap::ext::gui::plugin::GetSize, + clap::ext::gui::plugin::CanResize, + clap::ext::gui::plugin::GetResizeHints, + clap::ext::gui::plugin::AdjustSize, + clap::ext::gui::plugin::SetSize, + clap::ext::gui::plugin::SetParent, + clap::ext::gui::plugin::Show, + clap::ext::gui::plugin::Hide, clap::ext::latency::plugin::Get, clap::ext::note_ports::plugin::Count, clap::ext::note_ports::plugin::Get, diff --git a/src/common/serialization/clap/plugin.cpp b/src/common/serialization/clap/plugin.cpp index 017aba47..8ea14d58 100644 --- a/src/common/serialization/clap/plugin.cpp +++ b/src/common/serialization/clap/plugin.cpp @@ -17,6 +17,7 @@ #include "plugin.h" #include +#include #include #include #include @@ -81,9 +82,10 @@ const clap_plugin_descriptor_t* Descriptor::get() const { return &clap_descriptor; } -std::array, 6> SupportedPluginExtensions::list() +std::array, 7> SupportedPluginExtensions::list() const noexcept { return {std::pair(supports_audio_ports, CLAP_EXT_AUDIO_PORTS), + std::pair(supports_gui, CLAP_EXT_GUI), std::pair(supports_latency, CLAP_EXT_LATENCY), std::pair(supports_note_ports, CLAP_EXT_NOTE_PORTS), std::pair(supports_params, CLAP_EXT_PARAMS), diff --git a/src/common/serialization/clap/plugin.h b/src/common/serialization/clap/plugin.h index 42d96558..269a8f02 100644 --- a/src/common/serialization/clap/plugin.h +++ b/src/common/serialization/clap/plugin.h @@ -115,6 +115,7 @@ struct Descriptor { struct SupportedPluginExtensions { // Don't forget to add new extensions to below method bool supports_audio_ports = false; + bool supports_gui = false; bool supports_latency = false; bool supports_note_ports = false; bool supports_params = false; @@ -125,11 +126,12 @@ struct SupportedPluginExtensions { * Get a list of `` tuples for the supported * extensions. Used during logging. */ - std::array, 6> list() const noexcept; + std::array, 7> list() const noexcept; template void serialize(S& s) { s.value1b(supports_audio_ports); + s.value1b(supports_gui); s.value1b(supports_latency); s.value1b(supports_note_ports); s.value1b(supports_params); diff --git a/src/wine-host/bridges/clap.cpp b/src/wine-host/bridges/clap.cpp index 4d808cd1..946d7e0e 100644 --- a/src/wine-host/bridges/clap.cpp +++ b/src/wine-host/bridges/clap.cpp @@ -29,6 +29,8 @@ namespace fs = ghc::filesystem; ClapPluginExtensions::ClapPluginExtensions(const clap_plugin& plugin) noexcept : audio_ports(static_cast( plugin.get_extension(&plugin, CLAP_EXT_AUDIO_PORTS))), + gui(static_cast( + plugin.get_extension(&plugin, CLAP_EXT_GUI))), latency(static_cast( plugin.get_extension(&plugin, CLAP_EXT_LATENCY))), note_ports(static_cast( @@ -46,6 +48,7 @@ clap::plugin::SupportedPluginExtensions ClapPluginExtensions::supported() const noexcept { return clap::plugin::SupportedPluginExtensions{ .supports_audio_ports = audio_ports != nullptr, + .supports_gui = gui != nullptr, .supports_latency = latency != nullptr, .supports_note_ports = note_ports != nullptr, .supports_params = params != nullptr, @@ -348,6 +351,226 @@ void ClapBridge::run() { .result = std::nullopt}; } }, + [&](const clap::ext::gui::plugin::IsApiSupported& request) + -> clap::ext::gui::plugin::IsApiSupported::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + return main_context_ + .run_in_context([&, plugin = instance.plugin.get(), + gui = instance.extensions.gui]() { + // It's a bit unnecessary to bridge the entire + // `is_api_supported()` function since we'll only bridge + // a single config (non-floating, X11), but this is + // makes it easier to expand in the future. The X11 API + // type gets translated to WIN32 for the plugin. We also + // prematurely return false when `is_floating` is false + // because we cannot set the transient window correctly + // when the plugin opens its own Wine window. + switch (request.api) { + case clap::ext::gui::ApiType::X11: + return gui->is_api_supported( + plugin, CLAP_WINDOW_API_WIN32, + request.is_floating); + break; + } + }) + .get(); + }, + [&](const clap::ext::gui::plugin::Create& request) + -> clap::ext::gui::plugin::Create::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + return main_context_ + .run_in_context([&, plugin = instance.plugin.get(), + gui = instance.extensions.gui]() { + // We don't need to do anything here yet. The actual + // window is created at the final `.set_parent()` call. + // Like the above function, we'll translate the API type + // and `is_floating` will always be `false`. + switch (request.api) { + case clap::ext::gui::ApiType::X11: + return gui->create(plugin, + CLAP_WINDOW_API_WIN32, + request.is_floating); + break; + } + }) + .get(); + }, + [&](const clap::ext::gui::plugin::Destroy& request) + -> clap::ext::gui::plugin::Destroy::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + return main_context_ + .run_in_context([&, plugin = instance.plugin.get(), + gui = instance.extensions.gui, + &editor = instance.editor]() { + gui->destroy(plugin); + + // Cleanup is handled through RAII + editor.reset(); + + return Ack{}; + }) + .get(); + }, + [&](clap::ext::gui::plugin::SetScale& request) + -> clap::ext::gui::plugin::SetScale::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + return main_context_ + .run_in_context([&, plugin = instance.plugin.get(), + gui = instance.extensions.gui]() { + return gui->set_scale(plugin, request.scale); + }) + .get(); + }, + [&](const clap::ext::gui::plugin::GetSize& request) + -> clap::ext::gui::plugin::GetSize::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + return main_context_ + .run_in_context([&, plugin = instance.plugin.get(), + gui = instance.extensions.gui]() { + uint32_t width{}; + uint32_t height{}; + const bool result = + gui->get_size(plugin, &width, &height); + + return clap::ext::gui::plugin::GetSizeResponse{ + .result = result, .width = width, .height = height}; + }) + .get(); + }, + [&](clap::ext::gui::plugin::CanResize& request) + -> clap::ext::gui::plugin::CanResize::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + return main_context_ + .run_in_context([&, plugin = instance.plugin.get(), + gui = instance.extensions.gui]() { + return gui->can_resize(plugin); + }) + .get(); + }, + [&](const clap::ext::gui::plugin::GetResizeHints& request) + -> clap::ext::gui::plugin::GetResizeHints::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + return main_context_ + .run_in_context([&, plugin = instance.plugin.get(), + gui = instance.extensions.gui]() { + clap_gui_resize_hints_t hints{}; + if (gui->get_resize_hints(plugin, &hints)) { + return clap::ext::gui::plugin:: + GetResizeHintsResponse{.result = + std::move(hints)}; + } else { + return clap::ext::gui::plugin:: + GetResizeHintsResponse{.result = std::nullopt}; + } + }) + .get(); + }, + [&](const clap::ext::gui::plugin::AdjustSize& request) + -> clap::ext::gui::plugin::AdjustSize::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + return main_context_ + .run_in_context([&, plugin = instance.plugin.get(), + gui = instance.extensions.gui]() { + uint32_t width = request.width; + uint32_t height = request.height; + const bool result = + gui->adjust_size(plugin, &width, &height); + + return clap::ext::gui::plugin::AdjustSizeResponse{ + .result = result, + .updated_width = width, + .updated_height = height}; + }) + .get(); + }, + [&](const clap::ext::gui::plugin::SetSize& request) + -> clap::ext::gui::plugin::SetSize::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + return main_context_ + .run_in_context([&, plugin = instance.plugin.get(), + gui = instance.extensions.gui]() { + return gui->set_size(plugin, request.width, + request.height); + }) + .get(); + }, + [&](const clap::ext::gui::plugin::SetParent& request) + -> clap::ext::gui::plugin::SetParent::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + return main_context_ + .run_in_context([&, plugin = instance.plugin.get(), + gui = instance.extensions.gui, + &editor = instance.editor]() { + Editor& editor_instance = + editor.emplace(main_context_, config_, + generic_logger_, request.x11_window); + + const clap_window_t window{ + .api = CLAP_WINDOW_API_WIN32, + .win32 = editor_instance.get_win32_handle()}; + const bool result = gui->set_parent(plugin, &window); + + // Set the window's initial size according to what the + // plugin reports. Otherwise get rid of the editor again + // if the plugin didn't embed itself in it. + if (result) { + uint32_t width{}; + uint32_t height{}; + if (gui->get_size(plugin, &width, &height)) { + editor->resize(width, height); + } + + // NOTE: There's zero reason why the window couldn't + // already be visible from the start, but + // Waves V13 VST3 plugins think it would be a + // splendid idea to randomly dereference null + // pointers when the window is already + // visible. Thanks Waves. We'll do the same + // thing for CLAP plugins just to be safe + editor->show(); + } else { + editor.reset(); + } + + return result; + }) + .get(); + }, + [&](const clap::ext::gui::plugin::Show& request) + -> clap::ext::gui::plugin::Show::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + // We don't need any special handling for our editor window, but + // the plugin may use these functions to suspend drawing or stop + // other tasks while the window is hdden + return main_context_ + .run_in_context([&, plugin = instance.plugin.get(), + gui = instance.extensions.gui]() { + return gui->show(plugin); + }) + .get(); + }, + [&](const clap::ext::gui::plugin::Hide& request) + -> clap::ext::gui::plugin::Hide::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + return main_context_ + .run_in_context([&, plugin = instance.plugin.get(), + gui = instance.extensions.gui]() { + return gui->hide(plugin); + }) + .get(); + }, [&](clap::ext::latency::plugin::Get& request) -> clap::ext::latency::plugin::Get::Response { const auto& [instance, _] = get_instance(request.instance_id); diff --git a/src/wine-host/bridges/clap.h b/src/wine-host/bridges/clap.h index 3dbe8411..63d97759 100644 --- a/src/wine-host/bridges/clap.h +++ b/src/wine-host/bridges/clap.h @@ -67,6 +67,7 @@ struct ClapPluginExtensions { clap::plugin::SupportedPluginExtensions supported() const noexcept; const clap_plugin_audio_ports_t* audio_ports = nullptr; + const clap_plugin_gui_t* gui = nullptr; const clap_plugin_latency_t* latency = nullptr; const clap_plugin_note_ports_t* note_ports = nullptr; const clap_plugin_params_t* params = nullptr;