DPF

DISTRHO Plugin Framework
Log | Files | Refs | Submodules | README | LICENSE

ExternalExampleUI.cpp (7436B)


      1 /*
      2  * DISTRHO Plugin Framework (DPF)
      3  * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
      4  *
      5  * Permission to use, copy, modify, and/or distribute this software for any purpose with
      6  * or without fee is hereby granted, provided that the above copyright notice and this
      7  * permission notice appear in all copies.
      8  *
      9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
     10  * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
     11  * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
     12  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
     13  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
     14  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 // needed for IDE
     18 #include "DistrhoPluginInfo.h"
     19 
     20 #include "DistrhoUI.hpp"
     21 
     22 #define MPV_TEST
     23 // #define KDE_FIFO_TEST
     24 
     25 #ifdef KDE_FIFO_TEST
     26 // Extra includes for current path and fifo stuff
     27 #include <dlfcn.h>
     28 #include <fcntl.h>
     29 #include <sys/stat.h>
     30 #include <sys/types.h>
     31 #endif
     32 
     33 START_NAMESPACE_DISTRHO
     34 
     35 #ifdef KDE_FIFO_TEST
     36 // TODO: generate a random, not-yet-existing, filename
     37 const char* const kFifoFilename = "/tmp/dpf-fifo-test";
     38 
     39 // Helper to get current path of this plugin
     40 static const char* getCurrentPluginFilename()
     41 {
     42     Dl_info exeInfo;
     43     void* localSymbol = (void*)kFifoFilename;
     44     dladdr(localSymbol, &exeInfo);
     45     return exeInfo.dli_fname;
     46 }
     47 
     48 // Helper to check if a file exists
     49 static bool fileExists(const char* const filename)
     50 {
     51     return access(filename, F_OK) != -1;
     52 }
     53 
     54 // Helper function to keep trying to write until it succeeds or really errors out
     55 static ssize_t
     56 writeRetry(int fd, const void* src, size_t size)
     57 {
     58     ssize_t error;
     59     int attempts = 0;
     60 
     61     do {
     62         error = write(fd, src, size);
     63     } while (error == -1 && (errno == EINTR || errno == EPIPE) && ++attempts < 5);
     64 
     65     return error;
     66 }
     67 #endif
     68 
     69 // -----------------------------------------------------------------------------------------------------------
     70 
     71 class ExternalExampleUI : public UI
     72 {
     73 public:
     74     ExternalExampleUI()
     75         : UI(405, 256),
     76          #ifdef KDE_FIFO_TEST
     77           fFifo(-1),
     78           fExternalScript(getNextBundlePath()),
     79          #endif
     80           fValue(0.0f)
     81     {
     82        #ifdef KDE_FIFO_TEST
     83         if (fExternalScript.isEmpty())
     84         {
     85             fExternalScript = getCurrentPluginFilename();
     86             fExternalScript.truncate(fExternalScript.rfind('/'));
     87         }
     88 
     89         fExternalScript += "/ExternalLauncher.sh";
     90         d_stdout("External script = %s", fExternalScript.buffer());
     91        #endif
     92 
     93         if (isVisible() || isEmbed())
     94             visibilityChanged(true);
     95     }
     96 
     97     ~ExternalExampleUI()
     98     {
     99         if (isEmbed())
    100             terminateAndWaitForExternalProcess();
    101     }
    102 
    103 protected:
    104    /* --------------------------------------------------------------------------------------------------------
    105     * DSP/Plugin Callbacks */
    106 
    107    /**
    108       A parameter has changed on the plugin side.
    109       This is called by the host to inform the UI about parameter changes.
    110     */
    111     void parameterChanged(uint32_t index, float value) override
    112     {
    113         if (index != 0)
    114             return;
    115 
    116         fValue = value;
    117 
    118        #ifdef KDE_FIFO_TEST
    119         if (fFifo == -1)
    120             return;
    121 
    122         // NOTE: This is a terrible way to pass values, also locale might get in the way...
    123         char valueStr[24];
    124         std::memset(valueStr, 0, sizeof(valueStr));
    125         std::snprintf(valueStr, 23, "%i\n", static_cast<int>(value + 0.5f));
    126 
    127         DISTRHO_SAFE_ASSERT(writeRetry(fFifo, valueStr, 24) == sizeof(valueStr));
    128        #endif
    129     }
    130 
    131    /* --------------------------------------------------------------------------------------------------------
    132     * External Window overrides */
    133 
    134    /**
    135       Keep-alive.
    136     */
    137     void uiIdle() override
    138     {
    139        #ifdef KDE_FIFO_TEST
    140         if (fFifo == -1)
    141             return;
    142 
    143         writeRetry(fFifo, "idle\n", 5);
    144        #endif
    145     }
    146 
    147    /**
    148       Manage external process and IPC when UI is requested to be visible.
    149     */
    150     void visibilityChanged(const bool visible) override
    151     {
    152        #ifdef KDE_FIFO_TEST
    153         if (visible)
    154         {
    155             DISTRHO_SAFE_ASSERT_RETURN(fileExists(fExternalScript),);
    156 
    157             mkfifo(kFifoFilename, 0666);
    158             sync();
    159 
    160             char winIdStr[24];
    161             std::memset(winIdStr, 0, sizeof(winIdStr));
    162             std::snprintf(winIdStr, 23, "%lu", getTransientWindowId());
    163 
    164             const char* args[] = {
    165                 fExternalScript.buffer(),
    166                 kFifoFilename,
    167                 "--progressbar", "External UI example",
    168                 "--title", getTitle(),
    169                 nullptr,
    170             };
    171             DISTRHO_SAFE_ASSERT_RETURN(startExternalProcess(args),);
    172 
    173             // NOTE: this can lockup the current thread if the other side does not read the file!
    174             fFifo = open(kFifoFilename, O_WRONLY);
    175             DISTRHO_SAFE_ASSERT_RETURN(fFifo != -1,);
    176 
    177             parameterChanged(0, fValue);
    178         }
    179         else
    180         {
    181             if (fFifo != -1)
    182             {
    183                 if (isRunning())
    184                 {
    185                     DISTRHO_SAFE_ASSERT(writeRetry(fFifo, "quit\n", 5) == 5);
    186                     fsync(fFifo);
    187                 }
    188                 ::close(fFifo);
    189                 fFifo = -1;
    190             }
    191 
    192             unlink(kFifoFilename);
    193             terminateAndWaitForExternalProcess();
    194         }
    195        #endif
    196        #ifdef MPV_TEST
    197         if (visible)
    198         {
    199             const char* const file = "/home/falktx/Videos/HD/"; // TODO make this a state file?
    200 
    201             if (isEmbed())
    202             {
    203                 char winIdStr[64];
    204                 snprintf(winIdStr, sizeof(winIdStr), "--wid=%lu", getParentWindowHandle());
    205                 const char* args[] = {
    206                     "mpv",
    207                     "--ao=jack",
    208                     winIdStr,
    209                     file,
    210                     nullptr
    211                 };
    212                 unsetenv("LD_LIBRARY_PATH");
    213                 startExternalProcess(args);
    214             }
    215             else
    216             {
    217                 const char* args[] = {
    218                     "mpv",
    219                     "--ao=jack",
    220                     file,
    221                     nullptr
    222                 };
    223                 startExternalProcess(args);
    224             }
    225         }
    226         else
    227         {
    228             terminateAndWaitForExternalProcess();
    229         }
    230        #endif
    231     }
    232 
    233     // -------------------------------------------------------------------------------------------------------
    234 
    235 private:
    236    #ifdef KDE_FIFO_TEST
    237     // IPC Stuff
    238     int fFifo;
    239 
    240     // Path to external ui script
    241     String fExternalScript;
    242    #endif
    243 
    244     // Current value, cached for when UI becomes visible
    245     float fValue;
    246 
    247    /**
    248       Set our UI class as non-copyable and add a leak detector just in case.
    249     */
    250     DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ExternalExampleUI)
    251 };
    252 
    253 /* ------------------------------------------------------------------------------------------------------------
    254  * UI entry point, called by DPF to create a new UI instance. */
    255 
    256 UI* createUI()
    257 {
    258     return new ExternalExampleUI();
    259 }
    260 
    261 // -----------------------------------------------------------------------------------------------------------
    262 
    263 END_NAMESPACE_DISTRHO