1 /*
2  * DISTRHO Plugin Framework (DPF)
3  * Copyright (C) 2012-2019 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 #include "DistrhoUI.hpp"
18 
19 // Extra includes for current path and fifo stuff
20 #include <dlfcn.h>
21 #include <fcntl.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 
25 START_NAMESPACE_DISTRHO
26 
27 // TODO: generate a random, not-yet-existing, filename
28 const char* const kFifoFilename = "/tmp/dpf-fifo-test";
29 
30 // Helper to get current path of this plugin
getCurrentPluginFilename()31 static const char* getCurrentPluginFilename()
32 {
33     Dl_info exeInfo;
34     void* localSymbol = (void*)kFifoFilename;
35     dladdr(localSymbol, &exeInfo);
36     return exeInfo.dli_fname;
37 }
38 
39 // Helper to check if a file exists
fileExists(const char * const filename)40 static bool fileExists(const char* const filename)
41 {
42     return access(filename, F_OK) != -1;
43 }
44 
45 // Helper function to keep trying to write until it succeeds or really errors out
46 static ssize_t
writeRetry(int fd,const void * src,size_t size)47 writeRetry(int fd, const void* src, size_t size)
48 {
49 	ssize_t error;
50 
51 	do {
52 		error = write(fd, src, size);
53 	} while (error == -1 && (errno == EINTR || errno == EPIPE));
54 
55 	return error;
56 }
57 
58 // -----------------------------------------------------------------------------------------------------------
59 
60 class ExternalExampleUI : public UI
61 {
62 public:
ExternalExampleUI()63     ExternalExampleUI()
64         : UI(405, 256),
65           fFifo(-1),
66           fValue(0.0f),
67           fExternalScript(getNextBundlePath())
68     {
69         if (fExternalScript.isEmpty())
70         {
71             fExternalScript = getCurrentPluginFilename();
72             fExternalScript.truncate(fExternalScript.rfind('/'));
73         }
74 
75         fExternalScript += "/d_extui.sh";
76         d_stdout("External script = %s", fExternalScript.buffer());
77     }
78 
79 protected:
80    /* --------------------------------------------------------------------------------------------------------
81     * DSP/Plugin Callbacks */
82 
83    /**
84       A parameter has changed on the plugin side.
85       This is called by the host to inform the UI about parameter changes.
86     */
parameterChanged(uint32_t index,float value)87     void parameterChanged(uint32_t index, float value) override
88     {
89         if (index != 0)
90             return;
91 
92         fValue = value;
93 
94         if (fFifo == -1)
95             return;
96 
97         // NOTE: This is a terrible way to pass values, also locale might get in the way...
98         char valueStr[24];
99         std::memset(valueStr, 0, sizeof(valueStr));
100         std::snprintf(valueStr, 23, "%i\n", static_cast<int>(value + 0.5f));
101 
102         DISTRHO_SAFE_ASSERT(writeRetry(fFifo, valueStr, 24) == sizeof(valueStr));
103     }
104 
105    /* --------------------------------------------------------------------------------------------------------
106     * External Window overrides */
107 
108    /**
109       Manage external process and IPC when UI is requested to be visible.
110     */
setVisible(const bool yesNo)111     void setVisible(const bool yesNo) override
112     {
113         if (yesNo)
114         {
115             DISTRHO_SAFE_ASSERT_RETURN(fileExists(fExternalScript),);
116 
117             mkfifo(kFifoFilename, 0666);
118             sync();
119 
120             char winIdStr[24];
121             std::memset(winIdStr, 0, sizeof(winIdStr));
122             std::snprintf(winIdStr, 23, "%lu", getTransientWinId());
123 
124             const char* args[] = {
125                 fExternalScript.buffer(),
126                 kFifoFilename,
127                 "--progressbar", "External UI example",
128                 "--title", getTitle(),
129                 nullptr,
130             };
131             DISTRHO_SAFE_ASSERT_RETURN(startExternalProcess(args),);
132 
133             // NOTE: this can lockup the current thread if the other side does not read the file!
134             fFifo = open(kFifoFilename, O_WRONLY);
135             DISTRHO_SAFE_ASSERT_RETURN(fFifo != -1,);
136 
137             parameterChanged(0, fValue);
138         }
139         else
140         {
141             if (fFifo != -1)
142             {
143                 if (isRunning())
144                 {
145                     DISTRHO_SAFE_ASSERT(writeRetry(fFifo, "quit\n", 5) == 5);
146                     fsync(fFifo);
147                 }
148                 close(fFifo);
149                 fFifo = -1;
150             }
151 
152             unlink(kFifoFilename);
153             terminateAndWaitForProcess();
154         }
155 
156         UI::setVisible(yesNo);
157     }
158 
159     // -------------------------------------------------------------------------------------------------------
160 
161 private:
162     // IPC Stuff
163     int fFifo;
164 
165     // Current value, cached for when UI becomes visible
166     float fValue;
167 
168     // Path to external ui script
169     String fExternalScript;
170 
171    /**
172       Set our UI class as non-copyable and add a leak detector just in case.
173     */
174     DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ExternalExampleUI)
175 };
176 
177 /* ------------------------------------------------------------------------------------------------------------
178  * UI entry point, called by DPF to create a new UI instance. */
179 
createUI()180 UI* createUI()
181 {
182     return new ExternalExampleUI();
183 }
184 
185 // -----------------------------------------------------------------------------------------------------------
186 
187 END_NAMESPACE_DISTRHO
188