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.

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