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.

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