From 248456fe6013a18b51d61ea5562b4daa5f3a6360 Mon Sep 17 00:00:00 2001 From: falkTX Date: Wed, 8 May 2024 20:19:26 +0200 Subject: [PATCH] Continue web-ui, start hooking plugin functions Signed-off-by: falkTX --- dgl/Web.hpp | 5 + dgl/src/Web.cpp | 14 +- distrho/DistrhoUI.hpp | 7 + distrho/extra/WebViewImpl.cpp | 40 +++--- distrho/extra/WebViewImpl.hpp | 17 ++- distrho/src/DistrhoUI.cpp | 125 +++++++++++++++--- examples/WebMeters/ExamplePluginWebMeters.cpp | 3 +- examples/WebMeters/ExampleUIWebMeters.cpp | 33 +---- examples/WebMeters/index.html | 21 ++- 9 files changed, 194 insertions(+), 71 deletions(-) diff --git a/dgl/Web.hpp b/dgl/Web.hpp index b366bf70..aaeab9ee 100644 --- a/dgl/Web.hpp +++ b/dgl/Web.hpp @@ -21,6 +21,7 @@ // -------------------------------------------------------------------------------------------------------------------- +// TODO private data START_NAMESPACE_DISTRHO struct WebViewData; @@ -53,12 +54,16 @@ public: void reload(); protected: + virtual void onMessage(char* message); void onResize(const ResizeEvent& ev) override; private: const DISTRHO_NAMESPACE::WebViewHandle webview; void onDisplay() override {} + // TODO inside private data + static void _on_msg(void*, char*); + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WebViewWidget) }; diff --git a/dgl/src/Web.cpp b/dgl/src/Web.cpp index d7a0dc38..4e8257fe 100644 --- a/dgl/src/Web.cpp +++ b/dgl/src/Web.cpp @@ -33,10 +33,9 @@ WebViewWidget::WebViewWidget(Window& windowToMapTo) webview(webViewCreate(windowToMapTo.getNativeWindowHandle(), windowToMapTo.getWidth(), windowToMapTo.getHeight(), - windowToMapTo.getScaleFactor())) + windowToMapTo.getScaleFactor(), + WebViewOptions(_on_msg, this))) { - // FIXME wait till process is here? - d_msleep(200); } WebViewWidget::~WebViewWidget() @@ -57,6 +56,15 @@ void WebViewWidget::reload() webViewReload(webview); } +void WebViewWidget::_on_msg(void* const arg, char* const message) +{ + static_cast(arg)->onMessage(message); +} + +void WebViewWidget::onMessage(char*) +{ +} + void WebViewWidget::onResize(const ResizeEvent& ev) { TopLevelWidget::onResize(ev); diff --git a/distrho/DistrhoUI.hpp b/distrho/DistrhoUI.hpp index f12e319d..def9e958 100644 --- a/distrho/DistrhoUI.hpp +++ b/distrho/DistrhoUI.hpp @@ -371,6 +371,13 @@ protected: void onResize(const ResizeEvent& ev) override; #endif + /* -------------------------------------------------------------------------------------------------------- + * WebView message handling, internal */ + + #if DISTRHO_UI_WEB_VIEW + void onMessage(char* message) override; + #endif + // ------------------------------------------------------------------------------------------------------- private: diff --git a/distrho/extra/WebViewImpl.cpp b/distrho/extra/WebViewImpl.cpp index f708439d..b8904624 100644 --- a/distrho/extra/WebViewImpl.cpp +++ b/distrho/extra/WebViewImpl.cpp @@ -82,13 +82,15 @@ #define WEB_VIEW_DELEGATE_CLASS_NAME \ MACRO_NAME(WebViewDelegate_, _, DISTRHO_NAMESPACE) -// FIXME -static bool loaded = false; - @interface WEB_VIEW_DELEGATE_CLASS_NAME : NSObject @end -@implementation WEB_VIEW_DELEGATE_CLASS_NAME +@implementation WEB_VIEW_DELEGATE_CLASS_NAME { +@public + WebViewMessageCallback callback; + void* callbackPtr; + bool loaded; +} - (void)webView:(WKWebView *)webview didFinishNavigation:(WKNavigation*)navigation @@ -195,9 +197,13 @@ static bool loaded = false; didReceiveScriptMessage:(WKScriptMessage*)message { NSString* const nsstring = static_cast([message body]); - const char* const string = [nsstring UTF8String]; + char* const string = strdup([nsstring UTF8String]); + d_debug("JS call received '%s' %p", string, callback); + + if (callback != nullptr) + callback(callbackPtr, string); - d_stdout("JS call received '%s'", string); + std::free(string); } @end @@ -216,6 +222,7 @@ START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------------------------------------------- struct WebViewData { + const WebViewMessageCallback callback; #if WEB_VIEW_USING_CHOC choc::ui::WebView* const webview; #elif WEB_VIEW_USING_MACOS_WEBKIT @@ -358,7 +365,7 @@ WebViewHandle webViewCreate(const uintptr_t windowId, ShowWindow(hwnd, SW_SHOW); #endif - return new WebViewData{webview.release()}; + return new WebViewData{options.callback, webview.release()}; #elif WEB_VIEW_USING_MACOS_WEBKIT NSView* const view = reinterpret_cast(windowId); @@ -389,6 +396,10 @@ WebViewHandle webViewCreate(const uintptr_t windowId, // TODO webkit_web_view_set_background_color WEB_VIEW_DELEGATE_CLASS_NAME* const delegate = [[WEB_VIEW_DELEGATE_CLASS_NAME alloc] init]; + delegate->callback = options.callback; + delegate->callbackPtr = options.callbackPtr; + delegate->loaded = false; + webview.navigationDelegate = delegate; webview.UIDelegate = delegate; @@ -410,13 +421,13 @@ WebViewHandle webViewCreate(const uintptr_t windowId, d_stdout("waiting for load"); - if (! loaded) + if (! delegate->loaded) { NSAutoreleasePool* const pool = [[NSAutoreleasePool alloc] init]; - NSDate* const date = [NSDate distantPast]; + NSDate* const date = [NSDate distantFuture]; NSEvent* event; - while (! loaded) + while (! delegate->loaded) { event = [NSApp #ifdef __MAC_10_12 @@ -437,13 +448,15 @@ WebViewHandle webViewCreate(const uintptr_t windowId, [pool release]; } + d_stdout("waiting done"); + [webview setHidden:NO]; [nsurl release]; [config release]; [prefs release]; - return new WebViewData{view, webview, urlreq, delegate}; + return new WebViewData{options.callback, view, webview, urlreq, delegate}; #elif WEB_VIEW_USING_X11_IPC char ldlinux[PATH_MAX] = {}; getFilenameFromFunctionPtr(ldlinux, dlsym(nullptr, "_rtld_global")); @@ -482,10 +495,7 @@ WebViewHandle webViewCreate(const uintptr_t windowId, envp[e++] = nullptr; } - WebViewData* const handle = new WebViewData(); - handle->display = display; - handle->childWindow = 0; - handle->ourWindow = windowId; + WebViewData* const handle = new WebViewData{options.callback, {}, display, 0, windowId}; const char* const args[] = { ldlinux, filename, "dpf-ld-linux-webview", nullptr }; handle->p.start(args, envp); diff --git a/distrho/extra/WebViewImpl.hpp b/distrho/extra/WebViewImpl.hpp index 268e9009..00c5e94e 100644 --- a/distrho/extra/WebViewImpl.hpp +++ b/distrho/extra/WebViewImpl.hpp @@ -23,6 +23,7 @@ struct WebViewData; typedef WebViewData* WebViewHandle; +typedef void (*WebViewMessageCallback)(void* arg, char* msg); // -------------------------------------------------------------------------------------------------------------------- @@ -44,9 +45,23 @@ struct WebViewOptions { PositionOffset() : x(0), y(0) {} } offset; + /** + Message callback triggered from JavaScript code inside the WebView. + */ + WebViewMessageCallback callback; + void* callbackPtr; + /** Constructor for default values */ WebViewOptions() - : offset() {} + : offset(), + callback(nullptr), + callbackPtr(nullptr) {} + + /** Constructor providing a callback */ + WebViewOptions(const WebViewMessageCallback cb, void* const ptr) + : offset(), + callback(cb), + callbackPtr(ptr) {} }; // -------------------------------------------------------------------------------------------------------------------- diff --git a/distrho/src/DistrhoUI.cpp b/distrho/src/DistrhoUI.cpp index c768ac51..eb851f8e 100644 --- a/distrho/src/DistrhoUI.cpp +++ b/distrho/src/DistrhoUI.cpp @@ -264,6 +264,18 @@ UI::UI(const uint width, const uint height, const bool automaticallyScaleAndSetA // unused (void)automaticallyScaleAndSetAsMinimumSize; #endif + + #if DISTRHO_UI_WEB_VIEW + evaluateJS("\ +function editParameter(index, started){ window.webkit.messageHandlers.external.postMessage('editparam ' + index + ' ' + (started ? 1 : 0)) }\ +function setParameterValue(index, value){ window.webkit.messageHandlers.external.postMessage('setparam ' + index + ' ' + value) }\ +"); + #if DISTRHO_PLUGIN_WANT_STATE + evaluateJS("\ +function setState(key, value){ window.webkit.messageHandlers.external.postMessage('setstate ' + key + ' ' + value) }\ +"); + #endif + #endif } UI::~UI() @@ -381,37 +393,57 @@ uintptr_t UI::getNextWindowId() noexcept void UI::parameterChanged(const uint32_t index, const float value) { -#if DISTRHO_UI_WEB_VIEW - evaluateJS("typeof(parameterChanged) === 'function' && parameterChanged(0, 0);"); -#else + #if DISTRHO_UI_WEB_VIEW + char msg[128]; + { + const ScopedSafeLocale ssl; + std::snprintf(msg, sizeof(msg) - 1, + "typeof(parameterChanged) === 'function' && parameterChanged(%u,%f)", index, value); + } + evaluateJS(msg); + #else // unused (void)index; (void)value; -#endif + #endif } #if DISTRHO_PLUGIN_WANT_PROGRAMS void UI::programLoaded(const uint32_t index) { -#if DISTRHO_UI_WEB_VIEW - evaluateJS("typeof(programLoaded) === 'function' && programLoaded(0);"); -#else + #if DISTRHO_UI_WEB_VIEW + char msg[128]; + std::snprintf(msg, sizeof(msg) - 1, + "typeof(programLoaded) === 'function' && programLoaded(%u)", index); + evaluateJS(msg); + #else // unused (void)index; -#endif + #endif } #endif #if DISTRHO_PLUGIN_WANT_STATE void UI::stateChanged(const char* const key, const char* const value) { -#if DISTRHO_UI_WEB_VIEW - evaluateJS("typeof(stateChanged) === 'function' && stateChanged('', '');"); -#else + #if DISTRHO_UI_WEB_VIEW + const size_t keylen = std::strlen(key); + const size_t valuelen = std::strlen(value); + const size_t msglen = keylen + valuelen + 60; + if (char* const msg = static_cast(std::malloc(msglen))) + { + // TODO escape \\' + std::snprintf(msg, sizeof(msglen) - 1, + "typeof(stateChanged) === 'function' && stateChanged('%s','%s')", key, value); + msg[msglen - 1] = '\0'; + evaluateJS(msg); + std::free(msg); + } + #else // unused (void)key; (void)value; -#endif + #endif } #endif @@ -420,12 +452,18 @@ void UI::stateChanged(const char* const key, const char* const value) void UI::sampleRateChanged(const double sampleRate) { -#if DISTRHO_UI_WEB_VIEW - evaluateJS("typeof(sampleRateChanged) === 'function' && sampleRateChanged(0);"); -#else + #if DISTRHO_UI_WEB_VIEW + char msg[128]; + { + const ScopedSafeLocale ssl; + std::snprintf(msg, sizeof(msg) - 1, + "typeof(sampleRateChanged) === 'function' && sampleRateChanged(%f)", sampleRate); + } + evaluateJS(msg); + #else // unused (void)sampleRate; -#endif + #endif } /* ------------------------------------------------------------------------------------------------------------ @@ -515,4 +553,59 @@ void UI::requestSizeChange(const uint width, const uint height) // ----------------------------------------------------------------------------------------------------------- +#if DISTRHO_UI_WEB_VIEW +void UI::onMessage(char* const message) +{ + if (std::strncmp(message, "setparam ", 9) == 0) + { + char* const strindex = message + 9; + char* const sep = std::strchr(strindex, ' '); + DISTRHO_SAFE_ASSERT_RETURN(sep != nullptr,); + *sep = 0; + char* const strvalue = sep + 1; + + const uint32_t index = std::atoi(strindex); + float value; + { + const ScopedSafeLocale ssl; + value = std::atof(strvalue); + } + setParameterValue(index, value); + return; + } + + if (std::strncmp(message, "editparam ", 10) == 0) + { + char* const strindex = message + 10; + char* const sep = std::strchr(strindex, ' '); + DISTRHO_SAFE_ASSERT_RETURN(sep != nullptr,); + *sep = 0; + char* const strstarted = sep + 1; + + const uint32_t index = std::atoi(strindex); + const bool started = std::atoi(strstarted) != 0; + editParameter(index, started); + return; + } + + #if DISTRHO_PLUGIN_WANT_STATE + if (std::strncmp(message, "setstate ", 9) == 0) + { + char* const key = message + 9; + char* const sep = std::strchr(key, ' '); + DISTRHO_SAFE_ASSERT_RETURN(sep != nullptr,); + *sep = 0; + char* const value = sep + 1; + + setState(key, value); + return; + } + #endif + + d_stderr("UI received unknown message %s", message); +} +#endif + +// ----------------------------------------------------------------------------------------------------------- + END_NAMESPACE_DISTRHO diff --git a/examples/WebMeters/ExamplePluginWebMeters.cpp b/examples/WebMeters/ExamplePluginWebMeters.cpp index b91a461a..0b188017 100644 --- a/examples/WebMeters/ExamplePluginWebMeters.cpp +++ b/examples/WebMeters/ExamplePluginWebMeters.cpp @@ -237,8 +237,7 @@ protected: { fOutLeft = tmpLeft; fOutRight = tmpRight; - // TODO - // fNeedsReset = false; + fNeedsReset = false; } else { diff --git a/examples/WebMeters/ExampleUIWebMeters.cpp b/examples/WebMeters/ExampleUIWebMeters.cpp index 807471b7..1cf0a70b 100644 --- a/examples/WebMeters/ExampleUIWebMeters.cpp +++ b/examples/WebMeters/ExampleUIWebMeters.cpp @@ -24,41 +24,10 @@ class ExampleUIMeters : public UI { public: ExampleUIMeters() - : UI(600, 400, true) + : UI(100, 500, true) { } -protected: - /* -------------------------------------------------------------------------------------------------------- - * DSP/Plugin Callbacks */ - - /** - A parameter has changed on the plugin side. - This is called by the host to inform the UI about parameter changes. - */ - void parameterChanged(uint32_t index, float value) override - { - // d_stdout("param changed %u %f", index, value); - char msg[512]; - { - const ScopedSafeLocale ssl; - std::snprintf(msg, sizeof(msg) - 1, - "typeof(parameterChanged) === 'function' && parameterChanged(%u, %f)", index, value); - } - evaluateJS(msg); - } - - /** - A state has changed on the plugin side. - This is called by the host to inform the UI about state changes. - */ - void stateChanged(const char*, const char*) override - { - // nothing here - } - - // ------------------------------------------------------------------------------------------------------- - private: /** Set our UI class as non-copyable and add a leak detector just in case. diff --git a/examples/WebMeters/index.html b/examples/WebMeters/index.html index 956e0ab4..c657cec3 100644 --- a/examples/WebMeters/index.html +++ b/examples/WebMeters/index.html @@ -14,6 +14,20 @@ setTimeout(function() { document.getElementById('user-agent').textContent = window.navigator.userAgent; + document.getElementById('left-meter').onclick = function() { + console.log("left meter clicked"); + fColorValue = fColorValue == 1 ? 0 : 1; + updateColor(fColorValue, true); + setParameterValue(0, fColorValue); + repaint(); + } + document.getElementById('right-meter').onclick = function() { + console.log("right meter clicked"); + fColorValue = fColorValue == 1 ? 0 : 1; + updateColor(fColorValue, true); + setParameterValue(0, fColorValue); + repaint(); + } }, 1) function repaint() { @@ -21,9 +35,12 @@ const rmeter = document.getElementById('right-meter-x'); lmeter.setAttribute('style', 'background:' + fColor + ';top:' + (100 * (1.0 - fOutLeft)) + '%;height:' + (100 * fOutLeft) + '%'); rmeter.setAttribute('style', 'background:' + fColor + ';top:' + (100 * (1.0 - fOutRight)) + '%;height:' + (100 * fOutRight) + '%'); + setTimeout(function() { + setState('reset', ''); + }, 1) } - function updateColor(color) { - if (fColorValue === color) + function updateColor(color, forced) { + if (fColorValue === color && !forced) return; fColorValue = color;