diff DPF-Prymula-audioplugins/dpf/distrho/src/DistrhoUIVST3.cpp @ 3:84e66ea83026

DPF-Prymula-audioplugins-0.231015-2
author prymula <prymula76@outlook.com>
date Mon, 16 Oct 2023 21:53:34 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DPF-Prymula-audioplugins/dpf/distrho/src/DistrhoUIVST3.cpp	Mon Oct 16 21:53:34 2023 +0200
@@ -0,0 +1,1644 @@
+/*
+ * DISTRHO Plugin Framework (DPF)
+ * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any purpose with
+ * or without fee is hereby granted, provided that the above copyright notice and this
+ * permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+ * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "DistrhoUIInternal.hpp"
+
+#include "travesty/base.h"
+#include "travesty/edit_controller.h"
+#include "travesty/host.h"
+#include "travesty/view.h"
+
+#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+# if defined(DISTRHO_OS_MAC)
+#  include <CoreFoundation/CoreFoundation.h>
+# elif defined(DISTRHO_OS_WINDOWS)
+#  include <winuser.h>
+#  define DPF_VST3_WIN32_TIMER_ID 1
+# endif
+#endif
+
+/* TODO items:
+ * - mousewheel event
+ * - file request?
+ */
+
+#if !(defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS))
+# define DPF_VST3_USING_HOST_RUN_LOOP 1
+#else
+# define DPF_VST3_USING_HOST_RUN_LOOP 0
+#endif
+
+#ifndef DPF_VST3_TIMER_INTERVAL
+# define DPF_VST3_TIMER_INTERVAL 16 /* ~60 fps */
+#endif
+
+START_NAMESPACE_DISTRHO
+
+// --------------------------------------------------------------------------------------------------------------------
+
+#if ! DISTRHO_PLUGIN_WANT_STATE
+static constexpr const setStateFunc setStateCallback = nullptr;
+#endif
+#if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT
+static constexpr const sendNoteFunc sendNoteCallback = nullptr;
+#endif
+
+// --------------------------------------------------------------------------------------------------------------------
+// Static data, see DistrhoPlugin.cpp
+
+extern const char* d_nextBundlePath;
+
+// --------------------------------------------------------------------------------------------------------------------
+// Utility functions (defined on plugin side)
+
+const char* tuid2str(const v3_tuid iid);
+
+// --------------------------------------------------------------------------------------------------------------------
+
+static void applyGeometryConstraints(const uint minimumWidth,
+                                     const uint minimumHeight,
+                                     const bool keepAspectRatio,
+                                     v3_view_rect* const rect)
+{
+    d_debug("applyGeometryConstraints %u %u %d {%d,%d,%d,%d} | BEFORE",
+            minimumWidth, minimumHeight, keepAspectRatio, rect->top, rect->left, rect->right, rect->bottom);
+    const int32_t minWidth = static_cast<int32_t>(minimumWidth);
+    const int32_t minHeight = static_cast<int32_t>(minimumHeight);
+
+    if (keepAspectRatio)
+    {
+        if (rect->right < 1)
+            rect->right = 1;
+        if (rect->bottom < 1)
+            rect->bottom = 1;
+
+        const double ratio = static_cast<double>(minWidth) / static_cast<double>(minHeight);
+        const double reqRatio = static_cast<double>(rect->right) / static_cast<double>(rect->bottom);
+
+        if (d_isNotEqual(ratio, reqRatio))
+        {
+            // fix width
+            if (reqRatio > ratio)
+                rect->right = static_cast<int32_t>(rect->bottom * ratio + 0.5);
+            // fix height
+            else
+                rect->bottom = static_cast<int32_t>(static_cast<double>(rect->right) / ratio + 0.5);
+        }
+    }
+
+    if (minWidth > rect->right)
+        rect->right = minWidth;
+    if (minHeight > rect->bottom)
+        rect->bottom = minHeight;
+
+    d_debug("applyGeometryConstraints %u %u %d {%d,%d,%d,%d} | AFTER",
+            minimumWidth, minimumHeight, keepAspectRatio, rect->top, rect->left, rect->right, rect->bottom);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+static uint translateVST3Modifiers(const int64_t modifiers) noexcept
+{
+    using namespace DGL_NAMESPACE;
+
+    uint dglmods = 0;
+    if (modifiers & (1 << 0))
+        dglmods |= kModifierShift;
+    if (modifiers & (1 << 1))
+        dglmods |= kModifierAlt;
+   #ifdef DISTRHO_OS_MAC
+    if (modifiers & (1 << 2))
+        dglmods |= kModifierSuper;
+    if (modifiers & (1 << 3))
+        dglmods |= kModifierControl;
+   #else
+    if (modifiers & (1 << 2))
+        dglmods |= kModifierControl;
+    if (modifiers & (1 << 3))
+        dglmods |= kModifierSuper;
+   #endif
+
+    return dglmods;
+}
+#endif
+
+// --------------------------------------------------------------------------------------------------------------------
+
+#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !DPF_VST3_USING_HOST_RUN_LOOP
+/**
+ * Helper class for getting a native idle timer via native APIs.
+ */
+class NativeIdleHelper
+{
+public:
+    NativeIdleHelper(IdleCallback* const callback)
+        : fCallback(callback),
+       #ifdef DISTRHO_OS_MAC
+          fTimerRef(nullptr)
+       #else
+          fTimerWindow(nullptr),
+          fTimerWindowClassName()
+       #endif
+    {
+    }
+
+    void registerNativeIdleCallback()
+    {
+       #ifdef DISTRHO_OS_MAC
+        constexpr const CFTimeInterval interval = DPF_VST3_TIMER_INTERVAL * 0.0001;
+
+        CFRunLoopTimerContext context = {};
+        context.info = this;
+        fTimerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + interval, interval, 0, 0,
+                                         platformIdleTimerCallback, &context);
+        DISTRHO_SAFE_ASSERT_RETURN(fTimerRef != nullptr,);
+
+        CFRunLoopAddTimer(CFRunLoopGetCurrent(), fTimerRef, kCFRunLoopCommonModes);
+       #else
+        /* 
+         * Create an invisible window to handle a timer.
+         * There is no need for implementing a window proc because DefWindowProc already calls the
+         * callback function when processing WM_TIMER messages.
+         */
+        fTimerWindowClassName = (
+           #ifdef DISTRHO_PLUGIN_BRAND
+            DISTRHO_PLUGIN_BRAND
+           #else
+            DISTRHO_MACRO_AS_STRING(DISTRHO_NAMESPACE)
+           #endif
+            "-" DISTRHO_PLUGIN_NAME "-"
+        );
+
+        char suffix[9];
+        std::snprintf(suffix, sizeof(suffix), "%08x", std::rand());
+        suffix[sizeof(suffix)-1] = '\0';
+        fTimerWindowClassName += suffix;
+
+        WNDCLASSEX cls;
+        ZeroMemory(&cls, sizeof(cls));
+        cls.cbSize = sizeof(WNDCLASSEX);
+        cls.cbWndExtra = sizeof(LONG_PTR);
+        cls.lpszClassName = fTimerWindowClassName.buffer();
+        cls.lpfnWndProc = DefWindowProc;
+        RegisterClassEx(&cls);
+
+        fTimerWindow = CreateWindowEx(0, cls.lpszClassName, "DPF Timer Helper",
+                                      0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, nullptr, nullptr);
+        DISTRHO_SAFE_ASSERT_RETURN(fTimerWindow != nullptr,);
+
+        SetWindowLongPtr(fTimerWindow, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(static_cast<void*>(this)));
+        SetTimer(fTimerWindow, DPF_VST3_WIN32_TIMER_ID, DPF_VST3_TIMER_INTERVAL,
+                 static_cast<TIMERPROC>(platformIdleTimerCallback));
+       #endif
+    }
+
+    void unregisterNativeIdleCallback()
+    {
+       #ifdef DISTRHO_OS_MAC
+        CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), fTimerRef, kCFRunLoopCommonModes);
+        CFRelease(fTimerRef);
+       #else
+        DISTRHO_SAFE_ASSERT_RETURN(fTimerWindow != nullptr,);
+        KillTimer(fTimerWindow, DPF_VST3_WIN32_TIMER_ID);
+        DestroyWindow(fTimerWindow);
+        UnregisterClass(fTimerWindowClassName, nullptr);
+       #endif
+    }
+
+private:
+    IdleCallback* const fCallback;
+
+   #ifdef DISTRHO_OS_MAC
+    CFRunLoopTimerRef fTimerRef;
+
+    static void platformIdleTimerCallback(CFRunLoopTimerRef, void* const info)
+    {
+        static_cast<NativeIdleHelper*>(info)->fCallback->idleCallback();
+    }
+   #else
+    HWND fTimerWindow;
+    String fTimerWindowClassName;
+
+    static void WINAPI platformIdleTimerCallback(const HWND hwnd, UINT, UINT_PTR, DWORD)
+    {
+        reinterpret_cast<NativeIdleHelper*>(GetWindowLongPtr(hwnd, GWLP_USERDATA))->fCallback->idleCallback();
+    }
+   #endif
+};
+#endif
+
+/**
+ * Helper class for getting a native idle timer, either through pugl or via native APIs.
+ */
+#if !DPF_VST3_USING_HOST_RUN_LOOP
+class NativeIdleCallback : public IdleCallback
+{
+public:
+    NativeIdleCallback(UIExporter& ui)
+        : fCallbackRegistered(false),
+         #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+          fIdleHelper(this)
+         #else
+          fUI(ui)
+         #endif
+    {
+       #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+        // unused
+        (void)ui;
+       #endif
+    }
+
+    void registerNativeIdleCallback()
+    {
+        DISTRHO_SAFE_ASSERT_RETURN(!fCallbackRegistered,);
+        fCallbackRegistered = true;
+
+       #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+        fIdleHelper.registerNativeIdleCallback();
+       #else
+        fUI.addIdleCallbackForNativeIdle(this, DPF_VST3_TIMER_INTERVAL);
+       #endif
+    }
+
+    void unregisterNativeIdleCallback()
+    {
+        DISTRHO_SAFE_ASSERT_RETURN(fCallbackRegistered,);
+        fCallbackRegistered = false;
+
+       #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+        fIdleHelper.unregisterNativeIdleCallback();
+       #else
+        fUI.removeIdleCallbackForNativeIdle(this);
+       #endif
+    }
+
+private:
+    bool fCallbackRegistered;
+   #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+    NativeIdleHelper fIdleHelper;
+   #else
+    UIExporter& fUI;
+   #endif
+};
+#endif
+
+// --------------------------------------------------------------------------------------------------------------------
+
+/**
+ * VST3 UI class.
+ *
+ * All the dynamic things from VST3 get implemented here, free of complex low-level VST3 pointer things.
+ * The UI is created during the "attach" view event, and destroyed during "removed".
+ *
+ * The low-level VST3 stuff comes after.
+ */
+class UIVst3
+#if !DPF_VST3_USING_HOST_RUN_LOOP
+  : public NativeIdleCallback
+#endif
+{
+public:
+    UIVst3(v3_plugin_view** const view,
+           v3_host_application** const host,
+           v3_connection_point** const connection,
+           v3_plugin_frame** const frame,
+           const intptr_t winId,
+           const float scaleFactor,
+           const double sampleRate,
+           void* const instancePointer,
+           const bool willResizeFromHost,
+           const bool needsResizeFromPlugin)
+        :
+#if !DPF_VST3_USING_HOST_RUN_LOOP
+          NativeIdleCallback(fUI),
+#endif
+          fView(view),
+          fHostApplication(host),
+          fConnection(connection),
+          fFrame(frame),
+          fScaleFactor(scaleFactor),
+          fReadyForPluginData(false),
+          fIsResizingFromPlugin(false),
+          fIsResizingFromHost(willResizeFromHost),
+          fNeedsResizeFromPlugin(needsResizeFromPlugin),
+          fNextPluginRect(),
+          fUI(this, winId, sampleRate,
+              editParameterCallback,
+              setParameterCallback,
+              setStateCallback,
+              sendNoteCallback,
+              setSizeCallback,
+              nullptr, // TODO file request
+              d_nextBundlePath,
+              instancePointer,
+              scaleFactor)
+    {
+    }
+
+    ~UIVst3()
+    {
+       #if !DPF_VST3_USING_HOST_RUN_LOOP
+        unregisterNativeIdleCallback();
+       #endif
+
+        if (fConnection != nullptr)
+            disconnect();
+    }
+
+    void postInit(uint32_t nextWidth, uint32_t nextHeight)
+    {
+        if (fIsResizingFromHost && nextWidth > 0 && nextHeight > 0)
+        {
+           #ifdef DISTRHO_OS_MAC
+            const double scaleFactor = fUI.getScaleFactor();
+            nextWidth *= scaleFactor;
+            nextHeight *= scaleFactor;
+           #endif
+
+            if (fUI.getWidth() != nextWidth || fUI.getHeight() != nextHeight)
+            {
+                d_debug("postInit sets new size as %u %u", nextWidth, nextHeight);
+                fUI.setWindowSizeFromHost(nextWidth, nextHeight);
+            }
+        }
+        else if (fNeedsResizeFromPlugin)
+        {
+            d_debug("postInit forcely sets size from plugin as %u %u", fUI.getWidth(), fUI.getHeight());
+            setSize(fUI.getWidth(), fUI.getHeight());
+        }
+
+        if (fConnection != nullptr)
+            connect(fConnection);
+
+       #if !DPF_VST3_USING_HOST_RUN_LOOP
+        registerNativeIdleCallback();
+       #endif
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // v3_plugin_view interface calls
+
+#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+    v3_result onWheel(float /*distance*/)
+    {
+        // TODO
+        return V3_NOT_IMPLEMENTED;
+    }
+
+    v3_result onKeyDown(const int16_t keychar, const int16_t keycode, const int16_t modifiers)
+    {
+        DISTRHO_SAFE_ASSERT_INT_RETURN(keychar >= 0 && keychar < 0x7f, keychar, V3_FALSE);
+
+        bool special;
+        const uint key = translateVstKeyCode(special, keychar, keycode);
+        d_debug("onKeyDown %d %d %x -> %d %d", keychar, keycode, modifiers, special, key);
+
+        return fUI.handlePluginKeyboardVST(true, special, key,
+                                           keycode >= 0 ? static_cast<uint>(keycode) : 0,
+                                           translateVST3Modifiers(modifiers)) ? V3_TRUE : V3_FALSE;
+    }
+
+    v3_result onKeyUp(const int16_t keychar, const int16_t keycode, const int16_t modifiers)
+    {
+        DISTRHO_SAFE_ASSERT_INT_RETURN(keychar >= 0 && keychar < 0x7f, keychar, V3_FALSE);
+
+        bool special;
+        const uint key = translateVstKeyCode(special, keychar, keycode);
+        d_debug("onKeyUp %d %d %x -> %d %d", keychar, keycode, modifiers, special, key);
+
+        return fUI.handlePluginKeyboardVST(false, special, key,
+                                           keycode >= 0 ? static_cast<uint>(keycode) : 0,
+                                           translateVST3Modifiers(modifiers)) ? V3_TRUE : V3_FALSE;
+    }
+
+    v3_result onFocus(const bool state)
+    {
+        if (state)
+            fUI.focus();
+        fUI.notifyFocusChanged(state);
+        return V3_OK;
+    }
+#endif
+
+    v3_result getSize(v3_view_rect* const rect) const noexcept
+    {
+        if (fIsResizingFromPlugin)
+        {
+            *rect = fNextPluginRect;
+        }
+        else
+        {
+            rect->left = rect->top = 0;
+            rect->right = fUI.getWidth();
+            rect->bottom = fUI.getHeight();
+           #ifdef DISTRHO_OS_MAC
+            const double scaleFactor = fUI.getScaleFactor();
+            rect->right /= scaleFactor;
+            rect->bottom /= scaleFactor;
+           #endif
+        }
+
+        d_debug("getSize request returning %i %i", rect->right, rect->bottom);
+        return V3_OK;
+    }
+
+    v3_result onSize(v3_view_rect* const orect)
+    {
+        v3_view_rect rect = *orect;
+
+       #ifdef DISTRHO_OS_MAC
+        const double scaleFactor = fUI.getScaleFactor();
+        rect.top *= scaleFactor;
+        rect.left *= scaleFactor;
+        rect.right *= scaleFactor;
+        rect.bottom *= scaleFactor;
+       #endif
+
+        if (fIsResizingFromPlugin)
+        {
+            d_debug("host->plugin onSize request %i %i (plugin resize was active, unsetting now)",
+                    rect.right - rect.left, rect.bottom - rect.top);
+            fIsResizingFromPlugin = false;
+        }
+        else
+        {
+            d_debug("host->plugin onSize request %i %i (OK)", rect.right - rect.left, rect.bottom - rect.top);
+        }
+
+        fIsResizingFromHost = true;
+        fUI.setWindowSizeFromHost(rect.right - rect.left, rect.bottom - rect.top);
+        return V3_OK;
+    }
+
+    v3_result setFrame(v3_plugin_frame** const frame) noexcept
+    {
+        fFrame = frame;
+        return V3_OK;
+    }
+
+    v3_result canResize() noexcept
+    {
+        return fUI.isResizable() ? V3_TRUE : V3_FALSE;
+    }
+
+    v3_result checkSizeConstraint(v3_view_rect* const rect)
+    {
+        uint minimumWidth, minimumHeight;
+        bool keepAspectRatio;
+        fUI.getGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio);
+
+       #ifdef DISTRHO_OS_MAC
+        const double scaleFactor = fUI.getScaleFactor();
+        minimumWidth /= scaleFactor;
+        minimumHeight /= scaleFactor;
+       #endif
+
+        applyGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio, rect);
+        return V3_TRUE;
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // v3_connection_point interface calls
+
+    void connect(v3_connection_point** const point) noexcept
+    {
+        DISTRHO_SAFE_ASSERT_RETURN(point != nullptr,);
+
+        fConnection = point;
+
+        d_debug("requesting current plugin state");
+
+        v3_message** const message = createMessage("init");
+        DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,);
+
+        v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message);
+        DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,);
+
+        v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1);
+        v3_cpp_obj(fConnection)->notify(fConnection, message);
+
+        v3_cpp_obj_unref(message);
+    }
+
+    void disconnect() noexcept
+    {
+        DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,);
+
+        d_debug("reporting UI closed");
+        fReadyForPluginData = false;
+
+        v3_message** const message = createMessage("close");
+        DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,);
+
+        v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message);
+        DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,);
+
+        v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1);
+        v3_cpp_obj(fConnection)->notify(fConnection, message);
+
+        v3_cpp_obj_unref(message);
+
+        fConnection = nullptr;
+    }
+
+    v3_result notify(v3_message** const message)
+    {
+        const char* const msgid = v3_cpp_obj(message)->get_message_id(message);
+        DISTRHO_SAFE_ASSERT_RETURN(msgid != nullptr, V3_INVALID_ARG);
+
+        v3_attribute_list** const attrs = v3_cpp_obj(message)->get_attributes(message);
+        DISTRHO_SAFE_ASSERT_RETURN(attrs != nullptr, V3_INVALID_ARG);
+
+        if (std::strcmp(msgid, "ready") == 0)
+        {
+            DISTRHO_SAFE_ASSERT_RETURN(! fReadyForPluginData, V3_INTERNAL_ERR);
+            fReadyForPluginData = true;
+            return V3_OK;
+        }
+
+        if (std::strcmp(msgid, "parameter-set") == 0)
+        {
+            int64_t rindex;
+            double value;
+            v3_result res;
+
+            res = v3_cpp_obj(attrs)->get_int(attrs, "rindex", &rindex);
+            DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res);
+
+            res = v3_cpp_obj(attrs)->get_float(attrs, "value", &value);
+            DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res);
+
+            if (rindex < kVst3InternalParameterBaseCount)
+            {
+                switch (rindex)
+                {
+               #if DPF_VST3_USES_SEPARATE_CONTROLLER
+                case kVst3InternalParameterSampleRate:
+                    DISTRHO_SAFE_ASSERT_RETURN(value >= 0.0, V3_INVALID_ARG);
+                    fUI.setSampleRate(value, true);
+                    break;
+               #endif
+               #if DISTRHO_PLUGIN_WANT_PROGRAMS
+                case kVst3InternalParameterProgram:
+                    DISTRHO_SAFE_ASSERT_RETURN(value >= 0.0, V3_INVALID_ARG);
+                    fUI.programLoaded(static_cast<uint32_t>(value + 0.5));
+                    break;
+               #endif
+                }
+
+                // others like latency and buffer-size do not matter on UI side
+                return V3_OK;
+            }
+
+            DISTRHO_SAFE_ASSERT_UINT2_RETURN(rindex >= kVst3InternalParameterCount, rindex, kVst3InternalParameterCount, V3_INVALID_ARG);
+            const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterCount);
+
+            fUI.parameterChanged(index, value);
+            return V3_OK;
+        }
+
+       #if DISTRHO_PLUGIN_WANT_STATE
+        if (std::strcmp(msgid, "state-set") == 0)
+        {
+            int64_t keyLength = -1;
+            int64_t valueLength = -1;
+            v3_result res;
+
+            res = v3_cpp_obj(attrs)->get_int(attrs, "key:length", &keyLength);
+            DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res);
+            DISTRHO_SAFE_ASSERT_INT_RETURN(keyLength >= 0, keyLength, V3_INTERNAL_ERR);
+
+            res = v3_cpp_obj(attrs)->get_int(attrs, "value:length", &valueLength);
+            DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res);
+            DISTRHO_SAFE_ASSERT_INT_RETURN(valueLength >= 0, valueLength, V3_INTERNAL_ERR);
+
+            int16_t* const key16 = (int16_t*)std::malloc(sizeof(int16_t)*(keyLength + 1));
+            DISTRHO_SAFE_ASSERT_RETURN(key16 != nullptr, V3_NOMEM);
+
+            int16_t* const value16 = (int16_t*)std::malloc(sizeof(int16_t)*(valueLength + 1));
+            DISTRHO_SAFE_ASSERT_RETURN(value16 != nullptr, V3_NOMEM);
+
+            res = v3_cpp_obj(attrs)->get_string(attrs, "key", key16, sizeof(int16_t)*(keyLength+1));
+            DISTRHO_SAFE_ASSERT_INT2_RETURN(res == V3_OK, res, keyLength, res);
+
+            if (valueLength != 0)
+            {
+                res = v3_cpp_obj(attrs)->get_string(attrs, "value", value16, sizeof(int16_t)*(valueLength+1));
+                DISTRHO_SAFE_ASSERT_INT2_RETURN(res == V3_OK, res, valueLength, res);
+            }
+
+            // do cheap inline conversion
+            char* const key = (char*)key16;
+            char* const value = (char*)value16;
+
+            for (int64_t i=0; i<keyLength; ++i)
+                key[i] = key16[i];
+            for (int64_t i=0; i<valueLength; ++i)
+                value[i] = value16[i];
+
+            key[keyLength] = '\0';
+            value[valueLength] = '\0';
+
+            fUI.stateChanged(key, value);
+
+            std::free(key16);
+            std::free(value16);
+            return V3_OK;
+        }
+       #endif
+
+        d_stderr("UIVst3 received unknown msg '%s'", msgid);
+
+        return V3_NOT_IMPLEMENTED;
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // v3_plugin_view_content_scale_steinberg interface calls
+
+    v3_result setContentScaleFactor(const float factor)
+    {
+        if (d_isEqual(fScaleFactor, factor))
+            return V3_OK;
+
+        fScaleFactor = factor;
+        fUI.notifyScaleFactorChanged(factor);
+        return V3_OK;
+    }
+
+   #if DPF_VST3_USING_HOST_RUN_LOOP
+    // ----------------------------------------------------------------------------------------------------------------
+    // v3_timer_handler interface calls
+
+    void onTimer()
+    {
+        fUI.plugin_idle();
+        doIdleStuff();
+    }
+   #else
+    // ----------------------------------------------------------------------------------------------------------------
+    // special idle callback without v3_timer_handler
+
+    void idleCallback() override
+    {
+        fUI.idleFromNativeIdle();
+        doIdleStuff();
+    }
+   #endif
+
+    void doIdleStuff()
+    {
+        if (fReadyForPluginData)
+        {
+            fReadyForPluginData = false;
+            requestMorePluginData();
+        }
+
+        if (fNeedsResizeFromPlugin)
+        {
+            fNeedsResizeFromPlugin = false;
+            d_debug("first resize forced behaviour is now stopped");
+        }
+
+        if (fIsResizingFromHost)
+        {
+            fIsResizingFromHost = false;
+            d_debug("was resizing from host, now stopped");
+        }
+
+        if (fIsResizingFromPlugin)
+        {
+            fIsResizingFromPlugin = false;
+            d_debug("was resizing from plugin, now stopped");
+        }
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+
+private:
+    // VST3 stuff
+    v3_plugin_view** const fView;
+    v3_host_application** const fHostApplication;
+    v3_connection_point** fConnection;
+    v3_plugin_frame** fFrame;
+
+    // Temporary data
+    float fScaleFactor;
+    bool fReadyForPluginData;
+    bool fIsResizingFromPlugin;
+    bool fIsResizingFromHost;
+    bool fNeedsResizeFromPlugin;
+    v3_view_rect fNextPluginRect; // for when plugin requests a new size
+
+    // Plugin UI (after VST3 stuff so the UI can call into us during its constructor)
+    UIExporter fUI;
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // helper functions called during message passing
+
+    v3_message** createMessage(const char* const id) const
+    {
+        DISTRHO_SAFE_ASSERT_RETURN(fHostApplication != nullptr, nullptr);
+
+        v3_tuid iid;
+        std::memcpy(iid, v3_message_iid, sizeof(v3_tuid));
+        v3_message** msg = nullptr;
+        const v3_result res = v3_cpp_obj(fHostApplication)->create_instance(fHostApplication, iid, iid, (void**)&msg);
+        DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_TRUE, res, nullptr);
+        DISTRHO_SAFE_ASSERT_RETURN(msg != nullptr, nullptr);
+
+        v3_cpp_obj(msg)->set_message_id(msg, id);
+        return msg;
+    }
+
+    void requestMorePluginData() const
+    {
+        DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,);
+
+        v3_message** const message = createMessage("idle");
+        DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,);
+
+        v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message);
+        DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,);
+
+        v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1);
+        v3_cpp_obj(fConnection)->notify(fConnection, message);
+
+        v3_cpp_obj_unref(message);
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // DPF callbacks
+
+    void editParameter(const uint32_t rindex, const bool started) const
+    {
+        DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,);
+
+        v3_message** const message = createMessage("parameter-edit");
+        DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,);
+
+        v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message);
+        DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,);
+
+        v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1);
+        v3_cpp_obj(attrlist)->set_int(attrlist, "rindex", rindex);
+        v3_cpp_obj(attrlist)->set_int(attrlist, "started", started ? 1 : 0);
+        v3_cpp_obj(fConnection)->notify(fConnection, message);
+
+        v3_cpp_obj_unref(message);
+    }
+
+    static void editParameterCallback(void* const ptr, const uint32_t rindex, const bool started)
+    {
+        static_cast<UIVst3*>(ptr)->editParameter(rindex, started);
+    }
+
+    void setParameterValue(const uint32_t rindex, const float realValue)
+    {
+        DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,);
+
+        v3_message** const message = createMessage("parameter-set");
+        DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,);
+
+        v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message);
+        DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,);
+
+        v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1);
+        v3_cpp_obj(attrlist)->set_int(attrlist, "rindex", rindex);
+        v3_cpp_obj(attrlist)->set_float(attrlist, "value", realValue);
+        v3_cpp_obj(fConnection)->notify(fConnection, message);
+
+        v3_cpp_obj_unref(message);
+    }
+
+    static void setParameterCallback(void* const ptr, const uint32_t rindex, const float value)
+    {
+        static_cast<UIVst3*>(ptr)->setParameterValue(rindex, value);
+    }
+
+   #if DISTRHO_PLUGIN_WANT_STATE
+    void setState(const char* const key, const char* const value)
+    {
+        DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,);
+
+        v3_message** const message = createMessage("state-set");
+        DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,);
+
+        v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message);
+        DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,);
+
+        v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1);
+        v3_cpp_obj(attrlist)->set_int(attrlist, "key:length", std::strlen(key));
+        v3_cpp_obj(attrlist)->set_int(attrlist, "value:length", std::strlen(value));
+        v3_cpp_obj(attrlist)->set_string(attrlist, "key", ScopedUTF16String(key));
+        v3_cpp_obj(attrlist)->set_string(attrlist, "value", ScopedUTF16String(value));
+        v3_cpp_obj(fConnection)->notify(fConnection, message);
+
+        v3_cpp_obj_unref(message);
+    }
+
+    static void setStateCallback(void* const ptr, const char* const key, const char* const value)
+    {
+        static_cast<UIVst3*>(ptr)->setState(key, value);
+    }
+   #endif
+
+   #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+    void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity)
+    {
+        DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,);
+
+        v3_message** const message = createMessage("midi");
+        DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,);
+
+        v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message);
+        DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,);
+
+        uint8_t midiData[3];
+        midiData[0] = (velocity != 0 ? 0x90 : 0x80) | channel;
+        midiData[1] = note;
+        midiData[2] = velocity;
+
+        v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1);
+        v3_cpp_obj(attrlist)->set_binary(attrlist, "data", midiData, sizeof(midiData));
+        v3_cpp_obj(fConnection)->notify(fConnection, message);
+
+        v3_cpp_obj_unref(message);
+    }
+
+    static void sendNoteCallback(void* const ptr, const uint8_t channel, const uint8_t note, const uint8_t velocity)
+    {
+        static_cast<UIVst3*>(ptr)->sendNote(channel, note, velocity);
+    }
+   #endif
+
+    void setSize(uint width, uint height)
+    {
+        DISTRHO_SAFE_ASSERT_RETURN(fView != nullptr,);
+        DISTRHO_SAFE_ASSERT_RETURN(fFrame != nullptr,);
+
+       #ifdef DISTRHO_OS_MAC
+        const double scaleFactor = fUI.getScaleFactor();
+        width /= scaleFactor;
+        height /= scaleFactor;
+       #endif
+
+        if (fIsResizingFromHost)
+        {
+            if (fNeedsResizeFromPlugin)
+            {
+                d_debug("plugin->host setSize %u %u (FORCED, exception for first resize)", width, height);
+            }
+            else
+            {
+                d_debug("plugin->host setSize %u %u (IGNORED, host resize active)", width, height);
+                return;
+            }
+        }
+        else
+        {
+            d_debug("plugin->host setSize %u %u (OK)", width, height);
+        }
+
+        fIsResizingFromPlugin = true;
+
+        v3_view_rect rect;
+        rect.left = rect.top = 0;
+        rect.right = width;
+        rect.bottom = height;
+        fNextPluginRect = rect;
+        v3_cpp_obj(fFrame)->resize_view(fFrame, fView, &rect);
+    }
+
+    static void setSizeCallback(void* const ptr, const uint width, const uint height)
+    {
+        static_cast<UIVst3*>(ptr)->setSize(width, height);
+    }
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+
+/**
+ * VST3 low-level pointer thingies follow, proceed with care.
+ */
+
+// --------------------------------------------------------------------------------------------------------------------
+// v3_funknown for classes with a single instance
+
+template<class T>
+static uint32_t V3_API dpf_single_instance_ref(void* const self)
+{
+    return ++(*static_cast<T**>(self))->refcounter;
+}
+
+template<class T>
+static uint32_t V3_API dpf_single_instance_unref(void* const self)
+{
+    return --(*static_cast<T**>(self))->refcounter;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// dpf_ui_connection_point
+
+struct dpf_ui_connection_point : v3_connection_point_cpp {
+    std::atomic_int refcounter;
+    ScopedPointer<UIVst3>& uivst3;
+    v3_connection_point** other;
+
+    dpf_ui_connection_point(ScopedPointer<UIVst3>& v)
+        : refcounter(1),
+          uivst3(v),
+          other(nullptr)
+    {
+        // v3_funknown, single instance
+        query_interface = query_interface_connection_point;
+        ref = dpf_single_instance_ref<dpf_ui_connection_point>;
+        unref = dpf_single_instance_unref<dpf_ui_connection_point>;
+
+        // v3_connection_point
+        point.connect = connect;
+        point.disconnect = disconnect;
+        point.notify = notify;
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // v3_funknown
+
+    static v3_result V3_API query_interface_connection_point(void* const self, const v3_tuid iid, void** const iface)
+    {
+        dpf_ui_connection_point* const point = *static_cast<dpf_ui_connection_point**>(self);
+
+        if (v3_tuid_match(iid, v3_funknown_iid) ||
+            v3_tuid_match(iid, v3_connection_point_iid))
+        {
+            d_debug("UI|query_interface_connection_point => %p %s %p | OK", self, tuid2str(iid), iface);
+            ++point->refcounter;
+            *iface = self;
+            return V3_OK;
+        }
+
+        d_debug("DSP|query_interface_connection_point => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface);
+
+        *iface = NULL;
+        return V3_NO_INTERFACE;
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // v3_connection_point
+
+    static v3_result V3_API connect(void* const self, v3_connection_point** const other)
+    {
+        dpf_ui_connection_point* const point = *static_cast<dpf_ui_connection_point**>(self);
+        d_debug("UI|dpf_ui_connection_point::connect => %p %p", self, other);
+
+        DISTRHO_SAFE_ASSERT_RETURN(point->other == nullptr, V3_INVALID_ARG);
+
+        point->other = other;
+
+        if (UIVst3* const uivst3 = point->uivst3)
+            uivst3->connect(other);
+
+        return V3_OK;
+    };
+
+    static v3_result V3_API disconnect(void* const self, v3_connection_point** const other)
+    {
+        d_debug("UI|dpf_ui_connection_point::disconnect => %p %p", self, other);
+        dpf_ui_connection_point* const point = *static_cast<dpf_ui_connection_point**>(self);
+
+        DISTRHO_SAFE_ASSERT_RETURN(point->other != nullptr, V3_INVALID_ARG);
+        DISTRHO_SAFE_ASSERT(point->other == other);
+
+        point->other = nullptr;
+
+        if (UIVst3* const uivst3 = point->uivst3)
+            uivst3->disconnect();
+
+        return V3_OK;
+    };
+
+    static v3_result V3_API notify(void* const self, v3_message** const message)
+    {
+        dpf_ui_connection_point* const point = *static_cast<dpf_ui_connection_point**>(self);
+
+        UIVst3* const uivst3 = point->uivst3;
+        DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED);
+
+        return uivst3->notify(message);
+    }
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// dpf_plugin_view_content_scale
+
+struct dpf_plugin_view_content_scale : v3_plugin_view_content_scale_cpp {
+    std::atomic_int refcounter;
+    ScopedPointer<UIVst3>& uivst3;
+    // cached values
+    float scaleFactor;
+
+    dpf_plugin_view_content_scale(ScopedPointer<UIVst3>& v)
+        : refcounter(1),
+          uivst3(v),
+          scaleFactor(0.0f)
+    {
+        // v3_funknown, single instance
+        query_interface = query_interface_view_content_scale;
+        ref = dpf_single_instance_ref<dpf_plugin_view_content_scale>;
+        unref = dpf_single_instance_unref<dpf_plugin_view_content_scale>;
+
+        // v3_plugin_view_content_scale
+        scale.set_content_scale_factor = set_content_scale_factor;
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // v3_funknown
+
+    static v3_result V3_API query_interface_view_content_scale(void* const self, const v3_tuid iid, void** const iface)
+    {
+        dpf_plugin_view_content_scale* const scale = *static_cast<dpf_plugin_view_content_scale**>(self);
+
+        if (v3_tuid_match(iid, v3_funknown_iid) ||
+            v3_tuid_match(iid, v3_plugin_view_content_scale_iid))
+        {
+            d_debug("query_interface_view_content_scale => %p %s %p | OK", self, tuid2str(iid), iface);
+            ++scale->refcounter;
+            *iface = self;
+            return V3_OK;
+        }
+
+        d_debug("query_interface_view_content_scale => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface);
+
+        *iface = NULL;
+        return V3_NO_INTERFACE;
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // v3_plugin_view_content_scale
+
+    static v3_result V3_API set_content_scale_factor(void* const self, const float factor)
+    {
+        dpf_plugin_view_content_scale* const scale = *static_cast<dpf_plugin_view_content_scale**>(self);
+        d_debug("dpf_plugin_view::set_content_scale_factor => %p %f", self, factor);
+
+        scale->scaleFactor = factor;
+
+        if (UIVst3* const uivst3 = scale->uivst3)
+            return uivst3->setContentScaleFactor(factor);
+
+        return V3_NOT_INITIALIZED;
+    }
+};
+
+#if DPF_VST3_USING_HOST_RUN_LOOP
+// --------------------------------------------------------------------------------------------------------------------
+// dpf_timer_handler
+
+struct dpf_timer_handler : v3_timer_handler_cpp {
+    std::atomic_int refcounter;
+    ScopedPointer<UIVst3>& uivst3;
+    bool valid;
+
+    dpf_timer_handler(ScopedPointer<UIVst3>& v)
+        : refcounter(1),
+          uivst3(v),
+          valid(true)
+    {
+        // v3_funknown, single instance
+        query_interface = query_interface_timer_handler;
+        ref = dpf_single_instance_ref<dpf_timer_handler>;
+        unref = dpf_single_instance_unref<dpf_timer_handler>;
+
+        // v3_timer_handler
+        timer.on_timer = on_timer;
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // v3_funknown
+
+    static v3_result V3_API query_interface_timer_handler(void* self, const v3_tuid iid, void** iface)
+    {
+        dpf_timer_handler* const timer = *static_cast<dpf_timer_handler**>(self);
+
+        if (v3_tuid_match(iid, v3_funknown_iid) ||
+            v3_tuid_match(iid, v3_timer_handler_iid))
+        {
+            d_debug("query_interface_timer_handler => %p %s %p | OK", self, tuid2str(iid), iface);
+            ++timer->refcounter;
+            *iface = self;
+            return V3_OK;
+        }
+
+        d_debug("query_interface_timer_handler => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface);
+
+        *iface = NULL;
+        return V3_NO_INTERFACE;
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // v3_timer_handler
+
+    static void V3_API on_timer(void* self)
+    {
+        dpf_timer_handler* const timer = *static_cast<dpf_timer_handler**>(self);
+
+        DISTRHO_SAFE_ASSERT_RETURN(timer->valid,);
+
+        timer->uivst3->onTimer();
+    }
+};
+#endif
+
+// --------------------------------------------------------------------------------------------------------------------
+// dpf_plugin_view
+
+static const char* const kSupportedPlatforms[] = {
+#if defined(DISTRHO_OS_WINDOWS)
+    V3_VIEW_PLATFORM_TYPE_HWND,
+#elif defined(DISTRHO_OS_MAC)
+    V3_VIEW_PLATFORM_TYPE_NSVIEW,
+#else
+    V3_VIEW_PLATFORM_TYPE_X11,
+#endif
+};
+
+struct dpf_plugin_view : v3_plugin_view_cpp {
+    std::atomic_int refcounter;
+    ScopedPointer<dpf_ui_connection_point> connection;
+    ScopedPointer<dpf_plugin_view_content_scale> scale;
+   #if DPF_VST3_USING_HOST_RUN_LOOP
+    ScopedPointer<dpf_timer_handler> timer;
+   #endif
+    ScopedPointer<UIVst3> uivst3;
+    // cached values
+    v3_host_application** const hostApplication;
+    void* const instancePointer;
+    double sampleRate;
+    v3_plugin_frame** frame;
+    v3_run_loop** runloop;
+    uint32_t nextWidth, nextHeight;
+    bool sizeRequestedBeforeBeingAttached;
+
+    dpf_plugin_view(v3_host_application** const host, void* const instance, const double sr)
+        : refcounter(1),
+          hostApplication(host),
+          instancePointer(instance),
+          sampleRate(sr),
+          frame(nullptr),
+          runloop(nullptr),
+          nextWidth(0),
+          nextHeight(0),
+          sizeRequestedBeforeBeingAttached(false)
+    {
+        d_debug("dpf_plugin_view() with hostApplication %p", hostApplication);
+
+        // make sure host application is valid through out this view lifetime
+        if (hostApplication != nullptr)
+            v3_cpp_obj_ref(hostApplication);
+
+        // v3_funknown, everything custom
+        query_interface = query_interface_view;
+        ref = ref_view;
+        unref = unref_view;
+
+        // v3_plugin_view
+        view.is_platform_type_supported = is_platform_type_supported;
+        view.attached = attached;
+        view.removed = removed;
+        view.on_wheel = on_wheel;
+        view.on_key_down = on_key_down;
+        view.on_key_up = on_key_up;
+        view.get_size = get_size;
+        view.on_size = on_size;
+        view.on_focus = on_focus;
+        view.set_frame = set_frame;
+        view.can_resize = can_resize;
+        view.check_size_constraint = check_size_constraint;
+    }
+
+    ~dpf_plugin_view()
+    {
+        d_debug("~dpf_plugin_view()");
+
+        connection = nullptr;
+        scale = nullptr;
+       #if DPF_VST3_USING_HOST_RUN_LOOP
+        timer = nullptr;
+       #endif
+        uivst3 = nullptr;
+
+        if (hostApplication != nullptr)
+            v3_cpp_obj_unref(hostApplication);
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // v3_funknown
+
+    static v3_result V3_API query_interface_view(void* self, const v3_tuid iid, void** iface)
+    {
+        dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self);
+
+        if (v3_tuid_match(iid, v3_funknown_iid) ||
+            v3_tuid_match(iid, v3_plugin_view_iid))
+        {
+            d_debug("query_interface_view => %p %s %p | OK", self, tuid2str(iid), iface);
+            ++view->refcounter;
+            *iface = self;
+            return V3_OK;
+        }
+
+        if (v3_tuid_match(v3_connection_point_iid, iid))
+        {
+            d_debug("query_interface_view => %p %s %p | OK convert %p",
+                    self, tuid2str(iid), iface, view->connection.get());
+
+            if (view->connection == nullptr)
+                view->connection = new dpf_ui_connection_point(view->uivst3);
+            else
+                ++view->connection->refcounter;
+            *iface = &view->connection;
+            return V3_OK;
+        }
+
+       #ifndef DISTRHO_OS_MAC
+        if (v3_tuid_match(v3_plugin_view_content_scale_iid, iid))
+        {
+            d_debug("query_interface_view => %p %s %p | OK convert %p",
+                    self, tuid2str(iid), iface, view->scale.get());
+
+            if (view->scale == nullptr)
+                view->scale = new dpf_plugin_view_content_scale(view->uivst3);
+            else
+                ++view->scale->refcounter;
+            *iface = &view->scale;
+            return V3_OK;
+        }
+       #endif
+
+        d_debug("query_interface_view => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface);
+
+        *iface = nullptr;
+        return V3_NO_INTERFACE;
+    }
+
+    static uint32_t V3_API ref_view(void* self)
+    {
+        dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self);
+        const int refcount = ++view->refcounter;
+        d_debug("dpf_plugin_view::ref => %p | refcount %i", self, refcount);
+        return refcount;
+    }
+
+    static uint32_t V3_API unref_view(void* self)
+    {
+        dpf_plugin_view** const viewptr = static_cast<dpf_plugin_view**>(self);
+        dpf_plugin_view* const view = *viewptr;
+
+        if (const int refcount = --view->refcounter)
+        {
+            d_debug("dpf_plugin_view::unref => %p | refcount %i", self, refcount);
+            return refcount;
+        }
+
+        if (view->connection != nullptr && view->connection->other)
+            v3_cpp_obj(view->connection->other)->disconnect(view->connection->other,
+                                                            (v3_connection_point**)&view->connection);
+
+        /**
+         * Some hosts will have unclean instances of a few of the view child classes at this point.
+         * We check for those here, going through the whole possible chain to see if it is safe to delete.
+         * TODO cleanup.
+         */
+
+        bool unclean = false;
+
+        if (dpf_ui_connection_point* const conn = view->connection)
+        {
+            if (const int refcount = conn->refcounter)
+            {
+                unclean = true;
+                d_stderr("DPF warning: asked to delete view while connection point still active (refcount %d)", refcount);
+            }
+        }
+
+       #ifndef DISTRHO_OS_MAC
+        if (dpf_plugin_view_content_scale* const scale = view->scale)
+        {
+            if (const int refcount = scale->refcounter)
+            {
+                unclean = true;
+                d_stderr("DPF warning: asked to delete view while content scale still active (refcount %d)", refcount);
+            }
+        }
+       #endif
+
+        if (unclean)
+            return 0;
+
+        d_debug("dpf_plugin_view::unref => %p | refcount is zero, deleting everything now!", self);
+
+        delete view;
+        delete viewptr;
+        return 0;
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // v3_plugin_view
+
+    static v3_result V3_API is_platform_type_supported(void* const self, const char* const platform_type)
+    {
+        d_debug("dpf_plugin_view::is_platform_type_supported => %p %s", self, platform_type);
+
+        for (size_t i=0; i<ARRAY_SIZE(kSupportedPlatforms); ++i)
+        {
+            if (std::strcmp(kSupportedPlatforms[i], platform_type) == 0)
+                return V3_OK;
+        }
+
+        return V3_NOT_IMPLEMENTED;
+
+        // unused unless debug
+        (void)self;
+    }
+
+    static v3_result V3_API attached(void* const self, void* const parent, const char* const platform_type)
+    {
+        d_debug("dpf_plugin_view::attached => %p %p %s", self, parent, platform_type);
+        dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self);
+        DISTRHO_SAFE_ASSERT_RETURN(view->uivst3 == nullptr, V3_INVALID_ARG);
+
+        for (size_t i=0; i<ARRAY_SIZE(kSupportedPlatforms); ++i)
+        {
+            if (std::strcmp(kSupportedPlatforms[i], platform_type) == 0)
+            {
+               #if DPF_VST3_USING_HOST_RUN_LOOP
+                // find host run loop to plug ourselves into (required on some systems)
+                DISTRHO_SAFE_ASSERT_RETURN(view->frame != nullptr, V3_INVALID_ARG);
+
+                v3_run_loop** runloop = nullptr;
+                v3_cpp_obj_query_interface(view->frame, v3_run_loop_iid, &runloop);
+                DISTRHO_SAFE_ASSERT_RETURN(runloop != nullptr, V3_INVALID_ARG);
+
+                view->runloop = runloop;
+               #endif
+
+                const float lastScaleFactor = view->scale != nullptr ? view->scale->scaleFactor : 0.0f;
+                view->uivst3 = new UIVst3((v3_plugin_view**)self,
+                                          view->hostApplication,
+                                          view->connection != nullptr ? view->connection->other : nullptr,
+                                          view->frame,
+                                          (uintptr_t)parent,
+                                          lastScaleFactor,
+                                          view->sampleRate,
+                                          view->instancePointer,
+                                          view->nextWidth > 0 && view->nextHeight > 0,
+                                          view->sizeRequestedBeforeBeingAttached);
+
+                view->uivst3->postInit(view->nextWidth, view->nextHeight);
+                view->nextWidth = 0;
+                view->nextHeight = 0;
+                view->sizeRequestedBeforeBeingAttached = false;
+
+               #if DPF_VST3_USING_HOST_RUN_LOOP
+                // register a timer host run loop stuff
+                view->timer = new dpf_timer_handler(view->uivst3);
+                v3_cpp_obj(runloop)->register_timer(runloop,
+                                                    (v3_timer_handler**)&view->timer,
+                                                    DPF_VST3_TIMER_INTERVAL);
+               #endif
+
+                return V3_OK;
+            }
+        }
+
+        return V3_NOT_IMPLEMENTED;
+    }
+
+    static v3_result V3_API removed(void* const self)
+    {
+        d_debug("dpf_plugin_view::removed => %p", self);
+        dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self);
+        DISTRHO_SAFE_ASSERT_RETURN(view->uivst3 != nullptr, V3_INVALID_ARG);
+
+       #if DPF_VST3_USING_HOST_RUN_LOOP
+        // unregister our timer as needed
+        if (v3_run_loop** const runloop = view->runloop)
+        {
+            if (view->timer != nullptr && view->timer->valid)
+            {
+                v3_cpp_obj(runloop)->unregister_timer(runloop, (v3_timer_handler**)&view->timer);
+
+                if (const int refcount = --view->timer->refcounter)
+                {
+                    view->timer->valid = false;
+                    d_stderr("VST3 warning: Host run loop did not give away timer (refcount %d)", refcount);
+                }
+                else
+                {
+                    view->timer = nullptr;
+                }
+            }
+
+            v3_cpp_obj_unref(runloop);
+            view->runloop = nullptr;
+        }
+       #endif
+
+        view->uivst3 = nullptr;
+        return V3_OK;
+    }
+
+    static v3_result V3_API on_wheel(void* const self, const float distance)
+    {
+#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+        d_debug("dpf_plugin_view::on_wheel => %p %f", self, distance);
+        dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self);
+
+        UIVst3* const uivst3 = view->uivst3;
+        DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED);
+
+        return uivst3->onWheel(distance);
+#else
+        return V3_NOT_IMPLEMENTED;
+        // unused
+        (void)self; (void)distance;
+#endif
+    }
+
+    static v3_result V3_API on_key_down(void* const self, const int16_t key_char, const int16_t key_code, const int16_t modifiers)
+    {
+#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+        d_debug("dpf_plugin_view::on_key_down => %p %i %i %i", self, key_char, key_code, modifiers);
+        dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self);
+
+        UIVst3* const uivst3 = view->uivst3;
+        DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED);
+
+        return uivst3->onKeyDown(key_char, key_code, modifiers);
+#else
+        return V3_NOT_IMPLEMENTED;
+        // unused
+        (void)self; (void)key_char; (void)key_code; (void)modifiers;
+#endif
+    }
+
+    static v3_result V3_API on_key_up(void* const self, const int16_t key_char, const int16_t key_code, const int16_t modifiers)
+    {
+#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+        d_debug("dpf_plugin_view::on_key_up => %p %i %i %i", self, key_char, key_code, modifiers);
+        dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self);
+
+        UIVst3* const uivst3 = view->uivst3;
+        DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED);
+
+        return uivst3->onKeyUp(key_char, key_code, modifiers);
+#else
+        return V3_NOT_IMPLEMENTED;
+        // unused
+        (void)self; (void)key_char; (void)key_code; (void)modifiers;
+#endif
+    }
+
+    static v3_result V3_API get_size(void* const self, v3_view_rect* const rect)
+    {
+        d_debug("dpf_plugin_view::get_size => %p", self);
+        dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self);
+
+        if (UIVst3* const uivst3 = view->uivst3)
+            return uivst3->getSize(rect);
+
+        d_debug("dpf_plugin_view::get_size => %p | NOTE: size request before attach", self);
+
+        view->sizeRequestedBeforeBeingAttached = true;
+
+        double scaleFactor = view->scale != nullptr ? view->scale->scaleFactor : 0.0;
+       #if defined(DISTRHO_UI_DEFAULT_WIDTH) && defined(DISTRHO_UI_DEFAULT_HEIGHT)
+        rect->right = DISTRHO_UI_DEFAULT_WIDTH;
+        rect->bottom = DISTRHO_UI_DEFAULT_HEIGHT;
+        if (d_isZero(scaleFactor))
+            scaleFactor = 1.0;
+       #else
+        UIExporter tmpUI(nullptr, 0, view->sampleRate,
+                         nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, d_nextBundlePath,
+                         view->instancePointer, scaleFactor);
+        rect->right = tmpUI.getWidth();
+        rect->bottom = tmpUI.getHeight();
+        scaleFactor = tmpUI.getScaleFactor();
+        tmpUI.quit();
+       #endif
+        rect->left = rect->top = 0;
+       #ifdef DISTRHO_OS_MAC
+        rect->right /= scaleFactor;
+        rect->bottom /= scaleFactor;
+       #endif
+
+        return V3_OK;
+    }
+
+    static v3_result V3_API on_size(void* const self, v3_view_rect* const rect)
+    {
+        d_debug("dpf_plugin_view::on_size => %p {%d,%d,%d,%d}",
+                self, rect->top, rect->left, rect->right, rect->bottom);
+        DISTRHO_SAFE_ASSERT_INT2_RETURN(rect->right > rect->left, rect->right, rect->left, V3_INVALID_ARG);
+        DISTRHO_SAFE_ASSERT_INT2_RETURN(rect->bottom > rect->top, rect->bottom, rect->top, V3_INVALID_ARG);
+
+        dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self);
+
+        if (UIVst3* const uivst3 = view->uivst3)
+            return uivst3->onSize(rect);
+
+        view->nextWidth = static_cast<uint32_t>(rect->right - rect->left);
+        view->nextHeight = static_cast<uint32_t>(rect->bottom - rect->top);
+        return V3_OK;
+    }
+
+    static v3_result V3_API on_focus(void* const self, const v3_bool state)
+    {
+#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+        d_debug("dpf_plugin_view::on_focus => %p %u", self, state);
+        dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self);
+
+        UIVst3* const uivst3 = view->uivst3;
+        DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED);
+
+        return uivst3->onFocus(state);
+#else
+        return V3_NOT_IMPLEMENTED;
+        // unused
+        (void)self; (void)state;
+#endif
+    }
+
+    static v3_result V3_API set_frame(void* const self, v3_plugin_frame** const frame)
+    {
+        d_debug("dpf_plugin_view::set_frame => %p %p", self, frame);
+        dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self);
+
+        view->frame = frame;
+
+        if (UIVst3* const uivst3 = view->uivst3)
+            return uivst3->setFrame(frame);
+
+        return V3_OK;
+    }
+
+    static v3_result V3_API can_resize(void* const self)
+    {
+#if DISTRHO_UI_USER_RESIZABLE
+        dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self);
+
+        if (UIVst3* const uivst3 = view->uivst3)
+            return uivst3->canResize();
+
+        return V3_TRUE;
+#else
+        return V3_FALSE;
+
+        // unused
+        (void)self;
+#endif
+    }
+
+    static v3_result V3_API check_size_constraint(void* const self, v3_view_rect* const rect)
+    {
+        d_debug("dpf_plugin_view::check_size_constraint => %p {%d,%d,%d,%d}",
+                self, rect->top, rect->left, rect->right, rect->bottom);
+        dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self);
+
+        if (UIVst3* const uivst3 = view->uivst3)
+            return uivst3->checkSizeConstraint(rect);
+
+        return V3_NOT_INITIALIZED;
+    }
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// dpf_plugin_view_create (called from plugin side)
+
+v3_plugin_view** dpf_plugin_view_create(v3_host_application** host, void* instancePointer, double sampleRate);
+
+v3_plugin_view** dpf_plugin_view_create(v3_host_application** const host,
+                                        void* const instancePointer,
+                                        const double sampleRate)
+{
+    dpf_plugin_view** const viewptr = new dpf_plugin_view*;
+    *viewptr = new dpf_plugin_view(host, instancePointer, sampleRate);
+    return static_cast<v3_plugin_view**>(static_cast<void*>(viewptr));
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+END_NAMESPACE_DISTRHO