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.

473 lines
14KB

  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. namespace rack {
  11. namespace settings {
  12. std::string settingsPath;
  13. bool devMode = false;
  14. bool headless = false;
  15. bool isPlugin = false;
  16. std::string token;
  17. bool windowMaximized = false;
  18. math::Vec windowSize = math::Vec(1024, 720);
  19. math::Vec windowPos = math::Vec(NAN, NAN);
  20. bool invertZoom = false;
  21. float pixelRatio = 0.0;
  22. float cableOpacity = 0.5;
  23. float cableTension = 1.0;
  24. float rackBrightness = 1.0;
  25. float haloBrightness = 0.25;
  26. bool allowCursorLock = true;
  27. KnobMode knobMode = KNOB_MODE_LINEAR;
  28. bool knobScroll = false;
  29. float knobLinearSensitivity = 0.001f;
  30. float knobScrollSensitivity = 0.001f;
  31. float sampleRate = 0;
  32. int threadCount = 1;
  33. bool tooltips = true;
  34. bool cpuMeter = false;
  35. bool lockModules = false;
  36. #if defined ARCH_MAC
  37. // Most Mac GPUs can't handle rendering the screen every frame, so use ~30 Hz by default.
  38. int frameSwapInterval = 2;
  39. #else
  40. int frameSwapInterval = 1;
  41. #endif
  42. float autosaveInterval = 15.0;
  43. bool skipLoadOnLaunch = false;
  44. std::list<std::string> recentPatchPaths;
  45. std::vector<NVGcolor> cableColors = {
  46. color::fromHexString("#f3374b"), // red
  47. color::fromHexString("#ffb437"), // yellow
  48. color::fromHexString("#00b56e"), // green
  49. color::fromHexString("#3695ef"), // blue
  50. color::fromHexString("#8b4ade"), // purple
  51. };
  52. bool autoCheckUpdates = true;
  53. bool showTipsOnLaunch = true;
  54. int tipIndex = -1;
  55. bool discordUpdateActivity = true;
  56. BrowserSort browserSort = BROWSER_SORT_UPDATED;
  57. float browserZoom = -1.f;
  58. std::map<std::string, std::map<std::string, ModuleInfo>> moduleInfos;
  59. std::map<std::string, PluginWhitelist> moduleWhitelist;
  60. ModuleInfo* getModuleInfo(const std::string& pluginSlug, const std::string& moduleSlug) {
  61. auto pluginIt = moduleInfos.find(pluginSlug);
  62. if (pluginIt == moduleInfos.end())
  63. return NULL;
  64. auto moduleIt = pluginIt->second.find(moduleSlug);
  65. if (moduleIt == pluginIt->second.end())
  66. return NULL;
  67. return &moduleIt->second;
  68. }
  69. bool isModuleWhitelisted(const std::string& pluginSlug, const std::string& moduleSlug) {
  70. auto pluginIt = moduleWhitelist.find(pluginSlug);
  71. // All modules in a plugin are visible if plugin set is empty.
  72. if (pluginIt == moduleWhitelist.end())
  73. return true;
  74. // All modules in a plugin are visible if plugin set is subscribed.
  75. const PluginWhitelist& plugin = pluginIt->second;
  76. if (plugin.subscribed)
  77. return true;
  78. // Check if plugin whitelist contains module
  79. auto moduleIt = plugin.moduleSlugs.find(moduleSlug);
  80. if (moduleIt == plugin.moduleSlugs.end())
  81. return false;
  82. return true;
  83. }
  84. void init() {
  85. settingsPath = asset::user("settings.json");
  86. }
  87. json_t* toJson() {
  88. json_t* rootJ = json_object();
  89. json_object_set_new(rootJ, "token", json_string(token.c_str()));
  90. json_object_set_new(rootJ, "windowMaximized", json_boolean(windowMaximized));
  91. json_t* windowSizeJ = json_pack("[f, f]", windowSize.x, windowSize.y);
  92. json_object_set_new(rootJ, "windowSize", windowSizeJ);
  93. json_t* windowPosJ = json_pack("[f, f]", windowPos.x, windowPos.y);
  94. json_object_set_new(rootJ, "windowPos", windowPosJ);
  95. json_object_set_new(rootJ, "invertZoom", json_boolean(invertZoom));
  96. json_object_set_new(rootJ, "pixelRatio", json_real(pixelRatio));
  97. json_object_set_new(rootJ, "cableOpacity", json_real(cableOpacity));
  98. json_object_set_new(rootJ, "cableTension", json_real(cableTension));
  99. json_object_set_new(rootJ, "rackBrightness", json_real(rackBrightness));
  100. json_object_set_new(rootJ, "haloBrightness", json_real(haloBrightness));
  101. json_object_set_new(rootJ, "allowCursorLock", json_boolean(allowCursorLock));
  102. json_object_set_new(rootJ, "knobMode", json_integer((int) knobMode));
  103. json_object_set_new(rootJ, "knobScroll", json_boolean(knobScroll));
  104. json_object_set_new(rootJ, "knobLinearSensitivity", json_real(knobLinearSensitivity));
  105. json_object_set_new(rootJ, "knobScrollSensitivity", json_real(knobScrollSensitivity));
  106. json_object_set_new(rootJ, "sampleRate", json_real(sampleRate));
  107. json_object_set_new(rootJ, "threadCount", json_integer(threadCount));
  108. json_object_set_new(rootJ, "tooltips", json_boolean(tooltips));
  109. json_object_set_new(rootJ, "cpuMeter", json_boolean(cpuMeter));
  110. json_object_set_new(rootJ, "lockModules", json_boolean(lockModules));
  111. json_object_set_new(rootJ, "frameSwapInterval", json_integer(frameSwapInterval));
  112. json_object_set_new(rootJ, "autosaveInterval", json_real(autosaveInterval));
  113. if (skipLoadOnLaunch)
  114. json_object_set_new(rootJ, "skipLoadOnLaunch", json_boolean(true));
  115. json_t* recentPatchPathsJ = json_array();
  116. for (const std::string& path : recentPatchPaths) {
  117. json_array_append_new(recentPatchPathsJ, json_string(path.c_str()));
  118. }
  119. json_object_set_new(rootJ, "recentPatchPaths", recentPatchPathsJ);
  120. json_t* cableColorsJ = json_array();
  121. for (NVGcolor cableColor : cableColors) {
  122. std::string colorStr = color::toHexString(cableColor);
  123. json_array_append_new(cableColorsJ, json_string(colorStr.c_str()));
  124. }
  125. json_object_set_new(rootJ, "cableColors", cableColorsJ);
  126. json_object_set_new(rootJ, "autoCheckUpdates", json_boolean(autoCheckUpdates));
  127. json_object_set_new(rootJ, "showTipsOnLaunch", json_boolean(showTipsOnLaunch));
  128. json_object_set_new(rootJ, "tipIndex", json_integer(tipIndex));
  129. if (!discordUpdateActivity)
  130. json_object_set_new(rootJ, "discordUpdateActivity", json_boolean(discordUpdateActivity));
  131. json_object_set_new(rootJ, "browserSort", json_integer((int) browserSort));
  132. json_object_set_new(rootJ, "browserZoom", json_real(browserZoom));
  133. // moduleInfos
  134. json_t* moduleInfosJ = json_object();
  135. for (const auto& pluginPair : moduleInfos) {
  136. json_t* pluginJ = json_object();
  137. for (const auto& modulePair : pluginPair.second) {
  138. const ModuleInfo& m = modulePair.second;
  139. json_t* moduleJ = json_object();
  140. {
  141. // To make setting.json smaller, only set properties if not default values.
  142. if (!m.enabled)
  143. json_object_set_new(moduleJ, "enabled", json_boolean(m.enabled));
  144. if (m.favorite)
  145. json_object_set_new(moduleJ, "favorite", json_boolean(m.favorite));
  146. if (m.added > 0)
  147. json_object_set_new(moduleJ, "added", json_integer(m.added));
  148. if (std::isfinite(m.lastAdded))
  149. json_object_set_new(moduleJ, "lastAdded", json_real(m.lastAdded));
  150. }
  151. if (json_object_size(moduleJ))
  152. json_object_set_new(pluginJ, modulePair.first.c_str(), moduleJ);
  153. else
  154. json_decref(moduleJ);
  155. }
  156. if (json_object_size(pluginJ))
  157. json_object_set_new(moduleInfosJ, pluginPair.first.c_str(), pluginJ);
  158. else
  159. json_decref(pluginJ);
  160. }
  161. json_object_set_new(rootJ, "moduleInfos", moduleInfosJ);
  162. // moduleWhitelist
  163. json_t* moduleWhitelistJ = json_object();
  164. for (const auto& pluginPair : moduleWhitelist) {
  165. const PluginWhitelist& plugin = pluginPair.second;
  166. json_t* pluginJ;
  167. // If plugin is subscribed, set to true, otherwise an array of module slugs.
  168. if (plugin.subscribed) {
  169. pluginJ = json_true();
  170. }
  171. else {
  172. pluginJ = json_array();
  173. for (const std::string& moduleSlug : plugin.moduleSlugs) {
  174. json_array_append_new(pluginJ, json_stringn(moduleSlug.c_str(), moduleSlug.size()));
  175. }
  176. }
  177. json_object_set_new(moduleWhitelistJ, pluginPair.first.c_str(), pluginJ);
  178. }
  179. json_object_set_new(rootJ, "moduleWhitelist", moduleWhitelistJ);
  180. return rootJ;
  181. }
  182. void fromJson(json_t* rootJ) {
  183. json_t* tokenJ = json_object_get(rootJ, "token");
  184. if (tokenJ)
  185. token = json_string_value(tokenJ);
  186. json_t* windowMaximizedJ = json_object_get(rootJ, "windowMaximized");
  187. if (windowMaximizedJ)
  188. windowMaximized = json_boolean_value(windowMaximizedJ);
  189. json_t* windowSizeJ = json_object_get(rootJ, "windowSize");
  190. if (windowSizeJ) {
  191. double x, y;
  192. json_unpack(windowSizeJ, "[F, F]", &x, &y);
  193. windowSize = math::Vec(x, y);
  194. }
  195. json_t* windowPosJ = json_object_get(rootJ, "windowPos");
  196. if (windowPosJ) {
  197. double x, y;
  198. json_unpack(windowPosJ, "[F, F]", &x, &y);
  199. windowPos = math::Vec(x, y);
  200. }
  201. json_t* invertZoomJ = json_object_get(rootJ, "invertZoom");
  202. if (invertZoomJ)
  203. invertZoom = json_boolean_value(invertZoomJ);
  204. json_t* pixelRatioJ = json_object_get(rootJ, "pixelRatio");
  205. if (pixelRatioJ)
  206. pixelRatio = json_number_value(pixelRatioJ);
  207. json_t* cableOpacityJ = json_object_get(rootJ, "cableOpacity");
  208. if (cableOpacityJ)
  209. cableOpacity = json_number_value(cableOpacityJ);
  210. json_t* cableTensionJ = json_object_get(rootJ, "cableTension");
  211. if (cableTensionJ)
  212. cableTension = json_number_value(cableTensionJ);
  213. json_t* rackBrightnessJ = json_object_get(rootJ, "rackBrightness");
  214. if (rackBrightnessJ)
  215. rackBrightness = json_number_value(rackBrightnessJ);
  216. json_t* haloBrightnessJ = json_object_get(rootJ, "haloBrightness");
  217. if (haloBrightnessJ)
  218. haloBrightness = json_number_value(haloBrightnessJ);
  219. json_t* allowCursorLockJ = json_object_get(rootJ, "allowCursorLock");
  220. if (allowCursorLockJ)
  221. allowCursorLock = json_boolean_value(allowCursorLockJ);
  222. json_t* knobModeJ = json_object_get(rootJ, "knobMode");
  223. if (knobModeJ)
  224. knobMode = (KnobMode) json_integer_value(knobModeJ);
  225. json_t* knobScrollJ = json_object_get(rootJ, "knobScroll");
  226. if (knobScrollJ)
  227. knobScroll = json_boolean_value(knobScrollJ);
  228. json_t* knobLinearSensitivityJ = json_object_get(rootJ, "knobLinearSensitivity");
  229. if (knobLinearSensitivityJ)
  230. knobLinearSensitivity = json_number_value(knobLinearSensitivityJ);
  231. json_t* knobScrollSensitivityJ = json_object_get(rootJ, "knobScrollSensitivity");
  232. if (knobScrollSensitivityJ)
  233. knobScrollSensitivity = json_number_value(knobScrollSensitivityJ);
  234. json_t* sampleRateJ = json_object_get(rootJ, "sampleRate");
  235. if (sampleRateJ)
  236. sampleRate = json_number_value(sampleRateJ);
  237. json_t* threadCountJ = json_object_get(rootJ, "threadCount");
  238. if (threadCountJ)
  239. threadCount = json_integer_value(threadCountJ);
  240. json_t* tooltipsJ = json_object_get(rootJ, "tooltips");
  241. if (tooltipsJ)
  242. tooltips = json_boolean_value(tooltipsJ);
  243. json_t* cpuMeterJ = json_object_get(rootJ, "cpuMeter");
  244. if (cpuMeterJ)
  245. cpuMeter = json_boolean_value(cpuMeterJ);
  246. json_t* lockModulesJ = json_object_get(rootJ, "lockModules");
  247. if (lockModulesJ)
  248. lockModules = json_boolean_value(lockModulesJ);
  249. json_t* frameSwapIntervalJ = json_object_get(rootJ, "frameSwapInterval");
  250. if (frameSwapIntervalJ)
  251. frameSwapInterval = json_integer_value(frameSwapIntervalJ);
  252. json_t* autosaveIntervalJ = json_object_get(rootJ, "autosaveInterval");
  253. if (autosaveIntervalJ)
  254. autosaveInterval = json_number_value(autosaveIntervalJ);
  255. json_t* skipLoadOnLaunchJ = json_object_get(rootJ, "skipLoadOnLaunch");
  256. if (skipLoadOnLaunchJ)
  257. skipLoadOnLaunch = json_boolean_value(skipLoadOnLaunchJ);
  258. recentPatchPaths.clear();
  259. json_t* recentPatchPathsJ = json_object_get(rootJ, "recentPatchPaths");
  260. if (recentPatchPathsJ) {
  261. size_t i;
  262. json_t* pathJ;
  263. json_array_foreach(recentPatchPathsJ, i, pathJ) {
  264. std::string path = json_string_value(pathJ);
  265. recentPatchPaths.push_back(path);
  266. }
  267. }
  268. cableColors.clear();
  269. json_t* cableColorsJ = json_object_get(rootJ, "cableColors");
  270. if (cableColorsJ) {
  271. size_t i;
  272. json_t* cableColorJ;
  273. json_array_foreach(cableColorsJ, i, cableColorJ) {
  274. std::string colorStr = json_string_value(cableColorJ);
  275. cableColors.push_back(color::fromHexString(colorStr));
  276. }
  277. }
  278. json_t* autoCheckUpdatesJ = json_object_get(rootJ, "autoCheckUpdates");
  279. if (autoCheckUpdatesJ)
  280. autoCheckUpdates = json_boolean_value(autoCheckUpdatesJ);
  281. json_t* showTipsOnLaunchJ = json_object_get(rootJ, "showTipsOnLaunch");
  282. if (showTipsOnLaunchJ)
  283. showTipsOnLaunch = json_boolean_value(showTipsOnLaunchJ);
  284. json_t* tipIndexJ = json_object_get(rootJ, "tipIndex");
  285. if (tipIndexJ)
  286. tipIndex = json_integer_value(tipIndexJ);
  287. json_t* discordUpdateActivityJ = json_object_get(rootJ, "discordUpdateActivity");
  288. if (discordUpdateActivityJ)
  289. discordUpdateActivity = json_boolean_value(discordUpdateActivityJ);
  290. json_t* browserSortJ = json_object_get(rootJ, "browserSort");
  291. if (browserSortJ)
  292. browserSort = (BrowserSort) json_integer_value(browserSortJ);
  293. json_t* browserZoomJ = json_object_get(rootJ, "browserZoom");
  294. if (browserZoomJ)
  295. browserZoom = json_number_value(browserZoomJ);
  296. moduleInfos.clear();
  297. json_t* moduleInfosJ = json_object_get(rootJ, "moduleInfos");
  298. if (moduleInfosJ) {
  299. const char* pluginSlug;
  300. json_t* pluginJ;
  301. json_object_foreach(moduleInfosJ, pluginSlug, pluginJ) {
  302. const char* moduleSlug;
  303. json_t* moduleJ;
  304. json_object_foreach(pluginJ, moduleSlug, moduleJ) {
  305. ModuleInfo m;
  306. json_t* enabledJ = json_object_get(moduleJ, "enabled");
  307. if (enabledJ)
  308. m.enabled = json_boolean_value(enabledJ);
  309. json_t* favoriteJ = json_object_get(moduleJ, "favorite");
  310. if (favoriteJ)
  311. m.favorite = json_boolean_value(favoriteJ);
  312. json_t* addedJ = json_object_get(moduleJ, "added");
  313. if (addedJ)
  314. m.added = json_integer_value(addedJ);
  315. json_t* lastAddedJ = json_object_get(moduleJ, "lastAdded");
  316. if (lastAddedJ)
  317. m.lastAdded = json_number_value(lastAddedJ);
  318. moduleInfos[pluginSlug][moduleSlug] = m;
  319. }
  320. }
  321. }
  322. moduleWhitelist.clear();
  323. json_t* moduleWhitelistJ = json_object_get(rootJ, "moduleWhitelist");
  324. if (moduleWhitelistJ) {
  325. const char* pluginSlug;
  326. json_t* pluginJ;
  327. json_object_foreach(moduleWhitelistJ, pluginSlug, pluginJ) {
  328. auto& plugin = moduleWhitelist[pluginSlug];
  329. if (json_is_true(pluginJ)) {
  330. plugin.subscribed = true;
  331. continue;
  332. }
  333. size_t moduleIndex;
  334. json_t* moduleJ;
  335. json_array_foreach(pluginJ, moduleIndex, moduleJ) {
  336. std::string moduleSlug = json_string_value(moduleJ);
  337. plugin.moduleSlugs.insert(moduleSlug);
  338. }
  339. }
  340. }
  341. }
  342. void save(std::string path) {
  343. if (path.empty())
  344. path = settingsPath;
  345. INFO("Saving settings %s", path.c_str());
  346. json_t* rootJ = toJson();
  347. if (!rootJ)
  348. return;
  349. FILE* file = std::fopen(path.c_str(), "w");
  350. if (!file)
  351. return;
  352. DEFER({std::fclose(file);});
  353. json_dumpf(rootJ, file, JSON_INDENT(2));
  354. json_decref(rootJ);
  355. }
  356. void load(std::string path) {
  357. if (path.empty())
  358. path = settingsPath;
  359. INFO("Loading settings %s", path.c_str());
  360. FILE* file = std::fopen(path.c_str(), "r");
  361. if (!file)
  362. return;
  363. DEFER({std::fclose(file);});
  364. json_error_t error;
  365. json_t* rootJ = json_loadf(file, 0, &error);
  366. if (!rootJ)
  367. throw Exception("Settings file has invalid JSON at %d:%d %s", error.line, error.column, error.text);
  368. fromJson(rootJ);
  369. json_decref(rootJ);
  370. }
  371. } // namespace settings
  372. } // namespace rack