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