From 33b39626aa6021ddb4b393867fdb34e66299fe69 Mon Sep 17 00:00:00 2001 From: Boof2015 <75185879+Boof2015@users.noreply.github.com> Date: Wed, 3 Jun 2026 09:15:18 -0400 Subject: [PATCH] add logging --- plugin/CMakeLists.txt | 44 +++++- plugin/Source/PluginEditor.cpp | 273 +++++++++++++++++++++++++++++++-- plugin/Source/PluginEditor.h | 14 +- 3 files changed, 316 insertions(+), 15 deletions(-) diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt index 20017ab..eebe0f7 100644 --- a/plugin/CMakeLists.txt +++ b/plugin/CMakeLists.txt @@ -44,9 +44,42 @@ endif() set(PRISM_PLUGIN_FORMATS "${PRISM_DEFAULT_PLUGIN_FORMATS}" CACHE STRING "JUCE plugin formats to build") option(PRISM_COPY_PLUGIN_AFTER_BUILD "Copy built plugins into user plugin folders after build" ON) +set(PRISM_LINUX_UI_MODE "webview" CACHE STRING "Linux-only UI diagnostic mode: webview, web_smoke, native_smoke, or floating_webview") +set_property(CACHE PRISM_LINUX_UI_MODE PROPERTY STRINGS webview web_smoke native_smoke floating_webview) +set(PRISM_VALID_LINUX_UI_MODES webview web_smoke native_smoke floating_webview) + +if(UNIX AND NOT APPLE) + list(FIND PRISM_VALID_LINUX_UI_MODES "${PRISM_LINUX_UI_MODE}" PRISM_LINUX_UI_MODE_INDEX) + if(PRISM_LINUX_UI_MODE_INDEX EQUAL -1) + message(FATAL_ERROR "Invalid PRISM_LINUX_UI_MODE='${PRISM_LINUX_UI_MODE}'. Expected one of: ${PRISM_VALID_LINUX_UI_MODES}") + endif() +else() + if(NOT PRISM_LINUX_UI_MODE STREQUAL "webview") + message(FATAL_ERROR "PRISM_LINUX_UI_MODE is Linux-only; macOS/Windows builds must use 'webview'.") + endif() +endif() + +set(PRISM_NEEDS_WEBUI OFF) +if(NOT PRISM_DEV_SERVER) + if(NOT UNIX OR APPLE OR PRISM_LINUX_UI_MODE STREQUAL "webview" OR PRISM_LINUX_UI_MODE STREQUAL "floating_webview") + set(PRISM_NEEDS_WEBUI ON) + endif() +endif() + +if(PRISM_NEEDS_WEBUI) + set(PRISM_EMBED_WEBUI_DEFINITION PRISM_EMBED_WEBUI=1) +else() + set(PRISM_EMBED_WEBUI_DEFINITION PRISM_EMBED_WEBUI=0) +endif() + +string(TOUPPER "${PRISM_LINUX_UI_MODE}" PRISM_LINUX_UI_MODE_UPPER) +set(PRISM_LINUX_UI_MODE_DEFINITIONS + PRISM_LINUX_UI_MODE_NAME="${PRISM_LINUX_UI_MODE}" + PRISM_LINUX_UI_MODE_${PRISM_LINUX_UI_MODE_UPPER}=1) + # Embed the built webview bundle once; every scope plugin shares it (the C++ tells # the UI which scope to mount via initialisation data). -if(NOT PRISM_DEV_SERVER) +if(PRISM_NEEDS_WEBUI) set(PRISM_WEBUI_DIST ${CMAKE_CURRENT_SOURCE_DIR}/webview-dist) file(GLOB_RECURSE PRISM_WEBUI_FILES "${PRISM_WEBUI_DIST}/*") if(NOT PRISM_WEBUI_FILES) @@ -97,7 +130,9 @@ function(add_prism_scope TARGET PRODUCT PLUGIN_CODE SCOPE_DEFINE) target_compile_definitions(${TARGET} PRIVATE JUCE_WEB_BROWSER=1 # enables WebBrowserComponent (WKWebView on macOS) JUCE_USE_CURL=0 - JUCE_VST3_CAN_REPLACE_VST2=0) + JUCE_VST3_CAN_REPLACE_VST2=0 + ${PRISM_EMBED_WEBUI_DEFINITION} + ${PRISM_LINUX_UI_MODE_DEFINITIONS}) if(WIN32) # NEEDS_WEBVIEW2 links the WebView2 SDK but doesn't turn on the JUCE compile # paths that gate withResourceProvider. JUCE_USE_WIN_WEBVIEW2=1 enables the @@ -121,10 +156,13 @@ function(add_prism_scope TARGET PRODUCT PLUGIN_CODE SCOPE_DEFINE) if(PRISM_DEV_SERVER) target_compile_definitions(${TARGET} PRIVATE PRISM_USE_DEV_SERVER=1) else() - target_link_libraries(${TARGET} PRIVATE PrismWebUI) target_compile_definitions(${TARGET} PRIVATE PRISM_USE_DEV_SERVER=0) endif() + if(PRISM_NEEDS_WEBUI) + target_link_libraries(${TARGET} PRIVATE PrismWebUI) + endif() + target_link_libraries(${TARGET} PRIVATE juce::juce_audio_utils juce::juce_gui_extra diff --git a/plugin/Source/PluginEditor.cpp b/plugin/Source/PluginEditor.cpp index ab8fb03..f871d47 100644 --- a/plugin/Source/PluginEditor.cpp +++ b/plugin/Source/PluginEditor.cpp @@ -7,9 +7,18 @@ #include "SpectrogramEngine.h" #include "WaveformEngine.h" #include +#include #include -#if ! PRISM_USE_DEV_SERVER +#ifndef PRISM_EMBED_WEBUI + #define PRISM_EMBED_WEBUI 0 +#endif + +#ifndef PRISM_LINUX_UI_MODE_NAME + #define PRISM_LINUX_UI_MODE_NAME "webview" +#endif + +#if PRISM_EMBED_WEBUI #include "BinaryData.h" #endif @@ -41,6 +50,77 @@ namespace #if JUCE_LINUX constexpr int kLinuxFrameRateHz = 20; + + juce::String envValue(const char* name) + { + if (const auto* value = std::getenv(name); value != nullptr && value[0] != '\0') + return value; + + return ""; + } + + juce::File linuxDiagnosticLogFile() + { + juce::File baseDir; + + if (const auto* xdgState = std::getenv("XDG_STATE_HOME"); xdgState != nullptr && xdgState[0] != '\0') + baseDir = juce::File(juce::String(xdgState)); + else if (const auto* home = std::getenv("HOME"); home != nullptr && home[0] != '\0') + baseDir = juce::File(juce::String(home)).getChildFile(".local").getChildFile("state"); + else + baseDir = juce::File::getSpecialLocation(juce::File::userApplicationDataDirectory); + + return baseDir.getChildFile("prism").getChildFile("plugin-ui-diagnostics.log"); + } + + void logLinuxDiagnostic(const juce::String& message) + { + const auto file = linuxDiagnosticLogFile(); + file.getParentDirectory().createDirectory(); + file.appendText(juce::Time::getCurrentTime().toISO8601(true) + " " + message + "\n", + false, + false, + "\n"); + } + + juce::String wrapperTypeName() + { + switch (juce::PluginHostType::getPluginLoadedAs()) + { + case juce::AudioProcessor::wrapperType_VST3: return "VST3"; + case juce::AudioProcessor::wrapperType_VST: return "VST2"; + case juce::AudioProcessor::wrapperType_Standalone: return "Standalone"; + case juce::AudioProcessor::wrapperType_AudioUnit: return "AudioUnit"; + case juce::AudioProcessor::wrapperType_AudioUnitv3:return "AudioUnitv3"; + case juce::AudioProcessor::wrapperType_AAX: return "AAX"; + case juce::AudioProcessor::wrapperType_LV2: return "LV2"; + case juce::AudioProcessor::wrapperType_Unity: return "Unity"; + default: return "Undefined"; + } + } + + juce::String linuxHostDisplayContext() + { + const juce::PluginHostType host; + juce::String context; + context << "mode=" << PRISM_LINUX_UI_MODE_NAME + << " host=\"" << host.getHostDescription() << "\"" + << " wrapper=" << wrapperTypeName() + << " display=" << envValue("DISPLAY") + << " waylandDisplay=" << envValue("WAYLAND_DISPLAY") + << " sessionType=" << envValue("XDG_SESSION_TYPE") + << " gdkBackend=" << envValue("GDK_BACKEND") + << " desktop=" << envValue("XDG_CURRENT_DESKTOP"); + return context; + } + + juce::String describeBounds(const juce::Rectangle& bounds) + { + juce::String text; + text << bounds.getX() << "," << bounds.getY() + << " " << bounds.getWidth() << "x" << bounds.getHeight(); + return text; + } #endif std::unique_ptr makeEngine() @@ -64,7 +144,7 @@ namespace #if PRISM_USE_DEV_SERVER const juce::String kDevServerUrl { "http://localhost:5174" }; -#else +#elif PRISM_EMBED_WEBUI const char* getWebResourceData(const juce::String& name, int& dataSize) { dataSize = 0; @@ -140,6 +220,47 @@ namespace #endif #endif +#if JUCE_LINUX + juce::String makeWebSmokeUrl() + { + const juce::String html = + "" + "" + "" + "
Prism WebView smoke test\\nWaiting for JUCE bridge...
" + ""; + + return "data:text/html;charset=utf-8," + juce::URL::addEscapeChars(html, false); + } + + class FloatingWebViewWindow final : public juce::DocumentWindow + { + public: + FloatingWebViewWindow() + : juce::DocumentWindow("Prism WebView diagnostic", + juce::Colours::black, + juce::DocumentWindow::closeButton) + { + setUsingNativeTitleBar(true); + setResizable(true, true); + } + + void closeButtonPressed() override + { + logLinuxDiagnostic("floating_webview closeButtonPressed"); + setVisible(false); + } + }; +#endif + juce::WebBrowserComponent::Options makeWebOptions(PrismSpectrumEditor& editor, const char* scopeId) { auto options = juce::WebBrowserComponent::Options{} @@ -168,7 +289,7 @@ namespace juce::WebBrowserComponent::Options::WinWebView2{}.withUserDataFolder(webView2DataDir)); #endif -#if ! PRISM_USE_DEV_SERVER +#if PRISM_EMBED_WEBUI options = options.withResourceProvider( [](const auto& url) { return provideResource(url); }, getResourceProviderOrigin()); @@ -185,7 +306,13 @@ PrismSpectrumEditor::PrismSpectrumEditor(PrismSpectrumProcessor& p) drainLeft.assign((size_t) kDrainCapacity, 0.0f); drainRight.assign((size_t) kDrainCapacity, 0.0f); - loadWebView(); +#if JUCE_LINUX + logLinuxDiagnostic("editor constructed scope=" + juce::String(engine->scopeId()) + + " " + linuxHostDisplayContext() + + " logFile=" + linuxDiagnosticLogFile().getFullPathName()); +#endif + + loadUi(); // Per-scope sizing: open at the scope's preferred default, with a per-scope min. const auto pref = engine->preferredSize(); @@ -198,19 +325,50 @@ PrismSpectrumEditor::~PrismSpectrumEditor() { #if JUCE_WINDOWS || JUCE_LINUX stopTimer(); +#endif +#if JUCE_LINUX + logLinuxDiagnostic("editor destructed scope=" + juce::String(engine->scopeId()) + + " mode=" + juce::String(PRISM_LINUX_UI_MODE_NAME)); + floatingWebViewWindow.reset(); #endif webViewReady = false; } void PrismSpectrumEditor::resized() { - if (webView != nullptr) + if (webView != nullptr && webView->getParentComponent() == this) webView->setBounds(getLocalBounds()); webViewFallback.setBounds(getLocalBounds()); + +#if JUCE_LINUX + logLinuxDiagnostic("editor resized local=" + describeBounds(getLocalBounds()) + + " screen=" + describeBounds(getScreenBounds()) + + " mode=" + juce::String(PRISM_LINUX_UI_MODE_NAME)); +#endif } -void PrismSpectrumEditor::loadWebView() +void PrismSpectrumEditor::loadUi() +{ +#if JUCE_LINUX + logLinuxDiagnostic("loadUi " + linuxHostDisplayContext()); + + #if defined(PRISM_LINUX_UI_MODE_NATIVE_SMOKE) && PRISM_LINUX_UI_MODE_NATIVE_SMOKE + loadNativeSmoke(); + return; + #elif defined(PRISM_LINUX_UI_MODE_WEB_SMOKE) && PRISM_LINUX_UI_MODE_WEB_SMOKE + loadWebSmoke(); + return; + #elif defined(PRISM_LINUX_UI_MODE_FLOATING_WEBVIEW) && PRISM_LINUX_UI_MODE_FLOATING_WEBVIEW + loadFloatingWebView(); + return; + #endif +#endif + + loadEmbeddedWebView(); +} + +bool PrismSpectrumEditor::createWebView() { auto options = makeWebOptions(*this, engine->scopeId()); @@ -218,29 +376,118 @@ void PrismSpectrumEditor::loadWebView() if (! juce::WebBrowserComponent::areOptionsSupported(options)) { showWebViewFallback(); - return; + return false; } #endif +#if JUCE_LINUX + logLinuxDiagnostic("creating WebBrowserComponent mode=" + juce::String(PRISM_LINUX_UI_MODE_NAME)); +#endif + webView = std::make_unique(options); + return true; +} + +void PrismSpectrumEditor::loadEmbeddedWebView() +{ + if (! createWebView()) + return; + addAndMakeVisible(*webView); #if PRISM_USE_DEV_SERVER + #if JUCE_LINUX + logLinuxDiagnostic("navigating embedded WebView url=" + kDevServerUrl); + #endif webView->goToURL(kDevServerUrl); -#else - #if JUCE_LINUX +#elif PRISM_EMBED_WEBUI + #if JUCE_LINUX const auto indexFile = writeLinuxWebViewIndexFile(); if (indexFile.existsAsFile()) { - webView->goToURL(juce::URL(indexFile).toString(false)); + const auto url = juce::URL(indexFile).toString(false); + logLinuxDiagnostic("navigating embedded WebView url=" + url); + webView->goToURL(url); return; } - #endif + + logLinuxDiagnostic("navigating embedded WebView url=" + juce::WebBrowserComponent::getResourceProviderRoot()); + #endif webView->goToURL(juce::WebBrowserComponent::getResourceProviderRoot()); #endif } +#if JUCE_LINUX +void PrismSpectrumEditor::showLinuxDiagnosticPlaceholder(const juce::String& text) +{ + webViewFallback.setText(text, juce::dontSendNotification); + webViewFallback.setJustificationType(juce::Justification::centred); + webViewFallback.setColour(juce::Label::backgroundColourId, juce::Colours::black); + webViewFallback.setColour(juce::Label::textColourId, juce::Colour(0xffe5e7eb)); + webViewFallback.setMinimumHorizontalScale(0.7f); + addAndMakeVisible(webViewFallback); +} + +void PrismSpectrumEditor::loadNativeSmoke() +{ + logLinuxDiagnostic("native_smoke loaded"); + webViewReady = true; + showLinuxDiagnosticPlaceholder("Prism native smoke test\n" + "Root JUCE editor rendered without WebView\n" + "Mode: native_smoke"); +} + +void PrismSpectrumEditor::loadWebSmoke() +{ + if (! createWebView()) + return; + + addAndMakeVisible(*webView); + + const auto url = makeWebSmokeUrl(); + logLinuxDiagnostic("navigating web_smoke WebView url=data:text/html;charset=utf-8,"); + webView->goToURL(url); +} + +void PrismSpectrumEditor::loadFloatingWebView() +{ + showLinuxDiagnosticPlaceholder("Prism floating WebView diagnostic\n" + "Embedded native placeholder is visible\n" + "The real Prism WebView should open in a separate window"); + + if (! createWebView()) + return; + + floatingWebViewWindow = std::make_unique(); + floatingWebViewWindow->setContentNonOwned(webView.get(), false); + + const auto pref = engine->preferredSize(); + floatingWebViewWindow->setSize(pref.defaultWidth, pref.defaultHeight); + floatingWebViewWindow->centreWithSize(pref.defaultWidth, pref.defaultHeight); + floatingWebViewWindow->setVisible(true); + floatingWebViewWindow->toFront(true); + + #if PRISM_USE_DEV_SERVER + logLinuxDiagnostic("navigating floating WebView url=" + kDevServerUrl); + webView->goToURL(kDevServerUrl); + #elif PRISM_EMBED_WEBUI + const auto indexFile = writeLinuxWebViewIndexFile(); + + if (indexFile.existsAsFile()) + { + const auto url = juce::URL(indexFile).toString(false); + logLinuxDiagnostic("navigating floating WebView url=" + url); + webView->goToURL(url); + return; + } + + logLinuxDiagnostic("navigating floating WebView url=" + juce::WebBrowserComponent::getResourceProviderRoot()); + webView->goToURL(juce::WebBrowserComponent::getResourceProviderRoot()); + #endif +} +#endif + void PrismSpectrumEditor::showWebViewFallback() { webViewFallback.setText( @@ -307,6 +554,10 @@ void PrismSpectrumEditor::onPrismConfig(juce::var payload) void PrismSpectrumEditor::onPrismReady() { +#if JUCE_LINUX + logLinuxDiagnostic("prismReady received mode=" + juce::String(PRISM_LINUX_UI_MODE_NAME) + + " scope=" + juce::String(engine->scopeId())); +#endif webViewReady = true; pushRestoreSettings(); sendAppDefaults(); diff --git a/plugin/Source/PluginEditor.h b/plugin/Source/PluginEditor.h index aada306..fa4809c 100644 --- a/plugin/Source/PluginEditor.h +++ b/plugin/Source/PluginEditor.h @@ -50,11 +50,20 @@ public: void sendAppDefaults(); private: - void loadWebView(); + void loadUi(); + bool createWebView(); + void loadEmbeddedWebView(); void showWebViewFallback(); void startFrameDriver(); void renderFrame(); +#if JUCE_LINUX + void loadNativeSmoke(); + void loadWebSmoke(); + void loadFloatingWebView(); + void showLinuxDiagnosticPlaceholder(const juce::String& text); +#endif + PrismSpectrumProcessor& processorRef; std::unique_ptr engine; @@ -71,6 +80,9 @@ private: int uncapAttempts = 0; std::unique_ptr webView; +#if JUCE_LINUX + std::unique_ptr floatingWebViewWindow; +#endif juce::Label webViewFallback; // Declared last so it is destroyed first; no vblank callback can fire into a