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.

230 lines
5.6KB

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