The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

387 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #include <JuceHeader.h>
  19. #include "UI/MainHostWindow.h"
  20. #include "Plugins/InternalPlugins.h"
  21. #if ! (JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_VST3 || JUCE_PLUGINHOST_AU)
  22. #error "If you're building the audio plugin host, you probably want to enable VST and/or AU support"
  23. #endif
  24. class PluginScannerSubprocess : private ChildProcessWorker,
  25. private AsyncUpdater
  26. {
  27. public:
  28. using ChildProcessWorker::initialiseFromCommandLine;
  29. private:
  30. void handleMessageFromCoordinator (const MemoryBlock& mb) override
  31. {
  32. if (mb.isEmpty())
  33. return;
  34. if (! doScan (mb))
  35. {
  36. {
  37. const std::lock_guard<std::mutex> lock (mutex);
  38. pendingBlocks.emplace (mb);
  39. }
  40. triggerAsyncUpdate();
  41. }
  42. }
  43. void handleConnectionLost() override
  44. {
  45. JUCEApplicationBase::quit();
  46. }
  47. void handleAsyncUpdate() override
  48. {
  49. for (;;)
  50. {
  51. const auto block = [&]() -> MemoryBlock
  52. {
  53. const std::lock_guard<std::mutex> lock (mutex);
  54. if (pendingBlocks.empty())
  55. return {};
  56. auto out = std::move (pendingBlocks.front());
  57. pendingBlocks.pop();
  58. return out;
  59. }();
  60. if (block.isEmpty())
  61. return;
  62. doScan (block);
  63. }
  64. }
  65. bool doScan (const MemoryBlock& block)
  66. {
  67. AudioPluginFormatManager formatManager;
  68. formatManager.addDefaultFormats();
  69. MemoryInputStream stream { block, false };
  70. const auto formatName = stream.readString();
  71. const auto identifier = stream.readString();
  72. PluginDescription pd;
  73. pd.fileOrIdentifier = identifier;
  74. pd.uniqueId = pd.deprecatedUid = 0;
  75. const auto matchingFormat = [&]() -> AudioPluginFormat*
  76. {
  77. for (auto* format : formatManager.getFormats())
  78. if (format->getName() == formatName)
  79. return format;
  80. return nullptr;
  81. }();
  82. if (matchingFormat == nullptr
  83. || (! MessageManager::getInstance()->isThisTheMessageThread()
  84. && ! matchingFormat->requiresUnblockedMessageThreadDuringCreation (pd)))
  85. {
  86. return false;
  87. }
  88. OwnedArray<PluginDescription> results;
  89. matchingFormat->findAllTypesForFile (results, identifier);
  90. sendPluginDescriptions (results);
  91. return true;
  92. }
  93. void sendPluginDescriptions (const OwnedArray<PluginDescription>& results)
  94. {
  95. XmlElement xml ("LIST");
  96. for (const auto& desc : results)
  97. xml.addChildElement (desc->createXml().release());
  98. const auto str = xml.toString();
  99. sendMessageToCoordinator ({ str.toRawUTF8(), str.getNumBytesAsUTF8() });
  100. }
  101. std::mutex mutex;
  102. std::queue<MemoryBlock> pendingBlocks;
  103. };
  104. //==============================================================================
  105. class PluginHostApp : public JUCEApplication,
  106. private AsyncUpdater
  107. {
  108. public:
  109. PluginHostApp() = default;
  110. void initialise (const String& commandLine) override
  111. {
  112. auto scannerSubprocess = std::make_unique<PluginScannerSubprocess>();
  113. if (scannerSubprocess->initialiseFromCommandLine (commandLine, processUID))
  114. {
  115. storedScannerSubprocess = std::move (scannerSubprocess);
  116. return;
  117. }
  118. // initialise our settings file..
  119. PropertiesFile::Options options;
  120. options.applicationName = "Juce Audio Plugin Host";
  121. options.filenameSuffix = "settings";
  122. options.osxLibrarySubFolder = "Preferences";
  123. appProperties.reset (new ApplicationProperties());
  124. appProperties->setStorageParameters (options);
  125. mainWindow.reset (new MainHostWindow());
  126. commandManager.registerAllCommandsForTarget (this);
  127. commandManager.registerAllCommandsForTarget (mainWindow.get());
  128. mainWindow->menuItemsChanged();
  129. // Important note! We're going to use an async update here so that if we need
  130. // to re-open a file and instantiate some plugins, it will happen AFTER this
  131. // initialisation method has returned.
  132. // On Windows this probably won't make a difference, but on OSX there's a subtle event loop
  133. // issue that can happen if a plugin runs one of those irritating modal dialogs while it's
  134. // being loaded. If that happens inside this method, the OSX event loop seems to be in some
  135. // kind of special "initialisation" mode and things get confused. But if we load the plugin
  136. // later when the normal event loop is running, everything's fine.
  137. triggerAsyncUpdate();
  138. }
  139. void handleAsyncUpdate() override
  140. {
  141. File fileToOpen;
  142. #if JUCE_ANDROID || JUCE_IOS
  143. fileToOpen = PluginGraph::getDefaultGraphDocumentOnMobile();
  144. #else
  145. for (int i = 0; i < getCommandLineParameterArray().size(); ++i)
  146. {
  147. fileToOpen = File::getCurrentWorkingDirectory().getChildFile (getCommandLineParameterArray()[i]);
  148. if (fileToOpen.existsAsFile())
  149. break;
  150. }
  151. #endif
  152. if (! fileToOpen.existsAsFile())
  153. {
  154. RecentlyOpenedFilesList recentFiles;
  155. recentFiles.restoreFromString (getAppProperties().getUserSettings()->getValue ("recentFilterGraphFiles"));
  156. if (recentFiles.getNumFiles() > 0)
  157. fileToOpen = recentFiles.getFile (0);
  158. }
  159. if (fileToOpen.existsAsFile())
  160. if (auto* graph = mainWindow->graphHolder.get())
  161. if (auto* ioGraph = graph->graph.get())
  162. ioGraph->loadFrom (fileToOpen, true);
  163. }
  164. void shutdown() override
  165. {
  166. mainWindow = nullptr;
  167. appProperties = nullptr;
  168. LookAndFeel::setDefaultLookAndFeel (nullptr);
  169. }
  170. void suspended() override
  171. {
  172. #if JUCE_ANDROID || JUCE_IOS
  173. if (auto graph = mainWindow->graphHolder.get())
  174. if (auto ioGraph = graph->graph.get())
  175. ioGraph->saveDocument (PluginGraph::getDefaultGraphDocumentOnMobile());
  176. #endif
  177. }
  178. void systemRequestedQuit() override
  179. {
  180. if (mainWindow != nullptr)
  181. mainWindow->tryToQuitApplication();
  182. else
  183. JUCEApplicationBase::quit();
  184. }
  185. bool backButtonPressed() override
  186. {
  187. if (mainWindow->graphHolder != nullptr)
  188. mainWindow->graphHolder->hideLastSidePanel();
  189. return true;
  190. }
  191. const String getApplicationName() override { return "Juce Plug-In Host"; }
  192. const String getApplicationVersion() override { return ProjectInfo::versionString; }
  193. bool moreThanOneInstanceAllowed() override { return true; }
  194. ApplicationCommandManager commandManager;
  195. std::unique_ptr<ApplicationProperties> appProperties;
  196. private:
  197. std::unique_ptr<MainHostWindow> mainWindow;
  198. std::unique_ptr<PluginScannerSubprocess> storedScannerSubprocess;
  199. };
  200. static PluginHostApp& getApp() { return *dynamic_cast<PluginHostApp*>(JUCEApplication::getInstance()); }
  201. ApplicationProperties& getAppProperties() { return *getApp().appProperties; }
  202. ApplicationCommandManager& getCommandManager() { return getApp().commandManager; }
  203. bool isOnTouchDevice()
  204. {
  205. static bool isTouch = Desktop::getInstance().getMainMouseSource().isTouch();
  206. return isTouch;
  207. }
  208. //==============================================================================
  209. static AutoScale autoScaleFromString (StringRef str)
  210. {
  211. if (str.isEmpty()) return AutoScale::useDefault;
  212. if (str == CharPointer_ASCII { "0" }) return AutoScale::scaled;
  213. if (str == CharPointer_ASCII { "1" }) return AutoScale::unscaled;
  214. jassertfalse;
  215. return AutoScale::useDefault;
  216. }
  217. static const char* autoScaleToString (AutoScale autoScale)
  218. {
  219. if (autoScale == AutoScale::scaled) return "0";
  220. if (autoScale == AutoScale::unscaled) return "1";
  221. return {};
  222. }
  223. AutoScale getAutoScaleValueForPlugin (const String& identifier)
  224. {
  225. if (identifier.isNotEmpty())
  226. {
  227. auto plugins = StringArray::fromLines (getAppProperties().getUserSettings()->getValue ("autoScalePlugins"));
  228. plugins.removeEmptyStrings();
  229. for (auto& plugin : plugins)
  230. {
  231. auto fromIdentifier = plugin.fromFirstOccurrenceOf (identifier, false, false);
  232. if (fromIdentifier.isNotEmpty())
  233. return autoScaleFromString (fromIdentifier.fromFirstOccurrenceOf (":", false, false));
  234. }
  235. }
  236. return AutoScale::useDefault;
  237. }
  238. void setAutoScaleValueForPlugin (const String& identifier, AutoScale s)
  239. {
  240. auto plugins = StringArray::fromLines (getAppProperties().getUserSettings()->getValue ("autoScalePlugins"));
  241. plugins.removeEmptyStrings();
  242. auto index = [identifier, plugins]
  243. {
  244. auto it = std::find_if (plugins.begin(), plugins.end(),
  245. [&] (const String& str) { return str.startsWith (identifier); });
  246. return (int) std::distance (plugins.begin(), it);
  247. }();
  248. if (s == AutoScale::useDefault && index != plugins.size())
  249. {
  250. plugins.remove (index);
  251. }
  252. else
  253. {
  254. auto str = identifier + ":" + autoScaleToString (s);
  255. if (index != plugins.size())
  256. plugins.getReference (index) = str;
  257. else
  258. plugins.add (str);
  259. }
  260. getAppProperties().getUserSettings()->setValue ("autoScalePlugins", plugins.joinIntoString ("\n"));
  261. }
  262. static bool isAutoScaleAvailableForPlugin (const PluginDescription& description)
  263. {
  264. return autoScaleOptionAvailable
  265. && description.pluginFormatName.containsIgnoreCase ("VST");
  266. }
  267. bool shouldAutoScalePlugin (const PluginDescription& description)
  268. {
  269. if (! isAutoScaleAvailableForPlugin (description))
  270. return false;
  271. const auto scaleValue = getAutoScaleValueForPlugin (description.fileOrIdentifier);
  272. return (scaleValue == AutoScale::scaled
  273. || (scaleValue == AutoScale::useDefault
  274. && getAppProperties().getUserSettings()->getBoolValue ("autoScalePluginWindows")));
  275. }
  276. void addPluginAutoScaleOptionsSubMenu (AudioPluginInstance* pluginInstance,
  277. PopupMenu& menu)
  278. {
  279. if (pluginInstance == nullptr)
  280. return;
  281. auto description = pluginInstance->getPluginDescription();
  282. if (! isAutoScaleAvailableForPlugin (description))
  283. return;
  284. auto identifier = description.fileOrIdentifier;
  285. PopupMenu autoScaleMenu;
  286. autoScaleMenu.addItem ("Default",
  287. true,
  288. getAutoScaleValueForPlugin (identifier) == AutoScale::useDefault,
  289. [identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::useDefault); });
  290. autoScaleMenu.addItem ("Enabled",
  291. true,
  292. getAutoScaleValueForPlugin (identifier) == AutoScale::scaled,
  293. [identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::scaled); });
  294. autoScaleMenu.addItem ("Disabled",
  295. true,
  296. getAutoScaleValueForPlugin (identifier) == AutoScale::unscaled,
  297. [identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::unscaled); });
  298. menu.addSubMenu ("Auto-scale window", autoScaleMenu);
  299. }
  300. // This kicks the whole thing off..
  301. START_JUCE_APPLICATION (PluginHostApp)