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.

302 lines
9.9KB

  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 <QtCore/QProcess>
  21. CARLA_BACKEND_START_NAMESPACE
  22. static const char* PluginThreadMode2str(const CarlaPluginThread::Mode mode)
  23. {
  24. switch (mode)
  25. {
  26. case CarlaPluginThread::PLUGIN_THREAD_NULL:
  27. return "PLUGIN_THREAD_NULL";
  28. case CarlaPluginThread::PLUGIN_THREAD_DSSI_GUI:
  29. return "PLUGIN_THREAD_DSSI_GUI";
  30. case CarlaPluginThread::PLUGIN_THREAD_LV2_GUI:
  31. return "PLUGIN_THREAD_LV2_GUI";
  32. case CarlaPluginThread::PLUGIN_THREAD_VST_GUI:
  33. return "PLUGIN_THREAD_VST_GUI";
  34. case CarlaPluginThread::PLUGIN_THREAD_BRIDGE:
  35. return "PLUGIN_THREAD_BRIDGE";
  36. }
  37. carla_stderr("CarlaPluginThread::PluginThreadMode2str(%i) - invalid mode", mode);
  38. return nullptr;
  39. }
  40. CarlaPluginThread::CarlaPluginThread(CarlaBackend::CarlaEngine* const engine, CarlaBackend::CarlaPlugin* const plugin, const Mode mode)
  41. : CarlaThread("CarlaPluginThread"),
  42. fEngine(engine),
  43. fPlugin(plugin),
  44. fMode(mode),
  45. fProcess(nullptr)
  46. {
  47. carla_debug("CarlaPluginThread::CarlaPluginThread(%p, %p, %s)", engine, plugin, PluginThreadMode2str(mode));
  48. }
  49. CarlaPluginThread::~CarlaPluginThread()
  50. {
  51. carla_debug("CarlaPluginThread::~CarlaPluginThread()");
  52. if (fProcess != nullptr)
  53. {
  54. delete fProcess;
  55. fProcess = nullptr;
  56. }
  57. }
  58. void CarlaPluginThread::setMode(const CarlaPluginThread::Mode mode)
  59. {
  60. CARLA_ASSERT(! isRunning());
  61. carla_debug("CarlaPluginThread::setMode(%s)", PluginThreadMode2str(mode));
  62. fMode = mode;
  63. }
  64. void CarlaPluginThread::setOscData(const char* const binary, const char* const label, const char* const extra1, const char* const extra2)
  65. {
  66. CARLA_ASSERT(! isRunning());
  67. carla_debug("CarlaPluginThread::setOscData(\"%s\", \"%s\", \"%s\", \"%s\")", binary, label, extra1, extra2);
  68. fBinary = binary;
  69. fLabel = label;
  70. fExtra1 = extra1;
  71. fExtra2 = extra2;
  72. }
  73. ulong CarlaPluginThread::getPid() const
  74. {
  75. CARLA_SAFE_ASSERT_RETURN(fProcess != nullptr, 0);
  76. return (ulong)fProcess->pid();
  77. }
  78. void CarlaPluginThread::run()
  79. {
  80. carla_debug("CarlaPluginThread::run()");
  81. if (fProcess == nullptr)
  82. {
  83. fProcess = new QProcess(nullptr);
  84. fProcess->setProcessChannelMode(QProcess::ForwardedChannels);
  85. }
  86. else if (fProcess->state() == QProcess::Running)
  87. {
  88. carla_stderr("CarlaPluginThread::run() - already running, giving up...");
  89. switch (fMode)
  90. {
  91. case PLUGIN_THREAD_NULL:
  92. break;
  93. case PLUGIN_THREAD_DSSI_GUI:
  94. case PLUGIN_THREAD_LV2_GUI:
  95. case PLUGIN_THREAD_VST_GUI:
  96. fEngine->callback(CarlaBackend::ENGINE_CALLBACK_UI_STATE_CHANGED, fPlugin->getId(), -1, 0, 0.0f, nullptr);
  97. fProcess->terminate();
  98. return;
  99. case PLUGIN_THREAD_BRIDGE:
  100. break;
  101. }
  102. }
  103. QString name(fPlugin->getName());
  104. if (name.isEmpty())
  105. name = "(none)";
  106. QStringList arguments;
  107. QProcessEnvironment env(QProcessEnvironment::systemEnvironment());
  108. const EngineOptions& options(fEngine->getOptions());
  109. char strBuf[STR_MAX+1];
  110. env.insert("ENGINE_OPTION_UIS_ALWAYS_ON_TOP", options.uisAlwaysOnTop ? "true" : "false");
  111. if (options.maxParameters != 0)
  112. {
  113. std::sprintf(strBuf, "%u", options.maxParameters);
  114. env.insert("ENGINE_OPTION_MAX_PARAMETERS", strBuf);
  115. }
  116. if (options.uiBridgesTimeout != 0)
  117. {
  118. std::sprintf(strBuf, "%u", options.uiBridgesTimeout);
  119. env.insert("ENGINE_OPTION_UI_BRIDGES_TIMEOUT", strBuf);
  120. }
  121. if (options.frontendWinId != 0)
  122. {
  123. std::sprintf(strBuf, P_INTPTR, options.frontendWinId);
  124. env.insert("ENGINE_OPTION_FRONTEND_WIN_ID", strBuf);
  125. }
  126. if (options.binaryDir != nullptr)
  127. env.insert("ENGINE_OPTION_PATH_BINARIES", options.binaryDir);
  128. if (options.resourceDir != nullptr)
  129. env.insert("ENGINE_OPTION_PATH_RESOURCES", options.resourceDir);
  130. switch (fMode)
  131. {
  132. case PLUGIN_THREAD_NULL:
  133. break;
  134. case PLUGIN_THREAD_DSSI_GUI:
  135. /* osc-url */ arguments << QString("%1/%2").arg(fEngine->getOscServerPathUDP()).arg(fPlugin->getId());
  136. /* filename */ arguments << fPlugin->getFilename();
  137. /* label */ arguments << (const char*)fLabel;
  138. /* ui-title */ arguments << QString("%1 (GUI)").arg(fPlugin->getName());
  139. break;
  140. case PLUGIN_THREAD_LV2_GUI:
  141. /* osc-url */ arguments << QString("%1/%2").arg(fEngine->getOscServerPathUDP()).arg(fPlugin->getId());
  142. /* URI */ arguments << (const char*)fLabel;
  143. /* ui-URI */ arguments << (const char*)fExtra1;
  144. /* ui-title */ arguments << QString("%1 (GUI)").arg(fPlugin->getName());
  145. break;
  146. case PLUGIN_THREAD_VST_GUI:
  147. /* osc-url */ arguments << QString("%1/%2").arg(fEngine->getOscServerPathUDP()).arg(fPlugin->getId());
  148. /* filename */ arguments << fPlugin->getFilename();
  149. /* ui-title */ arguments << QString("%1 (GUI)").arg(fPlugin->getName());
  150. break;
  151. case PLUGIN_THREAD_BRIDGE:
  152. env.insert("ENGINE_BRIDGE_SHM_IDS", fExtra2.getBuffer());
  153. if (fPlugin->getType() != PLUGIN_JACK)
  154. {
  155. /* osc-url */ arguments << QString("%1/%2").arg(fEngine->getOscServerPathUDP()).arg(fPlugin->getId());
  156. /* stype */ arguments << (const char*)fExtra1;
  157. /* filename */ arguments << fPlugin->getFilename();
  158. /* name */ arguments << name;
  159. /* label */ arguments << (const char*)fLabel;
  160. }
  161. else
  162. {
  163. /* filename */ arguments << fPlugin->getFilename();
  164. }
  165. break;
  166. }
  167. fProcess->setProcessEnvironment(env);
  168. fProcess->start((const char*)fBinary, arguments);
  169. fProcess->waitForStarted();
  170. switch (fMode)
  171. {
  172. case PLUGIN_THREAD_NULL:
  173. break;
  174. case PLUGIN_THREAD_DSSI_GUI:
  175. case PLUGIN_THREAD_LV2_GUI:
  176. case PLUGIN_THREAD_VST_GUI:
  177. if (fPlugin->waitForOscGuiShow())
  178. {
  179. //fProcess->waitForFinished(-1);
  180. while (fProcess->state() != QProcess::NotRunning && ! shouldExit())
  181. carla_sleep(1);
  182. // we only get here if UI was closed or thread asked to exit
  183. if (fProcess->state() != QProcess::NotRunning && shouldExit())
  184. {
  185. fProcess->waitForFinished(static_cast<int>(fEngine->getOptions().uiBridgesTimeout));
  186. if (fProcess->state() == QProcess::Running)
  187. {
  188. carla_stdout("CarlaPluginThread::run() - UI refused to close, force kill now");
  189. fProcess->kill();
  190. }
  191. else
  192. {
  193. carla_stdout("CarlaPluginThread::run() - UI auto-closed successfully");
  194. }
  195. fEngine->callback(CarlaBackend::ENGINE_CALLBACK_UI_STATE_CHANGED, fPlugin->getId(), 0, 0, 0.0f, nullptr);
  196. }
  197. else if (fProcess->exitCode() != 0 || fProcess->exitStatus() == QProcess::CrashExit)
  198. {
  199. carla_stderr("CarlaPluginThread::run() - UI crashed while running");
  200. fEngine->callback(CarlaBackend::ENGINE_CALLBACK_UI_STATE_CHANGED, fPlugin->getId(), -1, 0, 0.0f, nullptr);
  201. }
  202. else
  203. {
  204. carla_stdout("CarlaPluginThread::run() - UI closed cleanly");
  205. fEngine->callback(CarlaBackend::ENGINE_CALLBACK_UI_STATE_CHANGED, fPlugin->getId(), 0, 0, 0.0f, nullptr);
  206. }
  207. }
  208. else
  209. {
  210. fProcess->close();
  211. CARLA_ASSERT(fProcess->state() == QProcess::NotRunning);
  212. if (fProcess->exitCode() != 0 || fProcess->exitStatus() == QProcess::CrashExit)
  213. {
  214. carla_stderr("CarlaPluginThread::run() - GUI crashed while opening");
  215. fEngine->callback(CarlaBackend::ENGINE_CALLBACK_UI_STATE_CHANGED, fPlugin->getId(), -1, 0, 0.0f, nullptr);
  216. }
  217. else
  218. {
  219. carla_debug("CarlaPluginThread::run() - GUI timeout");
  220. fEngine->callback(CarlaBackend::ENGINE_CALLBACK_UI_STATE_CHANGED, fPlugin->getId(), 0, 0, 0.0f, nullptr);
  221. }
  222. }
  223. break;
  224. case PLUGIN_THREAD_BRIDGE:
  225. //fProcess->waitForFinished(-1);
  226. while (fProcess->state() != QProcess::NotRunning && ! shouldExit())
  227. carla_sleep(1);
  228. // we only get here if bridge crashed or thread asked to exit
  229. if (shouldExit())
  230. {
  231. fProcess->waitForFinished(500);
  232. if (fProcess->state() == QProcess::Running)
  233. fProcess->close();
  234. }
  235. else
  236. {
  237. // forced quit, may have crashed
  238. if (fProcess->exitCode() != 0 || fProcess->exitStatus() == QProcess::CrashExit)
  239. {
  240. carla_stderr("CarlaPluginThread::run() - bridge crashed");
  241. CarlaString errorString("Plugin '" + CarlaString(fPlugin->getName()) + "' has crashed!\n"
  242. "Saving now will lose its current settings.\n"
  243. "Please remove this plugin, and not rely on it from this point.");
  244. fEngine->callback(CarlaBackend::ENGINE_CALLBACK_ERROR, fPlugin->getId(), 0, 0, 0.0f, (const char*)errorString);
  245. }
  246. }
  247. break;
  248. }
  249. }
  250. CARLA_BACKEND_END_NAMESPACE