1 #pragma once 2 3 #include <unordered_map> 4 5 #include "common.hpp" 6 #include "components/logger.hpp" 7 #include "errors.hpp" 8 #include "settings.hpp" 9 #include "utils/env.hpp" 10 #include "utils/file.hpp" 11 #include "utils/string.hpp" 12 #if WITH_XRM 13 #include "x11/xresources.hpp" 14 #endif 15 16 POLYBAR_NS 17 18 DEFINE_ERROR(value_error); 19 DEFINE_ERROR(key_error); 20 21 using valuemap_t = std::unordered_map<string, string>; 22 using sectionmap_t = std::map<string, valuemap_t>; 23 using file_list = vector<string>; 24 25 class config { 26 public: 27 using make_type = const config&; 28 static make_type make(string path = "", string bar = ""); 29 config(const logger & logger,string && path="",string && bar="")30 explicit config(const logger& logger, string&& path = "", string&& bar = "") 31 : m_log(logger), m_file(move(path)), m_barname(move(bar)){}; 32 33 const string& filepath() const; 34 string section() const; 35 36 /** 37 * \brief Instruct the config to connect to the xresource manager 38 */ 39 void use_xrm(); 40 41 void set_sections(sectionmap_t sections); 42 43 void set_included(file_list included); 44 45 void warn_deprecated(const string& section, const string& key, string replacement) const; 46 47 /** 48 * Returns true if a given parameter exists 49 */ has(const string & section,const string & key) const50 bool has(const string& section, const string& key) const { 51 auto it = m_sections.find(section); 52 return it != m_sections.end() && it->second.find(key) != it->second.end(); 53 } 54 55 /** 56 * Set parameter value 57 */ set(const string & section,const string & key,string && value)58 void set(const string& section, const string& key, string&& value) { 59 auto it = m_sections.find(section); 60 if (it == m_sections.end()) { 61 valuemap_t values; 62 values[key] = value; 63 m_sections[section] = move(values); 64 } 65 auto it2 = it->second.find(key); 66 if ((it2 = it->second.find(key)) == it->second.end()) { 67 it2 = it->second.emplace_hint(it2, key, value); 68 } else { 69 it2->second = value; 70 } 71 } 72 73 /** 74 * Get parameter for the current bar by name 75 */ 76 template <typename T = string> get(const string & key) const77 T get(const string& key) const { 78 return get<T>(section(), key); 79 } 80 81 /** 82 * Get value of a variable by section and parameter name 83 */ 84 template <typename T = string> get(const string & section,const string & key) const85 T get(const string& section, const string& key) const { 86 auto it = m_sections.find(section); 87 if (it == m_sections.end()) { 88 throw key_error("Missing section \"" + section + "\""); 89 } 90 if (it->second.find(key) == it->second.end()) { 91 throw key_error("Missing parameter \"" + section + "." + key + "\""); 92 } 93 return dereference<T>(section, key, it->second.at(key), convert<T>(string{it->second.at(key)})); 94 } 95 96 /** 97 * Get value of a variable by section and parameter name 98 * with a default value in case the parameter isn't defined 99 */ 100 template <typename T = string> get(const string & section,const string & key,const T & default_value) const101 T get(const string& section, const string& key, const T& default_value) const { 102 try { 103 string string_value{get<string>(section, key)}; 104 T result{convert<T>(string{string_value})}; 105 return dereference<T>(move(section), move(key), move(string_value), move(result)); 106 } catch (const key_error& err) { 107 return default_value; 108 } catch (const value_error& err) { 109 m_log.err("Invalid value for \"%s.%s\", using default value (reason: %s)", section, key, err.what()); 110 return default_value; 111 } 112 } 113 114 /** 115 * Get list of values for the current bar by name 116 */ 117 template <typename T = string> get_list(const string & key) const118 vector<T> get_list(const string& key) const { 119 return get_list<T>(section(), key); 120 } 121 122 /** 123 * Get list of values by section and parameter name 124 */ 125 template <typename T = string> get_list(const string & section,const string & key) const126 vector<T> get_list(const string& section, const string& key) const { 127 vector<T> results; 128 129 while (true) { 130 try { 131 string string_value{get<string>(section, key + "-" + to_string(results.size()))}; 132 T value{convert<T>(string{string_value})}; 133 134 if (!string_value.empty()) { 135 results.emplace_back(dereference<T>(section, key, move(string_value), move(value))); 136 } else { 137 results.emplace_back(move(value)); 138 } 139 } catch (const key_error& err) { 140 break; 141 } 142 } 143 144 if (results.empty()) { 145 throw key_error("Missing parameter \"" + section + "." + key + "-0\""); 146 } 147 148 return results; 149 } 150 151 /** 152 * Get list of values by section and parameter name 153 * with a default list in case the list isn't defined 154 */ 155 template <typename T = string> get_list(const string & section,const string & key,const vector<T> & default_value) const156 vector<T> get_list(const string& section, const string& key, const vector<T>& default_value) const { 157 vector<T> results; 158 159 while (true) { 160 try { 161 string string_value{get<string>(section, key + "-" + to_string(results.size()))}; 162 T value{convert<T>(string{string_value})}; 163 164 if (!string_value.empty()) { 165 results.emplace_back(dereference<T>(section, key, move(string_value), move(value))); 166 } else { 167 results.emplace_back(move(value)); 168 } 169 } catch (const key_error& err) { 170 break; 171 } catch (const value_error& err) { 172 m_log.err("Invalid value in list \"%s.%s\", using list as-is (reason: %s)", section, key, err.what()); 173 return default_value; 174 } 175 } 176 177 if (!results.empty()) { 178 return results; 179 ; 180 } 181 182 return default_value; 183 } 184 185 /** 186 * Attempt to load value using the deprecated key name. If successful show a 187 * warning message. If it fails load the value using the new key and given 188 * fallback value 189 */ 190 template <typename T = string> deprecated(const string & section,const string & old,const string & newkey,const T & fallback) const191 T deprecated(const string& section, const string& old, const string& newkey, const T& fallback) const { 192 try { 193 T value{get<T>(section, old)}; 194 warn_deprecated(section, old, newkey); 195 return value; 196 } catch (const key_error& err) { 197 return get<T>(section, newkey, fallback); 198 } 199 } 200 201 /** 202 * \see deprecated<T> 203 */ 204 template <typename T = string> deprecated_list(const string & section,const string & old,const string & newkey,const vector<T> & fallback) const205 T deprecated_list(const string& section, const string& old, const string& newkey, const vector<T>& fallback) const { 206 try { 207 vector<T> value{get_list<T>(section, old)}; 208 warn_deprecated(section, old, newkey); 209 return value; 210 } catch (const key_error& err) { 211 return get_list<T>(section, newkey, fallback); 212 } 213 } 214 215 protected: 216 void copy_inherited(); 217 218 template <typename T> 219 T convert(string&& value) const; 220 221 /** 222 * Dereference value reference 223 */ 224 template <typename T> dereference(const string & section,const string & key,const string & var,const T & fallback) const225 T dereference(const string& section, const string& key, const string& var, const T& fallback) const { 226 if (var.substr(0, 2) != "${" || var.substr(var.length() - 1) != "}") { 227 return fallback; 228 } 229 230 auto path = var.substr(2, var.length() - 3); 231 size_t pos; 232 233 if (path.compare(0, 4, "env:") == 0) { 234 return dereference_env<T>(path.substr(4)); 235 } else if (path.compare(0, 5, "xrdb:") == 0) { 236 return dereference_xrdb<T>(path.substr(5)); 237 } else if (path.compare(0, 5, "file:") == 0) { 238 return dereference_file<T>(path.substr(5)); 239 } else if ((pos = path.find(".")) != string::npos) { 240 return dereference_local<T>(path.substr(0, pos), path.substr(pos + 1), section); 241 } else { 242 throw value_error("Invalid reference defined at \"" + section + "." + key + "\""); 243 } 244 } 245 246 /** 247 * Dereference local value reference defined using: 248 * ${root.key} 249 * ${root.key:fallback} 250 * ${self.key} 251 * ${self.key:fallback} 252 * ${section.key} 253 * ${section.key:fallback} 254 */ 255 template <typename T> dereference_local(string section,const string & key,const string & current_section) const256 T dereference_local(string section, const string& key, const string& current_section) const { 257 if (section == "BAR") { 258 m_log.warn("${BAR.key} is deprecated. Use ${root.key} instead"); 259 } 260 261 section = string_util::replace(section, "BAR", this->section(), 0, 3); 262 section = string_util::replace(section, "root", this->section(), 0, 4); 263 section = string_util::replace(section, "self", current_section, 0, 4); 264 265 try { 266 string string_value{get<string>(section, key)}; 267 T result{convert<T>(string{string_value})}; 268 return dereference<T>(string(section), move(key), move(string_value), move(result)); 269 } catch (const key_error& err) { 270 size_t pos; 271 if ((pos = key.find(':')) != string::npos) { 272 string fallback = key.substr(pos + 1); 273 m_log.info("The reference ${%s.%s} does not exist, using defined fallback value \"%s\"", section, 274 key.substr(0, pos), fallback); 275 return convert<T>(move(fallback)); 276 } 277 throw value_error("The reference ${" + section + "." + key + "} does not exist (no fallback set)"); 278 } 279 } 280 281 /** 282 * Dereference environment variable reference defined using: 283 * ${env:key} 284 * ${env:key:fallback value} 285 */ 286 template <typename T> dereference_env(string var) const287 T dereference_env(string var) const { 288 size_t pos; 289 string env_default; 290 /* 291 * This is needed because with only the string we cannot distinguish 292 * between an empty string as default and not default 293 */ 294 bool has_default = false; 295 296 if ((pos = var.find(':')) != string::npos) { 297 env_default = var.substr(pos + 1); 298 has_default = true; 299 var.erase(pos); 300 } 301 302 if (env_util::has(var)) { 303 string env_value{env_util::get(var)}; 304 m_log.info("Environment var reference ${%s} found (value=%s)", var, env_value); 305 return convert<T>(move(env_value)); 306 } else if (has_default) { 307 m_log.info("Environment var ${%s} is undefined, using defined fallback value \"%s\"", var, env_default); 308 return convert<T>(move(env_default)); 309 } else { 310 throw value_error(sstream() << "Environment var ${" << var << "} does not exist (no fallback set)"); 311 } 312 } 313 314 /** 315 * Dereference X resource db value defined using: 316 * ${xrdb:key} 317 * ${xrdb:key:fallback value} 318 */ 319 template <typename T> dereference_xrdb(string var) const320 T dereference_xrdb(string var) const { 321 size_t pos; 322 #if not WITH_XRM 323 m_log.warn("No built-in support to dereference ${xrdb:%s} references (requires `xcb-util-xrm`)", var); 324 if ((pos = var.find(':')) != string::npos) { 325 return convert<T>(var.substr(pos + 1)); 326 } 327 return convert<T>(""); 328 #else 329 if (!m_xrm) { 330 throw application_error("xrm is not initialized"); 331 } 332 333 string fallback; 334 bool has_fallback = false; 335 if ((pos = var.find(':')) != string::npos) { 336 fallback = var.substr(pos + 1); 337 has_fallback = true; 338 var.erase(pos); 339 } 340 341 try { 342 auto value = m_xrm->require<string>(var.c_str()); 343 m_log.info("Found matching X resource \"%s\" (value=%s)", var, value); 344 return convert<T>(move(value)); 345 } catch (const xresource_error& err) { 346 if (has_fallback) { 347 m_log.info("%s, using defined fallback value \"%s\"", err.what(), fallback); 348 return convert<T>(move(fallback)); 349 } 350 throw value_error(sstream() << err.what() << " (no fallback set)"); 351 } 352 #endif 353 } 354 355 /** 356 * Dereference file reference by reading its contents 357 * ${file:/absolute/file/path} 358 * ${file:/absolute/file/path:fallback value} 359 */ 360 template <typename T> dereference_file(string var) const361 T dereference_file(string var) const { 362 size_t pos; 363 string fallback; 364 bool has_fallback = false; 365 if ((pos = var.find(':')) != string::npos) { 366 fallback = var.substr(pos + 1); 367 has_fallback = true; 368 var.erase(pos); 369 } 370 var = file_util::expand(var); 371 372 if (file_util::exists(var)) { 373 m_log.info("File reference \"%s\" found", var); 374 return convert<T>(string_util::trim(file_util::contents(var), '\n')); 375 } else if (has_fallback) { 376 m_log.info("File reference \"%s\" not found, using defined fallback value \"%s\"", var, fallback); 377 return convert<T>(move(fallback)); 378 } else { 379 throw value_error(sstream() << "The file \"" << var << "\" does not exist (no fallback set)"); 380 } 381 } 382 383 private: 384 const logger& m_log; 385 string m_file; 386 string m_barname; 387 sectionmap_t m_sections{}; 388 389 /** 390 * Absolute path of all files that were parsed in the process of parsing the 391 * config (Path of the main config file also included) 392 */ 393 file_list m_included; 394 #if WITH_XRM 395 unique_ptr<xresource_manager> m_xrm; 396 #endif 397 }; 398 399 POLYBAR_NS_END 400