/* * DISTRHO Plugin Framework (DPF) * Copyright (C) 2012-2019 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), 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