skins.cpp (6123B)
1 2 #include "skins.hpp" 3 #include "bogaudio.hpp" 4 #include <unistd.h> 5 #include <fstream> 6 #include <cstdio> 7 8 Skins globalSkins; 9 std::mutex globalSkinsLock; 10 11 Skins& Skins::skins() { 12 // Hacky/blind attempt to fix https://github.com/bogaudio/BogaudioModules/issues/195. 13 // Replace this totally correct scoped static instance with a global, 14 // on the chance it will be handled more simply by the compiler. 15 // static Skins instance; 16 // std::lock_guard<std::mutex> lock(instance._instanceLock); 17 // if (!instance._loaded) { 18 // instance.loadSkins(); 19 // instance.loadCssValues(); 20 // instance._loaded = true; 21 // } 22 // return instance; 23 24 std::lock_guard<std::mutex> lock(globalSkins._instanceLock); 25 if (!globalSkins._loaded) { 26 globalSkins.loadSkins(); 27 globalSkins.loadCssValues(); 28 globalSkins._loaded = true; 29 } 30 return globalSkins; 31 } 32 33 bool Skins::validKey(const std::string& key) const { 34 for (auto s : _available) { 35 if (s.key == key) { 36 return true; 37 } 38 } 39 return false; 40 } 41 42 const char* Skins::skinCssValue(const std::string& skinKey, const std::string& valueKey) const { 43 std::string sk = skinKey; 44 if (sk == "default") { 45 sk = defaultKey(); 46 } 47 if (!validKey(sk)) { 48 return NULL; 49 } 50 51 auto values = _skinCssValues.find(sk); 52 if (values == _skinCssValues.end()) { 53 return NULL; 54 } 55 auto value = values->second.find(valueKey); 56 if (value == values->second.end()) { 57 return NULL; 58 } 59 return value->second.c_str(); 60 } 61 62 NVGcolor Skins::cssColorToNVGColor(const char* color, const NVGcolor& ifError) { 63 auto h2i = [](char h) { 64 switch (h) { 65 case '0': return 0; 66 case '1': return 1; 67 case '2': return 2; 68 case '3': return 3; 69 case '4': return 4; 70 case '5': return 5; 71 case '6': return 6; 72 case '7': return 7; 73 case '8': return 8; 74 case '9': return 9; 75 case 'A': return 10; 76 case 'B': return 11; 77 case 'C': return 12; 78 case 'D': return 13; 79 case 'E': return 14; 80 case 'F': return 15; 81 case 'a': return 10; 82 case 'b': return 11; 83 case 'c': return 12; 84 case 'd': return 13; 85 case 'e': return 14; 86 case 'f': return 15; 87 default: return -1; 88 } 89 }; 90 if (color[0] == '#') { 91 if (strlen(color) == 4) { 92 int i1 = h2i(color[1]); 93 int i2 = h2i(color[2]); 94 int i3 = h2i(color[3]); 95 if (i1 >= 0 && i2 >= 0 && i3 >= 0) { 96 return nvgRGBA(16*i1 + i1, 16*i2 + i2, 16*i3 + i3, 0xff); 97 } 98 } 99 else if (strlen(color) == 7) { 100 int i11 = h2i(color[1]); 101 int i12 = h2i(color[1]); 102 int i21 = h2i(color[3]); 103 int i22 = h2i(color[4]); 104 int i31 = h2i(color[5]); 105 int i32 = h2i(color[6]); 106 if (i11 >= 0 && i12 >= 0 && i21 >= 0 && i22 >= 0 && i31 >= 0 && i32 >= 0) { 107 return nvgRGBA(16*i11 + i12, 16*i21 + i22, 16*i31 + i32, 0xff); 108 } 109 } 110 } 111 return ifError; 112 } 113 114 void Skins::setDefaultSkin(std::string skinKey) { 115 if (skinKey == "default") { 116 skinKey = "light"; 117 } 118 std::string path = rack::asset::user("Bogaudio.json"); 119 std::string error; 120 if (!validKey(skinKey)) { 121 error = "invalid key: " + skinKey; 122 } 123 else { 124 std::ofstream f(path); 125 f << "{\n \"skins\": {\n \"default\": \""; 126 f << skinKey; 127 f << "\"\n }\n}\n"; 128 if (!f) { 129 error = "error writing \"" + path + "\": " + strerror(errno); 130 } 131 } 132 133 if (error.size() > 0) { 134 WARN("Bogaudio: setting default skin: %s\n", error.c_str()); 135 } 136 else { 137 _default = skinKey; 138 INFO("Bogaudio: skin information written to %s\n", path.c_str()); 139 140 std::lock_guard<std::mutex> lock(_defaultSkinListenersLock); 141 for (auto listener : _defaultSkinListeners) { 142 listener->defaultSkinChanged(_default); 143 } 144 } 145 } 146 147 void Skins::registerDefaultSkinChangeListener(DefaultSkinChangeListener* listener) { 148 std::lock_guard<std::mutex> lock(_defaultSkinListenersLock); 149 _defaultSkinListeners.insert(listener); 150 } 151 152 void Skins::deregisterDefaultSkinChangeListener(DefaultSkinChangeListener* listener) { 153 std::lock_guard<std::mutex> lock(_defaultSkinListenersLock); 154 _defaultSkinListeners.erase(listener); 155 } 156 157 void Skins::loadSkins() { 158 _available.push_back(Skin("light", "Light")); 159 _available.push_back(Skin("dark", "Dark")); 160 _available.push_back(Skin("lowcontrast", "Dark (low-contrast)")); 161 _default = "light"; 162 163 std::string path = rack::asset::user("Bogaudio.json"); 164 if (access(path.c_str(), R_OK) != 0) { 165 return; 166 } 167 168 json_error_t error; 169 json_t* root = json_load_file(path.c_str(), 0, &error); 170 if (!root) { 171 WARN("Bogaudio: JSON error loading skins data from %s: %s\n", path.c_str(), error.text); 172 return; 173 } 174 175 json_t* skins = json_object_get(root, "skins"); 176 if (!skins) { 177 WARN("Bogaudio: no \"skins\" section found in %s\n", path.c_str()); 178 } 179 else { 180 json_t* d = json_object_get(skins, "default"); 181 if (!d) { 182 WARN("Bogaudio: \"skins\" section has no key \"default\" in %s\n", path.c_str()); 183 } 184 else { 185 std::string dk = json_string_value(d); 186 if (!validKey(dk)) { 187 WARN("Bogaudio: \"skins\" \"default\" value \"%s\" is invalid in %s\n", dk.c_str(), path.c_str()); 188 WARN("Bogaudio: available skins are:\n"); 189 for (auto s : _available) { 190 WARN("Bogaudio: %s\n", s.key.c_str()); 191 } 192 } 193 else { 194 _default = dk; 195 INFO("Bogaudio: skin information loaded successfully from %s\n", path.c_str()); 196 } 197 } 198 } 199 200 json_decref(root); 201 } 202 203 void Skins::loadCssValues() { 204 std::string path = rack::asset::plugin(pluginInstance, "res/skin_css_values.json"); 205 if (access(path.c_str(), R_OK) != 0) { 206 return; 207 } 208 209 json_error_t error; 210 json_t* root = json_load_file(path.c_str(), 0, &error); 211 if (!root) { 212 WARN("Bogaudio: JSON error loading CSS values data from %s: %s\n", path.c_str(), error.text); 213 return; 214 } 215 216 void* i = json_object_iter(root); 217 while (i) { 218 const char* skin = json_object_iter_key(i); 219 json_t* values = json_object_iter_value(i); 220 void* j = json_object_iter(values); 221 css_values_map valuesMap; 222 while (j) { 223 const char* k = json_object_iter_key(j); 224 const char* v = json_string_value(json_object_iter_value(j)); 225 valuesMap.insert(css_values_map::value_type(k, v)); 226 j = json_object_iter_next(values, j); 227 } 228 _skinCssValues.insert(skin_css_values_map::value_type(skin, valuesMap)); 229 i = json_object_iter_next(root, i); 230 } 231 232 json_decref(root); 233 }