diff --git a/Makefile b/Makefile index e27a4936..cf1b0cbc 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,7 @@ ifeq ($(HAVE_DGL),true) endif examples: dgl + $(MAKE) all -C examples/ExternalUI $(MAKE) all -C examples/Info $(MAKE) all -C examples/Latency $(MAKE) all -C examples/Meters @@ -23,6 +24,13 @@ examples: dgl $(MAKE) all -C examples/Parameters $(MAKE) all -C examples/States + # ExternalUI launcher + install -d bin/d_extui-dssi + install -d bin/d_extui.lv2 + install -m 755 examples/ExternalUI/ExternalLauncher.sh bin/d_extui.sh + install -m 755 examples/ExternalUI/ExternalLauncher.sh bin/d_extui-dssi/d_extui.sh + install -m 755 examples/ExternalUI/ExternalLauncher.sh bin/d_extui.lv2/d_extui.sh + ifneq ($(CROSS_COMPILING),true) gen: examples utils/lv2_ttl_generator @$(CURDIR)/utils/generate-ttl.sh @@ -40,6 +48,7 @@ endif clean: $(MAKE) clean -C dgl + $(MAKE) clean -C examples/ExternalUI $(MAKE) clean -C examples/Info $(MAKE) clean -C examples/Latency $(MAKE) clean -C examples/Meters diff --git a/distrho/DistrhoUI.hpp b/distrho/DistrhoUI.hpp index e33a4ca4..244b25bc 100644 --- a/distrho/DistrhoUI.hpp +++ b/distrho/DistrhoUI.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2018 Filipe Coelho * * 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 @@ -20,7 +20,8 @@ #include "extra/LeakDetector.hpp" #include "src/DistrhoPluginChecks.h" -#ifndef HAVE_DGL +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +# include "../dgl/Base.hpp" # include "extra/ExternalWindow.hpp" typedef DISTRHO_NAMESPACE::ExternalWindow UIWidget; #elif DISTRHO_UI_USE_NANOVG @@ -67,7 +68,7 @@ public: */ bool isUserResizable() const noexcept; -#ifdef HAVE_DGL +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI /** Set geometry constraints for the UI when resized by the user, and optionally scale UI automatically. @see Window::setGeometryConstraints(uint,uint,bool) @@ -181,7 +182,7 @@ protected: */ virtual void sampleRateChanged(double newSampleRate); -#ifdef HAVE_DGL +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI /* -------------------------------------------------------------------------------------------------------- * UI Callbacks (optional) */ @@ -191,13 +192,13 @@ protected: */ virtual void uiIdle() {} -#ifndef DGL_FILE_BROWSER_DISABLED +# ifndef DGL_FILE_BROWSER_DISABLED /** File browser selected function. @see Window::fileBrowserSelected(const char*) */ virtual void uiFileBrowserSelected(const char* filename); -#endif +# endif /** OpenGL window reshape function, called when parent window is resized. @@ -225,7 +226,7 @@ private: friend class UIExporter; friend class UIExporterWindow; -#ifdef HAVE_DGL +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI // these should not be used void setAbsoluteX(int) const noexcept {} void setAbsoluteY(int) const noexcept {} diff --git a/distrho/extra/ExternalWindow.hpp b/distrho/extra/ExternalWindow.hpp index 8ad2df0f..c39f55c7 100644 --- a/distrho/extra/ExternalWindow.hpp +++ b/distrho/extra/ExternalWindow.hpp @@ -40,6 +40,8 @@ public: : width(w), height(h), title(t), + transientWinId(0), + visible(false), pid(0) {} virtual ~ExternalWindow() @@ -62,9 +64,14 @@ public: return title; } - void setTitle(const char* const t) noexcept + uintptr_t getTransientWinId() const noexcept { - title = t; + return transientWinId; + } + + bool isVisible() const noexcept + { + return visible; } bool isRunning() noexcept @@ -84,6 +91,27 @@ public: return true; } + virtual void setSize(uint w, uint h) + { + width = w; + height = h; + } + + virtual void setTitle(const char* const t) + { + title = t; + } + + virtual void setTransientWinId(const uintptr_t winId) + { + transientWinId = winId; + } + + virtual void setVisible(const bool yesNo) + { + visible = yesNo; + } + protected: bool startExternalProcess(const char* args[]) { @@ -107,14 +135,6 @@ protected: } } -private: - uint width; - uint height; - String title; - pid_t pid; - - friend class UIExporter; - void terminateAndWaitForProcess() { if (pid <= 0) @@ -162,6 +182,16 @@ private: } } +private: + uint width; + uint height; + String title; + uintptr_t transientWinId; + bool visible; + pid_t pid; + + friend class UIExporter; + DISTRHO_DECLARE_NON_COPY_CLASS(ExternalWindow) }; diff --git a/distrho/src/DistrhoPluginJack.cpp b/distrho/src/DistrhoPluginJack.cpp index 98ce73ca..edafaf66 100644 --- a/distrho/src/DistrhoPluginJack.cpp +++ b/distrho/src/DistrhoPluginJack.cpp @@ -16,11 +16,6 @@ #include "DistrhoPluginInternal.hpp" -#if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_HAS_EMBED_UI -# undef DISTRHO_PLUGIN_HAS_UI -# define DISTRHO_PLUGIN_HAS_UI 0 -#endif - #if DISTRHO_PLUGIN_HAS_UI # include "DistrhoUIInternal.hpp" #else diff --git a/distrho/src/DistrhoUI.cpp b/distrho/src/DistrhoUI.cpp index 89162d67..a9502161 100644 --- a/distrho/src/DistrhoUI.cpp +++ b/distrho/src/DistrhoUI.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2018 Filipe Coelho * * 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 @@ -15,8 +15,7 @@ */ #include "DistrhoUIInternal.hpp" - -#ifdef HAVE_DGL +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI # include "src/WidgetPrivateData.hpp" #endif @@ -27,16 +26,21 @@ START_NAMESPACE_DISTRHO double d_lastUiSampleRate = 0.0; void* d_lastUiDspPtr = nullptr; -#ifdef HAVE_DGL -Window* d_lastUiWindow = nullptr; -#endif +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI uintptr_t g_nextWindowId = 0; const char* g_nextBundlePath = nullptr; +#else +Window* d_lastUiWindow = nullptr; +#endif /* ------------------------------------------------------------------------------------------------------------ * UI */ -#ifdef HAVE_DGL +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +UI::UI(uint width, uint height, bool userResizable) + : UIWidget(width, height), + pData(new PrivateData(userResizable)) {} +#else UI::UI(uint width, uint height, bool userResizable) : UIWidget(*d_lastUiWindow), pData(new PrivateData(userResizable)) @@ -46,10 +50,6 @@ UI::UI(uint width, uint height, bool userResizable) if (width > 0 && height > 0) setSize(width, height); } -#else -UI::UI(uint width, uint height, bool userResizable) - : UIWidget(width, height), - pData(new PrivateData(userResizable)) {} #endif UI::~UI() @@ -62,7 +62,7 @@ bool UI::isUserResizable() const noexcept return pData->userResizable; } -#ifdef HAVE_DGL +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI void UI::setGeometryConstraints(uint minWidth, uint minHeight, bool keepAspectRatio, bool automaticallyScale) { DISTRHO_SAFE_ASSERT_RETURN(minWidth > 0,); @@ -73,7 +73,6 @@ void UI::setGeometryConstraints(uint minWidth, uint minHeight, bool keepAspectRa pData->minHeight = minHeight; getParentWindow().setGeometryConstraints(minWidth, minHeight, keepAspectRatio); - } #endif @@ -141,15 +140,15 @@ uintptr_t UI::getNextWindowId() noexcept void UI::sampleRateChanged(double) {} -#ifdef HAVE_DGL +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI /* ------------------------------------------------------------------------------------------------------------ * UI Callbacks (optional) */ -#ifndef DGL_FILE_BROWSER_DISABLED +# ifndef DGL_FILE_BROWSER_DISABLED void UI::uiFileBrowserSelected(const char*) { } -#endif +# endif void UI::uiReshape(uint width, uint height) { @@ -173,7 +172,7 @@ void UI::onResize(const ResizeEvent& ev) pData->setSizeCallback(ev.size.getWidth(), ev.size.getHeight()); } -#endif +#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI // ----------------------------------------------------------------------------------------------------------- diff --git a/distrho/src/DistrhoUIDSSI.cpp b/distrho/src/DistrhoUIDSSI.cpp index b11e1d0f..1ff4b47d 100644 --- a/distrho/src/DistrhoUIDSSI.cpp +++ b/distrho/src/DistrhoUIDSSI.cpp @@ -112,6 +112,7 @@ public: void exec() { + d_stdout("exec 1"); for (;;) { fOscData.idle(); @@ -121,6 +122,7 @@ public: d_msleep(30); } + d_stdout("exec 3"); } // ------------------------------------------------------------------- diff --git a/distrho/src/DistrhoUIInternal.hpp b/distrho/src/DistrhoUIInternal.hpp index a7a8ac3b..4ea26580 100644 --- a/distrho/src/DistrhoUIInternal.hpp +++ b/distrho/src/DistrhoUIInternal.hpp @@ -19,7 +19,10 @@ #include "../DistrhoUI.hpp" -#ifdef HAVE_DGL +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +# include "../extra/Sleep.hpp" +using DGL_NAMESPACE::IdleCallback; +#else # include "../../dgl/Application.hpp" # include "../../dgl/Window.hpp" using DGL_NAMESPACE::Application; @@ -34,7 +37,7 @@ START_NAMESPACE_DISTRHO extern double d_lastUiSampleRate; extern void* d_lastUiDspPtr; -#ifdef HAVE_DGL +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI extern Window* d_lastUiWindow; #endif extern uintptr_t g_nextWindowId; @@ -146,7 +149,20 @@ struct UI::PrivateData { // ----------------------------------------------------------------------- // Plugin Window, needed to take care of resize properly -#ifdef HAVE_DGL +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +static inline +UI* createUiWrapper(void* const dspPtr, const uintptr_t winId, const char* const bundlePath) +{ + d_lastUiDspPtr = dspPtr; + g_nextWindowId = winId; + g_nextBundlePath = bundlePath; + UI* const ret = createUI(); + d_lastUiDspPtr = nullptr; + g_nextWindowId = 0; + g_nextBundlePath = nullptr; + return ret; +} +#else // DISTRHO_PLUGIN_HAS_EXTERNAL_UI static inline UI* createUiWrapper(void* const dspPtr, Window* const window) { @@ -212,7 +228,7 @@ protected: fIsReady = true; } -#ifndef DGL_FILE_BROWSER_DISABLED +# ifndef DGL_FILE_BROWSER_DISABLED // custom file-browser selected void fileBrowserSelected(const char* filename) override { @@ -220,26 +236,13 @@ protected: fUI->uiFileBrowserSelected(filename); } -#endif +# endif private: UI* const fUI; bool fIsReady; }; -#else -static inline -UI* createUiWrapper(void* const dspPtr, const uintptr_t winId, const char* const bundlePath) -{ - d_lastUiDspPtr = dspPtr; - g_nextWindowId = winId; - g_nextBundlePath = bundlePath; - UI* const ret = createUI(); - d_lastUiDspPtr = nullptr; - g_nextWindowId = 0; - g_nextBundlePath = nullptr; - return ret; -} -#endif +#endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI // ----------------------------------------------------------------------- // UI exporter class @@ -256,13 +259,13 @@ public: const setSizeFunc setSizeCall, void* const dspPtr = nullptr, const char* const bundlePath = nullptr) -#ifdef HAVE_DGL +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + : fUI(createUiWrapper(dspPtr, winId, bundlePath)), +#else : glApp(), glWindow(glApp, winId, dspPtr), fChangingSize(false), fUI(glWindow.getUI()), -#else - : fUI(createUiWrapper(dspPtr, winId, bundlePath)), #endif fData((fUI != nullptr) ? fUI->pData : nullptr) { @@ -276,54 +279,65 @@ public: fData->sendNoteCallbackFunc = sendNoteCall; fData->setSizeCallbackFunc = setSizeCall; -#ifdef HAVE_DGL +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI // unused return; (void)bundlePath; #endif } +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + ~UIExporter() + { + delete fUI; + } +#endif + // ------------------------------------------------------------------- +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI uint getWidth() const noexcept { -#ifdef HAVE_DGL - return glWindow.getWidth(); -#else DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr, 1); return fUI->getWidth(); -#endif } uint getHeight() const noexcept { -#ifdef HAVE_DGL - return glWindow.getHeight(); -#else DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr, 1); return fUI->getHeight(); -#endif } bool isVisible() const noexcept { -#ifdef HAVE_DGL - return glWindow.isVisible(); -#else DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr, false); return fUI->isRunning(); -#endif } - // ------------------------------------------------------------------- + intptr_t getWindowId() const noexcept + { + return 0; + } +#else + uint getWidth() const noexcept + { + return glWindow.getWidth(); + } + + uint getHeight() const noexcept + { + return glWindow.getHeight(); + } + + bool isVisible() const noexcept + { + return glWindow.isVisible(); + } intptr_t getWindowId() const noexcept { -#ifdef HAVE_DGL return glWindow.getWindowId(); -#else - return 0; -#endif } +#endif // ------------------------------------------------------------------- @@ -365,7 +379,39 @@ public: // ------------------------------------------------------------------- -#ifdef HAVE_DGL +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + void exec(IdleCallback* const cb) + { + DISTRHO_SAFE_ASSERT_RETURN(cb != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + + fUI->setVisible(true); + cb->idleCallback(); + + while (fUI->isRunning()) + { + d_msleep(10); + cb->idleCallback(); + } + } + + void exec_idle() + { + } + + bool idle() + { + return true; + } + + void quit() + { + DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + + fUI->setVisible(false); + fUI->terminateAndWaitForProcess(); + } +#else void exec(IdleCallback* const cb) { DISTRHO_SAFE_ASSERT_RETURN(cb != nullptr,); @@ -381,48 +427,64 @@ public: if (glWindow.isReady()) fUI->uiIdle(); } -#endif bool idle() { DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr, false); -#ifdef HAVE_DGL glApp.idle(); if (glWindow.isReady()) fUI->uiIdle(); return ! glApp.isQuiting(); -#else - return fUI->isRunning(); -#endif } void quit() { -#ifdef HAVE_DGL glWindow.close(); glApp.quit(); -#else - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - fUI->terminateAndWaitForProcess(); -#endif } +#endif // ------------------------------------------------------------------- +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI void setWindowTitle(const char* const uiTitle) { -#ifdef HAVE_DGL - glWindow.setTitle(uiTitle); -#else DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + fUI->setTitle(uiTitle); -#endif } -#ifdef HAVE_DGL + void setWindowSize(const uint width, const uint height, const bool = false) + { + DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + + fUI->setSize(width, height); + } + + void setWindowTransientWinId(const uintptr_t winId) + { + DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + + fUI->setTransientWinId(winId); + } + + bool setWindowVisible(const bool yesNo) + { + DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr, false); + + fUI->setVisible(yesNo); + + return fUI->isRunning(); + } +#else + void setWindowTitle(const char* const uiTitle) + { + glWindow.setTitle(uiTitle); + } + void setWindowSize(const uint width, const uint height, const bool updateUI = false) { DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); @@ -459,10 +521,6 @@ public: { return glWindow.handlePluginSpecial(press, key); } -#else - void setWindowSize(const uint, const uint, const bool = false) {} - void setWindowTransientWinId(const uintptr_t) {} - bool setWindowVisible(const bool) { return true; } #endif // ------------------------------------------------------------------- @@ -483,7 +541,7 @@ public: } private: -#ifdef HAVE_DGL +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI // ------------------------------------------------------------------- // DGL Application and Window for this widget diff --git a/examples/ExternalUI/DistrhoPluginInfo.h b/examples/ExternalUI/DistrhoPluginInfo.h new file mode 100644 index 00000000..3163c2c3 --- /dev/null +++ b/examples/ExternalUI/DistrhoPluginInfo.h @@ -0,0 +1,36 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2018 Filipe Coelho + * + * 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. + */ + +#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED +#define DISTRHO_PLUGIN_INFO_H_INCLUDED + +#define DISTRHO_PLUGIN_BRAND "DISTRHO" +#define DISTRHO_PLUGIN_NAME "ExternalUI" +#define DISTRHO_PLUGIN_URI "http://distrho.sf.net/examples/ExternalUI" + +#define DISTRHO_PLUGIN_HAS_UI 1 +#define DISTRHO_PLUGIN_HAS_EMBED_UI 0 +#define DISTRHO_PLUGIN_HAS_EXTERNAL_UI 1 +#define DISTRHO_PLUGIN_IS_RT_SAFE 1 +#define DISTRHO_PLUGIN_NUM_INPUTS 1 +#define DISTRHO_PLUGIN_NUM_OUTPUTS 1 + +enum Parameters { + kParameterLevel = 0, + kParameterCount +}; + +#endif // DISTRHO_PLUGIN_INFO_H_INCLUDED diff --git a/examples/ExternalUI/ExternalExamplePlugin.cpp b/examples/ExternalUI/ExternalExamplePlugin.cpp new file mode 100644 index 00000000..dbecdcc6 --- /dev/null +++ b/examples/ExternalUI/ExternalExamplePlugin.cpp @@ -0,0 +1,188 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2018 Filipe Coelho + * + * 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 "DistrhoPlugin.hpp" + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------------------------------------------- + +/** + Plugin to show how to get some basic information sent to the UI. + */ +class ExternalExamplePlugin : public Plugin +{ +public: + ExternalExamplePlugin() + : Plugin(kParameterCount, 0, 0), + fValue(0.0f) + { + } + +protected: + /* -------------------------------------------------------------------------------------------------------- + * Information */ + + /** + Get the plugin label. + This label is a short restricted name consisting of only _, a-z, A-Z and 0-9 characters. + */ + const char* getLabel() const override + { + return "ExternalUI"; + } + + /** + Get an extensive comment/description about the plugin. + */ + const char* getDescription() const override + { + return "Plugin to show how to use an external / remote UI."; + } + + /** + Get the plugin author/maker. + */ + const char* getMaker() const override + { + return "DISTRHO"; + } + + /** + Get the plugin homepage. + */ + const char* getHomePage() const override + { + return "https://github.com/DISTRHO/DPF"; + } + + /** + Get the plugin license name (a single line of text). + For commercial plugins this should return some short copyright information. + */ + const char* getLicense() const override + { + return "ISC"; + } + + /** + Get the plugin version, in hexadecimal. + */ + uint32_t getVersion() const override + { + return d_version(1, 0, 0); + } + + /** + Get the plugin unique Id. + This value is used by LADSPA, DSSI and VST plugin formats. + */ + int64_t getUniqueId() const override + { + return d_cconst('d', 'E', 'x', 't'); + } + + /* -------------------------------------------------------------------------------------------------------- + * Init */ + + /** + Initialize the parameter @a index. + This function will be called once, shortly after the plugin is created. + */ + void initParameter(uint32_t index, Parameter& parameter) override + { + if (index != 0) + return; + + parameter.hints = kParameterIsAutomable|kParameterIsInteger; + parameter.ranges.def = 0.0f; + parameter.ranges.min = 0.0f; + parameter.ranges.max = 100.0f; + parameter.name = "Value"; + parameter.symbol = "value"; + } + + /* -------------------------------------------------------------------------------------------------------- + * Internal data */ + + /** + Get the current value of a parameter. + The host may call this function from any context, including realtime processing. + */ + float getParameterValue(uint32_t index) const override + { + if (index != 0) + return 0.0f; + + return fValue; + + } + + /** + Change a parameter value. + The host may call this function from any context, including realtime processing. + When a parameter is marked as automable, you must ensure no non-realtime operations are performed. + @note This function will only be called for parameter inputs. + */ + void setParameterValue(uint32_t index, float value) override + { + if (index != 0) + return; + + fValue = value; + } + + /* -------------------------------------------------------------------------------------------------------- + * Audio/MIDI Processing */ + + /** + Run/process function for plugins without MIDI input. + @note Some parameters might be null if there are no audio inputs or outputs. + */ + void run(const float** inputs, float** outputs, uint32_t frames) override + { + /** + This plugin does nothing, it just demonstrates information usage. + So here we directly copy inputs over outputs, leaving the audio untouched. + We need to be careful in case the host re-uses the same buffer for both ins and outs. + */ + if (outputs[0] != inputs[0]) + std::memcpy(outputs[0], inputs[0], sizeof(float)*frames); + } + + // ------------------------------------------------------------------------------------------------------- + +private: + // Parameters + float fValue; + + /** + Set our plugin class as non-copyable and add a leak detector just in case. + */ + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ExternalExamplePlugin) +}; + +/* ------------------------------------------------------------------------------------------------------------ + * Plugin entry point, called by DPF to create a new plugin instance. */ + +Plugin* createPlugin() +{ + return new ExternalExamplePlugin(); +} + +// ----------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO diff --git a/examples/ExternalUI/ExternalExampleUI.cpp b/examples/ExternalUI/ExternalExampleUI.cpp new file mode 100644 index 00000000..66cfc85b --- /dev/null +++ b/examples/ExternalUI/ExternalExampleUI.cpp @@ -0,0 +1,187 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2018 Filipe Coelho + * + * 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 "DistrhoUI.hpp" + +// Extra includes for current path and fifo stuff +#include +#include +#include +#include + +START_NAMESPACE_DISTRHO + +// TODO: generate a random, not-yet-existing, filename +const char* const kFifoFilename = "/tmp/dpf-fifo-test"; + +// Helper to get current path of this plugin +static const char* getCurrentPluginFilename() +{ + Dl_info exeInfo; + void* localSymbol = (void*)kFifoFilename; + dladdr(localSymbol, &exeInfo); + return exeInfo.dli_fname; +} + +// Helper to check if a file exists +static bool fileExists(const char* const filename) +{ + return access(filename, F_OK) != -1; +} + +// Helper function to keep trying to write until it succeeds or really errors out +static ssize_t +writeRetry(int fd, const void* src, size_t size) +{ + ssize_t error; + + do { + error = write(fd, src, size); + } while (error == -1 && (errno == EINTR || errno == EPIPE)); + + return error; +} + +// ----------------------------------------------------------------------------------------------------------- + +class ExternalExampleUI : public UI +{ +public: + ExternalExampleUI() + : UI(405, 256, true), + fFifo(-1), + fValue(0.0f), + fExternalScript(getNextBundlePath()) + { + if (fExternalScript.isEmpty()) + { + fExternalScript = getCurrentPluginFilename(); + fExternalScript.truncate(fExternalScript.rfind('/')); + } + + fExternalScript += "/d_extui.sh"; + d_stdout("External script = %s", fExternalScript.buffer()); + } + +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 + { + if (index != 0) + return; + + fValue = value; + + if (fFifo == -1) + return; + + // NOTE: This is a terrible way to pass values, also locale might get in the way... + char valueStr[24]; + std::memset(valueStr, 0, sizeof(valueStr)); + std::snprintf(valueStr, 23, "%i\n", static_cast(value + 0.5f)); + + DISTRHO_SAFE_ASSERT(writeRetry(fFifo, valueStr, 24) == sizeof(valueStr)); + } + + /* -------------------------------------------------------------------------------------------------------- + * External Window overrides */ + + /** + Manage external process and IPC when UI is requested to be visible. + */ + void setVisible(const bool yesNo) override + { + if (yesNo) + { + DISTRHO_SAFE_ASSERT_RETURN(fileExists(fExternalScript),); + + mkfifo(kFifoFilename, 0666); + sync(); + + char winIdStr[24]; + std::memset(winIdStr, 0, sizeof(winIdStr)); + std::snprintf(winIdStr, 23, "%lu", getTransientWinId()); + + const char* args[] = { + fExternalScript.buffer(), + kFifoFilename, + "--progressbar", "External UI example", + "--title", getTitle(), + nullptr, + }; + DISTRHO_SAFE_ASSERT_RETURN(startExternalProcess(args),); + + // NOTE: this can lockup the current thread if the other side does not read the file! + fFifo = open(kFifoFilename, O_WRONLY); + DISTRHO_SAFE_ASSERT_RETURN(fFifo != -1,); + + parameterChanged(0, fValue); + } + else + { + if (fFifo != -1) + { + if (isRunning()) + { + DISTRHO_SAFE_ASSERT(writeRetry(fFifo, "quit\n", 5) == 5); + fsync(fFifo); + } + close(fFifo); + fFifo = -1; + } + + unlink(kFifoFilename); + terminateAndWaitForProcess(); + } + + UI::setVisible(yesNo); + } + + // ------------------------------------------------------------------------------------------------------- + +private: + // IPC Stuff + int fFifo; + + // Current value, cached for when UI becomes visible + float fValue; + + // Path to external ui script + String fExternalScript; + + /** + Set our UI class as non-copyable and add a leak detector just in case. + */ + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ExternalExampleUI) +}; + +/* ------------------------------------------------------------------------------------------------------------ + * UI entry point, called by DPF to create a new UI instance. */ + +UI* createUI() +{ + return new ExternalExampleUI(); +} + +// ----------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO diff --git a/examples/ExternalUI/ExternalLauncher.sh b/examples/ExternalUI/ExternalLauncher.sh new file mode 100755 index 00000000..1ca7c94c --- /dev/null +++ b/examples/ExternalUI/ExternalLauncher.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# Read FIFO argument from CLI +FIFO=${1} +shift + +if [ ! -e "${FIFO}" ]; then + echo "Fifo file ${FIFO} does not exist, cannot run" + exit 1 +fi + +# Start kdialog with all other arguments and get dbus reference +dbusRef=$(kdialog "$@" 100) + +if [ $? -ne 0 ] || [ -z "${dbusRef}" ]; then + echo "Failed to start kdialog" + exit 1 +fi + +# Setup cancellation point for this script +quitfn() { + qdbus ${dbusRef} close 2>/dev/null +} + +trap quitfn SIGINT +trap quitfn SIGTERM + +# Read Fifo for new values or a quit message +while read line <"${FIFO}"; do + if echo "${line}" | grep -q "quit"; then + break + fi + if ! qdbus ${dbusRef} Set "" value "${line}"; then + break + fi +done + +# Cleanup +rm -f "${FIFO}" +quitfn diff --git a/examples/ExternalUI/Makefile b/examples/ExternalUI/Makefile new file mode 100644 index 00000000..ba1df228 --- /dev/null +++ b/examples/ExternalUI/Makefile @@ -0,0 +1,40 @@ +#!/usr/bin/make -f +# Makefile for DISTRHO Plugins # +# ---------------------------- # +# Created by falkTX +# + +# -------------------------------------------------------------- +# Project name, used for binaries + +NAME = d_extui + +# -------------------------------------------------------------- +# Files to build + +FILES_DSP = \ + ExternalExamplePlugin.cpp + +FILES_UI = \ + ExternalExampleUI.cpp + +# -------------------------------------------------------------- +# Do some magic + +include ../../Makefile.plugins.mk + +LINK_FLAGS += -ldl + +# -------------------------------------------------------------- +# Enable all possible plugin types + +ifeq ($(HAVE_JACK),true) +TARGETS += jack +endif + +TARGETS += dssi +TARGETS += lv2_sep + +all: $(TARGETS) + +# -------------------------------------------------------------- diff --git a/examples/ExternalUI/README.md b/examples/ExternalUI/README.md new file mode 100644 index 00000000..6fb4d3ea --- /dev/null +++ b/examples/ExternalUI/README.md @@ -0,0 +1,11 @@ +# External UI example + +This example will show how to use an external / remote UI together with DPF.
+ +The Plugin has a shell script that calls kdialog and uses qdbus for sending values to it.
+It is a very ugly way to show a remote UI (using a shell script!), but it is only to prove the point.
+ +Note that everything regarding external UIs is still a bit experimental in DPF.
+There is Unix-specific code in there.
+ +If this is something you are interested on using and contributing to, please let us know.