/* * * Conky, a system monitor, based on torsmo * * Please see COPYING for details * * Copyright (C) 2010 Pavel Labath et al. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include "setting.hh" #include #include #include #include #include namespace conky { namespace { typedef std::unordered_map settings_map; using settings_vector = std::vector; /* * We cannot construct this object statically, because order of object * construction in different modules is not defined, so config_setting_base * could be called before this object is constructed. Therefore, we create it on * the first call to config_setting_base constructor. */ settings_map *settings; /* * Returns the setting record corresponding to the value at the specified index. * If the value is not valid, returns nullptr and prints an error. */ priv::config_setting_base *get_setting(lua::state &l, int index) { lua::Type type = l.type(index); if (type != lua::TSTRING) { NORM_ERR("invalid setting of type '%s'", l.type_name(type)); return nullptr; } const std::string &name = l.tostring(index); auto iter = settings->find(name); if (iter == settings->end()) { NORM_ERR("Unknown setting '%s'", name.c_str()); return nullptr; } return iter->second; } // returns a vector of all settings, sorted in order of registration settings_vector make_settings_vector() { settings_vector ret; ret.reserve(settings->size()); for (auto &setting : *settings) { ret.push_back(setting.second); } sort(ret.begin(), ret.end(), &priv::config_setting_base::seq_compare); return ret; } /* * Returns the seq_no for the new setting object. Also constructs settings * object if needed. */ size_t get_next_seq_no() { struct settings_constructor { settings_constructor() { settings = new settings_map; } ~settings_constructor() { delete settings; settings = nullptr; } }; static settings_constructor constructor; return settings->size(); } } // namespace namespace priv { config_setting_base::config_setting_base(std::string name_) : name(std::move(name_)), seq_no(get_next_seq_no()) { bool inserted = settings->insert({name, this}).second; if (!inserted) { throw std::logic_error("Setting with name '" + name + "' already registered"); } } void config_setting_base::lua_set(lua::state &l) { std::lock_guard guard(l); lua::stack_sentry s(l, -1); l.checkstack(2); l.getglobal("conky"); l.rawgetfield(-1, "config"); l.replace(-2); l.insert(-2); l.setfield(-2, name.c_str()); l.pop(); } /* * Performs the actual assignment of settings. Calls the setting-specific setter * after some sanity-checking. stack on entry: | ..., new_config_table, key, * value, old_value | stack on exit: | ..., new_config_table | */ void config_setting_base::process_setting(lua::state &l, bool init) { lua::stack_sentry s(l, -3); config_setting_base *ptr = get_setting(l, -3); if (ptr == nullptr) { return; } ptr->lua_setter(l, init); l.pushvalue(-2); l.insert(-2); l.rawset(-4); } /* * Called when user sets a new value for a setting * stack on entry: | config_table, key, value | * stack on exit: | | */ int config_setting_base::config__newindex(lua::state *l) { lua::stack_sentry s(*l, -3); l->checkstack(1); l->getmetatable(-3); l->replace(-4); l->pushvalue(-2); l->rawget(-4); process_setting(*l, false); return 0; } /* * conky.config will not be a table, but a userdata with some metamethods we do * this because we want to control access to the settings we use the metatable * for storing the settings, that means having a setting whose name starts with * "__" is a bad idea stack on entry: | ... | stack on exit: | ... * new_config_table | */ void config_setting_base::make_conky_config(lua::state &l) { lua::stack_sentry s(l); l.checkstack(3); l.newuserdata(1); l.newtable(); { l.pushboolean(false); l.rawsetfield(-2, "__metatable"); l.pushvalue(-1); l.rawsetfield(-2, "__index"); l.pushfunction(&priv::config_setting_base::config__newindex); l.rawsetfield(-2, "__newindex"); } l.setmetatable(-2); ++s; } } // namespace priv void set_config_settings(lua::state &l) { lua::stack_sentry s(l); l.checkstack(6); // Force creation of settings map. In the off chance we have no settings. get_next_seq_no(); l.getglobal("conky"); { if (l.type(-1) != lua::TTABLE) { throw std::runtime_error("conky must be a table"); } l.rawgetfield(-1, "config"); { if (l.type(-1) != lua::TTABLE) { throw std::runtime_error("conky.config must be a table"); } priv::config_setting_base::make_conky_config(l); l.rawsetfield(-3, "config"); l.rawgetfield(-2, "config"); l.getmetatable(-1); l.replace(-2); { const settings_vector &v = make_settings_vector(); for (auto i : v) { l.pushstring(i->name); l.rawgetfield(-3, i->name.c_str()); l.pushnil(); priv::config_setting_base::process_setting(l, true); } } l.pop(); // print error messages for unknown settings l.pushnil(); while (l.next(-2)) { l.pop(); get_setting(l, -1); } } l.pop(); } l.pop(); } void cleanup_config_settings(lua::state &l) { lua::stack_sentry s(l); l.checkstack(2); l.getglobal("conky"); l.rawgetfield(-1, "config"); l.replace(-2); const settings_vector &v = make_settings_vector(); for (size_t i = v.size(); i > 0; --i) { l.getfield(-1, v[i - 1]->name.c_str()); v[i - 1]->cleanup(l); } l.pop(); } /////////// example settings, remove after real settings are available /////// range_config_setting asdf("asdf", 42, 47, 45, true); } // namespace conky