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.

255 lines
6.6KB

  1. #include <common.hpp>
  2. #include <random.hpp>
  3. #include <asset.hpp>
  4. #include <audio.hpp>
  5. #include <rtaudio.hpp>
  6. #include <midi.hpp>
  7. #include <rtmidi.hpp>
  8. #include <keyboard.hpp>
  9. #include <gamepad.hpp>
  10. #include <settings.hpp>
  11. #include <engine/Engine.hpp>
  12. #include <app/common.hpp>
  13. #include <app/Scene.hpp>
  14. #include <plugin.hpp>
  15. #include <context.hpp>
  16. #include <window.hpp>
  17. #include <patch.hpp>
  18. #include <history.hpp>
  19. #include <ui.hpp>
  20. #include <system.hpp>
  21. #include <string.hpp>
  22. #include <updater.hpp>
  23. #include <network.hpp>
  24. #include <osdialog.h>
  25. #include <thread>
  26. #include <unistd.h> // for getopt
  27. #include <signal.h> // for signal
  28. #if defined ARCH_WIN
  29. #include <windows.h> // for CreateMutex
  30. #endif
  31. #if defined ARCH_MAC
  32. #define GLFW_EXPOSE_NATIVE_COCOA
  33. #include <GLFW/glfw3native.h> // for glfwGetOpenedFilenames()
  34. #endif
  35. using namespace rack;
  36. static void fatalSignalHandler(int sig) {
  37. // Ignore this signal to avoid recursion.
  38. signal(sig, NULL);
  39. // Ignore abort() since we call it below.
  40. signal(SIGABRT, NULL);
  41. FATAL("Fatal signal %d. Stack trace:\n%s", sig, system::getStackTrace().c_str());
  42. // This might fail because we might not be in the main thread.
  43. // But oh well, we're crashing anyway.
  44. std::string text = APP_NAME + " has crashed. See " + asset::logPath + " for details.";
  45. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, text.c_str());
  46. abort();
  47. }
  48. int main(int argc, char* argv[]) {
  49. #if defined ARCH_WIN
  50. // Windows global mutex to prevent multiple instances
  51. // Handle will be closed by Windows when the process ends
  52. HANDLE instanceMutex = CreateMutexW(NULL, true, string::toWstring(APP_NAME).c_str());
  53. if (GetLastError() == ERROR_ALREADY_EXISTS) {
  54. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Rack is already running. Multiple Rack instances are not supported.");
  55. exit(1);
  56. }
  57. (void) instanceMutex;
  58. // Don't display "Assertion failed!" dialog message.
  59. _set_error_mode(_OUT_TO_STDERR);
  60. #endif
  61. std::string patchPath;
  62. bool screenshot = false;
  63. float screenshotZoom = 1.f;
  64. // Parse command line arguments
  65. int c;
  66. opterr = 0;
  67. while ((c = getopt(argc, argv, "dht:s:u:p:")) != -1) {
  68. switch (c) {
  69. case 'd': {
  70. settings::devMode = true;
  71. } break;
  72. case 'h': {
  73. settings::headless = true;
  74. } break;
  75. case 't': {
  76. screenshot = true;
  77. // If parsing number failed, use default value
  78. std::sscanf(optarg, "%f", &screenshotZoom);
  79. } break;
  80. case 's': {
  81. asset::systemDir = optarg;
  82. } break;
  83. case 'u': {
  84. asset::userDir = optarg;
  85. } break;
  86. // Mac "app translocation" passes a nonsense -psn_... flag, so -p is reserved.
  87. case 'p': break;
  88. default: break;
  89. }
  90. }
  91. if (optind < argc) {
  92. patchPath = argv[optind];
  93. }
  94. // Initialize environment
  95. asset::init();
  96. bool loggerWasTruncated = logger::isTruncated();
  97. logger::init();
  98. // We can now install a signal handler and log the output
  99. if (!settings::devMode) {
  100. signal(SIGABRT, fatalSignalHandler);
  101. signal(SIGFPE, fatalSignalHandler);
  102. signal(SIGILL, fatalSignalHandler);
  103. signal(SIGSEGV, fatalSignalHandler);
  104. signal(SIGTERM, fatalSignalHandler);
  105. }
  106. // Log environment
  107. INFO("%s v%s", APP_NAME.c_str(), APP_VERSION.c_str());
  108. INFO("%s", system::getOperatingSystemInfo().c_str());
  109. std::string argsList;
  110. for (int i = 0; i < argc; i++) {
  111. argsList += argv[i];
  112. argsList += " ";
  113. }
  114. INFO("Args: %s", argsList.c_str());
  115. if (settings::devMode)
  116. INFO("Development mode");
  117. INFO("System directory: %s", asset::systemDir.c_str());
  118. INFO("User directory: %s", asset::userDir.c_str());
  119. #if defined ARCH_MAC
  120. INFO("Bundle path: %s", asset::bundlePath.c_str());
  121. #endif
  122. // Load settings
  123. try {
  124. settings::load(asset::settingsPath);
  125. }
  126. catch (Exception& e) {
  127. std::string msg = e.what();
  128. msg += "\n\nReset settings to default?";
  129. if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, msg.c_str())) {
  130. exit(1);
  131. }
  132. }
  133. // Check existence of the system res/ directory
  134. std::string resDir = asset::system("res");
  135. if (!system::isDirectory(resDir)) {
  136. std::string message = string::f("Rack's resource directory \"%s\" does not exist. Make sure Rack is correctly installed and launched.", resDir.c_str());
  137. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, message.c_str());
  138. exit(1);
  139. }
  140. INFO("Initializing environment");
  141. random::init();
  142. network::init();
  143. audio::init();
  144. rtaudioInit();
  145. midi::init();
  146. rtmidiInit();
  147. keyboard::init();
  148. gamepad::init();
  149. plugin::init();
  150. updater::init();
  151. if (!settings::headless) {
  152. ui::init();
  153. windowInit();
  154. }
  155. // Initialize context
  156. INFO("Initializing context");
  157. contextSet(new Context);
  158. APP->engine = new engine::Engine;
  159. APP->patch = new PatchManager;
  160. if (!settings::headless) {
  161. APP->event = new event::State;
  162. APP->history = new history::State;
  163. APP->window = new Window;
  164. APP->scene = new app::Scene;
  165. APP->event->rootWidget = APP->scene;
  166. }
  167. // On Mac, use a hacked-in GLFW addition to get the launched path.
  168. #if defined ARCH_MAC
  169. // For some reason, launching from the command line sets glfwGetOpenedFilenames(), so make sure we're running the app bundle.
  170. if (asset::bundlePath != "") {
  171. const char* const* openedFilenames = glfwGetOpenedFilenames();
  172. if (openedFilenames && openedFilenames[0]) {
  173. patchPath = openedFilenames[0];
  174. }
  175. }
  176. #endif
  177. // Initialize patch
  178. if (loggerWasTruncated && osdialog_message(OSDIALOG_INFO, OSDIALOG_YES_NO, "Rack crashed during the last session, possibly due to a buggy module in your patch. Clear your patch and start over?")) {
  179. // This is the default state, but just call clear() to be explicit.
  180. APP->patch->clear();
  181. }
  182. else if (patchPath != "") {
  183. APP->patch->loadAction(patchPath);
  184. }
  185. else {
  186. APP->patch->path = settings::patchPath;
  187. APP->patch->loadAutosave();
  188. }
  189. // Run context
  190. if (settings::headless) {
  191. printf("Press enter to exit.\n");
  192. getchar();
  193. }
  194. else if (screenshot) {
  195. INFO("Taking screenshots of all modules at %gx zoom", screenshotZoom);
  196. APP->window->screenshot(screenshotZoom);
  197. }
  198. else {
  199. INFO("Running window");
  200. APP->window->run();
  201. INFO("Stopped window");
  202. }
  203. // Destroy context
  204. if (!settings::headless) {
  205. APP->patch->saveAutosave();
  206. // TODO If Rack crashes, the settings' patch path won't be changed although the autosave will. This could possibly cause the user to overwrite their old patch with the unrelated autosave.
  207. settings::patchPath = APP->patch->path;
  208. }
  209. INFO("Destroying context");
  210. delete APP;
  211. contextSet(NULL);
  212. if (!settings::headless) {
  213. settings::save(asset::settingsPath);
  214. }
  215. // Destroy environment
  216. INFO("Destroying environment");
  217. if (!settings::headless) {
  218. windowDestroy();
  219. ui::destroy();
  220. }
  221. midi::destroy();
  222. audio::destroy();
  223. plugin::destroy();
  224. INFO("Destroying logger");
  225. logger::destroy();
  226. return 0;
  227. }