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.

388 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. mainWindow->setUsingNativeTitleBar (true);
  127. commandManager.registerAllCommandsForTarget (this);
  128. commandManager.registerAllCommandsForTarget (mainWindow.get());
  129. mainWindow->menuItemsChanged();
  130. // Important note! We're going to use an async update here so that if we need
  131. // to re-open a file and instantiate some plugins, it will happen AFTER this
  132. // initialisation method has returned.
  133. // On Windows this probably won't make a difference, but on OSX there's a subtle event loop
  134. // issue that can happen if a plugin runs one of those irritating modal dialogs while it's
  135. // being loaded. If that happens inside this method, the OSX event loop seems to be in some
  136. // kind of special "initialisation" mode and things get confused. But if we load the plugin
  137. // later when the normal event loop is running, everything's fine.
  138. triggerAsyncUpdate();
  139. }
  140. void handleAsyncUpdate() override
  141. {
  142. File fileToOpen;
  143. #if JUCE_ANDROID || JUCE_IOS
  144. fileToOpen = PluginGraph::getDefaultGraphDocumentOnMobile();
  145. #else
  146. for (int i = 0; i < getCommandLineParameterArray().size(); ++i)
  147. {
  148. fileToOpen = File::getCurrentWorkingDirectory().getChildFile (getCommandLineParameterArray()[i]);
  149. if (fileToOpen.existsAsFile())
  150. break;
  151. }
  152. #endif
  153. if (! fileToOpen.existsAsFile())
  154. {
  155. RecentlyOpenedFilesList recentFiles;
  156. recentFiles.restoreFromString (getAppProperties().getUserSettings()->getValue ("recentFilterGraphFiles"));
  157. if (recentFiles.getNumFiles() > 0)
  158. fileToOpen = recentFiles.getFile (0);
  159. }
  160. if (fileToOpen.existsAsFile())
  161. if (auto* graph = mainWindow->graphHolder.get())
  162. if (auto* ioGraph = graph->graph.get())
  163. ioGraph->loadFrom (fileToOpen, true);
  164. }
  165. void shutdown() override
  166. {
  167. mainWindow = nullptr;
  168. appProperties = nullptr;
  169. LookAndFeel::setDefaultLookAndFeel (nullptr);
  170. }
  171. void suspended() override
  172. {
  173. #if JUCE_ANDROID || JUCE_IOS
  174. if (auto graph = mainWindow->graphHolder.get())
  175. if (auto ioGraph = graph->graph.get())
  176. ioGraph->saveDocument (PluginGraph::getDefaultGraphDocumentOnMobile());
  177. #endif
  178. }
  179. void systemRequestedQuit() override
  180. {
  181. if (mainWindow != nullptr)
  182. mainWindow->tryToQuitApplication();
  183. else
  184. JUCEApplicationBase::quit();
  185. }
  186. bool backButtonPressed() override
  187. {
  188. if (mainWindow->graphHolder != nullptr)
  189. mainWindow->graphHolder->hideLastSidePanel();
  190. return true;
  191. }
  192. const String getApplicationName() override { return "Juce Plug-In Host"; }
  193. const String getApplicationVersion() override { return ProjectInfo::versionString; }
  194. bool moreThanOneInstanceAllowed() override { return true; }
  195. ApplicationCommandManager commandManager;
  196. std::unique_ptr<ApplicationProperties> appProperties;
  197. private:
  198. std::unique_ptr<MainHostWindow> mainWindow;
  199. std::unique_ptr<PluginScannerSubprocess> storedScannerSubprocess;
  200. };
  201. static PluginHostApp& getApp() { return *dynamic_cast<PluginHostApp*>(JUCEApplication::getInstance()); }
  202. ApplicationProperties& getAppProperties() { return *getApp().appProperties; }
  203. ApplicationCommandManager& getCommandManager() { return getApp().commandManager; }
  204. bool isOnTouchDevice()
  205. {
  206. static bool isTouch = Desktop::getInstance().getMainMouseSource().isTouch();
  207. return isTouch;
  208. }
  209. //==============================================================================
  210. static AutoScale autoScaleFromString (StringRef str)
  211. {
  212. if (str.isEmpty()) return AutoScale::useDefault;
  213. if (str == CharPointer_ASCII { "0" }) return AutoScale::scaled;
  214. if (str == CharPointer_ASCII { "1" }) return AutoScale::unscaled;
  215. jassertfalse;
  216. return AutoScale::useDefault;
  217. }
  218. static const char* autoScaleToString (AutoScale autoScale)
  219. {
  220. if (autoScale == AutoScale::scaled) return "0";
  221. if (autoScale == AutoScale::unscaled) return "1";
  222. return {};
  223. }
  224. AutoScale getAutoScaleValueForPlugin (const String& identifier)
  225. {
  226. if (identifier.isNotEmpty())
  227. {
  228. auto plugins = StringArray::fromLines (getAppProperties().getUserSettings()->getValue ("autoScalePlugins"));
  229. plugins.removeEmptyStrings();
  230. for (auto& plugin : plugins)
  231. {
  232. auto fromIdentifier = plugin.fromFirstOccurrenceOf (identifier, false, false);
  233. if (fromIdentifier.isNotEmpty())
  234. return autoScaleFromString (fromIdentifier.fromFirstOccurrenceOf (":", false, false));
  235. }
  236. }
  237. return AutoScale::useDefault;
  238. }
  239. void setAutoScaleValueForPlugin (const String& identifier, AutoScale s)
  240. {
  241. auto plugins = StringArray::fromLines (getAppProperties().getUserSettings()->getValue ("autoScalePlugins"));
  242. plugins.removeEmptyStrings();
  243. auto index = [identifier, plugins]
  244. {
  245. auto it = std::find_if (plugins.begin(), plugins.end(),
  246. [&] (const String& str) { return str.startsWith (identifier); });
  247. return (int) std::distance (plugins.begin(), it);
  248. }();
  249. if (s == AutoScale::useDefault && index != plugins.size())
  250. {
  251. plugins.remove (index);
  252. }
  253. else
  254. {
  255. auto str = identifier + ":" + autoScaleToString (s);
  256. if (index != plugins.size())
  257. plugins.getReference (index) = str;
  258. else
  259. plugins.add (str);
  260. }
  261. getAppProperties().getUserSettings()->setValue ("autoScalePlugins", plugins.joinIntoString ("\n"));
  262. }
  263. static bool isAutoScaleAvailableForPlugin (const PluginDescription& description)
  264. {
  265. return autoScaleOptionAvailable
  266. && description.pluginFormatName.containsIgnoreCase ("VST");
  267. }
  268. bool shouldAutoScalePlugin (const PluginDescription& description)
  269. {
  270. if (! isAutoScaleAvailableForPlugin (description))
  271. return false;
  272. const auto scaleValue = getAutoScaleValueForPlugin (description.fileOrIdentifier);
  273. return (scaleValue == AutoScale::scaled
  274. || (scaleValue == AutoScale::useDefault
  275. && getAppProperties().getUserSettings()->getBoolValue ("autoScalePluginWindows")));
  276. }
  277. void addPluginAutoScaleOptionsSubMenu (AudioPluginInstance* pluginInstance,
  278. PopupMenu& menu)
  279. {
  280. if (pluginInstance == nullptr)
  281. return;
  282. auto description = pluginInstance->getPluginDescription();
  283. if (! isAutoScaleAvailableForPlugin (description))
  284. return;
  285. auto identifier = description.fileOrIdentifier;
  286. PopupMenu autoScaleMenu;
  287. autoScaleMenu.addItem ("Default",
  288. true,
  289. getAutoScaleValueForPlugin (identifier) == AutoScale::useDefault,
  290. [identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::useDefault); });
  291. autoScaleMenu.addItem ("Enabled",
  292. true,
  293. getAutoScaleValueForPlugin (identifier) == AutoScale::scaled,
  294. [identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::scaled); });
  295. autoScaleMenu.addItem ("Disabled",
  296. true,
  297. getAutoScaleValueForPlugin (identifier) == AutoScale::unscaled,
  298. [identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::unscaled); });
  299. menu.addSubMenu ("Auto-scale window", autoScaleMenu);
  300. }
  301. // This kicks the whole thing off..
  302. START_JUCE_APPLICATION (PluginHostApp)