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.

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