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.

256 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 <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. // 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. asset::init();
  97. bool loggerWasTruncated = logger::isTruncated();
  98. logger::init();
  99. // We can now install a signal handler and log the output
  100. if (!settings::devMode) {
  101. signal(SIGABRT, fatalSignalHandler);
  102. signal(SIGFPE, fatalSignalHandler);
  103. signal(SIGILL, fatalSignalHandler);
  104. signal(SIGSEGV, fatalSignalHandler);
  105. signal(SIGTERM, fatalSignalHandler);
  106. }
  107. // Log environment
  108. INFO("%s v%s", APP_NAME.c_str(), APP_VERSION.c_str());
  109. INFO("%s", system::getOperatingSystemInfo().c_str());
  110. std::string argsList;
  111. for (int i = 0; i < argc; i++) {
  112. argsList += argv[i];
  113. argsList += " ";
  114. }
  115. INFO("Args: %s", argsList.c_str());
  116. if (settings::devMode)
  117. INFO("Development mode");
  118. INFO("System directory: %s", asset::systemDir.c_str());
  119. INFO("User directory: %s", asset::userDir.c_str());
  120. #if defined ARCH_MAC
  121. INFO("Bundle path: %s", asset::bundlePath.c_str());
  122. #endif
  123. // Load settings
  124. try {
  125. settings::load(asset::settingsPath);
  126. }
  127. catch (Exception& e) {
  128. std::string msg = e.what();
  129. msg += "\n\nReset settings to default?";
  130. if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, msg.c_str())) {
  131. exit(1);
  132. }
  133. }
  134. // Check existence of the system res/ directory
  135. std::string resDir = asset::system("res");
  136. if (!system::isDirectory(resDir)) {
  137. std::string message = string::f("Rack's resource directory \"%s\" does not exist. Make sure Rack is correctly installed and launched.", resDir.c_str());
  138. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, message.c_str());
  139. exit(1);
  140. }
  141. INFO("Initializing environment");
  142. random::init();
  143. network::init();
  144. audio::init();
  145. rtaudioInit();
  146. midi::init();
  147. rtmidiInit();
  148. keyboard::init();
  149. gamepad::init();
  150. plugin::init();
  151. updater::init();
  152. if (!settings::headless) {
  153. ui::init();
  154. windowInit();
  155. }
  156. // Initialize context
  157. INFO("Initializing context");
  158. contextSet(new Context);
  159. APP->engine = new engine::Engine;
  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. APP->patch = new PatchManager;
  168. // On Mac, use a hacked-in GLFW addition to get the launched path.
  169. #if defined ARCH_MAC
  170. // For some reason, launching from the command line sets glfwGetOpenedFilenames(), so make sure we're running the app bundle.
  171. if (asset::bundlePath != "") {
  172. const char* const* openedFilenames = glfwGetOpenedFilenames();
  173. if (openedFilenames && openedFilenames[0]) {
  174. patchPath = openedFilenames[0];
  175. }
  176. }
  177. #endif
  178. // Initialize patch
  179. 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?")) {
  180. // This is the default state, but just call clear() to be explicit.
  181. APP->patch->clear();
  182. }
  183. else if (patchPath != "") {
  184. APP->patch->loadAction(patchPath);
  185. }
  186. else {
  187. APP->patch->path = settings::patchPath;
  188. APP->patch->loadAutosave();
  189. }
  190. // Run context
  191. if (settings::headless) {
  192. printf("Press enter to exit.\n");
  193. getchar();
  194. }
  195. else if (screenshot) {
  196. INFO("Taking screenshots of all modules at %gx zoom", screenshotZoom);
  197. APP->window->screenshot(screenshotZoom);
  198. }
  199. else {
  200. INFO("Running window");
  201. APP->window->run();
  202. INFO("Stopped window");
  203. }
  204. // Destroy context
  205. if (!settings::headless) {
  206. APP->patch->saveAutosave();
  207. // 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.
  208. settings::patchPath = APP->patch->path;
  209. }
  210. INFO("Destroying context");
  211. delete APP;
  212. contextSet(NULL);
  213. if (!settings::headless) {
  214. settings::save(asset::settingsPath);
  215. }
  216. // Destroy environment
  217. INFO("Destroying environment");
  218. if (!settings::headless) {
  219. windowDestroy();
  220. ui::destroy();
  221. }
  222. midi::destroy();
  223. audio::destroy();
  224. plugin::destroy();
  225. INFO("Destroying logger");
  226. logger::destroy();
  227. return 0;
  228. }