diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6eae3b35..333e8798 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,7 +38,7 @@ set(conky_sources colours.cc combine.cc common.cc conky.cc core.cc diskio.cc entropy.cc exec.cc fs.cc mail.cc mixer.cc net_stat.cc template.cc mboxscan.cc read_tcp.cc scroll.cc specials.cc tailhead.cc temphelper.cc text_object.cc timeinfo.cc top.cc algebra.cc prioqueue.c proc.cc - user.cc luamm.cc data-source.cc lua-config.cc) + user.cc luamm.cc data-source.cc lua-config.cc setting.cc) # add timed thread library add_library(timed-thread timed-thread.cc) diff --git a/src/conky.cc b/src/conky.cc index 6203e64c..44acc0e7 100644 --- a/src/conky.cc +++ b/src/conky.cc @@ -96,6 +96,7 @@ #include #include "lua-config.hh" +#include "setting.hh" /* check for OS and include appropriate headers */ #if defined(__linux__) @@ -4325,6 +4326,15 @@ int main(int argc, char **argv) "print(conky.asnumber(conky.variables.zxcv{}));\n" "print(conky.variables.asdf{}.text);\n" "print(conky.variables.asdf{}.xxx);\n" + "conky.config = { a='z', asdf=47, [42]=47 };\n" + ); + l.call(0, 0); + conky::check_config_settings(l); + std::cout << "config.asdf = " << conky::asdf.get(l) << std::endl; + l.loadstring( + "print('config.asdf = ', conky.config.asdf);\n" + "conky.config.asdf = 42;\n" + "print('config.asdf = ', conky.config.asdf);\n" ); l.call(0, 0); } diff --git a/src/lua-config.cc b/src/lua-config.cc index 8b05b70e..5c6b66ca 100644 --- a/src/lua-config.cc +++ b/src/lua-config.cc @@ -26,6 +26,7 @@ #include "lua-config.hh" #include "data-source.hh" +#include "setting.hh" namespace conky { void export_symbols(lua::state &l) @@ -35,6 +36,9 @@ namespace conky { l.newtable(); ++s; { export_data_sources(l); - } l.setglobal("conky"); --s; + + l.newtable(); + l.rawsetfield(-2, "config"); + } --s; l.setglobal("conky"); } } diff --git a/src/setting.cc b/src/setting.cc new file mode 100644 index 00000000..5046a625 --- /dev/null +++ b/src/setting.cc @@ -0,0 +1,159 @@ +/* -*- mode: c++; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*- + * vim: ts=4 sw=4 noet ai cindent syntax=cpp + * + * 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 "logging.h" + +namespace conky { + namespace priv { + /* + * 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. + */ + config_settings_t *config_settings; + + config_setting_base::config_setting_base(const std::string &name_, + const lua_setter_t &lua_setter_) + : name(name_), lua_setter(lua_setter_) + { + struct config_settings_constructor { + config_settings_constructor() { priv::config_settings = new config_settings_t; } + ~config_settings_constructor() { delete config_settings; config_settings = NULL; } + }; + static config_settings_constructor constructor; + + bool inserted = config_settings->insert({name, this}).second; + if(not inserted) + throw std::logic_error("Setting with name '" + name + "' already registered"); + } + } + + namespace { + /* + * 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, key | + */ + void process_setting(lua::state &l, bool init) + { + lua::stack_sentry s(l, 2); + l.checkstack(1); + + int type = l.type(-3); + if(type != lua::TSTRING) { + NORM_ERR("invalid setting of type '%s'", l.type_name(type)); + return; + } + + std::string name = l.tostring(-3); + auto iter = priv::config_settings->find(name); + if(iter == priv::config_settings->end()) { + NORM_ERR("Unknown setting '%s'", name.c_str()); + return; + } + + s-=2; iter->second->lua_setter(&l, init); ++s; + l.pushvalue(-2); ++s; + l.insert(-2); + s-=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__newindex(lua::state *l) + { + lua::stack_sentry s(*l, 3); + l->checkstack(1); + + l->getmetatable(-3); + l->replace(-4); + + l->pushvalue(-2); ++s; + --s; l->rawget(-4); ++s; + s-=2; process_setting(*l, false); + + return 0; + } + } + + void simple_lua_setter(lua::state *l, bool) + { l->pop(); } + + /* + * Called after the initial loading of the config file. Performs the initial assignments. + * at least one setting should always be registered, so config_settings will not be null + * stack on entry: | ... | + * stack on exit: | ... | + */ + void check_config_settings(lua::state &l) + { + lua::stack_sentry s(l); + l.checkstack(6); + + l.getglobal("conky"); ++s; { + l.rawgetfield(-1, "config"); ++s; { + if(l.type(-1) != lua::TTABLE) + throw std::runtime_error("conky.config must be a table"); + + // new conky.config table, containing only valid settings + l.newtable(); ++s; { + l.pushnil(); ++s; + while(l.next(-3)) { ++s; + l.pushnil(); ++s; + s-=2; process_setting(l, true); + } --s; + } --s; l.replace(-2); + + l.pushboolean(false); + l.rawsetfield(-2, "__metatable"); + + l.pushvalue(-1); + l.rawsetfield(-2, "__index"); + + l.pushfunction(&config__newindex); + l.rawsetfield(-2, "__newindex"); + + // 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 stars with "__" is a bad idea + l.newuserdata(1); ++s; + l.insert(-2); + --s; l.setmetatable(-2); + } --s; l.rawsetfield(-2, "config"); + } --s; l.pop(1); + } + +/////////// example settings, remove after real settings are available /////// + config_setting asdf("asdf"); + config_setting aasf("aasf"); +} diff --git a/src/setting.hh b/src/setting.hh new file mode 100644 index 00000000..29d7ca3d --- /dev/null +++ b/src/setting.hh @@ -0,0 +1,171 @@ +/* -*- mode: c++; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*- + * vim: ts=4 sw=4 noet ai cindent syntax=cpp + * + * 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 . + * + */ + +#ifndef SETTING_HH +#define SETTING_HH + +#include +#include +#include + +#include "luamm.hh" + +namespace conky { + + // performs the assignment without any error checking + void simple_lua_setter(lua::state *l, bool init); + + // converts standard lua types to C types + template::value or std::is_enum::value, + bool floating_point = std::is_floating_point::value> + struct simple_getter { + // integral_or_enum is here to force the compiler to evaluate the assert at instantiation + // time + static_assert(integral_or_enum && false, + "Only specializations for string, integral, enum" + "and floating point types are available" ); + }; + + // Specialization for integral type and enums. In case of enums, one should provide a setter + // function that makes sure the user sets a sane value + template + struct simple_getter { + static T do_it_def(lua::state *l, T def) + { + if(l->isnil(-1)) + return def; + + // for enums we need to force a conversion + T t = static_cast(l->tointeger(-1)); + l->pop(); + return t; + } + + static T do_it(lua::state *l) + { return do_it_def(l, static_cast(0)); } + }; + + // specialization for floating point types + template + struct simple_getter { + static T do_it_def(lua::state *l, T def) + { + if(l->isnil(-1)) + return def; + + T t = l->tonumber(-1); + l->pop(); + return t; + } + + static T do_it(lua::state *l) + { return do_it_def(l, T(0)); } + }; + + // specialization for std::string + template<> + struct simple_getter { + static std::string do_it_def(lua::state *l, const std::string &def) + { + if(l->isnil(-1)) + return def; + + std::string t = l->tostring(-1); + l->pop(); + return t; + } + + static std::string do_it(lua::state *l) + { return do_it_def(l, std::string()); } + }; + + namespace priv { + class config_setting_base { + public: + typedef std::function lua_setter_t; + + const std::string name; + const lua_setter_t lua_setter; + + config_setting_base(const std::string &name_, const lua_setter_t &lua_setter_); + }; + + typedef std::unordered_map config_settings_t; + + extern config_settings_t *config_settings; + } + + /* + * Declares a setting in the conky.config table. + * Getter function is used to translate the lua value into C++. It recieves the value on the + * lua stack. It should pop it and return the C++ value. In case the value is nil, it should + * return a predefined default value. Translation into basic types is provided with the + * default simple_getter::do_it functions. + * The lua_setter function is called when the user tries to set the value it the lua script. + * It recieves the new and the old value on the stack (old one is on top). It should return + * the new value for the setting. It doesn't have to be the value the user set, if e.g. the + * value doesn't make sense. The second parameter is true if the assignment occurs during the + * initial parsing of the config file, and false afterwards. Some settings obviously cannot + * be changed (easily?) when conky is running, but some (e.g. x/y position of the window) + * can. + */ + template + class config_setting: public priv::config_setting_base { + public: + typedef std::function getter_t; + + config_setting(const std::string &name_, + const getter_t &getter_ = &simple_getter::do_it, + const lua_setter_t &lua_setter_ = &simple_lua_setter) + : config_setting_base(name_, lua_setter_), getter(getter_) + {} + + T get(lua::state &l); + private: + getter_t getter; + }; + + template + T config_setting::get(lua::state &l) + { + lua::stack_sentry s(l); + l.checkstack(2); + + l.getglobal("conky"); ++s; + l.getfield(-1, "config"); ++s; + --s; l.replace(-2); + l.getfield(-1, name.c_str()); ++s; + --s; l.replace(-2); + --s; return getter(&l); + } + + void check_config_settings(lua::state &l); + +/////////// example settings, remove after real settings are available /////// + enum foo { bar, baz }; + extern config_setting asdf; + extern config_setting aasf; +} + +#endif /* SETTING_HH */