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.

591 lines
18KB

  1. #include <jansson.h>
  2. #include <settings.hpp>
  3. #include <window/Window.hpp>
  4. #include <plugin.hpp>
  5. #include <app/Scene.hpp>
  6. #include <engine/Engine.hpp>
  7. #include <context.hpp>
  8. #include <patch.hpp>
  9. #include <asset.hpp>
  10. #include <system.hpp>
  11. namespace rack {
  12. namespace settings {
  13. std::string settingsPath;
  14. bool devMode = false;
  15. bool headless = false;
  16. bool isPlugin = false;
  17. bool safeMode = false;
  18. std::string token;
  19. bool windowMaximized = false;
  20. math::Vec windowSize = math::Vec(1024, 720);
  21. math::Vec windowPos = math::Vec(NAN, NAN);
  22. bool invertZoom = false;
  23. bool mouseWheelZoom = false;
  24. float pixelRatio = 0.0;
  25. std::string uiTheme = "dark";
  26. float cableOpacity = 0.5;
  27. float cableTension = 1.0;
  28. float rackBrightness = 1.0;
  29. float haloBrightness = 0.25;
  30. bool allowCursorLock = true;
  31. KnobMode knobMode = KNOB_MODE_LINEAR;
  32. bool knobScroll = false;
  33. float knobLinearSensitivity = 0.001f;
  34. float knobScrollSensitivity = 0.001f;
  35. float sampleRate = 0;
  36. int threadCount = 1;
  37. bool tooltips = true;
  38. bool cpuMeter = false;
  39. bool lockModules = false;
  40. bool squeezeModules = true;
  41. bool preferDarkPanels = false;
  42. #if defined ARCH_MAC
  43. // Most Mac GPUs can't handle rendering the screen every frame, so use 30 Hz by default.
  44. float frameRateLimit = 30.f;
  45. #else
  46. float frameRateLimit = 60.f;
  47. #endif
  48. float autosaveInterval = 15.0;
  49. bool skipLoadOnLaunch = false;
  50. std::list<std::string> recentPatchPaths;
  51. bool cableAutoRotate = true;
  52. std::vector<NVGcolor> cableColors;
  53. std::vector<std::string> cableLabels;
  54. bool autoCheckUpdates = true;
  55. bool verifyHttpsCerts = true;
  56. bool showTipsOnLaunch = true;
  57. int tipIndex = -1;
  58. BrowserSort browserSort = BROWSER_SORT_UPDATED;
  59. float browserZoom = -1.f;
  60. json_t* pluginSettingsJ = NULL;
  61. std::map<std::string, std::map<std::string, ModuleInfo>> moduleInfos;
  62. std::map<std::string, PluginWhitelist> moduleWhitelist;
  63. ModuleInfo* getModuleInfo(const std::string& pluginSlug, const std::string& moduleSlug) {
  64. auto pluginIt = moduleInfos.find(pluginSlug);
  65. if (pluginIt == moduleInfos.end())
  66. return NULL;
  67. auto moduleIt = pluginIt->second.find(moduleSlug);
  68. if (moduleIt == pluginIt->second.end())
  69. return NULL;
  70. return &moduleIt->second;
  71. }
  72. bool isModuleWhitelisted(const std::string& pluginSlug, const std::string& moduleSlug) {
  73. auto pluginIt = moduleWhitelist.find(pluginSlug);
  74. // All modules in a plugin are visible if plugin set is empty.
  75. if (pluginIt == moduleWhitelist.end())
  76. return true;
  77. // All modules in a plugin are visible if plugin set is subscribed.
  78. const PluginWhitelist& plugin = pluginIt->second;
  79. if (plugin.subscribed)
  80. return true;
  81. // Check if plugin whitelist contains module
  82. auto moduleIt = plugin.moduleSlugs.find(moduleSlug);
  83. if (moduleIt == plugin.moduleSlugs.end())
  84. return false;
  85. return true;
  86. }
  87. void resetCables() {
  88. cableColors = {
  89. color::fromHexString("#f3374b"), // red
  90. color::fromHexString("#ffb437"), // yellow
  91. color::fromHexString("#00b56e"), // green
  92. color::fromHexString("#3695ef"), // blue
  93. color::fromHexString("#8b4ade"), // purple
  94. };
  95. cableLabels.clear();
  96. cableLabels.resize(cableColors.size());
  97. }
  98. void init() {
  99. settingsPath = asset::user("settings.json");
  100. resetCables();
  101. }
  102. void destroy() {
  103. if (pluginSettingsJ) {
  104. json_decref(pluginSettingsJ);
  105. pluginSettingsJ = NULL;
  106. }
  107. }
  108. json_t* toJson() {
  109. json_t* rootJ = json_object();
  110. // Always disable safe mode when settings are saved.
  111. json_object_set_new(rootJ, "safeMode", json_boolean(false));
  112. json_object_set_new(rootJ, "token", json_string(token.c_str()));
  113. json_object_set_new(rootJ, "windowMaximized", json_boolean(windowMaximized));
  114. json_t* windowSizeJ = json_pack("[f, f]", windowSize.x, windowSize.y);
  115. json_object_set_new(rootJ, "windowSize", windowSizeJ);
  116. json_t* windowPosJ = json_pack("[f, f]", windowPos.x, windowPos.y);
  117. json_object_set_new(rootJ, "windowPos", windowPosJ);
  118. json_object_set_new(rootJ, "invertZoom", json_boolean(invertZoom));
  119. json_object_set_new(rootJ, "mouseWheelZoom", json_boolean(mouseWheelZoom));
  120. json_object_set_new(rootJ, "pixelRatio", json_real(pixelRatio));
  121. json_object_set_new(rootJ, "uiTheme", json_string(uiTheme.c_str()));
  122. json_object_set_new(rootJ, "cableOpacity", json_real(cableOpacity));
  123. json_object_set_new(rootJ, "cableTension", json_real(cableTension));
  124. json_object_set_new(rootJ, "rackBrightness", json_real(rackBrightness));
  125. json_object_set_new(rootJ, "haloBrightness", json_real(haloBrightness));
  126. json_object_set_new(rootJ, "allowCursorLock", json_boolean(allowCursorLock));
  127. json_object_set_new(rootJ, "knobMode", json_integer((int) knobMode));
  128. json_object_set_new(rootJ, "knobScroll", json_boolean(knobScroll));
  129. json_object_set_new(rootJ, "knobLinearSensitivity", json_real(knobLinearSensitivity));
  130. json_object_set_new(rootJ, "knobScrollSensitivity", json_real(knobScrollSensitivity));
  131. json_object_set_new(rootJ, "sampleRate", json_real(sampleRate));
  132. json_object_set_new(rootJ, "threadCount", json_integer(threadCount));
  133. json_object_set_new(rootJ, "tooltips", json_boolean(tooltips));
  134. json_object_set_new(rootJ, "cpuMeter", json_boolean(cpuMeter));
  135. json_object_set_new(rootJ, "lockModules", json_boolean(lockModules));
  136. json_object_set_new(rootJ, "squeezeModules", json_boolean(squeezeModules));
  137. json_object_set_new(rootJ, "preferDarkPanels", json_boolean(preferDarkPanels));
  138. json_object_set_new(rootJ, "frameRateLimit", json_real(frameRateLimit));
  139. json_object_set_new(rootJ, "autosaveInterval", json_real(autosaveInterval));
  140. if (skipLoadOnLaunch)
  141. json_object_set_new(rootJ, "skipLoadOnLaunch", json_boolean(true));
  142. json_t* recentPatchPathsJ = json_array();
  143. for (const std::string& path : recentPatchPaths) {
  144. json_array_append_new(recentPatchPathsJ, json_string(path.c_str()));
  145. }
  146. json_object_set_new(rootJ, "recentPatchPaths", recentPatchPathsJ);
  147. json_t* cableColorsJ = json_array();
  148. for (const NVGcolor& cableColor : cableColors) {
  149. std::string colorStr = color::toHexString(cableColor);
  150. json_array_append_new(cableColorsJ, json_string(colorStr.c_str()));
  151. }
  152. json_object_set_new(rootJ, "cableColors", cableColorsJ);
  153. json_t* cableLabelsJ = json_array();
  154. for (const std::string& cableLabel : cableLabels) {
  155. json_array_append_new(cableLabelsJ, json_string(cableLabel.c_str()));
  156. }
  157. json_object_set_new(rootJ, "cableLabels", cableLabelsJ);
  158. json_object_set_new(rootJ, "cableAutoRotate", json_boolean(cableAutoRotate));
  159. json_object_set_new(rootJ, "autoCheckUpdates", json_boolean(autoCheckUpdates));
  160. json_object_set_new(rootJ, "verifyHttpsCerts", json_boolean(verifyHttpsCerts));
  161. json_object_set_new(rootJ, "showTipsOnLaunch", json_boolean(showTipsOnLaunch));
  162. json_object_set_new(rootJ, "tipIndex", json_integer(tipIndex));
  163. json_object_set_new(rootJ, "browserSort", json_integer((int) browserSort));
  164. json_object_set_new(rootJ, "browserZoom", json_real(browserZoom));
  165. // Merge pluginSettings instead of replace so plugins that fail to load don't cause their settings to be deleted.
  166. if (!pluginSettingsJ)
  167. pluginSettingsJ = json_object();
  168. plugin::settingsMergeJson(pluginSettingsJ);
  169. // Don't use *_set_new() here because we need to keep the reference to pluginSettingsJ.
  170. json_object_set(rootJ, "pluginSettings", pluginSettingsJ);
  171. // moduleInfos
  172. json_t* moduleInfosJ = json_object();
  173. for (const auto& pluginPair : moduleInfos) {
  174. json_t* pluginJ = json_object();
  175. for (const auto& modulePair : pluginPair.second) {
  176. const ModuleInfo& m = modulePair.second;
  177. json_t* moduleJ = json_object();
  178. {
  179. // To make setting.json smaller, only set properties if not default values.
  180. if (!m.enabled)
  181. json_object_set_new(moduleJ, "enabled", json_boolean(m.enabled));
  182. if (m.favorite)
  183. json_object_set_new(moduleJ, "favorite", json_boolean(m.favorite));
  184. if (m.added > 0)
  185. json_object_set_new(moduleJ, "added", json_integer(m.added));
  186. if (std::isfinite(m.lastAdded))
  187. json_object_set_new(moduleJ, "lastAdded", json_real(m.lastAdded));
  188. }
  189. if (json_object_size(moduleJ))
  190. json_object_set_new(pluginJ, modulePair.first.c_str(), moduleJ);
  191. else
  192. json_decref(moduleJ);
  193. }
  194. if (json_object_size(pluginJ))
  195. json_object_set_new(moduleInfosJ, pluginPair.first.c_str(), pluginJ);
  196. else
  197. json_decref(pluginJ);
  198. }
  199. json_object_set_new(rootJ, "moduleInfos", moduleInfosJ);
  200. // moduleWhitelist
  201. json_t* moduleWhitelistJ = json_object();
  202. for (const auto& pluginPair : moduleWhitelist) {
  203. const PluginWhitelist& plugin = pluginPair.second;
  204. json_t* pluginJ;
  205. // If plugin is subscribed, set to true, otherwise an array of module slugs.
  206. if (plugin.subscribed) {
  207. pluginJ = json_true();
  208. }
  209. else {
  210. pluginJ = json_array();
  211. for (const std::string& moduleSlug : plugin.moduleSlugs) {
  212. json_array_append_new(pluginJ, json_stringn(moduleSlug.c_str(), moduleSlug.size()));
  213. }
  214. }
  215. json_object_set_new(moduleWhitelistJ, pluginPair.first.c_str(), pluginJ);
  216. }
  217. json_object_set_new(rootJ, "moduleWhitelist", moduleWhitelistJ);
  218. return rootJ;
  219. }
  220. void fromJson(json_t* rootJ) {
  221. json_t* safeModeJ = json_object_get(rootJ, "safeMode");
  222. if (safeModeJ) {
  223. // If safe mode is enabled (e.g. by command line flag), don't disable it when loading.
  224. if (json_boolean_value(safeModeJ))
  225. safeMode = true;
  226. }
  227. json_t* tokenJ = json_object_get(rootJ, "token");
  228. if (tokenJ)
  229. token = json_string_value(tokenJ);
  230. json_t* windowMaximizedJ = json_object_get(rootJ, "windowMaximized");
  231. if (windowMaximizedJ)
  232. windowMaximized = json_boolean_value(windowMaximizedJ);
  233. json_t* windowSizeJ = json_object_get(rootJ, "windowSize");
  234. if (windowSizeJ) {
  235. double x, y;
  236. json_unpack(windowSizeJ, "[F, F]", &x, &y);
  237. windowSize = math::Vec(x, y);
  238. }
  239. json_t* windowPosJ = json_object_get(rootJ, "windowPos");
  240. if (windowPosJ) {
  241. double x, y;
  242. json_unpack(windowPosJ, "[F, F]", &x, &y);
  243. windowPos = math::Vec(x, y);
  244. }
  245. json_t* invertZoomJ = json_object_get(rootJ, "invertZoom");
  246. if (invertZoomJ)
  247. invertZoom = json_boolean_value(invertZoomJ);
  248. json_t* mouseWheelZoomJ = json_object_get(rootJ, "mouseWheelZoom");
  249. if (mouseWheelZoomJ)
  250. mouseWheelZoom = json_boolean_value(mouseWheelZoomJ);
  251. json_t* pixelRatioJ = json_object_get(rootJ, "pixelRatio");
  252. if (pixelRatioJ)
  253. pixelRatio = json_number_value(pixelRatioJ);
  254. json_t* uiThemeJ = json_object_get(rootJ, "uiTheme");
  255. if (uiThemeJ)
  256. uiTheme = json_string_value(uiThemeJ);
  257. json_t* cableOpacityJ = json_object_get(rootJ, "cableOpacity");
  258. if (cableOpacityJ)
  259. cableOpacity = json_number_value(cableOpacityJ);
  260. json_t* cableTensionJ = json_object_get(rootJ, "cableTension");
  261. if (cableTensionJ)
  262. cableTension = json_number_value(cableTensionJ);
  263. json_t* rackBrightnessJ = json_object_get(rootJ, "rackBrightness");
  264. if (rackBrightnessJ)
  265. rackBrightness = json_number_value(rackBrightnessJ);
  266. json_t* haloBrightnessJ = json_object_get(rootJ, "haloBrightness");
  267. if (haloBrightnessJ)
  268. haloBrightness = json_number_value(haloBrightnessJ);
  269. json_t* allowCursorLockJ = json_object_get(rootJ, "allowCursorLock");
  270. if (allowCursorLockJ)
  271. allowCursorLock = json_boolean_value(allowCursorLockJ);
  272. json_t* knobModeJ = json_object_get(rootJ, "knobMode");
  273. if (knobModeJ)
  274. knobMode = (KnobMode) json_integer_value(knobModeJ);
  275. json_t* knobScrollJ = json_object_get(rootJ, "knobScroll");
  276. if (knobScrollJ)
  277. knobScroll = json_boolean_value(knobScrollJ);
  278. json_t* knobLinearSensitivityJ = json_object_get(rootJ, "knobLinearSensitivity");
  279. if (knobLinearSensitivityJ)
  280. knobLinearSensitivity = json_number_value(knobLinearSensitivityJ);
  281. json_t* knobScrollSensitivityJ = json_object_get(rootJ, "knobScrollSensitivity");
  282. if (knobScrollSensitivityJ)
  283. knobScrollSensitivity = json_number_value(knobScrollSensitivityJ);
  284. json_t* sampleRateJ = json_object_get(rootJ, "sampleRate");
  285. if (sampleRateJ)
  286. sampleRate = json_number_value(sampleRateJ);
  287. json_t* threadCountJ = json_object_get(rootJ, "threadCount");
  288. if (threadCountJ)
  289. threadCount = json_integer_value(threadCountJ);
  290. json_t* tooltipsJ = json_object_get(rootJ, "tooltips");
  291. if (tooltipsJ)
  292. tooltips = json_boolean_value(tooltipsJ);
  293. json_t* cpuMeterJ = json_object_get(rootJ, "cpuMeter");
  294. if (cpuMeterJ)
  295. cpuMeter = json_boolean_value(cpuMeterJ);
  296. json_t* lockModulesJ = json_object_get(rootJ, "lockModules");
  297. if (lockModulesJ)
  298. lockModules = json_boolean_value(lockModulesJ);
  299. json_t* squeezeModulesJ = json_object_get(rootJ, "squeezeModules");
  300. if (squeezeModulesJ)
  301. squeezeModules = json_boolean_value(squeezeModulesJ);
  302. json_t* preferDarkPanelsJ = json_object_get(rootJ, "preferDarkPanels");
  303. if (preferDarkPanelsJ)
  304. preferDarkPanels = json_boolean_value(preferDarkPanelsJ);
  305. // Legacy setting in Rack <2.2
  306. json_t* frameSwapIntervalJ = json_object_get(rootJ, "frameSwapInterval");
  307. if (frameSwapIntervalJ) {
  308. // Assume 60 Hz monitor refresh rate.
  309. int frameSwapInterval = json_integer_value(frameSwapIntervalJ);
  310. if (frameSwapInterval > 0)
  311. frameRateLimit = 60.f / frameSwapInterval;
  312. else
  313. frameRateLimit = 0.f;
  314. }
  315. json_t* frameRateLimitJ = json_object_get(rootJ, "frameRateLimit");
  316. if (frameRateLimitJ)
  317. frameRateLimit = json_number_value(frameRateLimitJ);
  318. json_t* autosaveIntervalJ = json_object_get(rootJ, "autosaveInterval");
  319. if (autosaveIntervalJ)
  320. autosaveInterval = json_number_value(autosaveIntervalJ);
  321. json_t* skipLoadOnLaunchJ = json_object_get(rootJ, "skipLoadOnLaunch");
  322. if (skipLoadOnLaunchJ)
  323. skipLoadOnLaunch = json_boolean_value(skipLoadOnLaunchJ);
  324. recentPatchPaths.clear();
  325. json_t* recentPatchPathsJ = json_object_get(rootJ, "recentPatchPaths");
  326. if (recentPatchPathsJ) {
  327. size_t i;
  328. json_t* pathJ;
  329. json_array_foreach(recentPatchPathsJ, i, pathJ) {
  330. std::string path = json_string_value(pathJ);
  331. recentPatchPaths.push_back(path);
  332. }
  333. }
  334. // Update recent patches to use new dir
  335. if (asset::oldUserDir != "") {
  336. for (std::string& path : recentPatchPaths) {
  337. if (string::startsWith(path, asset::oldUserDir)) {
  338. path.replace(0, asset::oldUserDir.size(), asset::userDir);
  339. }
  340. }
  341. }
  342. cableColors.clear();
  343. json_t* cableColorsJ = json_object_get(rootJ, "cableColors");
  344. if (cableColorsJ) {
  345. size_t i;
  346. json_t* cableColorJ;
  347. json_array_foreach(cableColorsJ, i, cableColorJ) {
  348. std::string colorStr = json_string_value(cableColorJ);
  349. cableColors.push_back(color::fromHexString(colorStr));
  350. }
  351. }
  352. cableLabels.clear();
  353. json_t* cableLabelsJ = json_object_get(rootJ, "cableLabels");
  354. if (cableLabelsJ) {
  355. size_t i;
  356. json_t* cableLabelJ;
  357. json_array_foreach(cableLabelsJ, i, cableLabelJ) {
  358. cableLabels.push_back(json_string_value(cableLabelJ));
  359. }
  360. }
  361. json_t* cableAutoRotateJ = json_object_get(rootJ, "cableAutoRotate");
  362. if (cableAutoRotateJ)
  363. cableAutoRotate = json_boolean_value(cableAutoRotateJ);
  364. json_t* autoCheckUpdatesJ = json_object_get(rootJ, "autoCheckUpdates");
  365. if (autoCheckUpdatesJ)
  366. autoCheckUpdates = json_boolean_value(autoCheckUpdatesJ);
  367. json_t* verifyHttpsCertsJ = json_object_get(rootJ, "verifyHttpsCerts");
  368. if (verifyHttpsCertsJ)
  369. verifyHttpsCerts = json_boolean_value(verifyHttpsCertsJ);
  370. json_t* showTipsOnLaunchJ = json_object_get(rootJ, "showTipsOnLaunch");
  371. if (showTipsOnLaunchJ)
  372. showTipsOnLaunch = json_boolean_value(showTipsOnLaunchJ);
  373. json_t* tipIndexJ = json_object_get(rootJ, "tipIndex");
  374. if (tipIndexJ)
  375. tipIndex = json_integer_value(tipIndexJ);
  376. json_t* browserSortJ = json_object_get(rootJ, "browserSort");
  377. if (browserSortJ)
  378. browserSort = (BrowserSort) json_integer_value(browserSortJ);
  379. json_t* browserZoomJ = json_object_get(rootJ, "browserZoom");
  380. if (browserZoomJ)
  381. browserZoom = json_number_value(browserZoomJ);
  382. // Delete previous pluginSettings object
  383. if (pluginSettingsJ) {
  384. json_decref(pluginSettingsJ);
  385. pluginSettingsJ = NULL;
  386. }
  387. pluginSettingsJ = json_object_get(rootJ, "pluginSettings");
  388. if (pluginSettingsJ)
  389. json_incref(pluginSettingsJ);
  390. moduleInfos.clear();
  391. json_t* moduleInfosJ = json_object_get(rootJ, "moduleInfos");
  392. if (moduleInfosJ) {
  393. const char* pluginSlug;
  394. json_t* pluginJ;
  395. json_object_foreach(moduleInfosJ, pluginSlug, pluginJ) {
  396. const char* moduleSlug;
  397. json_t* moduleJ;
  398. json_object_foreach(pluginJ, moduleSlug, moduleJ) {
  399. ModuleInfo m;
  400. json_t* enabledJ = json_object_get(moduleJ, "enabled");
  401. if (enabledJ)
  402. m.enabled = json_boolean_value(enabledJ);
  403. json_t* favoriteJ = json_object_get(moduleJ, "favorite");
  404. if (favoriteJ)
  405. m.favorite = json_boolean_value(favoriteJ);
  406. json_t* addedJ = json_object_get(moduleJ, "added");
  407. if (addedJ)
  408. m.added = json_integer_value(addedJ);
  409. json_t* lastAddedJ = json_object_get(moduleJ, "lastAdded");
  410. if (lastAddedJ)
  411. m.lastAdded = json_number_value(lastAddedJ);
  412. moduleInfos[pluginSlug][moduleSlug] = m;
  413. }
  414. }
  415. }
  416. moduleWhitelist.clear();
  417. json_t* moduleWhitelistJ = json_object_get(rootJ, "moduleWhitelist");
  418. if (moduleWhitelistJ) {
  419. const char* pluginSlug;
  420. json_t* pluginJ;
  421. json_object_foreach(moduleWhitelistJ, pluginSlug, pluginJ) {
  422. auto& plugin = moduleWhitelist[pluginSlug];
  423. if (json_is_true(pluginJ)) {
  424. plugin.subscribed = true;
  425. continue;
  426. }
  427. size_t moduleIndex;
  428. json_t* moduleJ;
  429. json_array_foreach(pluginJ, moduleIndex, moduleJ) {
  430. std::string moduleSlug = json_string_value(moduleJ);
  431. plugin.moduleSlugs.insert(moduleSlug);
  432. }
  433. }
  434. }
  435. }
  436. void save(std::string path) {
  437. if (path.empty())
  438. path = settingsPath;
  439. INFO("Saving settings %s", path.c_str());
  440. json_t* rootJ = toJson();
  441. if (!rootJ)
  442. return;
  443. DEFER({json_decref(rootJ);});
  444. std::string tmpPath = path + ".tmp";
  445. FILE* file = std::fopen(tmpPath.c_str(), "w");
  446. if (!file)
  447. return;
  448. json_dumpf(rootJ, file, JSON_INDENT(2));
  449. std::fclose(file);
  450. system::remove(path);
  451. system::rename(tmpPath, path);
  452. }
  453. void load(std::string path) {
  454. if (path.empty())
  455. path = settingsPath;
  456. INFO("Loading settings %s", path.c_str());
  457. FILE* file = std::fopen(path.c_str(), "r");
  458. if (!file)
  459. return;
  460. DEFER({std::fclose(file);});
  461. json_error_t error;
  462. json_t* rootJ = json_loadf(file, 0, &error);
  463. if (!rootJ)
  464. throw Exception("Settings file has invalid JSON at %d:%d %s", error.line, error.column, error.text);
  465. DEFER({json_decref(rootJ);});
  466. fromJson(rootJ);
  467. }
  468. } // namespace settings
  469. } // namespace rack