Signed-off-by: falkTX <falktx@falktx.com>undefined
| @@ -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) | |||
| }; | |||
| @@ -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<WebViewWidget*>(arg)->onMessage(message); | |||
| } | |||
| void WebViewWidget::onMessage(char*) | |||
| { | |||
| } | |||
| void WebViewWidget::onResize(const ResizeEvent& ev) | |||
| { | |||
| TopLevelWidget::onResize(ev); | |||
| @@ -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: | |||
| @@ -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<WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate> | |||
| @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<NSString*>([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<NSView*>(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); | |||
| @@ -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) {} | |||
| }; | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -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<char*>(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 | |||
| @@ -237,8 +237,7 @@ protected: | |||
| { | |||
| fOutLeft = tmpLeft; | |||
| fOutRight = tmpRight; | |||
| // TODO | |||
| // fNeedsReset = false; | |||
| fNeedsReset = false; | |||
| } | |||
| else | |||
| { | |||
| @@ -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. | |||
| @@ -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; | |||