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.

plugin.cpp 11KB

8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. #include <thread>
  2. #include <map>
  3. #include <stdexcept>
  4. #include <tuple>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <unistd.h>
  8. #include <sys/param.h> // for MAXPATHLEN
  9. #include <fcntl.h>
  10. #if defined ARCH_WIN
  11. #include <windows.h>
  12. #include <direct.h>
  13. #else
  14. #include <dlfcn.h> // for dlopen
  15. #endif
  16. #include <dirent.h>
  17. #include <osdialog.h>
  18. #include <jansson.h>
  19. #include <plugin.hpp>
  20. #include <system.hpp>
  21. #include <asset.hpp>
  22. #include <string.hpp>
  23. #include <context.hpp>
  24. #include <plugin/callbacks.hpp>
  25. #include <settings.hpp>
  26. namespace rack {
  27. namespace core {
  28. void init(rack::plugin::Plugin* plugin);
  29. } // namespace core
  30. namespace plugin {
  31. ////////////////////
  32. // private API
  33. ////////////////////
  34. static void* loadLibrary(std::string libraryPath) {
  35. #if defined ARCH_WIN
  36. SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
  37. std::wstring libraryFilenameW = string::UTF8toUTF16(libraryPath);
  38. HINSTANCE handle = LoadLibraryW(libraryFilenameW.c_str());
  39. SetErrorMode(0);
  40. if (!handle) {
  41. int error = GetLastError();
  42. throw Exception("Failed to load library %s: code %d", libraryPath.c_str(), error);
  43. }
  44. #else
  45. // Since Rack 2, plugins on Linux/Mac link to the absolute path /tmp/Rack2/libRack.<ext>
  46. // Create a symlink at /tmp/Rack2 to the system dir containting libRack.
  47. std::string systemDir = system::getAbsolute(asset::systemDir);
  48. std::string linkPath = "/tmp/Rack2";
  49. if (!settings::devMode) {
  50. // Clean up old symbolic link in case a different edition was run earlier
  51. system::remove(linkPath);
  52. system::createSymbolicLink(systemDir, linkPath);
  53. }
  54. // Load library with dlopen
  55. void* handle = NULL;
  56. #if defined ARCH_LIN
  57. handle = dlopen(libraryPath.c_str(), RTLD_NOW | RTLD_LOCAL);
  58. #elif defined ARCH_MAC
  59. handle = dlopen(libraryPath.c_str(), RTLD_NOW | RTLD_LOCAL);
  60. #endif
  61. if (!handle)
  62. throw Exception("Failed to load library %s: %s", libraryPath.c_str(), dlerror());
  63. #endif
  64. return handle;
  65. }
  66. typedef void (*InitCallback)(Plugin*);
  67. static InitCallback loadPluginCallback(Plugin* plugin) {
  68. // Load plugin library
  69. std::string libraryExt;
  70. #if defined ARCH_LIN
  71. libraryExt = "so";
  72. #elif defined ARCH_WIN
  73. libraryExt = "dll";
  74. #elif ARCH_MAC
  75. libraryExt = "dylib";
  76. #endif
  77. std::string libraryPath = system::join(plugin->path, "plugin." + libraryExt);
  78. // Check file existence
  79. if (!system::isFile(libraryPath))
  80. throw Exception("Plugin binary not found at %s", libraryPath.c_str());
  81. // Load dynamic/shared library
  82. plugin->handle = loadLibrary(libraryPath);
  83. // Get plugin's init() function
  84. InitCallback initCallback;
  85. #if defined ARCH_WIN
  86. initCallback = (InitCallback) GetProcAddress((HMODULE) plugin->handle, "init");
  87. #else
  88. initCallback = (InitCallback) dlsym(plugin->handle, "init");
  89. #endif
  90. if (!initCallback)
  91. throw Exception("Failed to read init() symbol in %s", libraryPath.c_str());
  92. return initCallback;
  93. }
  94. /** If path is blank, loads Core */
  95. static Plugin* loadPlugin(std::string path) {
  96. if (path == "")
  97. INFO("Loading Core plugin");
  98. else
  99. INFO("Loading plugin from %s", path.c_str());
  100. Plugin* plugin = new Plugin;
  101. try {
  102. // Set plugin path
  103. plugin->path = (path == "") ? asset::systemDir : path;
  104. // Get modified timestamp
  105. if (path != "") {
  106. struct stat statbuf;
  107. if (!stat(path.c_str(), &statbuf)) {
  108. #if defined ARCH_MAC
  109. plugin->modifiedTimestamp = (double) statbuf.st_mtimespec.tv_sec + statbuf.st_mtimespec.tv_nsec * 1e-9;
  110. #elif defined ARCH_WIN
  111. plugin->modifiedTimestamp = (double) statbuf.st_mtime;
  112. #elif defined ARCH_LIN
  113. plugin->modifiedTimestamp = (double) statbuf.st_mtim.tv_sec + statbuf.st_mtim.tv_nsec * 1e-9;
  114. #endif
  115. }
  116. }
  117. // Load plugin.json
  118. std::string manifestFilename = (path == "") ? asset::system("Core.json") : system::join(path, "plugin.json");
  119. FILE* file = std::fopen(manifestFilename.c_str(), "r");
  120. if (!file)
  121. throw Exception("Manifest file %s does not exist", manifestFilename.c_str());
  122. DEFER({std::fclose(file);});
  123. json_error_t error;
  124. json_t* rootJ = json_loadf(file, 0, &error);
  125. if (!rootJ)
  126. throw Exception("JSON parsing error at %s %d:%d %s", manifestFilename.c_str(), error.line, error.column, error.text);
  127. DEFER({json_decref(rootJ);});
  128. // Call init callback
  129. InitCallback initCallback;
  130. if (path == "") {
  131. initCallback = core::init;
  132. }
  133. else {
  134. initCallback = loadPluginCallback(plugin);
  135. }
  136. initCallback(plugin);
  137. // Load manifest
  138. plugin->fromJson(rootJ);
  139. // Reject plugin if slug already exists
  140. Plugin* existingPlugin = getExactPlugin(plugin->slug);
  141. if (existingPlugin)
  142. throw Exception("Plugin %s is already loaded, not attempting to load it again", plugin->slug.c_str());
  143. }
  144. catch (Exception& e) {
  145. WARN("Could not load plugin %s: %s", path.c_str(), e.what());
  146. delete plugin;
  147. return NULL;
  148. }
  149. INFO("Loaded %s v%s", plugin->slug.c_str(), plugin->version.c_str());
  150. plugins.push_back(plugin);
  151. return plugin;
  152. }
  153. static void loadPlugins(std::string path) {
  154. for (std::string pluginPath : system::getEntries(path)) {
  155. if (!system::isDirectory(pluginPath))
  156. continue;
  157. if (!loadPlugin(pluginPath)) {
  158. // Ignore bad plugins. They are reported in the log.
  159. }
  160. }
  161. }
  162. static void extractPackages(std::string path) {
  163. std::string message;
  164. for (std::string packagePath : system::getEntries(path)) {
  165. if (!system::isFile(packagePath))
  166. continue;
  167. if (system::getExtension(packagePath) != ".vcvplugin")
  168. continue;
  169. // Extract package
  170. INFO("Extracting package %s", packagePath.c_str());
  171. try {
  172. system::unarchiveToDirectory(packagePath, path);
  173. }
  174. catch (Exception& e) {
  175. WARN("Plugin package %s failed to extract: %s", packagePath.c_str(), e.what());
  176. message += string::f("Could not extract plugin package %s\n", packagePath.c_str());
  177. continue;
  178. }
  179. // Remove package
  180. system::remove(packagePath.c_str());
  181. }
  182. if (!message.empty()) {
  183. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  184. }
  185. }
  186. ////////////////////
  187. // public API
  188. ////////////////////
  189. void init() {
  190. // Don't re-initialize
  191. assert(plugins.empty());
  192. // Load Core
  193. loadPlugin("");
  194. pluginsPath = asset::user("plugins");
  195. // Get user plugins directory
  196. system::createDirectory(pluginsPath);
  197. // Extract packages and load plugins
  198. extractPackages(pluginsPath);
  199. loadPlugins(pluginsPath);
  200. // If Fundamental wasn't loaded, copy the bundled Fundamental package and load it
  201. if (!settings::devMode && !getPlugin("Fundamental")) {
  202. std::string fundamentalSrc = asset::system("Fundamental.vcvplugin");
  203. std::string fundamentalDir = system::join(pluginsPath, "Fundamental");
  204. if (system::isFile(fundamentalSrc)) {
  205. INFO("Extracting bundled Fundamental package");
  206. system::unarchiveToDirectory(fundamentalSrc.c_str(), pluginsPath.c_str());
  207. loadPlugin(fundamentalDir);
  208. }
  209. }
  210. }
  211. void destroy() {
  212. for (Plugin* plugin : plugins) {
  213. void* handle = plugin->handle;
  214. // Call destroy() if defined in the plugin library
  215. typedef void (*DestroyCallback)();
  216. DestroyCallback destroyCallback = NULL;
  217. if (handle) {
  218. #if defined ARCH_WIN
  219. destroyCallback = (DestroyCallback) GetProcAddress((HMODULE) handle, "destroy");
  220. #else
  221. destroyCallback = (DestroyCallback) dlsym(handle, "destroy");
  222. #endif
  223. }
  224. if (destroyCallback)
  225. destroyCallback();
  226. // We must delete the Plugin instance *before* freeing the library, because the vtables of Model subclasses are defined in the library, which are needed in the Plugin destructor.
  227. delete plugin;
  228. // Free library handle
  229. if (handle) {
  230. #if defined ARCH_WIN
  231. FreeLibrary((HINSTANCE) handle);
  232. #else
  233. dlclose(handle);
  234. #endif
  235. }
  236. }
  237. plugins.clear();
  238. }
  239. // To request fallback slugs to be added to this list, open a GitHub issue.
  240. static const std::map<std::string, std::string> pluginSlugFallbacks = {
  241. {"VultModulesFree", "VultModules"},
  242. {"VultModules", "VultModulesFree"},
  243. // {"", ""},
  244. };
  245. Plugin* getExactPlugin(const std::string& pluginSlug) {
  246. if (pluginSlug.empty())
  247. return NULL;
  248. auto it = std::find_if(plugins.begin(), plugins.end(), [=](Plugin* p) {
  249. return p->slug == pluginSlug;
  250. });
  251. if (it != plugins.end())
  252. return *it;
  253. return NULL;
  254. }
  255. Plugin* getPlugin(const std::string& pluginSlug) {
  256. Plugin* p = getExactPlugin(pluginSlug);
  257. if (p)
  258. return p;
  259. // Use fallback plugin slug
  260. auto it = pluginSlugFallbacks.find(pluginSlug);
  261. if (it != pluginSlugFallbacks.end())
  262. return getExactPlugin(it->second);
  263. return NULL;
  264. }
  265. // To request fallback slugs to be added to this list, open a GitHub issue.
  266. using PluginModuleSlug = std::tuple<std::string, std::string>;
  267. static const std::map<PluginModuleSlug, PluginModuleSlug> moduleSlugFallbacks = {
  268. {{"AudibleInstrumentsPreview", "Plaits"}, {"AudibleInstruments", "Plaits"}},
  269. {{"AudibleInstrumentsPreview", "Marbles"}, {"AudibleInstruments", "Marbles"}},
  270. {{"MindMeld-ShapeMasterPro", "ShapeMasterPro"}, {"MindMeldModular", "ShapeMaster"}},
  271. {{"MindMeldModular", "ShapeMaster"}, {"MindMeld-ShapeMasterPro", "ShapeMasterPro"}},
  272. // {{"", ""}, {"", ""}},
  273. };
  274. Model* getExactModel(const std::string& pluginSlug, const std::string& modelSlug) {
  275. if (pluginSlug.empty() || modelSlug.empty())
  276. return NULL;
  277. Plugin* p = getPlugin(pluginSlug);
  278. if (p) {
  279. Model* model = p->getModel(modelSlug);
  280. if (model)
  281. return model;
  282. }
  283. return NULL;
  284. }
  285. Model* getModel(const std::string& pluginSlug, const std::string& modelSlug) {
  286. Model* m = getExactModel(pluginSlug, modelSlug);
  287. if (m)
  288. return m;
  289. // Use fallback (module slug, plugin slug)
  290. auto it = moduleSlugFallbacks.find(std::make_tuple(pluginSlug, modelSlug));
  291. if (it != moduleSlugFallbacks.end()) {
  292. return getExactModel(std::get<0>(it->second), std::get<1>(it->second));
  293. }
  294. return NULL;
  295. }
  296. Model* modelFromJson(json_t* moduleJ) {
  297. // Get slugs
  298. json_t* pluginSlugJ = json_object_get(moduleJ, "plugin");
  299. if (!pluginSlugJ)
  300. throw Exception("\"plugin\" property not found in module JSON");
  301. std::string pluginSlug = json_string_value(pluginSlugJ);
  302. pluginSlug = normalizeSlug(pluginSlug);
  303. json_t* modelSlugJ = json_object_get(moduleJ, "model");
  304. if (!modelSlugJ)
  305. throw Exception("\"model\" property not found in module JSON");
  306. std::string modelSlug = json_string_value(modelSlugJ);
  307. modelSlug = normalizeSlug(modelSlug);
  308. // Get Model
  309. Model* model = getModel(pluginSlug, modelSlug);
  310. if (!model)
  311. throw Exception("Could not find module \"%s\" \"%s\"", pluginSlug.c_str(), modelSlug.c_str());
  312. return model;
  313. }
  314. bool isSlugValid(const std::string& slug) {
  315. for (char c : slug) {
  316. if (!(std::isalnum(c) || c == '-' || c == '_'))
  317. return false;
  318. }
  319. return true;
  320. }
  321. std::string normalizeSlug(const std::string& slug) {
  322. std::string s;
  323. for (char c : slug) {
  324. if (!(std::isalnum(c) || c == '-' || c == '_'))
  325. continue;
  326. s += c;
  327. }
  328. return s;
  329. }
  330. std::string pluginsPath;
  331. std::vector<Plugin*> plugins;
  332. } // namespace plugin
  333. } // namespace rack