|  | /*
 * Carla Native Plugins
 * Copyright (C) 2013-2022 Filipe Coelho <falktx@falktx.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
 */
#ifndef HAVE_PYQT
# error This file should not be built
#endif
#include "CarlaLv2Utils.hpp"
#include "CarlaPipeUtils.hpp"
#include "CarlaScopeUtils.hpp"
// --------------------------------------------------------------------------------------------------------------------
class NativePluginUI : public LV2_External_UI_Widget_Compat
{
public:
    NativePluginUI(LV2UI_Write_Function writeFunction, LV2UI_Controller controller,
                   LV2UI_Widget* widget, const LV2_Feature* const* features)
        : fUridMap(nullptr),
          fUridUnmap(nullptr),
          fUridTranser(0),
          fUridTranser2(0),
          fUI()
    {
        run  = extui_run;
        show = extui_show;
        hide = extui_hide;
        fUI.writeFunction = writeFunction;
        fUI.controller = controller;
        const LV2_URID_Map*   uridMap   = nullptr;
        const LV2_URID_Unmap* uridUnmap = nullptr;
        for (int i=0; features[i] != nullptr; ++i)
        {
            /**/ if (std::strcmp(features[i]->URI, LV2_URID__map) == 0)
                uridMap = (const LV2_URID_Map*)features[i]->data;
            else if (std::strcmp(features[i]->URI, LV2_URID__unmap) == 0)
                uridUnmap = (const LV2_URID_Unmap*)features[i]->data;
        }
        if (uridMap == nullptr)
        {
            carla_stderr("Host doesn't provide urid-map feature");
            return;
        }
        fUridMap = uridMap;
        fUridUnmap = uridUnmap;
        fUridTranser = uridMap->map(uridMap->handle, LV2_ATOM__eventTransfer);
        fUridTranser2 = uridMap->map(uridMap->handle, "urn:carla:transmitEv");
        // ---------------------------------------------------------------
        // see if the host supports external-ui
        for (int i=0; features[i] != nullptr; ++i)
        {
            if (std::strcmp(features[i]->URI, LV2_EXTERNAL_UI__Host) == 0 ||
                std::strcmp(features[i]->URI, LV2_EXTERNAL_UI_DEPRECATED_URI) == 0)
            {
                fUI.host = (const LV2_External_UI_Host*)features[i]->data;
                break;
            }
        }
        if (fUI.host != nullptr)
        {
            fUI.name = carla_strdup(fUI.host->plugin_human_id);
            *widget = (LV2_External_UI_Widget_Compat*)this;
            return;
        }
        // ---------------------------------------------------------------
        // no external-ui support, use showInterface
        for (int i=0; features[i] != nullptr; ++i)
        {
            if (std::strcmp(features[i]->URI, LV2_OPTIONS__options) != 0)
                continue;
            const LV2_Options_Option* const options((const LV2_Options_Option*)features[i]->data);
            CARLA_SAFE_ASSERT_BREAK(options != nullptr);
            for (int j=0; options[j].key != 0; ++j)
            {
                if (options[j].key != uridMap->map(uridMap->handle, LV2_UI__windowTitle))
                    continue;
                const char* const title((const char*)options[j].value);
                CARLA_SAFE_ASSERT_BREAK(title != nullptr && title[0] != '\0');
                fUI.name = carla_strdup(title);
                break;
            }
            break;
        }
        if (fUI.name == nullptr)
            fUI.name = carla_strdup("Carla");
        *widget = nullptr;
    }
    ~NativePluginUI()
    {
        if (fUI.isVisible)
            writeAtomMessage("quit");
        fUI.host = nullptr;
        fUI.writeFunction = nullptr;
        fUI.controller = nullptr;
    }
    // ----------------------------------------------------------------------------------------------------------------
    void lv2ui_port_event(uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer)
    {
        CARLA_SAFE_ASSERT_RETURN(buffer != nullptr,);
        if (format == 0)
        {
            char msg[128];
            const float* const valuePtr = (const float*)buffer;
            {
                const CarlaScopedLocale csl;
                std::snprintf(msg, 127, "control %u %.12g", portIndex, static_cast<double>(*valuePtr));
            }
            msg[127] = '\0';
            writeAtomMessage(msg);
            return;
        }
        if (format == fUridTranser)
        {
            CARLA_SAFE_ASSERT_RETURN(bufferSize > sizeof(LV2_Atom),);
            const LV2_Atom* const atom = (const LV2_Atom*)buffer;
            if (atom->type == fUridTranser2)
            {
                const char* const msg = (const char*)(atom + 1);
                if (std::strcmp(msg, "quit") == 0)
                {
                    handleUiClosed();
                }
                return;
            }
        }
        if (fUridUnmap != nullptr)
        {
            carla_stdout("lv2ui_port_event %u %u %u:%s %p",
                         portIndex, bufferSize, format, fUridUnmap->unmap(fUridUnmap->handle, format), buffer);
        }
    }
    // ----------------------------------------------------------------------------------------------------------------
    void lv2ui_select_program(uint32_t bank, uint32_t program) const
    {
        char msg[128];
        std::snprintf(msg, 127, "program %u %u", bank, program);
        msg[127] = '\0';
        writeAtomMessage(msg);
    }
    // ----------------------------------------------------------------------------------------------------------------
    int lv2ui_idle() const
    {
        if (! fUI.isVisible)
            return 1;
        handleUiRun();
        return 0;
    }
    int lv2ui_show()
    {
        handleUiShow();
        return 0;
    }
    int lv2ui_hide()
    {
        handleUiHide();
        return 0;
    }
    // ----------------------------------------------------------------------------------------------------------------
protected:
    void handleUiShow()
    {
        writeAtomMessage("show");
        fUI.isVisible = true;
    }
    void handleUiHide()
    {
        if (fUI.isVisible)
        {
            fUI.isVisible = false;
            writeAtomMessage("hide");
        }
    }
    void handleUiRun() const
    {
        if (fUI.isVisible)
            writeAtomMessage("idle");
    }
    void handleUiClosed()
    {
        fUI.isVisible = false;
        if (fUI.host != nullptr && fUI.host->ui_closed != nullptr && fUI.controller != nullptr)
            fUI.host->ui_closed(fUI.controller);
        fUI.host = nullptr;
        fUI.writeFunction = nullptr;
        fUI.controller = nullptr;
    }
    bool writeAtomMessage(const char* const msg) const
    {
        CARLA_SAFE_ASSERT_RETURN(fUI.writeFunction != nullptr, false);
        CARLA_SAFE_ASSERT_RETURN(fUridTranser != 0, false);
        CARLA_SAFE_ASSERT_RETURN(fUridTranser2 != 0, false);
#ifdef DEBUG
        if (std::strcmp(msg, "idle") != 0) {
            carla_debug("writeAtomMessage(%s)", msg);
        }
#endif
        const size_t msgSize  = std::strlen(msg)+1;
        const size_t atomSize = sizeof(LV2_Atom) + msgSize;
        if (atomSize <= 128)
        {
            char atomBuf[atomSize];
            carla_zeroChars(atomBuf, atomSize);
            LV2_Atom* const atom = (LV2_Atom*)atomBuf;
            atom->size = static_cast<uint32_t>(msgSize);
            atom->type = fUridTranser2;
            std::memcpy(atomBuf+sizeof(LV2_Atom), msg, msgSize);
            fUI.writeFunction(fUI.controller, 0, static_cast<uint32_t>(atomSize), fUridTranser, atomBuf);
        }
        else
        {
            char* const atomBuf = new char[atomSize];
            carla_zeroChars(atomBuf, atomSize);
            LV2_Atom* const atom = (LV2_Atom*)atomBuf;
            atom->size = static_cast<uint32_t>(msgSize);
            atom->type = fUridTranser2;
            std::memcpy(atomBuf+sizeof(LV2_Atom), msg, msgSize);
            fUI.writeFunction(fUI.controller, 0, static_cast<uint32_t>(atomSize), fUridTranser, atomBuf);
            delete[] atomBuf;
        }
        return true;
    }
    // ----------------------------------------------------------------------------------------------------------------
private:
    const LV2_URID_Map* fUridMap;
    const LV2_URID_Unmap* fUridUnmap;
    LV2_URID fUridTranser, fUridTranser2;
    struct UI {
        const LV2_External_UI_Host* host;
        LV2UI_Write_Function writeFunction;
        LV2UI_Controller controller;
        const char* name;
        bool isVisible;
        UI()
            : host(nullptr),
              writeFunction(nullptr),
              controller(nullptr),
              name(nullptr),
              isVisible(false) {}
        ~UI()
        {
            if (name != nullptr)
            {
                delete[] name;
                name = nullptr;
            }
        }
        CARLA_DECLARE_NON_COPYABLE(UI);
    } fUI;
    // ----------------------------------------------------------------------------------------------------------------
    #define handlePtr ((NativePluginUI*)handle)
    static void extui_run(LV2_External_UI_Widget_Compat* handle)
    {
        handlePtr->handleUiRun();
    }
    static void extui_show(LV2_External_UI_Widget_Compat* handle)
    {
        carla_debug("extui_show(%p)", handle);
        handlePtr->handleUiShow();
    }
    static void extui_hide(LV2_External_UI_Widget_Compat* handle)
    {
        carla_debug("extui_hide(%p)", handle);
        handlePtr->handleUiHide();
    }
    #undef handlePtr
    // -------------------------------------------------------------------
    CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NativePluginUI)
};
// -----------------------------------------------------------------------
// LV2 UI descriptor functions
static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*, const char*, const char*,
                                      LV2UI_Write_Function writeFunction, LV2UI_Controller controller,
                                      LV2UI_Widget* widget, const LV2_Feature* const* features)
{
    carla_debug("lv2ui_instantiate(..., %p, %p, %p)", writeFunction, controller, widget, features);
    NativePluginUI* const ui = new NativePluginUI(writeFunction, controller, widget, features);
    // TODO: check ok
    return (LV2UI_Handle)ui;
}
#define uiPtr ((NativePluginUI*)ui)
static void lv2ui_port_event(LV2UI_Handle ui, uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer)
{
    carla_debug("lv2ui_port_event(%p, %i, %i, %i, %p)", ui, portIndex, bufferSize, format, buffer);
    uiPtr->lv2ui_port_event(portIndex, bufferSize, format, buffer);
}
static void lv2ui_cleanup(LV2UI_Handle ui)
{
    carla_debug("lv2ui_cleanup(%p)", ui);
    delete uiPtr;
}
static void lv2ui_select_program(LV2UI_Handle ui, uint32_t bank, uint32_t program)
{
    carla_debug("lv2ui_select_program(%p, %i, %i)", ui, bank, program);
    uiPtr->lv2ui_select_program(bank, program);
}
static int lv2ui_idle(LV2UI_Handle ui)
{
    return uiPtr->lv2ui_idle();
}
static int lv2ui_show(LV2UI_Handle ui)
{
    carla_debug("lv2ui_show(%p)", ui);
    return uiPtr->lv2ui_show();
}
static int lv2ui_hide(LV2UI_Handle ui)
{
    carla_debug("lv2ui_hide(%p)", ui);
    return uiPtr->lv2ui_hide();
}
static const void* lv2ui_extension_data(const char* uri)
{
    carla_stdout("lv2ui_extension_data(\"%s\")", uri);
    static const LV2UI_Idle_Interface uiidle = { lv2ui_idle };
    static const LV2UI_Show_Interface uishow = { lv2ui_show, lv2ui_hide };
    static const LV2_Programs_UI_Interface uiprograms = { lv2ui_select_program };
    if (std::strcmp(uri, LV2_UI__idleInterface) == 0)
        return &uiidle;
    if (std::strcmp(uri, LV2_UI__showInterface) == 0)
        return &uishow;
    if (std::strcmp(uri, LV2_PROGRAMS__UIInterface) == 0)
        return &uiprograms;
    return nullptr;
}
#undef uiPtr
// -----------------------------------------------------------------------
// Startup code
CARLA_PLUGIN_EXPORT
const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index)
{
    carla_debug("lv2ui_descriptor(%i)", index);
    static const LV2UI_Descriptor lv2UiExtDesc = {
    /* URI            */ "http://kxstudio.sf.net/carla/ui-bridge-ext",
    /* instantiate    */ lv2ui_instantiate,
    /* cleanup        */ lv2ui_cleanup,
    /* port_event     */ lv2ui_port_event,
    /* extension_data */ lv2ui_extension_data
    };
    return (index == 0) ? &lv2UiExtDesc : nullptr;
}
// -----------------------------------------------------------------------
#include "CarlaPipeUtils.cpp"
// -----------------------------------------------------------------------
 |