Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

319 lines
10KB

  1. /*
  2. * Carla Plugin
  3. * Copyright (C) 2011-2014 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 2 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the doc/GPL.txt file.
  16. */
  17. #include "CarlaPlugin.hpp"
  18. #include "CarlaPluginThread.hpp"
  19. #include "CarlaEngine.hpp"
  20. #include "juce_core.h"
  21. using juce::String;
  22. using juce::StringArray;
  23. CARLA_BACKEND_START_NAMESPACE
  24. // -----------------------------------------------------------------------
  25. #ifdef DEBUG
  26. static inline
  27. const char* PluginThreadMode2str(const CarlaPluginThread::Mode mode) noexcept
  28. {
  29. switch (mode)
  30. {
  31. case CarlaPluginThread::PLUGIN_THREAD_NULL:
  32. return "PLUGIN_THREAD_NULL";
  33. case CarlaPluginThread::PLUGIN_THREAD_DSSI_GUI:
  34. return "PLUGIN_THREAD_DSSI_GUI";
  35. case CarlaPluginThread::PLUGIN_THREAD_LV2_GUI:
  36. return "PLUGIN_THREAD_LV2_GUI";
  37. case CarlaPluginThread::PLUGIN_THREAD_VST_GUI:
  38. return "PLUGIN_THREAD_VST_GUI";
  39. case CarlaPluginThread::PLUGIN_THREAD_BRIDGE:
  40. return "PLUGIN_THREAD_BRIDGE";
  41. }
  42. carla_stderr("CarlaPluginThread::PluginThreadMode2str(%i) - invalid mode", mode);
  43. return nullptr;
  44. }
  45. #endif
  46. // -----------------------------------------------------------------------
  47. CarlaPluginThread::CarlaPluginThread(CarlaBackend::CarlaEngine* const engine, CarlaBackend::CarlaPlugin* const plugin, const Mode mode) noexcept
  48. : CarlaThread("CarlaPluginThread"),
  49. fEngine(engine),
  50. fPlugin(plugin),
  51. fMode(mode),
  52. fProcess(nullptr)
  53. {
  54. carla_debug("CarlaPluginThread::CarlaPluginThread(%p, %p, %s)", engine, plugin, PluginThreadMode2str(mode));
  55. }
  56. CarlaPluginThread::~CarlaPluginThread() noexcept
  57. {
  58. carla_debug("CarlaPluginThread::~CarlaPluginThread()");
  59. if (fProcess != nullptr)
  60. {
  61. //fProcess.release();
  62. //try {
  63. //delete fProcess;
  64. //} CARLA_SAFE_EXCEPTION("~CarlaPluginThread(): delete ChildProcess");
  65. fProcess = nullptr;
  66. }
  67. }
  68. void CarlaPluginThread::setMode(const CarlaPluginThread::Mode mode) noexcept
  69. {
  70. CARLA_SAFE_ASSERT(! isThreadRunning());
  71. carla_debug("CarlaPluginThread::setMode(%s)", PluginThreadMode2str(mode));
  72. fMode = mode;
  73. }
  74. void CarlaPluginThread::setOscData(const char* const binary, const char* const label, const char* const extra1, const char* const extra2) noexcept
  75. {
  76. CARLA_SAFE_ASSERT(! isThreadRunning());
  77. carla_debug("CarlaPluginThread::setOscData(\"%s\", \"%s\", \"%s\", \"%s\")", binary, label, extra1, extra2);
  78. fBinary = binary;
  79. fLabel = label;
  80. fExtra1 = extra1;
  81. fExtra2 = extra2;
  82. }
  83. uintptr_t CarlaPluginThread::getPid() const
  84. {
  85. CARLA_SAFE_ASSERT_RETURN(fProcess != nullptr, 0);
  86. return 0;
  87. //return (uintptr_t)fProcess->pid();
  88. }
  89. void CarlaPluginThread::run()
  90. {
  91. carla_debug("CarlaPluginThread::run()");
  92. if (fProcess == nullptr)
  93. {
  94. fProcess = new ChildProcess;
  95. //fProcess->setProcessChannelMode(QProcess::ForwardedChannels);
  96. }
  97. else if (fProcess->isRunning())
  98. {
  99. carla_stderr("CarlaPluginThread::run() - already running, giving up...");
  100. switch (fMode)
  101. {
  102. case PLUGIN_THREAD_NULL:
  103. break;
  104. case PLUGIN_THREAD_DSSI_GUI:
  105. case PLUGIN_THREAD_LV2_GUI:
  106. case PLUGIN_THREAD_VST_GUI:
  107. fEngine->callback(CarlaBackend::ENGINE_CALLBACK_UI_STATE_CHANGED, fPlugin->getId(), 0, 0, 0.0f, nullptr);
  108. fProcess->kill();
  109. fProcess = nullptr;
  110. return;
  111. case PLUGIN_THREAD_BRIDGE:
  112. break;
  113. }
  114. }
  115. String name(fPlugin->getName());
  116. if (name.isEmpty())
  117. name = "(none)";
  118. if (fLabel.isEmpty())
  119. fLabel = "\"\"";
  120. StringArray arguments;
  121. #ifndef CARLA_OS_WIN
  122. if (fBinary.endsWith(".exe"))
  123. arguments.add("wine");
  124. #endif
  125. arguments.add(fBinary.buffer());
  126. switch (fMode)
  127. {
  128. case PLUGIN_THREAD_NULL:
  129. break;
  130. case PLUGIN_THREAD_DSSI_GUI:
  131. /* osc-url */ arguments.add(String(fEngine->getOscServerPathUDP()) + String("/") + String(fPlugin->getId()));
  132. /* filename */ arguments.add(fPlugin->getFilename());
  133. /* label */ arguments.add(fLabel.buffer());
  134. /* ui-title */ arguments.add(name + String(" (GUI)"));
  135. break;
  136. case PLUGIN_THREAD_LV2_GUI:
  137. /* osc-url */ arguments.add(String(fEngine->getOscServerPathUDP()) + String("/") + String(fPlugin->getId()));
  138. /* URI */ arguments.add(fLabel.buffer());
  139. /* ui-URI */ arguments.add(fExtra1.buffer());
  140. /* ui-title */ arguments.add(name + String(" (GUI)"));
  141. break;
  142. case PLUGIN_THREAD_VST_GUI:
  143. /* osc-url */ arguments.add(String(fEngine->getOscServerPathUDP()) + String("/") + String(fPlugin->getId()));
  144. /* filename */ arguments.add(fPlugin->getFilename());
  145. /* ui-title */ arguments.add(name + String(" (GUI)"));
  146. break;
  147. case PLUGIN_THREAD_BRIDGE:
  148. // FIXME
  149. carla_setenv("ENGINE_BRIDGE_SHM_IDS", fExtra2.buffer());
  150. carla_setenv("ENGINE_BRIDGE_CLIENT_NAME", name.toRawUTF8());
  151. carla_setenv("ENGINE_BRIDGE_OSC_URL", String(String(fEngine->getOscServerPathUDP()) + String("/") + String(fPlugin->getId())).toRawUTF8());
  152. carla_setenv("WINEDEBUG", "-all");
  153. /* osc-url */ arguments.add(String(fEngine->getOscServerPathUDP()) + String("/") + String(fPlugin->getId()));
  154. /* stype */ arguments.add(fExtra1.buffer());
  155. /* filename */ arguments.add(fPlugin->getFilename());
  156. /* name */ arguments.add(name);
  157. /* label */ arguments.add(fLabel.buffer());
  158. /* uniqueId */ arguments.add(String(static_cast<juce::int64>(fPlugin->getUniqueId())));
  159. break;
  160. }
  161. carla_stdout("starting app..");
  162. //qWarning() << arguments;
  163. fProcess->start(arguments);
  164. //fProcess->waitForStarted();
  165. switch (fMode)
  166. {
  167. case PLUGIN_THREAD_NULL:
  168. break;
  169. case PLUGIN_THREAD_DSSI_GUI:
  170. case PLUGIN_THREAD_LV2_GUI:
  171. case PLUGIN_THREAD_VST_GUI:
  172. if (fPlugin->waitForOscGuiShow())
  173. {
  174. while (fProcess->isRunning() && ! shouldThreadExit())
  175. carla_sleep(1);
  176. // we only get here if UI was closed or thread asked to exit
  177. if (fProcess->isRunning() && shouldThreadExit())
  178. {
  179. //fProcess->waitForFinished(static_cast<int>(fEngine->getOptions().uiBridgesTimeout));
  180. if (fProcess->isRunning())
  181. {
  182. carla_stdout("CarlaPluginThread::run() - UI refused to close, force kill now");
  183. fProcess->kill();
  184. }
  185. else
  186. {
  187. carla_stdout("CarlaPluginThread::run() - UI auto-closed successfully");
  188. }
  189. }
  190. else if (fProcess->getExitCode() != 0 /*|| fProcess->exitStatus() == QProcess::CrashExit*/)
  191. carla_stderr("CarlaPluginThread::run() - UI crashed while running");
  192. else
  193. carla_stdout("CarlaPluginThread::run() - UI closed cleanly");
  194. fEngine->callback(CarlaBackend::ENGINE_CALLBACK_UI_STATE_CHANGED, fPlugin->getId(), 0, 0, 0.0f, nullptr);
  195. }
  196. else
  197. {
  198. fProcess->kill();
  199. carla_stdout("CarlaPluginThread::run() - GUI timeout");
  200. fEngine->callback(CarlaBackend::ENGINE_CALLBACK_UI_STATE_CHANGED, fPlugin->getId(), 0, 0, 0.0f, nullptr);
  201. }
  202. break;
  203. case PLUGIN_THREAD_BRIDGE:
  204. //fProcess->waitForFinished(-1);
  205. while (fProcess->isRunning() && ! shouldThreadExit())
  206. carla_sleep(1);
  207. // we only get here if bridge crashed or thread asked to exit
  208. if (shouldThreadExit())
  209. {
  210. fProcess->getExitCode(); // TEST
  211. if (fProcess->isRunning())
  212. fProcess->kill();
  213. }
  214. else
  215. {
  216. // forced quit, may have crashed
  217. if (fProcess->getExitCode() != 0 /*|| fProcess->exitStatus() == QProcess::CrashExit*/)
  218. {
  219. carla_stderr("CarlaPluginThread::run() - bridge crashed");
  220. CarlaString errorString("Plugin '" + CarlaString(fPlugin->getName()) + "' has crashed!\n"
  221. "Saving now will lose its current settings.\n"
  222. "Please remove this plugin, and not rely on it from this point.");
  223. fEngine->callback(CarlaBackend::ENGINE_CALLBACK_ERROR, fPlugin->getId(), 0, 0, 0.0f, errorString);
  224. }
  225. }
  226. break;
  227. }
  228. carla_stdout("app finished");
  229. fProcess = nullptr;
  230. }
  231. // -----------------------------------------------------------------------
  232. #if 0
  233. QProcessEnvironment env(QProcessEnvironment::systemEnvironment());
  234. const EngineOptions& options(fEngine->getOptions());
  235. char strBuf[STR_MAX+1];
  236. env.insert("ENGINE_OPTION_UIS_ALWAYS_ON_TOP", options.uisAlwaysOnTop ? "true" : "false");
  237. if (options.maxParameters != 0)
  238. {
  239. std::sprintf(strBuf, "%u", options.maxParameters);
  240. env.insert("ENGINE_OPTION_MAX_PARAMETERS", strBuf);
  241. }
  242. if (options.uiBridgesTimeout != 0)
  243. {
  244. std::sprintf(strBuf, "%u", options.uiBridgesTimeout);
  245. env.insert("ENGINE_OPTION_UI_BRIDGES_TIMEOUT", strBuf);
  246. }
  247. if (options.frontendWinId != 0)
  248. {
  249. std::sprintf(strBuf, P_UINTPTR, options.frontendWinId);
  250. env.insert("ENGINE_OPTION_FRONTEND_WIN_ID", strBuf);
  251. }
  252. if (options.binaryDir != nullptr)
  253. env.insert("ENGINE_OPTION_PATH_BINARIES", options.binaryDir);
  254. if (options.resourceDir != nullptr)
  255. env.insert("ENGINE_OPTION_PATH_RESOURCES", options.resourceDir);
  256. fProcess->setProcessEnvironment(env);
  257. #endif
  258. // -----------------------------------------------------------------------
  259. CARLA_BACKEND_END_NAMESPACE