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.

258 lines
6.7KB

  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 <library.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. // TODO Use -municode on Windows and change this to wmain. Convert argv to UTF-8.
  49. int main(int argc, char* argv[]) {
  50. #if defined ARCH_WIN
  51. // Windows global mutex to prevent multiple instances
  52. // Handle will be closed by Windows when the process ends
  53. HANDLE instanceMutex = CreateMutexW(NULL, true, string::U8toU16(APP_NAME).c_str());
  54. if (GetLastError() == ERROR_ALREADY_EXISTS) {
  55. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Rack is already running. Multiple Rack instances are not supported.");
  56. exit(1);
  57. }
  58. (void) instanceMutex;
  59. // Don't display "Assertion failed!" dialog message.
  60. _set_error_mode(_OUT_TO_STDERR);
  61. #endif
  62. std::string patchPath;
  63. bool screenshot = false;
  64. float screenshotZoom = 1.f;
  65. // Parse command line arguments
  66. int c;
  67. opterr = 0;
  68. while ((c = getopt(argc, argv, "dht:s:u:p:")) != -1) {
  69. switch (c) {
  70. case 'd': {
  71. settings::devMode = true;
  72. } break;
  73. case 'h': {
  74. settings::headless = true;
  75. } break;
  76. case 't': {
  77. screenshot = true;
  78. // If parsing number failed, use default value
  79. std::sscanf(optarg, "%f", &screenshotZoom);
  80. } break;
  81. case 's': {
  82. asset::systemDir = optarg;
  83. } break;
  84. case 'u': {
  85. asset::userDir = optarg;
  86. } break;
  87. // Mac "app translocation" passes a nonsense -psn_... flag, so -p is reserved.
  88. case 'p': break;
  89. default: break;
  90. }
  91. }
  92. if (optind < argc) {
  93. patchPath = argv[optind];
  94. }
  95. // Initialize environment
  96. system::init();
  97. asset::init();
  98. bool loggerWasTruncated = logger::isTruncated();
  99. logger::init();
  100. random::init();
  101. // We can now install a signal handler and log the output
  102. if (!settings::devMode) {
  103. signal(SIGABRT, fatalSignalHandler);
  104. signal(SIGFPE, fatalSignalHandler);
  105. signal(SIGILL, fatalSignalHandler);
  106. signal(SIGSEGV, fatalSignalHandler);
  107. signal(SIGTERM, fatalSignalHandler);
  108. }
  109. // Log environment
  110. INFO("%s v%s", APP_NAME.c_str(), APP_VERSION.c_str());
  111. INFO("%s", system::getOperatingSystemInfo().c_str());
  112. std::string argsList;
  113. for (int i = 0; i < argc; i++) {
  114. argsList += argv[i];
  115. argsList += " ";
  116. }
  117. INFO("Args: %s", argsList.c_str());
  118. if (settings::devMode)
  119. INFO("Development mode");
  120. INFO("System directory: %s", asset::systemDir.c_str());
  121. INFO("User directory: %s", asset::userDir.c_str());
  122. #if defined ARCH_MAC
  123. INFO("Bundle path: %s", asset::bundlePath.c_str());
  124. #endif
  125. // Load settings
  126. try {
  127. settings::load(asset::settingsPath);
  128. }
  129. catch (Exception& e) {
  130. std::string msg = e.what();
  131. msg += "\n\nReset settings to default?";
  132. if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, msg.c_str())) {
  133. exit(1);
  134. }
  135. }
  136. // Check existence of the system res/ directory
  137. std::string resDir = asset::system("res");
  138. if (!system::isDirectory(resDir)) {
  139. std::string message = string::f("Rack's resource directory \"%s\" does not exist. Make sure Rack is correctly installed and launched.", resDir.c_str());
  140. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, message.c_str());
  141. exit(1);
  142. }
  143. INFO("Initializing environment");
  144. network::init();
  145. audio::init();
  146. rtaudioInit();
  147. midi::init();
  148. rtmidiInit();
  149. keyboard::init();
  150. gamepad::init();
  151. plugin::init();
  152. library::init();
  153. if (!settings::headless) {
  154. ui::init();
  155. windowInit();
  156. }
  157. // Initialize context
  158. INFO("Initializing context");
  159. contextSet(new Context);
  160. APP->engine = new engine::Engine;
  161. if (!settings::headless) {
  162. APP->event = new event::State;
  163. APP->history = new history::State;
  164. APP->window = new Window;
  165. APP->scene = new app::Scene;
  166. APP->event->rootWidget = APP->scene;
  167. }
  168. APP->patch = new PatchManager;
  169. // On Mac, use a hacked-in GLFW addition to get the launched path.
  170. #if defined ARCH_MAC
  171. // For some reason, launching from the command line sets glfwGetOpenedFilenames(), so make sure we're running the app bundle.
  172. if (asset::bundlePath != "") {
  173. const char* const* openedFilenames = glfwGetOpenedFilenames();
  174. if (openedFilenames && openedFilenames[0]) {
  175. patchPath = openedFilenames[0];
  176. }
  177. }
  178. #endif
  179. // Initialize patch
  180. 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?")) {
  181. // This is the default state, but just call clear() to be explicit.
  182. APP->patch->clear();
  183. }
  184. else if (patchPath != "") {
  185. APP->patch->loadAction(patchPath);
  186. }
  187. else {
  188. APP->patch->path = settings::patchPath;
  189. APP->patch->loadAutosave();
  190. }
  191. // Run context
  192. if (settings::headless) {
  193. printf("Press enter to exit.\n");
  194. getchar();
  195. }
  196. else if (screenshot) {
  197. INFO("Taking screenshots of all modules at %gx zoom", screenshotZoom);
  198. APP->window->screenshotModules(asset::user("screenshots"), screenshotZoom);
  199. }
  200. else {
  201. INFO("Running window");
  202. APP->window->run();
  203. INFO("Stopped window");
  204. }
  205. // Destroy context
  206. if (!settings::headless) {
  207. APP->patch->saveAutosave();
  208. // 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.
  209. settings::patchPath = APP->patch->path;
  210. }
  211. INFO("Destroying context");
  212. delete APP;
  213. contextSet(NULL);
  214. if (!settings::headless) {
  215. settings::save(asset::settingsPath);
  216. }
  217. // Destroy environment
  218. INFO("Destroying environment");
  219. if (!settings::headless) {
  220. windowDestroy();
  221. ui::destroy();
  222. }
  223. library::destroy();
  224. midi::destroy();
  225. audio::destroy();
  226. plugin::destroy();
  227. INFO("Destroying logger");
  228. logger::destroy();
  229. return 0;
  230. }