pax_global_header00006660000000000000000000000064144644561530014526gustar00rootroot0000000000000052 comment=8d96a30a4def766ddb45c5620234ebf76788ccd4 wf-config-0.8.0/000077500000000000000000000000001446445615300134125ustar00rootroot00000000000000wf-config-0.8.0/.github/000077500000000000000000000000001446445615300147525ustar00rootroot00000000000000wf-config-0.8.0/.github/workflows/000077500000000000000000000000001446445615300170075ustar00rootroot00000000000000wf-config-0.8.0/.github/workflows/ci.yaml000066400000000000000000000017241446445615300202720ustar00rootroot00000000000000name: CI on: [push, pull_request] jobs: test_code_style: name: "Check code style with uncrustify" runs-on: ubuntu-latest steps: - run: sudo apt-get update - run: sudo apt-get install -y git cmake gcc make - uses: actions/checkout@v1 - run: git clone http://github.com/ammen99/uncrustify - run: cd uncrustify && mkdir build && cd build && cmake ../ && make && cd ../../ - run: curl https://raw.githubusercontent.com/WayfireWM/wayfire/master/uncrustify.ini > uncrustify.ini - run: git ls-files | grep "hpp$\|cpp$" | xargs ./uncrustify/build/uncrustify -c uncrustify.ini --check run_tests: name: "Check that tests do not break" runs-on: ubuntu-latest steps: - run: sudo apt-get update - run: sudo apt-get install -y cmake git gcc meson doctest-dev libevdev-dev libxml2-dev libglm-dev - uses: actions/checkout@v1 - run: meson build - run: ninja -C build test wf-config-0.8.0/LICENSE000066400000000000000000000020751446445615300144230ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2018-2019 Ilia Bozhinov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. wf-config-0.8.0/include/000077500000000000000000000000001446445615300150355ustar00rootroot00000000000000wf-config-0.8.0/include/meson.build000066400000000000000000000010131446445615300171720ustar00rootroot00000000000000headers_config = [ 'wayfire/config/xml.hpp', 'wayfire/config/config-manager.hpp', 'wayfire/config/section.hpp', 'wayfire/config/option-types.hpp', 'wayfire/config/types.hpp', 'wayfire/config/file.hpp', 'wayfire/config/option.hpp', 'wayfire/config/option-wrapper.hpp', 'wayfire/config/compound-option.hpp', ] headers_util = [ 'wayfire/util/log.hpp', 'wayfire/util/stringify.hpp', 'wayfire/util/duration.hpp' ] install_headers(headers_config, subdir: 'wayfire/config') install_headers(headers_util, subdir: 'wayfire/util') wf-config-0.8.0/include/wayfire/000077500000000000000000000000001446445615300165035ustar00rootroot00000000000000wf-config-0.8.0/include/wayfire/config/000077500000000000000000000000001446445615300177505ustar00rootroot00000000000000wf-config-0.8.0/include/wayfire/config/compound-option.hpp000066400000000000000000000205341446445615300236170ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include namespace wf { namespace config { template using compound_list_t = std::vector>; template using simple_list_t = std::vector>; template std::tuple pop_first(const std::tuple& tuple) { return std::apply([] (auto&&, const auto&... args) { return std::tie(args...); }, tuple); } template std::tuple push_first(const T1& t, const std::tuple& tuple) { return std::tuple_cat(std::tie(t), tuple); } /** * A base class containing information about an entry in a tuple. */ class compound_option_entry_base_t { public: virtual ~compound_option_entry_base_t() = default; /** @return The prefix of the tuple entry. */ std::string get_prefix() const { return prefix; } /** @return The name of the tuple entry. */ std::string get_name() const { return name; } /** @return The untyped default value of the tuple entry. */ std::optional get_default_value() const { return default_value; } /** * Try to parse the given value. * * @param update Whether to override the stored value. */ virtual bool is_parsable(const std::string&) const = 0; /** Clone this entry */ virtual compound_option_entry_base_t *clone() const = 0; protected: compound_option_entry_base_t() = default; std::string prefix; std::string name; std::optional default_value; }; template class compound_option_entry_t : public compound_option_entry_base_t { public: compound_option_entry_t(const std::string& prefix, const std::string& name = "", const std::optional& default_value = std::nullopt) { this->prefix = prefix; this->name = name; this->default_value = default_value; assert(!this->default_value || is_parsable(this->default_value.value())); } compound_option_entry_base_t *clone() const override { return new compound_option_entry_t(*this); } /** * Try to parse the given value. * * @param update Whether to override the stored value. */ bool is_parsable(const std::string& str) const override { return option_type::from_string(str).has_value(); } }; /** * Compound options are a special class of options which can hold multiple * string-tagged tuples. They are constructed from multiple untyped options * in the config file. */ class compound_option_t : public option_base_t { public: using entries_t = std::vector>; /** * Construct a new compound option, with the types given in the template * arguments. * * @param name The name of the option. * @param prefixes The prefixes used for grouping in the config file. * Example: Consider a compound option with type and two * prefixes {"prefix1_", "prefix2_"}. In the config file, the options are: * * prefix1_key1 = v11 * prefix2_key1 = v21 * prefix1_key2 = v12 * prefix2_key2 = v22 * * Options are grouped by suffixes (key1 and key2), and the tuples then * are formed by taking the values of the options with each prefix. * So, the tuples contained in the compound option in the end are: * * (key1, v11, v21) * (key2, v12, v22) * @param type_hint What type of dynamic-list is this option. This stores * the type-hint attribute of the option specified in the xml. Currently, * the valid types are plain, dict and tuple (default). Type-hint is * mainly used as hint to save the config option in a "pretty" way. * Config formats are free to ignore this type hint. */ compound_option_t(const std::string& name, entries_t&& entries, std::string type_hint = "tuple"); /** * Parse the compound option with the given types. * * Throws an exception in case of wrong template types. */ template compound_list_t get_value() const { compound_list_t result; result.resize(value.size()); build_recursive<0, Args...>(result); return result; } template simple_list_t get_value_simple() const { auto list = get_value(); simple_list_t result; for (const auto& val : list) { result.push_back(pop_first(val)); } return result; } /** * Set the value of the option. * * Throws an exception in case of wrong template types. */ template void set_value(const compound_list_t& value) { assert(sizeof...(Args) == this->entries.size()); this->value.assign(value.size(), {}); push_recursive<0>(value); notify_updated(); } /** * Set the value of the option. * * Throws an exception in case of wrong template types. */ template void set_value_simple(const simple_list_t& value) { compound_list_t list; for (int i = 0; i < (int)value.size(); i++) { list.push_back(push_first(std::to_string(i), value[i])); } this->set_value(list); } using stored_type_t = std::vector>; /** * Get the string data stored in the compound option. */ stored_type_t get_value_untyped(); /** * Set the data contained in the option, from a vector containing * strings which describe the individual elements. * * @return True if the operation was successful. */ bool set_value_untyped(stored_type_t value); /** * Get the type information about entries in the option. */ const entries_t& get_entries() const; /** * Check if this compound option has named tuples. */ std::string get_type_hint() const { return list_type_hint; } private: /** * Current value stored in the option. * The first element is the name of the tuple, followed by the string values * of each element. */ stored_type_t value; /** Entry types with which the option was created. */ entries_t entries; /** What type of dynamic-list is this: plain, dics, tuple */ std::string list_type_hint; /** * Set the n-th element in the result tuples by reading from the stored * values in this option. */ template void build_recursive(compound_list_t& result) const { for (size_t i = 0; i < result.size(); i++) { using type_t = typename std::tuple_element>::type; std::get(result[i]) = option_type::from_string( this->value[i][n]).value(); } // Recursively build the (N+1)'th entries if constexpr (n < sizeof...(Args)) { build_recursive(result); } } template void push_recursive(const compound_list_t& new_value) { for (size_t i = 0; i < new_value.size(); i++) { using type_t = typename std::tuple_element>::type; this->value[i].push_back(option_type::to_string( std::get(new_value[i]))); } // Recursively build the (N+1)'th entries if constexpr (n < sizeof...(Args)) { push_recursive(new_value); } } public: // Implementation of option_base_t std::shared_ptr clone_option() const override; bool set_value_str(const std::string&) override; void reset_to_default() override; bool set_default_value_str(const std::string&) override; std::string get_value_str() const override; std::string get_default_value_str() const override; }; } } wf-config-0.8.0/include/wayfire/config/config-manager.hpp000066400000000000000000000036121446445615300233400ustar00rootroot00000000000000#pragma once #include namespace wf { namespace config { /** * Manages the whole configuration of a program. * The configuration consists of a list of sections with their options. */ class config_manager_t { public: /** * Add the given config section to the configuration. * If the section already exists, each new option will be added to the * existing section, and already existing options will be overwritten. * * @param section The section to add, must be non-null. */ void merge_section(std::shared_ptr section); /** * Find the configuration section with the given name. * @return nullptr if the section doesn't exist. */ std::shared_ptr get_section(const std::string& name) const; /** * @return A list of all sections currently in the config manager. */ std::vector> get_all_sections() const; /** * Get the option with the given name. * The name consists of the name of the option section, followed by a '/', * then followed by the actual name of the option in the option section, * for example 'core/plugins'. * * If the option doesn't exist, nullptr is returned. */ std::shared_ptr get_option(const std::string& name) const; /** * Get the option with the given name. Same semantics as * get_option(std::string), but casts the result to the appropriate type. */ template std::shared_ptr> get_option(const std::string& name) const { return std::dynamic_pointer_cast>(get_option(name)); } config_manager_t(); config_manager_t(config_manager_t&& other); config_manager_t& operator =(config_manager_t&& other); virtual ~config_manager_t(); private: struct impl; std::unique_ptr priv; }; } } wf-config-0.8.0/include/wayfire/config/file.hpp000066400000000000000000000064271446445615300214110ustar00rootroot00000000000000#pragma once #include namespace wf { namespace config { /** * Parse a multi-line string as a configuration file. * The string consists of multiple sections of the following format: * * [section_name] * option1 = value1 * option2 = value2 * ... * * Blank lines and whitespace characters around the '=' sign are ignored, as * well as whitespaces at the beginning or at the end of each line. * * When a line contains a '#', the line from this point on is considered a * comment except when it is immediately preceded by a '\'. * * When a line ends in '\', it automatically joined with the next line, except * if it isn't escaped with another '\'. * * Each valid parsed option is used to set the value of the corresponding option * in @manager. Each line which contains errors is reported on the log and then * ignored. * * @param manager The config manager to update. * @param source The multi-line string representing the source * @param source_name The name to be used when reporting errors to the log */ void load_configuration_options_from_string(config_manager_t& manager, const std::string& source, const std::string& source_name = ""); /** * Create a string which conttains all the sections and the options in the given * configuration manager. The format is the same one as the one described in * load_configuration_options_from_string() * * @return The string representation of the config manager. */ std::string save_configuration_options_to_string( const config_manager_t& manager); /** * Load the options from the given config file. * * This is roughly equivalent to reading the file to a string, and then calling * load_configuration_options_from_string(), but this function also tries to get * a shared lock on the config file, and does not do anything if another process * already holds an exclusive lock. * * @param manager The config manager to update. * @param file The config file to use. * * @return True if the config file was reloaded, false if file could not be * opened or a lock could not be acquired. */ bool load_configuration_options_from_file(config_manager_t& manager, const std::string& file); /** * Writes the options in the given configuration to the given file. * It is roughly equivalent to calling serialize_configuration_manager() and * then replacing the file contents with the resulting string, but this function * waits until it can get an exclusive lock on the config file. */ void save_configuration_to_file(const config_manager_t& manager, const std::string& file); /** * Build a configuration for the given program from the files on the filesystem. * * The following steps are performed: * 1. Each of the XML files in each of @xmldirs are read, and options there * are used to build a configuration. Note that the XML nodes which are * allocated will not be freed. * 2. The @sysconf file is used to overwrite default values from XML files. * 3. The @userconf file is used to determine the actual values of options. * * If any of the steps results in an error, the error will be reported to the * command line and the process will continue. */ config_manager_t build_configuration(const std::vector& xmldirs, const std::string& sysconf, const std::string& userconf); } } wf-config-0.8.0/include/wayfire/config/option-types.hpp000066400000000000000000000012671446445615300231410ustar00rootroot00000000000000#pragma once #include #include namespace wf { namespace option_type { /** * To create an option of a given type, from_string must be specialized for * parsing the type. * * @param string The string representation of the value. * @return The parsed value, if the string was valid. */ template std::optional from_string( const std::string& string); /** * To create an option of a given type, to_string must be specialized for * converting the type to string. * @return The string representation of a value. * It is expected that from_string(to_string(value)) == value. */ template std::string to_string(const Type& value); } } wf-config-0.8.0/include/wayfire/config/option-wrapper.hpp000066400000000000000000000103721446445615300234520ustar00rootroot00000000000000#pragma once #include #include #include namespace wf { template using option_sptr_t = std::shared_ptr>; /** * Create an option which has a static value. */ template option_sptr_t create_option(T value) { return std::make_shared>("Static", value); } /** * Create an option which has a static value, * from the given string description. */ template option_sptr_t create_option_string(const std::string& value) { return std::make_shared>("Static", wf::option_type::from_string(value).value()); } /** * A helper to check whether the given type is a specialization of std::vector */ template struct is_std_vector : public std::false_type {}; template struct is_std_vector> : public std::true_type {}; template void get_value_from_compound_option( config::compound_option_t *opt, config::compound_list_t& list) { list = opt->get_value(); } /** * A simple wrapper around a config option. * * This is a base class. Each application which uses it needs to subclass it * and override the load_raw_option() method. */ template class base_option_wrapper_t { public: using OptionType = std::conditional_t< is_std_vector::value, config::compound_option_t, config::option_t>; public: base_option_wrapper_t(const base_option_wrapper_t& other) = delete; base_option_wrapper_t& operator =( const base_option_wrapper_t& other) = delete; base_option_wrapper_t(base_option_wrapper_t&& other) = delete; base_option_wrapper_t& operator =( base_option_wrapper_t&& other) = delete; /** * Load the option with the given name from the config. * @throws logic_error if the option wrapper already has an option loaded. * @throws runtime_error if the given option does not exist or does not * match the type of the option wrapper. */ void load_option(const std::string& name) { if (raw_option) { throw std::logic_error( "Loading an option into option wrapper twice!"); } auto untyped_option = load_raw_option(name); if (untyped_option == nullptr) { throw std::runtime_error("No such option: " + std::string(name)); } raw_option = std::dynamic_pointer_cast(untyped_option); if (raw_option == nullptr) { throw std::runtime_error("Bad option type: " + std::string(name)); } raw_option->add_updated_handler(&option_update_listener); } virtual ~base_option_wrapper_t() { if (raw_option) { raw_option->rem_updated_handler(&option_update_listener); } } /** Implicitly convertible to the value of the option */ operator Type() const { return this->value(); } Type value() const { if constexpr (is_std_vector::value) { Type list; get_value_from_compound_option(this->raw_option.get(), list); return list; } else { return raw_option->get_value(); } } operator std::shared_ptr() const { return raw_option; } /** Set a callback to execute when the option value changes. */ void set_callback(std::function callback) { this->on_update = callback; } protected: std::function on_update; wf::config::option_base_t::updated_callback_t option_update_listener; /** The actual option wrapped by the option wrapper */ std::shared_ptr raw_option; /** * Initialize the option wrapper. */ base_option_wrapper_t() { option_update_listener = [=] () { if (this->on_update) { this->on_update(); } }; } /** * Load the option with the given name from the application configuration. */ virtual std::shared_ptr load_raw_option( const std::string& name) = 0; }; } wf-config-0.8.0/include/wayfire/config/option.hpp000066400000000000000000000175561446445615300220070ustar00rootroot00000000000000#pragma once #include #include #include #include namespace wf { namespace config { /** * A base class for all option types. */ class option_base_t { public: virtual ~option_base_t(); option_base_t(const option_base_t& other) = delete; option_base_t& operator =(const option_base_t& other) = delete; /** @return The name of the option */ std::string get_name() const; /** @return A copy of the option */ virtual std::shared_ptr clone_option() const = 0; /** * Set the option value from the given string. * Invalid values are ignored. * * @return true if the option value was updated. */ virtual bool set_value_str(const std::string& value) = 0; /** Reset the option to its default value. */ virtual void reset_to_default() = 0; /** * Change the default value of an option. Note that this will not change the * option value, only its default value. * * If the new default value is invalid, the request will be ignored. * @return true if the default value was updated. */ virtual bool set_default_value_str(const std::string& default_value) = 0; /** Get the option value in string format */ virtual std::string get_value_str() const = 0; /** Get the default option value in string format */ virtual std::string get_default_value_str() const = 0; /** * A function to be executed when the option value changes. */ using updated_callback_t = std::function; /** * Register a new callback to execute when the option value changes. */ void add_updated_handler(updated_callback_t *callback); /** * Unregister a callback to execute when the option value changes. * If the same callback has been registered multiple times, this unregister * all registered instances. */ void rem_updated_handler(updated_callback_t *callback); /** * Set the lock status of an option, this is reference-counted. * * An option is unlocked by default. When an option is locked, the option * should not be modified by any config backend (for ex. when reading from * a file). * * Note that changing the value of the option manually still works. */ void set_locked(bool locked = true); /** * Get the current locked status. */ bool is_locked() const; struct impl; std::unique_ptr priv; protected: /** Construct a new option with the given name. */ option_base_t(const std::string& name); /** Notify all watchers */ void notify_updated() const; /** Initialize a cloned version of this option. */ void init_clone(option_base_t& clone) const; }; /** * A base class for options which can have minimum and maximum. * By default, no bounding checks are enabled. */ template class bounded_option_base_t { protected: Type closest_valid_value(const Type& value) const { return value; } }; /** * Specialization for option types which do support bounded values. */ template class bounded_option_base_t { public: /** @return The minimal permissible value for this option, if it is set. */ std::optional get_minimum() const { return minimum; } /** @return The maximal permissible value for this option, if it is set. */ std::optional get_maximum() const { return maximum; } protected: std::optional minimum; std::optional maximum; /** * @return The closest possible value */ Type closest_valid_value(const Type& value) const { auto real_minimum = minimum.value_or(std::numeric_limits::lowest()); auto real_maximum = maximum.value_or(std::numeric_limits::max()); if (value < real_minimum) { return real_minimum; } if (value > real_maximum) { return real_maximum; } return value; } }; namespace detail { template using boundable_type_only = std::enable_if_t::value, Result>; } /** * Represents an option of the given type. */ template class option_t : public option_base_t, public bounded_option_base_t::value> { public: /** * Create a new option with the given name and default value. */ option_t(const std::string& name, Type def_value) : option_base_t(name), default_value(def_value), value(default_value) {} /** * Create a copy of the option. */ virtual std::shared_ptr clone_option() const override { auto result = std::make_shared(get_name(), get_default_value()); result->set_value(get_value()); if constexpr (std::is_arithmetic::value) { result->minimum = this->minimum; result->maximum = this->maximum; } init_clone(*result); return result; } /** * Set the value of the option from the given string. * The value will be auto-clamped to the defined bounds, if they exist. * If the value actually changes, the updated handlers will be called. */ virtual bool set_value_str(const std::string& new_value_str) override { auto new_value = option_type::from_string(new_value_str); if (new_value) { set_value(new_value.value()); return true; } return false; } /** * Reset the option to its default value. */ virtual void reset_to_default() override { set_value(default_value); } /** * Change the default value of the function, if possible. */ virtual bool set_default_value_str(const std::string& defvalue) override { auto parsed = option_type::from_string(defvalue); if (parsed) { this->default_value = parsed.value(); return true; } return false; } /** * Set the value of the option. * The value will be auto-clamped to the defined bounds, if they exist. * If the value actually changes, the updated handlers will be called. */ void set_value(const Type& new_value) { auto real_value = this->closest_valid_value(new_value); if (!(this->value == real_value)) { this->value = real_value; this->notify_updated(); } } Type get_value() const { return value; } Type get_default_value() const { return default_value; } virtual std::string get_value_str() const override { return option_type::to_string(get_value()); } virtual std::string get_default_value_str() const override { return option_type::to_string(get_default_value()); } public: /** * Set the minimum permissible value for arithmetic type options. * An attempt to set the value to a value below the minimum will set the * value of the option to the minimum. */ template detail::boundable_type_only set_minimum(Type min) { this->minimum = {min}; this->value = this->closest_valid_value(this->value); } /** * Set the maximum permissible value for arithmetic type options. * An attempt to set the value to a value above the maximum will set the * value of the option to the maximum. */ template detail::boundable_type_only set_maximum(Type max) { this->maximum = {max}; this->value = this->closest_valid_value(this->value); } protected: Type default_value; /* default value */ Type value; /* current value */ }; } } wf-config-0.8.0/include/wayfire/config/section.hpp000066400000000000000000000036311446445615300221300ustar00rootroot00000000000000#pragma once #include #include #include namespace wf { namespace config { /** * Represents a section in the config file. * Each section has a list of options. */ class section_t { public: /** * Create a new empty section. * * The section name can be arbitrary, however when reading config from a * file there are additional restrictions. */ section_t(const std::string& name); virtual ~section_t(); /** @return The name of the config section. */ std::string get_name() const; /** @return A deep copy of the config section with a new name. */ std::shared_ptr clone_with_name(const std::string name) const; /** * @return The option with the given name, or nullptr if no such option * has been added yet. */ std::shared_ptr get_option_or(const std::string& name); /** * @return The option with the given name. * @throws std::invalid_argument if the option hasn't been added. */ std::shared_ptr get_option(const std::string& name); using option_list_t = std::vector>; /** * @return A list of all available options in this config section. */ option_list_t get_registered_options() const; /** * Register a new option, which means it is marked as belonging to this * section and it will show up in the list of get_registered_options(). * * If an option with the same name already exists, it will be overwritten. */ void register_new_option(std::shared_ptr option); /** * Remove an option from the registered options in this section. * No-op if the option is not part of the section, or if option is null. */ void unregister_option(std::shared_ptr option); struct impl; std::unique_ptr priv; }; } } wf-config-0.8.0/include/wayfire/config/types.hpp000066400000000000000000000371231446445615300216330ustar00rootroot00000000000000#pragma once #include #include #include #include namespace wf { namespace option_type { /** * To create an option of a given type, from_string must be specialized for * parsing the type. * * @param string The string representation of the value. * @return The parsed value, if the string was valid. */ template std::optional from_string( const std::string& string); /** * To create an option of a given type, to_string must be specialized for * converting the type to string. * @return The string representation of a value. * It is expected that from_string(to_string(value)) == value. */ template std::string to_string(const Type& value); /** * Parse the given string as a signed 32-bit integer in decimal system. */ template<> std::optional from_string(const std::string&); /** * Parse the given string as a boolean value. * Truthy values are "True" (any capitalization) and 1. * False values are "False" (any capitalization) and 0. */ template<> std::optional from_string(const std::string&); /** * Parse the given string as a signed 64-bit floating point number. */ template<> std::optional from_string(const std::string&); /** * Parse the string as a string. * The string should not contain newline characters. */ template<> std::optional from_string(const std::string&); /** * Convert the given bool to a string. */ template<> std::string to_string(const bool& value); /** * Convert the given integer to a string. */ template<> std::string to_string(const int& value); /** * Convert the given double to a string. */ template<> std::string to_string(const double& value); /** * Convert the given string to a string. */ template<> std::string to_string(const std::string& value); } /** * Represents a color in RGBA format. */ struct color_t { public: /** Initialize a black transparent color (default) */ color_t(); /** * Initialize a new color value with the given values * Values will be clamped to the [0, 1] range. */ color_t(double r, double g, double b, double a); /** * Initialize a new color value with the given values. * Values will be clamped to the [0, 1] range. */ explicit color_t(const glm::vec4& value); /** * Compare colors channel-for-channel. * Comparisons use a small epsilon 1e-6. */ bool operator ==(const color_t& other) const; /** Red channel value */ double r; /** Green channel value */ double g; /** Blue channel value */ double b; /** Alpha channel value */ double a; }; namespace option_type { /** * Create a new color value from the given hex string, format is either * #RRGGBBAA or #RGBA. */ template<> std::optional from_string(const std::string& value); /** Convert the color to its hex string representation. */ template<> std::string to_string(const color_t& value); } /** * A list of valid modifiers. * The enumerations values are the same as the ones in wlroots. */ enum keyboard_modifier_t { /* Shift modifier, */ KEYBOARD_MODIFIER_SHIFT = 1, /* Control modifier, */ KEYBOARD_MODIFIER_CTRL = 4, /* Alt modifier, */ KEYBOARD_MODIFIER_ALT = 8, /* Windows/Mac logo modifier, */ KEYBOARD_MODIFIER_LOGO = 64, }; /** * Represents a single keyboard shortcut. */ struct keybinding_t { public: /** * Construct a new keybinding with the given modifier and key. */ keybinding_t(uint32_t modifier, uint32_t keyval); /* Check whether two keybindings refer to the same shortcut */ bool operator ==(const keybinding_t& other) const; /** @return The modifiers of the keybinding */ uint32_t get_modifiers() const; /** @return The key of the keybinding */ uint32_t get_key() const; private: /** The modifier mask of this keybinding */ uint32_t mod; /** The key of this keybinding */ uint32_t keyval; }; namespace option_type { /** * Construct a new keybinding from the given string description. * Format is .. KEY_, where whitespace * characters between the different modifiers and KEY_* are ignored. * * For a list of available modifieres, see @keyboard_modifier_t. * * The KEY_ is derived from evdev, and possible names are * enumerated in linux/input-event-codes.h * * For example, " KEY_E" represents pressing the Logo, Alt and * E keys together. * * Special cases are "none" and "disabled", which result in modifiers and * key 0. */ template<> std::optional from_string( const std::string& description); /** Represent the keybinding as a string. */ template<> std::string to_string(const keybinding_t& value); } /** * Represents a single button shortcut (pressing a mouse button while holding * modifiers). */ struct buttonbinding_t { public: /** * Construct a new buttonbinding with the given modifier and button. */ buttonbinding_t(uint32_t modifier, uint32_t button); /* Check whether two keybindings refer to the same shortcut */ bool operator ==(const buttonbinding_t& other) const; /** @return The modifiers of the buttonbinding */ uint32_t get_modifiers() const; /** @return The button of the buttonbinding */ uint32_t get_button() const; private: /** The modifier mask of this keybinding */ uint32_t mod; /** The key of this keybinding */ uint32_t button; }; namespace option_type { /** * Construct a new buttonbinding from the given description. * The format is the same as a keybinding, however instead of KEY_* values, * the buttons are prefixed with BTN_* * * Special case are descriptions "none" and "disable", which result in * mod = button = 0 */ template<> std::optional from_string( const std::string& description); /** Represent the buttonbinding as a string. */ template<> std::string to_string(const buttonbinding_t& value); } /** * The different types of available gestures. */ enum touch_gesture_type_t { /* Invalid gesture */ GESTURE_TYPE_NONE = 0, /* Swipe gesture, i.e moving in one direction */ GESTURE_TYPE_SWIPE = 1, /* Edge swipe, which is a swipe originating from the edge of the screen */ GESTURE_TYPE_EDGE_SWIPE = 2, /* Pinch gesture, multiple touch points coming closer or farther apart * from the center */ GESTURE_TYPE_PINCH = 3, }; enum touch_gesture_direction_t { /* Swipe-specific */ GESTURE_DIRECTION_LEFT = (1 << 0), GESTURE_DIRECTION_RIGHT = (1 << 1), GESTURE_DIRECTION_UP = (1 << 2), GESTURE_DIRECTION_DOWN = (1 << 3), /* Pinch-specific */ GESTURE_DIRECTION_IN = (1 << 4), GESTURE_DIRECTION_OUT = (1 << 5), }; /** * Represents a touch gesture. * * A touch gesture has a type, direction and finger count. * Finger count can be arbitrary, although Wayfire supports only gestures * with finger count >= 3 currently. * * Direction can be either one of of @touch_gesture_direction_t or, in case of * the swipe gestures, it can be a bitwise OR of two non-opposing directions. */ struct touchgesture_t { /** * Construct a new touchgesture_t with the given type, direction and finger * count. Invalid combinations result in an invalid gesture with type NONE. */ touchgesture_t(touch_gesture_type_t type, uint32_t direction, int finger_count); /** @return The type of the gesture */ touch_gesture_type_t get_type() const; /** @return The finger count of the gesture, if valid. Undefined otherwise */ int get_finger_count() const; /** @return The direction of the gesture, if valid. Undefined otherwise */ uint32_t get_direction() const; /** * Check whether two bindings are equal. * Beware that a binding might be only partially set, i.e it might not have * a direction. In this case, the direction acts as a wildcard, so the * touchgesture_t matches any touchgesture_t of the same type with the same * finger count */ bool operator ==(const touchgesture_t& other) const; private: /** Type of the gesture */ touch_gesture_type_t type; /** Direction of the gesture */ uint32_t direction; /** Number of fingers of the gesture */ int finger_count; }; namespace option_type { /** * Construct a new touchgesture_t with the type, direction and finger count * indicated in the description. * * Format: * 1. pinch [in|out] * 2. [edge-]swipe up|down|left|right * 3. [edge-]swipe up-left|right-down|... * 4. disable | none */ template<> std::optional from_string( const std::string& description); /** Represent the touch gesture as a string. */ template<> std::string to_string(const touchgesture_t& value); } /** * The available edges of an output. */ enum output_edge_t { OUTPUT_EDGE_LEFT = (1 << 0), OUTPUT_EDGE_RIGHT = (1 << 1), OUTPUT_EDGE_TOP = (1 << 2), OUTPUT_EDGE_BOTTOM = (1 << 3), }; /** * Represents a binding which can be activated by moving the mouse into a * corner of the screen. */ struct hotspot_binding_t { /** * Initialize a hotspot with the given edges. * * @param edges The edges of the hotspot, a bitmask of output_edge_t * @param along_edge The size of the hotspot alongside the edge(s) * it is located on. * @param across_edge The size of the hotspot away from the edge(s) * it is located on. * @param timeout The time in milliseconds needed for the mouse to stay * in the hotspot to activate it. */ hotspot_binding_t(uint32_t edges = 0, int32_t along_edge = 0, int32_t away_from_edge = 0, int32_t timeout = 0); bool operator ==(const hotspot_binding_t& other) const; /** @return The edges this hotspot binding is on. */ uint32_t get_edges() const; /** @return The size along edges. */ int32_t get_size_along_edge() const; /** @return The size away from edges. */ int32_t get_size_away_from_edge() const; /** @return The timeout of the hotspot. */ int32_t get_timeout() const; private: uint32_t edges; int32_t along; int32_t away; int32_t timeout; }; namespace option_type { /** * Construct a new hotspot_binding_t with the specified edges and size * * Format: * hotspot top|...|top-left|... x */ template<> std::optional from_string( const std::string& description); /** Represent the hotspot binding as a string. */ template<> std::string to_string(const hotspot_binding_t& value); } /** * Represents a binding which can be activated via multiple actions - * keybindings, buttonbindings, touch gestures and hotspots. */ struct activatorbinding_t { public: /** * Initialize an empty activator binding, i.e one which cannot be activated * in any way. */ activatorbinding_t(); ~activatorbinding_t(); /* Copy constructor */ activatorbinding_t(const activatorbinding_t& other); /* Copy assignment */ activatorbinding_t& operator =(const activatorbinding_t& other); /** @return true if the activator is activated by the given keybinding. */ bool has_match(const keybinding_t& key) const; /** @return true if the activator is activated by the given buttonbinding. */ bool has_match(const buttonbinding_t& button) const; /** @return true if the activator is activated by the given gesture. */ bool has_match(const touchgesture_t& gesture) const; /** * @return A list of all hotspots which activate this binding. */ const std::vector& get_hotspots() const; /** * Check equality of two activator bindings. * * @return true if the two activator bindings are activated by the exact * same bindings, false otherwise. */ bool operator ==(const activatorbinding_t& other) const; public: struct impl; std::unique_ptr priv; }; namespace option_type { /** * Create an activator string from the given string description. * The string consists of valid descriptions of keybindings, buttonbindings * and touch gestures, separated by a single '|' sign. */ template<> std::optional from_string( const std::string& string); /** Represent the activator binding as a string. */ template<> std::string to_string(const activatorbinding_t& value); } /** * Types which are related to various output options. */ namespace output_config { enum mode_type_t { /** Output was configured in automatic mode. */ MODE_AUTO, /** Output was configured to be turned off. */ MODE_OFF, /** Output was configured with a given resolution. */ MODE_RESOLUTION, /** Output was configured to be a mirror of another output. */ MODE_MIRROR, }; /** * Represents the output mode. * It contains different values depending on the source. */ struct mode_t { /** * Initialize an OFF or AUTO mode. * * @param auto_on If true, the created mode will be an AUTO mode. */ mode_t(bool auto_on = false); /** * Initialize the mode with source self. * * @param width The configured width. * @param height The configured height. * @param refresh The configured refresh rate, or 0 if undefined. */ mode_t(int32_t width, int32_t height, int32_t refresh); /** * Initialize a mirror mode. */ mode_t(const std::string& mirror_from); /** @return The type of this mode. */ mode_type_t get_type() const; /** @return The configured width, if applicable. */ int32_t get_width() const; /** @return The configured height, if applicable. */ int32_t get_height() const; /** @return The configured refresh rate, if applicable. */ int32_t get_refresh() const; /** @return The configured mirror from output, if applicable. */ std::string get_mirror_from() const; /** * Check equality of two modes. * * @return true if the modes have the same source types and parameters. */ bool operator ==(const mode_t& other) const; private: int32_t width; int32_t height; int32_t refresh; std::string mirror_from; mode_type_t type; }; /** * Represents the output's position. */ struct position_t { /** Automatically positioned output. */ position_t(); /** Output positioned at a fixed position. */ position_t(int32_t x, int32_t y); /** @return The configured X coordinate. */ int32_t get_x() const; /** @return The configured X coordinate. */ int32_t get_y() const; /** @return whether the output is automatically positioned. */ bool is_automatic_position() const; bool operator ==(const position_t& other) const; private: int32_t x; int32_t y; bool automatic; }; } namespace option_type { /** * Create a mode from its string description. * The supported formats are: * * For MODE_AUTO: auto|default * For MODE_OFF: off * For MODE_RESOLUTION: WxH[@RR] * For MODE_MIRROR: mirror */ template<> std::optional from_string( const std::string& string); /** Represent the activator binding as a string. */ template<> std::string to_string(const output_config::mode_t& value); /** * Create an output position from its string description. * The supported formats are: * * auto|default * x , y */ template<> std::optional from_string( const std::string& string); /** Represent the activator binding as a string. */ template<> std::string to_string(const output_config::position_t& value); } } wf-config-0.8.0/include/wayfire/config/xml.hpp000066400000000000000000000043071446445615300212650ustar00rootroot00000000000000#pragma once /** * This file contains definitions related to parsing XML files and turning them * into config options and sections. */ #include #include #include #include namespace wf { namespace config { namespace xml { /** * Create a new option from the given data in the xmlNode. * Errors are printed to the log (see wayfire/util/log.hpp). * * If the operation is successful, the xmlNodePtr should not be freed, because * an internal reference will be taken. * * @param node The xmlNode which corresponds to the option. * The attributes used are name and type, as well as the child. * * @return The generated option, if the xmlNode contained a valid option, and * nullptr otherwise. The dynamic type of the option is * wf::config::option_t, depending on the type attribute of the xmlNode. */ std::shared_ptr create_option_from_xml_node( xmlNodePtr node); /** * Create a new section from the given xmlNode and add each successfully parsed * child as an option in the section. * Errors are printed to the log (see wayfire/util/log.hpp). * * If the operation is successful, the xmlNodePtr should not be freed, because * an internal reference will be taken. * * @param node the xmlNode which corresponds to the section node. The only * attribute of @node used is name, and it determines the name of the generated * section. * * @return nullptr if section name is missing from the xmlNode, and the * generated config section otherwise. */ std::shared_ptr create_section_from_xml_node(xmlNodePtr node); /** * Get the XML node which was used to create @option with * create_option_from_xml_node. * * @return The xmlNodePtr or NULL if the option wasn't created from an xml node. */ xmlNodePtr get_option_xml_node( std::shared_ptr option); /** * Get the XML node which was used to create @section with * create_section_from_xml_node. * * @return The xmlNodePtr or NULL if the section wasn't created from an xml * node. */ xmlNodePtr get_section_xml_node(std::shared_ptr section); } } } wf-config-0.8.0/include/wayfire/util/000077500000000000000000000000001446445615300174605ustar00rootroot00000000000000wf-config-0.8.0/include/wayfire/util/duration.hpp000066400000000000000000000110471446445615300220210ustar00rootroot00000000000000#pragma once #include namespace wf { namespace animation { namespace smoothing { /** * A smooth function is a function which takes a double in [0, 1] and returns * another double in R. Both ranges represent percentage of a progress of * an animation. */ using smooth_function = std::function; /** linear smoothing function, i.e x -> x */ extern smooth_function linear; /** "circle" smoothing function, i.e x -> sqrt(2x - x*x) */ extern smooth_function circle; /** "sigmoid" smoothing function, i.e x -> 1.0 / (1 + exp(-12 * x + 6)) */ extern smooth_function sigmoid; } /** * A transition from start to end. */ struct transition_t { double start, end; }; /** * duration_t is a class which can be used to track progress over a specific * time interval. */ class duration_t { public: /** * Construct a new duration. * Initially, the duration is not running and its progress is 1. * * @param length The length of the duration in milliseconds. * @param smooth The smoothing function for transitions. */ duration_t(std::shared_ptr> length = nullptr, smoothing::smooth_function smooth = smoothing::circle); /* Copy-constructor */ duration_t(const duration_t& other); /* Copy-assignment */ duration_t& operator =(const duration_t& other); /* Move-constructor */ duration_t(duration_t&& other) = default; /* Move-assignment */ duration_t& operator =(duration_t&& other) = default; /** * Start the duration. * This means that the progress will get reset to 0. */ void start(); /** * Get the progress of the duration in percentage. * The progress will be smoothed using the smoothing function. * * @return The current progress after smoothing. It is guaranteed that when * the duration starts, progress will be close to 0, and when it is * finished, it will be close to 1. */ double progress() const; /** * Check if the duration is still running. * Note that even when the duration first finishes, this function will * still return that the function is running one time. * * @return Whether the duration still has not elapsed. */ bool running(); /** * Reverse the duration. The progress will remain the same but the * direction will reverse toward the opposite start or end point. */ void reverse(); /** * Get duration direction. * 0: reverse * 1: forward */ int get_direction(); class impl; /** Implementation details. */ std::shared_ptr priv; }; /** * A timed transition is a transition between two states which happens * over a period of time. * * During the transition, the current state is smoothly interpolated between * start and end. */ struct timed_transition_t : public transition_t { /** * Construct a new timed transition using the given duration to measure * progress. * * @duration The duration to use for time measurement * @start The start state. * @end The end state. */ timed_transition_t(const duration_t& duration, double start = 0, double end = 0); /** * Set the transition start to the current state and the end to the given * @new_end. */ void restart_with_end(double new_end); /** * Set the transition start to the current state, and don't change the end. */ void restart_same_end(); /** * Set the transition start and end state. * @param start The start of the transition. * @param end The end of the transition. */ void set(double start, double end); /** * Swap start and end values. */ void flip(); /** * Implicitly convert the transition to its current state. */ operator double() const; private: std::shared_ptr duration; }; class simple_animation_t : public duration_t, public timed_transition_t { public: simple_animation_t( std::shared_ptr> length = nullptr, smoothing::smooth_function smooth = smoothing::circle); /** * Set the start and the end of the animation and start the duration. */ void animate(double start, double end); /** * Animate from the current progress to the given end, and start the * duration. */ void animate(double end); /** * Animate from the current progress to the current end, and start the * duration. */ void animate(); }; } } wf-config-0.8.0/include/wayfire/util/log.hpp000066400000000000000000000043471446445615300207620ustar00rootroot00000000000000#pragma once /** * Utilities for logging to a selected output stream. */ #include namespace wf { namespace log { enum log_level_t { /** Lowest level, gray if colors are enabled. */ LOG_LEVEL_DEBUG = 0, /** Information level, no coloring. */ LOG_LEVEL_INFO = 1, /** Warning level, yellow/orange. */ LOG_LEVEL_WARN = 2, /** Error level, red. */ LOG_LEVEL_ERROR = 3, }; enum color_mode_t { /** Color codes are on */ LOG_COLOR_MODE_ON = 1, /** Color codes are off */ LOG_COLOR_MODE_OFF = 2, }; /** * (Re-)Initialize the logging system. * The log output after this call will go to the indicated output stream. * * @param minimum_level The minimum severity that a log message needs to have so * that it can get published. * * @param color_mode Whether to enable coloring of the output. * * @param strip_path The prefix of file path to strip when debugging with the * helper macros LOG(D,I,W,E) */ void initialize_logging(std::ostream& output_stream, log_level_t minimum_level, color_mode_t color_mode, std::string strip_path = ""); /** * Log a plain message to the given output stream. * The output format is: * * LL DD-MM-YY HH:MM:SS.MSS - [source:line] message * * @param level The log level of the passed message. * @param contents The message to be printed. * @param source The file where the message originates from. The prefix * strip_path specified in initialize_logging will be removed, if it exists. * If source is empty, no file/line information will be printed. * @param line The line number of @source */ void log_plain(log_level_t level, const std::string& contents, const std::string& source = "", int line = 0); } } /** * A convenience wrapper around log_plain */ #define LOG(level, ...) \ wf::log::log_plain(level, \ wf::log::detail::format_concat(__VA_ARGS__), __FILE__, __LINE__) /** Log a debug message */ #define LOGD(...) LOG(wf::log::LOG_LEVEL_DEBUG, __VA_ARGS__) /** Log an info message */ #define LOGI(...) LOG(wf::log::LOG_LEVEL_INFO, __VA_ARGS__) /** Log a warning message */ #define LOGW(...) LOG(wf::log::LOG_LEVEL_WARN, __VA_ARGS__) /** Log an error message */ #define LOGE(...) LOG(wf::log::LOG_LEVEL_ERROR, __VA_ARGS__) wf-config-0.8.0/include/wayfire/util/stringify.hpp000066400000000000000000000017221446445615300222110ustar00rootroot00000000000000#pragma once #include #include namespace wf { namespace log { /** * Convert the given parameter to a string which can be logged. * This function can be specialized for custom types. */ template std::string to_string(T arg) { std::ostringstream out; out << arg; return out.str(); } /** Specialization for boolean arguments - print true or false. */ template<> std::string to_string(bool arg); /* Specialization for pointers - print the address */ template std::string to_string(T *arg) { if (!arg) { return "(null)"; } return to_string(arg); } namespace detail { /** * Convert each argument to a string and then concatenate them. */ template std::string format_concat(First arg) { return wf::log::to_string(arg); } template std::string format_concat(First first, Args... args) { return format_concat(first) + format_concat(args...); } } } } wf-config-0.8.0/meson.build000066400000000000000000000025671446445615300155660ustar00rootroot00000000000000project( 'wf-config', 'cpp', version: '0.8.0', license: 'MIT', meson_version: '>=0.47.0', default_options: [ 'cpp_std=c++17', 'warning_level=2', 'werror=false', ], ) add_project_arguments(['-Wno-deprecated-declarations'], language: ['cpp']) glm = dependency('glm') evdev = dependency('libevdev') libxml2 = dependency('libxml-2.0') sources = [ 'src/types.cpp', 'src/option.cpp', 'src/section.cpp', 'src/log.cpp', 'src/xml.cpp', 'src/config-manager.cpp', 'src/file.cpp', 'src/duration.cpp', 'src/compound-option.cpp', ] wfconfig_inc = include_directories('include') lib_wfconfig = library('wf-config', sources, dependencies: [evdev, glm, libxml2], include_directories: wfconfig_inc, install: true, version: meson.project_version(), soversion: '1') pkgconfig = import('pkgconfig') pkgconfig.generate( libraries: lib_wfconfig, version: meson.project_version(), filebase: meson.project_name(), name: meson.project_name(), description: 'Dynamic file-based configuration library for Wayfire') install_headers([], subdir: 'wayfire/config') wfconfig = declare_dependency(link_with: lib_wfconfig, include_directories: wfconfig_inc, dependencies: glm) # Install headers subdir('include') # Unit tests doctest = dependency('doctest', required: get_option('tests')) if doctest.found() subdir('test') endif wf-config-0.8.0/meson_options.txt000066400000000000000000000003411446445615300170450ustar00rootroot00000000000000option('tests', type: 'feature', value: 'auto', description: 'Enable unit tests') option('locale_test', type : 'boolean', value : false, description: 'Test number to string conversions with de_DE locale (must be installed)') wf-config-0.8.0/src/000077500000000000000000000000001446445615300142015ustar00rootroot00000000000000wf-config-0.8.0/src/compound-option.cpp000066400000000000000000000117271446445615300200470ustar00rootroot00000000000000#include #include #include "option-impl.hpp" using namespace wf::config; static bool begins_with(const std::string& a, const std::string& b) { return a.substr(0, b.size()) == b; } compound_option_t::compound_option_t(const std::string& name, entries_t&& entries, std::string type_hint) : option_base_t(name), list_type_hint( type_hint) { this->entries = std::move(entries); } void wf::config::update_compound_from_section( compound_option_t& compound, const std::shared_ptr& section) { const auto& options = section->get_registered_options(); const auto& should_ignore_option = [] (const std::shared_ptr& opt) { return xml::get_option_xml_node(opt) || !opt->priv->option_in_config_file; }; const auto& entries = compound.get_entries(); std::map> new_values; // find possible suffixes for (const auto& opt : options) { if (should_ignore_option(opt)) { continue; } // iterating from the end ta handle cases where there is a prefix which is a prefix of another prefix // for instance, if there are entries with prefixes `prefix_` and `prefix_smth_` (in that order), // then option with name `prefix_smth_suffix` will be recognised with prefix `prefix_smth_`. for (auto it = entries.rbegin(); it != entries.rend(); ++it) { const auto& entry = *it; if (begins_with(opt->get_name(), entry->get_prefix())) { new_values.emplace(opt->get_name().substr(entry->get_prefix().size()), entries.size() + 1); break; } } } compound_option_t::stored_type_t stored_value; for (auto& [suffix, value] : new_values) { value[0] = suffix; for (size_t i = 0; i < entries.size(); ++i) { if (const auto & entry_option = section->get_option_or(entries[i]->get_prefix() + suffix); entry_option && !should_ignore_option(entry_option)) { if (entries[i]->is_parsable(entry_option->get_value_str())) { value[i + 1] = entry_option->get_value_str(); continue; } else { LOGE("Failed parsing option ", section->get_name() + "/" + entry_option->get_name(), " as part of the list option ", section->get_name() + "/" + compound.get_name(), ". Trying to use the default value."); } } if (const auto& default_value = entries[i]->get_default_value()) { value[i + 1] = *default_value; } else { LOGE("The option ", section->get_name() + "/" + entries[i]->get_prefix() + suffix, " is neither specified nor has a default value"); value.clear(); break; } } if (!value.empty()) { stored_value.push_back(std::move(value)); } } compound.set_value_untyped(stored_value); } compound_option_t::stored_type_t compound_option_t::get_value_untyped() { return this->value; } bool compound_option_t::set_value_untyped(stored_type_t value) { for (auto& e : value) { if (e.size() != this->entries.size() + 1) { return false; } for (size_t i = 1; i <= this->entries.size(); i++) { if (!entries[i - 1]->is_parsable(e[i])) { return false; } } } this->value = value; notify_updated(); return true; } const compound_option_t::entries_t& compound_option_t::get_entries() const { return this->entries; } /* --------------------------- option_base_t impl --------------------------- */ std::shared_ptr compound_option_t::clone_option() const { entries_t cloned; for (auto& e : this->entries) { cloned.push_back( std::unique_ptr(e->clone())); } auto result = std::make_shared(get_name(), std::move(cloned)); result->value = this->value; return result; } bool wf::config::compound_option_t::set_value_str(const std::string&) { // XXX: not supported yet return false; } void wf::config::compound_option_t::reset_to_default() { this->value.clear(); } bool wf::config::compound_option_t::set_default_value_str(const std::string&) { // XXX: not supported yet return false; } std::string wf::config::compound_option_t::get_value_str() const { // XXX: not supported yet return ""; } std::string wf::config::compound_option_t::get_default_value_str() const { // XXX: not supported yet return ""; } wf-config-0.8.0/src/config-manager.cpp000066400000000000000000000050141446445615300175620ustar00rootroot00000000000000#include #include #include struct wf::config::config_manager_t::impl { std::map> sections; }; void wf::config::config_manager_t::merge_section( std::shared_ptr section) { assert(section); if (this->priv->sections.count(section->get_name()) == 0) { /* Did not exist previously, just add the new section */ this->priv->sections[section->get_name()] = section; return; } /* Merge with existing config section */ auto existing_section = get_section(section->get_name()); auto merging_options = section->get_registered_options(); for (auto& option : merging_options) { auto existing_option = existing_section->get_option_or(option->get_name()); if (existing_option) { existing_option->set_value_str(option->get_value_str()); } else { existing_section->register_new_option(option); } } } std::shared_ptr wf::config::config_manager_t::get_section( const std::string& name) const { if (this->priv->sections.count(name)) { return this->priv->sections.at(name); } return nullptr; } std::vector> wf::config::config_manager_t::get_all_sections() const { std::vector> list; for (auto& section : this->priv->sections) { list.push_back(section.second); } return list; } std::shared_ptr wf::config::config_manager_t::get_option( const std::string& name) const { size_t splitter = name.find_first_of("/"); if (splitter == std::string::npos) { return nullptr; } auto section_name = name.substr(0, splitter); auto option_name = name.substr(splitter + 1); if (section_name.empty() || option_name.empty()) { return nullptr; } auto section_ptr = get_section(section_name); if (section_ptr) { return section_ptr->get_option_or(option_name); } return nullptr; } wf::config::config_manager_t::config_manager_t() { this->priv = std::make_unique(); } /** Default move operations */ wf::config::config_manager_t::config_manager_t( config_manager_t&& other) = default; wf::config::config_manager_t& wf::config::config_manager_t::operator =( config_manager_t&& other) = default; wf::config::config_manager_t::~config_manager_t() = default; wf-config-0.8.0/src/duration.cpp000066400000000000000000000110271446445615300165330ustar00rootroot00000000000000#include #include #include #include namespace wf { namespace animation { namespace smoothing { smooth_function linear = [] (double x) -> double { return x; }; smooth_function circle = [] (double x) -> double { return std::sqrt(2 * x - x * x); }; const double sigmoid_max = 1 + std::exp(-6); smooth_function sigmoid = [] (double x) -> double { return sigmoid_max / (1 + exp(-12 * x + 6)); }; } } } class wf::animation::duration_t::impl { public: decltype(std::chrono::system_clock::now()) start_point; std::shared_ptr> length; smoothing::smooth_function smooth_function; bool is_running = false; bool reverse = false; int64_t get_elapsed() const { using namespace std::chrono; auto now = system_clock::now(); return duration_cast(now - start_point).count(); } int get_duration() const { if (length) { return std::max(1, length->get_value()); } LOGD("Calling methods on wf::animation::duration_t without a length"); return 1; } bool is_ready() const { return get_elapsed() >= get_duration(); } double get_progress_percentage() const { if (!length || is_ready()) { return 1.0; } auto progress = 1.0 * get_elapsed() / get_duration(); if (reverse) { progress = 1.0 - progress; } return progress; } double progress() const { if (is_ready()) { return reverse ? 0.0 : 1.0; } return smooth_function(get_progress_percentage()); } }; wf::animation::duration_t::duration_t( std::shared_ptr> length, smoothing::smooth_function smooth) { this->priv = std::make_shared(); this->priv->length = length; this->priv->is_running = false; this->priv->reverse = false; this->priv->smooth_function = smooth; } wf::animation::duration_t::duration_t(const duration_t& other) { this->priv = std::make_shared(*other.priv); } wf::animation::duration_t& wf::animation::duration_t::operator =( const duration_t& other) { if (&other != this) { this->priv = std::make_shared(*other.priv); } return *this; } void wf::animation::duration_t::start() { this->priv->is_running = 1; this->priv->start_point = std::chrono::system_clock::now(); } double wf::animation::duration_t::progress() const { return this->priv->progress(); } bool wf::animation::duration_t::running() { if (this->priv->is_ready()) { bool was_running = this->priv->is_running; this->priv->is_running = false; return was_running; } return true; } void wf::animation::duration_t::reverse() { std::chrono::milliseconds remaining(this->priv->get_duration() - this->priv->get_elapsed()); this->priv->start_point = std::chrono::system_clock::now() - remaining; this->priv->reverse = !this->priv->reverse; } int wf::animation::duration_t::get_direction() { return !this->priv->reverse; } wf::animation::timed_transition_t::timed_transition_t( const duration_t& dur, double start, double end) : duration(dur.priv) { this->set(start, end); } void wf::animation::timed_transition_t::restart_with_end(double new_end) { this->start = (double)*this; this->end = new_end; } void wf::animation::timed_transition_t::restart_same_end() { this->start = (double)*this; } void wf::animation::timed_transition_t::set(double start, double end) { this->start = start; this->end = end; } void wf::animation::timed_transition_t::flip() { std::swap(this->start, this->end); } wf::animation::timed_transition_t::operator double() const { double alpha = this->duration->progress(); return (1 - alpha) * start + alpha * end; } wf::animation::simple_animation_t::simple_animation_t( std::shared_ptr> length, smoothing::smooth_function smooth) : duration_t(length, smooth), timed_transition_t((duration_t&)*this) {} void wf::animation::simple_animation_t::animate(double start, double end) { this->set(start, end); this->duration_t::start(); } void wf::animation::simple_animation_t::animate(double end) { this->restart_with_end(end); this->duration_t::start(); } void wf::animation::simple_animation_t::animate() { this->restart_same_end(); this->duration_t::start(); } wf-config-0.8.0/src/file.cpp000066400000000000000000000424501446445615300156310ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "option-impl.hpp" #include #include #include #include class line_t : public std::string { public: template line_t(T source) : std::string(source) {} line_t() : std::string() {} line_t(const line_t& other) = default; line_t(line_t&& other) = default; line_t& operator =(const line_t& other) = default; line_t& operator =(line_t&& other) = default; public: line_t substr(size_t start, size_t length = npos) const { line_t result = std::string::substr(start, length); result.source_line_number = this->source_line_number; return result; } size_t source_line_number; }; using lines_t = std::vector; static lines_t split_to_lines(const std::string& source) { std::istringstream stream(source); lines_t output; line_t line; size_t line_idx = 1; while (std::getline(stream, line)) { line.source_line_number = line_idx; output.push_back(line); ++line_idx; } return output; } /** * Check whether at the given index @idx in @line, there is a character * @ch which isn't escaped (i.e preceded by \). */ static bool is_nonescaped(const std::string& line, char ch, int idx) { return line[idx] == ch && (idx == 0 || line[idx - 1] != '\\'); } static size_t find_first_nonescaped(const std::string& line, char ch) { /* Find first not-escaped # */ size_t pos = 0; while (pos != std::string::npos && !is_nonescaped(line, ch, pos)) { pos = line.find(ch, pos + 1); } return pos; } line_t remove_escaped_sharps(const line_t& line) { line_t result; result.source_line_number = line.source_line_number; bool had_escape = false; for (auto& ch : line) { if ((ch == '#') && had_escape) { result.pop_back(); } result += ch; had_escape = (ch == '\\'); } return result; } static lines_t remove_comments(const lines_t& lines) { lines_t result; for (const auto& line : lines) { auto pos = find_first_nonescaped(line, '#'); result.push_back( remove_escaped_sharps(line.substr(0, pos))); } return result; } static lines_t remove_trailing_whitespace(const lines_t& lines) { lines_t result; for (const auto& line : lines) { auto result_line = line; while (!result_line.empty() && std::isspace(result_line.back())) { result_line.pop_back(); } result.push_back(result_line); } return result; } lines_t join_lines(const lines_t& lines) { lines_t result; bool in_concat_mode = false; for (const auto& line : lines) { if (in_concat_mode) { assert(!result.empty()); result.back() += line; } else { result.push_back(line); } if (result.empty() || result.back().empty()) { in_concat_mode = false; } else { in_concat_mode = (result.back().back() == '\\'); if (in_concat_mode) /* pop last \ */ { result.back().pop_back(); } /* If last \ was escaped, we should ignore it */ bool was_escaped = !result.back().empty() && result.back().back() == '\\'; in_concat_mode = in_concat_mode && !was_escaped; } } return result; } lines_t skip_empty(const lines_t& lines) { lines_t result; for (auto& line : lines) { if (!line.empty()) { result.push_back(line); } } return result; } static std::string ignore_leading_trailing_whitespace(const std::string& string) { if (string.empty()) { return ""; } size_t i = 0; size_t j = string.size() - 1; while (i < j && std::isspace(string[i])) { ++i; } while (i < j && std::isspace(string[j])) { --j; } return string.substr(i, j - i + 1); } enum option_parsing_result { /* Line was valid */ OPTION_PARSED_OK, /* Line has wrong format */ OPTION_PARSED_WRONG_FORMAT, /* Specified value does not match existing option type */ OPTION_PARSED_INVALID_CONTENTS, }; /** * Try to parse an option line. * If the option line is valid, the corresponding option is modified or added * to @current_section, and the option is added to @reloaded. * * @return The parse status of the line. */ static option_parsing_result parse_option_line( wf::config::section_t& current_section, const line_t& line, std::set>& reloaded) { size_t equal_sign = line.find_first_of("="); if (equal_sign == std::string::npos) { return OPTION_PARSED_WRONG_FORMAT; } auto name = ignore_leading_trailing_whitespace(line.substr(0, equal_sign)); auto value = ignore_leading_trailing_whitespace(line.substr(equal_sign + 1)); auto option = current_section.get_option_or(name); if (!option) { using namespace wf; option = std::make_shared>(name, ""); option->set_value_str(value); current_section.register_new_option(option); } if (option->is_locked() || option->set_value_str(value)) { reloaded.insert(option); return OPTION_PARSED_OK; } return OPTION_PARSED_INVALID_CONTENTS; } /** * Check whether the @line is a valid section start. * If yes, it will either return the section in @config with the same name, or * create a new section and register it in config. * * @return nullptr if line is not a valid section, the section otherwise. */ static std::shared_ptr check_section( wf::config::config_manager_t& config, const line_t& line) { auto name = ignore_leading_trailing_whitespace(line); if (name.empty() || (name.front() != '[') || (name.back() != ']')) { return {}; } auto real_name = name.substr(1, name.length() - 2); auto section = config.get_section(real_name); if (!section) { size_t splitter = real_name.find_first_of(":"); if (splitter != std::string::npos) { auto obj_type_name = real_name.substr(0, splitter); auto section_name = real_name.substr(splitter + 1); // only for the // empty check if (!obj_type_name.empty() && !section_name.empty()) { auto parent_section = config.get_section(obj_type_name); if (parent_section) { section = parent_section->clone_with_name(real_name); config.merge_section(section); return section; } } } section = std::make_shared(real_name); config.merge_section(section); } return section; } void wf::config::load_configuration_options_from_string( config_manager_t& config, const std::string& source, const std::string& source_name) { std::set> reloaded; auto lines = skip_empty( join_lines( remove_trailing_whitespace( remove_comments( split_to_lines(source))))); std::shared_ptr current_section; for (const auto& line : lines) { auto next_section = check_section(config, line); if (next_section) { current_section = next_section; continue; } if (!current_section) { LOGE("Error in file ", source_name, ":", line.source_line_number, ", option declared before a section starts!"); continue; } auto status = parse_option_line(*current_section, line, reloaded); switch (status) { case OPTION_PARSED_WRONG_FORMAT: LOGE("Error in file ", source_name, ":", line.source_line_number, ", invalid option format ", "(allowed = )"); break; case OPTION_PARSED_INVALID_CONTENTS: LOGE("Error in file ", source_name, ":", line.source_line_number, ", invalid option value!"); break; default: break; } } // Go through all options and reset options which are loaded from the config // string but are not there anymore. for (auto section : config.get_all_sections()) { for (auto opt : section->get_registered_options()) { opt->priv->option_in_config_file = (reloaded.count(opt) > 0); if (!opt->priv->option_in_config_file && !opt->is_locked()) { opt->reset_to_default(); } } } // After resetting all options which are no longer in the config file, make // sure to rebuild compound options as well. for (auto section : config.get_all_sections()) { for (auto opt : section->get_registered_options()) { auto as_compound = std::dynamic_pointer_cast(opt); if (as_compound) { update_compound_from_section(*as_compound, section); } } } } std::string wf::config::save_configuration_options_to_string( const config_manager_t& config) { std::vector lines; for (auto& section : config.get_all_sections()) { lines.push_back("[" + section->get_name() + "]"); // Go through each option and add the necessary lines. // Take care so that regular options overwrite compound options // in case of conflict! std::map option_values; std::set all_compound_prefixes; for (auto& option : section->get_registered_options()) { auto as_compound = std::dynamic_pointer_cast(option); if (as_compound) { auto value = as_compound->get_value_untyped(); const auto& prefixes = as_compound->get_entries(); for (auto& p : prefixes) { all_compound_prefixes.insert(p->get_prefix()); } for (size_t i = 0; i < value.size(); i++) { for (size_t j = 0; j < prefixes.size(); j++) { auto full_name = prefixes[j]->get_prefix() + value[i][0]; option_values[full_name] = value[i][j + 1]; } } } } // An option is part of a compound option if it begins with any of the // prefixes. const auto& is_part_of_compound_option = [&] (const std::string& name) { return std::any_of( all_compound_prefixes.begin(), all_compound_prefixes.end(), [&] (const auto& prefix) { return name.substr(0, prefix.size()) == prefix; }); }; for (auto& option : section->get_registered_options()) { auto as_compound = std::dynamic_pointer_cast(option); if (!as_compound) { // Check whether this option does not conflict with a compound // option entry. if (xml::get_option_xml_node(option) || !is_part_of_compound_option(option->get_name())) { option_values[option->get_name()] = option->get_value_str(); } } } for (auto& [name, value] : option_values) { lines.push_back(name + " = " + value); } lines.push_back(""); } /* Check which characters need escaping */ for (auto& line : lines) { size_t sharp = line.find_first_of("#"); while (sharp != line.npos) { line.insert(line.begin() + sharp, '\\'); sharp = line.find_first_of("#", sharp + 2); } if (!line.empty() && (line.back() == '\\')) { line += '\\'; } } std::string result; for (const auto& line : lines) { result += line + "\n"; } return result; } static std::string load_file_contents(const std::string& file) { std::ifstream infile(file); std::string file_contents((std::istreambuf_iterator(infile)), std::istreambuf_iterator()); return file_contents; } bool wf::config::load_configuration_options_from_file(config_manager_t& manager, const std::string& file) { /* Try to lock the file */ auto fd = open(file.c_str(), O_RDONLY); if (flock(fd, LOCK_SH | LOCK_NB)) { close(fd); return false; } auto file_contents = load_file_contents(file); /* Release lock */ flock(fd, LOCK_UN); close(fd); load_configuration_options_from_string(manager, file_contents, file); return true; } void wf::config::save_configuration_to_file( const wf::config::config_manager_t& manager, const std::string& file) { auto contents = save_configuration_options_to_string(manager); contents.pop_back(); // remove last newline auto fd = open(file.c_str(), O_RDONLY); flock(fd, LOCK_EX); auto fout = std::ofstream(file, std::ios::trunc); fout << contents; flock(fd, LOCK_UN); close(fd); /* Modify the file one last time. Now programs waiting for updates can * acquire a shared lock. */ fout << std::endl; } static void process_xml_file(wf::config::config_manager_t& manager, const std::string & filename) { LOGI("Reading XML configuration options from file ", filename); /* Parse the XML file. */ auto doc = xmlParseFile(filename.c_str()); if (!doc) { LOGE("Failed to parse XML file ", filename); return; } auto root = xmlDocGetRootElement(doc); if (!root) { LOGE(filename, ": missing root element."); xmlFreeDoc(doc); return; } /* Seek the plugin/object sections */ auto section = root->children; while (section != nullptr) { if ((section->type == XML_ELEMENT_NODE) && (((const char*)section->name == (std::string)"plugin") || ((const char*)section->name == (std::string)"object"))) { manager.merge_section( wf::config::xml::create_section_from_xml_node(section)); } section = section->next; } // xmlFreeDoc(doc); - May clear the XML nodes before they are used } static wf::config::config_manager_t load_xml_files( const std::vector& xmldirs) { wf::config::config_manager_t manager; for (auto& xmldir : xmldirs) { auto xmld = opendir(xmldir.c_str()); if (NULL == xmld) { LOGW("Failed to open XML directory ", xmldir); continue; } LOGI("Reading XML configuration options from directory ", xmldir); struct dirent *entry; while ((entry = readdir(xmld)) != NULL) { if ((entry->d_type != DT_LNK) && (entry->d_type != DT_REG) && (entry->d_type != DT_UNKNOWN)) { continue; } std::string filename = xmldir + '/' + entry->d_name; if ((filename.length() > 4) && (filename.rfind(".xml") == filename.length() - 4)) { process_xml_file(manager, filename); } } closedir(xmld); } return manager; } void override_defaults(wf::config::config_manager_t& manager, const std::string& sysconf) { auto sysconf_str = load_file_contents(sysconf); wf::config::config_manager_t overrides; load_configuration_options_from_string(overrides, sysconf_str, sysconf); for (auto& section : overrides.get_all_sections()) { for (auto& option : section->get_registered_options()) { auto full_name = section->get_name() + '/' + option->get_name(); auto real_option = manager.get_option(full_name); if (real_option) { if (!real_option->set_default_value_str( option->get_value_str())) { LOGW("Invalid value for ", full_name, " in ", sysconf); } else { /* Set the value to the new default */ real_option->reset_to_default(); } } else { LOGW("Unused default value for ", full_name, " in ", sysconf); } } } } #include wf::config::config_manager_t wf::config::build_configuration( const std::vector& xmldirs, const std::string& sysconf, const std::string& userconf) { auto manager = load_xml_files(xmldirs); override_defaults(manager, sysconf); load_configuration_options_from_file(manager, userconf); return manager; } wf-config-0.8.0/src/log.cpp000066400000000000000000000101551446445615300154700ustar00rootroot00000000000000#include #include #include #include #include #include #include template<> std::string wf::log::to_string(void *arg) { if (!arg) { return "(null)"; } std::ostringstream out; out << arg; return out.str(); } template<> std::string wf::log::to_string(bool arg) { return arg ? "true" : "false"; } /** * A singleton to hold log configuration. */ struct log_global_t { std::reference_wrapper out = std::ref(std::cout); wf::log::log_level_t level = wf::log::LOG_LEVEL_INFO; wf::log::color_mode_t color_mode = wf::log::LOG_COLOR_MODE_OFF; std::string strip_path = ""; std::string clear_color = ""; static log_global_t& get() { static log_global_t instance; return instance; } private: log_global_t() {} }; void wf::log::initialize_logging(std::ostream& output_stream, log_level_t minimum_level, color_mode_t color_mode, std::string strip_path) { auto& state = log_global_t::get(); state.out = std::ref(output_stream); state.level = minimum_level; state.color_mode = color_mode; state.strip_path = strip_path; if (state.color_mode == LOG_COLOR_MODE_ON) { state.clear_color = "\033[0m"; } else { state.clear_color = ""; } } /** Get the line prefix for the given log level */ static std::string get_level_prefix(wf::log::log_level_t level) { bool color = log_global_t::get().color_mode == wf::log::LOG_COLOR_MODE_ON; static std::map color_codes = { {wf::log::LOG_LEVEL_DEBUG, "\033[0m"}, {wf::log::LOG_LEVEL_INFO, "\033[0;34m"}, {wf::log::LOG_LEVEL_WARN, "\033[0;33m"}, {wf::log::LOG_LEVEL_ERROR, "\033[1;31m"}, }; static std::map line_prefix = { {wf::log::LOG_LEVEL_DEBUG, "DD"}, {wf::log::LOG_LEVEL_INFO, "II"}, {wf::log::LOG_LEVEL_WARN, "WW"}, {wf::log::LOG_LEVEL_ERROR, "EE"}, }; if (color) { return color_codes[level] + line_prefix[level]; } return line_prefix[level]; } /** Get the current time and date in a suitable format. */ static std::string get_formatted_date_time() { using namespace std::chrono; auto now = system_clock::now(); auto tt = system_clock::to_time_t(now); auto ms = duration_cast(now.time_since_epoch()) % 1000; std::ostringstream out; out << std::put_time(std::localtime(&tt), "%d-%m-%y %H:%M:%S."); out << std::setfill('0') << std::setw(3) << ms.count(); return out.str(); } /** Strip the strip_path from the given path. */ static std::string strip_path(const std::string& path) { auto prefix = log_global_t::get().strip_path; if ((prefix.length() > 0) && (path.find(prefix) == 0)) { return path.substr(prefix.length()); } std::string skip_chars = "./"; size_t idx = path.find_first_not_of(skip_chars); if (idx != std::string::npos) { return path.substr(idx); } return path; } /** * Log a plain message to the given output stream. * The output format is: * * LL DD-MM-YY HH:MM:SS.MSS - [source:line] message * * @param level The log level of the passed message. * @param contents The message to be printed. * @param source The file where the message originates from. The prefix * strip_path specified in initialize_logging will be removed, if it exists. * @param line The line number of @source */ void wf::log::log_plain(log_level_t level, const std::string& contents, const std::string& source, int line_nr) { auto& state = log_global_t::get(); if (state.level > level) { return; } std::string path_info; if (!source.empty()) { path_info = wf::log::detail::format_concat( "[", strip_path(source), ":", line_nr, "] "); } state.out.get() << wf::log::detail::format_concat( get_level_prefix(level), " ", get_formatted_date_time(), " - ", path_info, contents) << state.clear_color << std::endl; } wf-config-0.8.0/src/option-impl.hpp000066400000000000000000000017001446445615300171570ustar00rootroot00000000000000#pragma once #include #include #include #include struct wf::config::option_base_t::impl { std::string name; std::vector updated_handlers; // Number of times the option has been locked int32_t lock_count = 0; // Associated XML node xmlNode *xml; // Is option in config file? bool option_in_config_file = false; }; namespace wf { namespace config { /** * Update the value of a compound option option by reading options from the section. * The format is as described in the compound option constructor docstring. * * Note: options which have been created from XML are ignored, and only * options which have been created from parsing a string/file with wf-config * are taken into account. */ void update_compound_from_section(compound_option_t& option, const std::shared_ptr& section); } } wf-config-0.8.0/src/option.cpp000066400000000000000000000027221446445615300162200ustar00rootroot00000000000000#include #include #include #include "option-impl.hpp" #include "wayfire/util/log.hpp" std::string wf::config::option_base_t::get_name() const { return this->priv->name; } void wf::config::option_base_t::add_updated_handler( updated_callback_t *callback) { this->priv->updated_handlers.push_back(callback); } void wf::config::option_base_t::rem_updated_handler( updated_callback_t *callback) { auto it = std::remove(priv->updated_handlers.begin(), priv->updated_handlers.end(), callback); priv->updated_handlers.erase(it, priv->updated_handlers.end()); } wf::config::option_base_t::option_base_t(const std::string& name) { this->priv = std::make_unique(); this->priv->name = name; } wf::config::option_base_t::~option_base_t() = default; void wf::config::option_base_t::notify_updated() const { auto to_call = priv->updated_handlers; for (auto& call : to_call) { (*call)(); } } void wf::config::option_base_t::set_locked(bool locked) { this->priv->lock_count += (locked ? 1 : -1); if (priv->lock_count < 0) { LOGE("Lock counter for option ", this->get_name(), " dropped below zero!"); } } bool wf::config::option_base_t::is_locked() const { return this->priv->lock_count > 0; } void wf::config::option_base_t::init_clone(option_base_t& other) const { other.priv->xml = this->priv->xml; other.priv->name = this->priv->name; } wf-config-0.8.0/src/section-impl.hpp000066400000000000000000000004351446445615300173170ustar00rootroot00000000000000#pragma once #include #include #include struct wf::config::section_t::impl { public: std::map> options; std::string name; // Associated XML node xmlNode *xml = NULL; }; wf-config-0.8.0/src/section.cpp000066400000000000000000000040301446445615300163460ustar00rootroot00000000000000#include #include "section-impl.hpp" wf::config::section_t::section_t(const std::string& name) { this->priv = std::make_unique(); this->priv->name = name; } wf::config::section_t::~section_t() = default; std::string wf::config::section_t::get_name() const { return this->priv->name; } std::shared_ptr wf::config::section_t::clone_with_name( const std::string name) const { auto result = std::make_shared(name); for (auto& option : priv->options) { result->register_new_option(option.second->clone_option()); } result->priv->xml = this->priv->xml; return result; } std::shared_ptr wf::config::section_t::get_option_or( const std::string& name) { if (this->priv->options.count(name)) { return this->priv->options[name]; } return nullptr; } std::shared_ptr wf::config::section_t::get_option( const std::string& name) { auto option = get_option_or(name); if (!option) { throw std::invalid_argument("Non-existing option " + name + " in config section " + this->get_name()); } return option; } wf::config::section_t::option_list_t wf::config::section_t::get_registered_options() const { option_list_t list; for (auto& option : priv->options) { list.push_back(option.second); } return list; } void wf::config::section_t::register_new_option( std::shared_ptr option) { if (!option) { throw std::invalid_argument( "Cannot add null option to section " + this->get_name()); } this->priv->options[option->get_name()] = option; } void wf::config::section_t::unregister_option( std::shared_ptr option) { if (!option) { return; } auto it = this->priv->options.find(option->get_name()); if ((it != this->priv->options.end()) && (it->second == option)) { this->priv->options.erase(it); } } wf-config-0.8.0/src/types.cpp000066400000000000000000000721511446445615300160570ustar00rootroot00000000000000#include #include #include #include #include #include #include /* --------------------------- Primitive types ------------------------------ */ template<> std::optional wf::option_type::from_string(const std::string& value) { std::string lowercase = value; for (auto& c : lowercase) { c = std::tolower(c); } if ((lowercase == "true") || (lowercase == "1")) { return true; } if ((lowercase == "false") || (lowercase == "0")) { return false; } return {}; } template<> std::optional wf::option_type::from_string(const std::string& value) { std::istringstream in{value}; in.imbue(std::locale::classic()); int result; in >> result; if (value != wf::option_type::to_string(result)) { return {}; } return result; } /** Attempt to parse a string as an double value */ template<> std::optional wf::option_type::from_string(const std::string& value) { std::istringstream in{value}; in.imbue(std::locale::classic()); double result; in >> result; if (!in.eof() || in.fail() || value.empty()) { /* XXX: is the check above enough??? Overflow? Underflow? */ return {}; } return result; } template<> std::optional wf::option_type::from_string(const std::string& value) { return value; } template<> std::string wf::option_type::to_string( const bool& value) { return value ? "true" : "false"; } template<> std::string wf::option_type::to_string( const int& value) { std::ostringstream s; s.imbue(std::locale::classic()); s << value; return s.str(); } template<> std::string wf::option_type::to_string( const double& value) { std::ostringstream s; s.imbue(std::locale::classic()); s << std::fixed << value; return s.str(); } template<> std::string wf::option_type::to_string( const std::string& value) { return value; } /* ----------------------------- wf::color_t -------------------------------- */ wf::color_t::color_t() : color_t(0.0, 0.0, 0.0, 0.0) {} wf::color_t::color_t(double r, double g, double b, double a) { this->r = r; this->g = g; this->b = b; this->a = a; } wf::color_t::color_t(const glm::vec4& value) : color_t(value.r, value.g, value.b, value.a) {} static double hex_to_double(std::string value) { char *dummy; return std::strtol(value.c_str(), &dummy, 16); } static std::optional try_parse_rgba(const std::string& value) { wf::color_t parsed = {0, 0, 0, 0}; std::stringstream ss(value); ss.imbue(std::locale::classic()); bool valid_color = (bool)(ss >> parsed.r >> parsed.g >> parsed.b >> parsed.a); /* Check nothing else after that */ std::string dummy; valid_color &= !(bool)(ss >> dummy); return valid_color ? parsed : std::optional{}; } #include static const std::string hex_digits = "0123456789ABCDEF"; template<> std::optional wf::option_type::from_string( const std::string& param_value) { auto value = param_value; for (auto& ch : value) { ch = std::toupper(ch); } auto as_rgba = try_parse_rgba(value); if (as_rgba) { return as_rgba; } /* Either #RGBA or #RRGGBBAA */ if ((value.size() != 5) && (value.size() != 9)) { return {}; } if (value[0] != '#') { return {}; } if (value.find_first_not_of(hex_digits, 1) != std::string::npos) { return {}; } double r, g, b, a; /* #RRGGBBAA case */ if (value.size() == 9) { r = hex_to_double(value.substr(1, 2)) / 255.0; g = hex_to_double(value.substr(3, 2)) / 255.0; b = hex_to_double(value.substr(5, 2)) / 255.0; a = hex_to_double(value.substr(7, 2)) / 255.0; } else { assert(value.size() == 5); r = hex_to_double(value.substr(1, 1)) / 15.0; g = hex_to_double(value.substr(2, 1)) / 15.0; b = hex_to_double(value.substr(3, 1)) / 15.0; a = hex_to_double(value.substr(4, 1)) / 15.0; } return wf::color_t{r, g, b, a}; } template<> std::string wf::option_type::to_string(const color_t& value) { const int max_byte = 255; const int min_byte = 0; auto to_hex = [=] (double number_d) { int number = std::round(number_d); /* Clamp */ number = std::min(number, max_byte); number = std::max(number, min_byte); std::string result; result += hex_digits[number / 16]; result += hex_digits[number % 16]; return result; }; return "#" + to_hex(value.r * max_byte) + to_hex(value.g * max_byte) + to_hex(value.b * max_byte) + to_hex(value.a * max_byte); } bool wf::color_t::operator ==(const color_t& other) const { constexpr double epsilon = 1e-6; bool equal = true; equal &= std::abs(this->r - other.r) < epsilon; equal &= std::abs(this->g - other.g) < epsilon; equal &= std::abs(this->b - other.b) < epsilon; equal &= std::abs(this->a - other.a) < epsilon; return equal; } /* ------------------------- wf::keybinding_t ------------------------------- */ struct general_binding_t { bool enabled; uint32_t mods; uint32_t value; }; /** * Split @value at non-empty tokens when encountering any of the characters * in @at */ static std::vector split_at(std::string value, std::string at, bool allow_empty = false) { /* Trick: add a delimiter at position 0 and at the end * to avoid special casing */ value = at[0] + value + at[0]; size_t current = 0; std::vector split_positions = {0}; while (current < value.size() - 1) { size_t next_split = value.find_first_of(at, current + 1); split_positions.push_back(next_split); current = next_split; } assert(split_positions.size() >= 2); std::vector tokens; for (size_t i = 1; i < split_positions.size(); i++) { if ((split_positions[i] == split_positions[i - 1] + 1) && !allow_empty) { continue; // skip empty tokens } tokens.push_back(value.substr(split_positions[i - 1] + 1, split_positions[i] - split_positions[i - 1] - 1)); } return tokens; } static std::map modifier_names = { {"ctrl", wf::KEYBOARD_MODIFIER_CTRL}, {"alt", wf::KEYBOARD_MODIFIER_ALT}, {"shift", wf::KEYBOARD_MODIFIER_SHIFT}, {"super", wf::KEYBOARD_MODIFIER_LOGO}, }; static std::string binding_to_string(general_binding_t binding) { std::string result = ""; for (auto& pair : modifier_names) { if (binding.mods & pair.second) { result += "<" + pair.first + "> "; } } if (binding.value > 0) { auto evdev_name = libevdev_event_code_get_name(EV_KEY, binding.value); result += evdev_name ?: "NULL"; } return result; } /** * @return A string which consists of the characters of value, but without * those contained in filter. */ static std::string filter_out(std::string value, std::string filter) { std::string result; for (auto& c : value) { if (filter.find(c) != std::string::npos) { continue; } result += c; } return result; } static const std::string whitespace_chars = " \t\n\r\v\b"; static std::optional parse_binding( std::string binding_description) { /* Handle disabled bindings */ auto binding_descr_no_whitespace = filter_out(binding_description, whitespace_chars); if ((binding_descr_no_whitespace == "none") || (binding_descr_no_whitespace == "disabled")) { return general_binding_t{false, 0, 0}; } /* Strategy: split the binding at modifier begin/end markings and spaces, * and then drop empty tokens. The tokens that are left should be either a * binding or something recognizable by evdev. */ static const std::string delims = "<>" + whitespace_chars; auto tokens = split_at(binding_description, delims); if (tokens.empty()) { return {}; } general_binding_t result = {true, 0, 0}; for (size_t i = 0; i < tokens.size() - 1; i++) { if (modifier_names.count(tokens[i])) { result.mods |= modifier_names[tokens[i]]; } else { return {}; // invalid modifier } } int code = libevdev_event_code_from_name(EV_KEY, tokens.back().c_str()); if (code == -1) { /* Last token might either be yet another modifier (in case of modifier * bindings) or it may be KEY_*. If neither, we have invalid binding */ if (modifier_names.count(tokens.back())) { result.mods |= modifier_names[tokens.back()]; code = 0; } else { return {}; // not found } } result.value = code; /* Do one last check: if we remove whitespaces, and add a whitespace after * each > character, then the resulting string should be almost equal to the * minimal description, generated by binding_to_string(). * * Since we have already checked all identifiers and they are valid * modifiers, it is enough to check just that the lengths are matching. * Note we can't directly compare because the order may be different. */ auto filtered_descr = filter_out(binding_description, whitespace_chars); std::string filtered_with_spaces; for (auto c : filtered_descr) { filtered_with_spaces += c; if (c == '>') { filtered_with_spaces += " "; } } auto minimal_descr = binding_to_string(result); if (filtered_with_spaces.length() == minimal_descr.length()) { return result; } return {}; } wf::keybinding_t::keybinding_t(uint32_t modifier, uint32_t keyval) { this->mod = modifier; this->keyval = keyval; } template<> std::optional wf::option_type::from_string( const std::string& description) { auto parsed_opt = parse_binding(description); if (!parsed_opt) { return {}; } auto parsed = parsed_opt.value(); /* Disallow buttons, because evdev treats buttons and keys the same */ if (parsed.enabled && (parsed.value > 0) && (description.find("KEY") == std::string::npos)) { return {}; } if (parsed.enabled && (parsed.mods == 0) && (parsed.value == 0)) { return {}; } return wf::keybinding_t{parsed.mods, parsed.value}; } template<> std::string wf::option_type::to_string(const wf::keybinding_t& value) { if ((value.get_modifiers() == 0) && (value.get_key() == 0)) { return "none"; } return binding_to_string({true, value.get_modifiers(), value.get_key()}); } bool wf::keybinding_t::operator ==(const keybinding_t& other) const { return this->mod == other.mod && this->keyval == other.keyval; } /** @return The modifiers of the keybinding */ uint32_t wf::keybinding_t::get_modifiers() const { return this->mod; } /** @return The key of the keybinding */ uint32_t wf::keybinding_t::get_key() const { return this->keyval; } /* -------------------------- wf::buttonbinding_t --------------------------- */ wf::buttonbinding_t::buttonbinding_t(uint32_t modifier, uint32_t buttonval) { this->mod = modifier; this->button = buttonval; } template<> std::optional wf::option_type::from_string( const std::string& description) { auto parsed_opt = parse_binding(description); if (!parsed_opt) { return {}; } auto parsed = parsed_opt.value(); if (!parsed.enabled) { return wf::buttonbinding_t{0, 0}; } /* Disallow keys, because evdev treats buttons and keys the same */ if (description.find("BTN") == std::string::npos) { return {}; } if (parsed.value == 0) { return {}; } return wf::buttonbinding_t{parsed.mods, parsed.value}; } template<> std::string wf::option_type::to_string( const wf::buttonbinding_t& value) { if ((value.get_modifiers() == 0) && (value.get_button() == 0)) { return "none"; } return binding_to_string({true, value.get_modifiers(), value.get_button()}); } bool wf::buttonbinding_t::operator ==(const buttonbinding_t& other) const { return this->mod == other.mod && this->button == other.button; } uint32_t wf::buttonbinding_t::get_modifiers() const { return this->mod; } uint32_t wf::buttonbinding_t::get_button() const { return this->button; } wf::touchgesture_t::touchgesture_t(touch_gesture_type_t type, uint32_t direction, int finger_count) { this->type = type; this->direction = direction; this->finger_count = finger_count; } static const std::map touch_gesture_direction_string_map = { {"up", wf::GESTURE_DIRECTION_UP}, {"down", wf::GESTURE_DIRECTION_DOWN}, {"left", wf::GESTURE_DIRECTION_LEFT}, {"right", wf::GESTURE_DIRECTION_RIGHT} }; static wf::touch_gesture_direction_t parse_single_direction( const std::string& direction) { if (touch_gesture_direction_string_map.count(direction)) { return touch_gesture_direction_string_map.at(direction); } throw std::domain_error("invalid swipe direction"); } uint32_t parse_direction(const std::string& direction) { size_t hyphen = direction.find("-"); if (hyphen == std::string::npos) { return parse_single_direction(direction); } else { /* we support up to 2 directions, because >= 3 will be invalid anyway */ auto first = direction.substr(0, hyphen); auto second = direction.substr(hyphen + 1); uint32_t mask = parse_single_direction(first) | parse_single_direction(second); const uint32_t both_horiz = wf::GESTURE_DIRECTION_LEFT | wf::GESTURE_DIRECTION_RIGHT; const uint32_t both_vert = wf::GESTURE_DIRECTION_UP | wf::GESTURE_DIRECTION_DOWN; if (((mask & both_horiz) == both_horiz) || ((mask & both_vert) == both_vert)) { throw std::domain_error("Cannot have two opposing directions in the" "same gesture"); } return mask; } } wf::touchgesture_t parse_gesture(const std::string& value) { if (value.empty()) { return {wf::GESTURE_TYPE_NONE, 0, 0}; } auto tokens = split_at(value, " \t\v\b\n\r"); assert(!tokens.empty()); if (tokens.size() != 3) { return {wf::GESTURE_TYPE_NONE, 0, 0}; } try { wf::touch_gesture_type_t type; uint32_t direction = 0; int32_t finger_count; if (tokens[0] == "pinch") { type = wf::GESTURE_TYPE_PINCH; if (tokens[1] == "in") { direction = wf::GESTURE_DIRECTION_IN; } else if (tokens[1] == "out") { direction = wf::GESTURE_DIRECTION_OUT; } else { throw std::domain_error("Invalid pinch direction: " + tokens[1]); } } else if (tokens[0] == "swipe") { type = wf::GESTURE_TYPE_SWIPE; direction = parse_direction(tokens[1]); } else if (tokens[0] == "edge-swipe") { type = wf::GESTURE_TYPE_EDGE_SWIPE; direction = parse_direction(tokens[1]); } else { throw std::domain_error("Invalid gesture type:" + tokens[0]); } // TODO: instead of atoi, check properly finger_count = std::atoi(tokens[2].c_str()); return wf::touchgesture_t{type, direction, finger_count}; } catch (std::exception& e) { // XXX: show error? // ignore it, will return GESTURE_NONE } return wf::touchgesture_t{wf::GESTURE_TYPE_NONE, 0, 0}; } template<> std::optional wf::option_type::from_string( const std::string& description) { auto as_binding = parse_binding(description); if (as_binding && !as_binding.value().enabled) { return touchgesture_t{GESTURE_TYPE_NONE, 0, 0}; } auto gesture = parse_gesture(description); if (gesture.get_type() == GESTURE_TYPE_NONE) { return {}; } return gesture; } static std::string direction_to_string(uint32_t direction) { std::string result = ""; for (auto& pair : touch_gesture_direction_string_map) { if (direction & pair.second) { result += pair.first + "-"; } } if (result.size() > 0) { /* Remove trailing - */ result.pop_back(); } return result; } template<> std::string wf::option_type::to_string(const touchgesture_t& value) { std::string result; switch (value.get_type()) { case GESTURE_TYPE_NONE: return ""; case GESTURE_TYPE_EDGE_SWIPE: result += "edge-"; // fallthrough case GESTURE_TYPE_SWIPE: result += "swipe "; result += direction_to_string(value.get_direction()) + " "; break; case GESTURE_TYPE_PINCH: result += "pinch "; if (value.get_direction() == GESTURE_DIRECTION_IN) { result += "in "; } if (value.get_direction() == GESTURE_DIRECTION_OUT) { result += "out "; } break; } result += wf::option_type::to_string(value.get_finger_count()); return result; } wf::touch_gesture_type_t wf::touchgesture_t::get_type() const { return this->type; } int wf::touchgesture_t::get_finger_count() const { return this->finger_count; } uint32_t wf::touchgesture_t::get_direction() const { return this->direction; } bool wf::touchgesture_t::operator ==(const touchgesture_t& other) const { return type == other.type && finger_count == other.finger_count && (direction == 0 || other.direction == 0 || direction == other.direction); } /* --------------------------- activatorbinding_t --------------------------- */ struct wf::activatorbinding_t::impl { std::vector keys; std::vector buttons; std::vector gestures; std::vector hotspots; }; wf::activatorbinding_t::activatorbinding_t() { this->priv = std::make_unique(); } wf::activatorbinding_t::~activatorbinding_t() = default; wf::activatorbinding_t::activatorbinding_t(const activatorbinding_t& other) { this->priv = std::make_unique(*other.priv); } wf::activatorbinding_t& wf::activatorbinding_t::operator =( const activatorbinding_t& other) { if (&other != this) { this->priv = std::make_unique(*other.priv); } return *this; } template bool try_add_binding( std::vector& to, const std::string& value) { auto binding = wf::option_type::from_string(value); if (binding) { to.push_back(binding.value()); return true; } return false; } template<> std::optional wf::option_type::from_string( const std::string& string) { activatorbinding_t binding; if (filter_out(string, whitespace_chars) == "") { return binding; // empty binding } auto tokens = split_at(string, "|", true); for (auto& token : tokens) { bool is_valid_binding = try_add_binding(binding.priv->keys, token) || try_add_binding(binding.priv->buttons, token) || try_add_binding(binding.priv->gestures, token) || try_add_binding(binding.priv->hotspots, token); if (!is_valid_binding) { return {}; } } return binding; } template static std::string concatenate_bindings(const std::vector& bindings) { std::string repr = ""; for (auto& b : bindings) { repr += wf::option_type::to_string(b); repr += " | "; } return repr; } template<> std::string wf::option_type::to_string( const activatorbinding_t& value) { std::string repr = concatenate_bindings(value.priv->keys) + concatenate_bindings(value.priv->buttons) + concatenate_bindings(value.priv->gestures) + concatenate_bindings(value.priv->hotspots); /* Remove trailing " | " */ if (repr.size() >= 3) { repr.erase(repr.size() - 3); } return repr; } template bool find_in_container(const std::vector& haystack, Type needle) { return std::find(haystack.begin(), haystack.end(), needle) != haystack.end(); } bool wf::activatorbinding_t::has_match(const keybinding_t& key) const { return find_in_container(priv->keys, key); } bool wf::activatorbinding_t::has_match(const buttonbinding_t& button) const { return find_in_container(priv->buttons, button); } bool wf::activatorbinding_t::has_match(const touchgesture_t& gesture) const { return find_in_container(priv->gestures, gesture); } bool wf::activatorbinding_t::operator ==(const activatorbinding_t& other) const { return priv->keys == other.priv->keys && priv->buttons == other.priv->buttons && priv->gestures == other.priv->gestures && priv->hotspots == other.priv->hotspots; } const std::vector& wf::activatorbinding_t::get_hotspots() const { return priv->hotspots; } wf::hotspot_binding_t::hotspot_binding_t(uint32_t edges, int32_t along_edge, int32_t away_from_edge, int32_t timeout) { this->edges = edges; this->along = along_edge; this->away = away_from_edge; this->timeout = timeout; } bool wf::hotspot_binding_t::operator ==(const hotspot_binding_t& other) const { return edges == other.edges && along == other.along && away == other.away && timeout == other.timeout; } int32_t wf::hotspot_binding_t::get_timeout() const { return timeout; } int32_t wf::hotspot_binding_t::get_size_away_from_edge() const { return away; } int32_t wf::hotspot_binding_t::get_size_along_edge() const { return along; } uint32_t wf::hotspot_binding_t::get_edges() const { return edges; } static std::map hotspot_edges = { {"top", wf::OUTPUT_EDGE_TOP}, {"bottom", wf::OUTPUT_EDGE_BOTTOM}, {"left", wf::OUTPUT_EDGE_LEFT}, {"right", wf::OUTPUT_EDGE_RIGHT}, }; template<> std::optional wf::option_type::from_string( const std::string& description) { std::istringstream stream{description}; std::string token; stream >> token; // "hotspot" if (token != "hotspot") { return {}; } stream >> token; // direction uint32_t edges = 0; size_t hyphen = token.find("-"); if (hyphen == token.npos) { if (hotspot_edges.count(token) == 0) { return {}; } edges = hotspot_edges[token]; } else { std::string first_direction = token.substr(0, hyphen); std::string second_direction = token.substr(hyphen + 1); if ((hotspot_edges.count(first_direction) == 0) || (hotspot_edges.count(second_direction) == 0)) { return {}; } edges = hotspot_edges[first_direction] | hotspot_edges[second_direction]; } stream >> token; int32_t along, away; if (2 != sscanf(token.c_str(), "%dx%d", &along, &away)) { return {}; } stream >> token; auto timeout = wf::option_type::from_string(token); if (!timeout || stream >> token) // check for trailing characters { return {}; } return wf::hotspot_binding_t(edges, along, away, timeout.value()); } template<> std::string wf::option_type::to_string( const wf::hotspot_binding_t& value) { std::ostringstream out; out << "hotspot "; uint32_t remaining_edges = value.get_edges(); const auto& find_edge = [&] (bool need_hyphen) { for (const auto& edge : hotspot_edges) { if (remaining_edges & edge.second) { remaining_edges &= ~edge.second; if (need_hyphen) { out << "-"; } out << edge.first; break; } } }; find_edge(false); find_edge(true); out << " " << value.get_size_along_edge() << "x" << value.get_size_away_from_edge() << " " << value.get_timeout(); return out.str(); } /* ------------------------- Output config types ---------------------------- */ wf::output_config::mode_t::mode_t(bool auto_on) { this->type = auto_on ? MODE_AUTO : MODE_OFF; } wf::output_config::mode_t::mode_t(int32_t width, int32_t height, int32_t refresh) { this->type = MODE_RESOLUTION; this->width = width; this->height = height; this->refresh = refresh; } /** * Initialize a mirror mode. */ wf::output_config::mode_t::mode_t(const std::string& mirror_from) { this->type = MODE_MIRROR; this->mirror_from = mirror_from; } /** @return The type of this mode. */ wf::output_config::mode_type_t wf::output_config::mode_t::get_type() const { return type; } int32_t wf::output_config::mode_t::get_width() const { return width; } int32_t wf::output_config::mode_t::get_height() const { return height; } int32_t wf::output_config::mode_t::get_refresh() const { return refresh; } std::string wf::output_config::mode_t::get_mirror_from() const { return mirror_from; } bool wf::output_config::mode_t::operator ==(const mode_t& other) const { if (type != other.get_type()) { return false; } switch (type) { case MODE_RESOLUTION: return width == other.width && height == other.height && refresh == other.refresh; case MODE_MIRROR: return mirror_from == other.mirror_from; case MODE_AUTO: case MODE_OFF: return true; } return false; } template<> std::optional wf::option_type::from_string( const std::string& string) { if (string == "off") { return wf::output_config::mode_t{false}; } if ((string == "auto") || (string == "default")) { return wf::output_config::mode_t{true}; } if (string.substr(0, 6) == "mirror") { std::stringstream ss(string); std::string from, dummy; ss >> from; // the mirror word if (!(ss >> from)) { return {}; } // trailing garbage if (ss >> dummy) { return {}; } return wf::output_config::mode_t{from}; } int w, h, rr = 0; char next; int read = std::sscanf(string.c_str(), "%d x %d @ %d%c", &w, &h, &rr, &next); if ((read < 2) || (read > 3)) { return {}; } if ((w < 0) || (h < 0) || (rr < 0)) { return {}; } // Ensure refresh rate in mHz if (rr < 1000) { rr *= 1000; } return wf::output_config::mode_t{w, h, rr}; } /** Represent the activator binding as a string. */ template<> std::string wf::option_type::to_string(const output_config::mode_t& value) { switch (value.get_type()) { case output_config::MODE_AUTO: return "auto"; case output_config::MODE_OFF: return "off"; case output_config::MODE_RESOLUTION: if (value.get_refresh() <= 0) { return to_string(value.get_width()) + "x" + to_string(value.get_height()); } else { return to_string(value.get_width()) + "x" + to_string(value.get_height()) + "@" + to_string( value.get_refresh()); } case output_config::MODE_MIRROR: return "mirror " + value.get_mirror_from(); } return {}; } wf::output_config::position_t::position_t() { this->automatic = true; } wf::output_config::position_t::position_t(int32_t x, int32_t y) { this->automatic = false; this->x = x; this->y = y; } int32_t wf::output_config::position_t::get_x() const { return x; } int32_t wf::output_config::position_t::get_y() const { return y; } bool wf::output_config::position_t::is_automatic_position() const { return automatic; } bool wf::output_config::position_t::operator ==(const position_t& other) const { if (is_automatic_position() != other.is_automatic_position()) { return false; } if (is_automatic_position()) { return true; } return x == other.x && y == other.y; } template<> std::optional wf::option_type::from_string( const std::string& string) { if ((string == "auto") || (string == "default")) { return wf::output_config::position_t(); } int x, y; char r; if (sscanf(string.c_str(), "%d , %d%c", &x, &y, &r) != 2) { return {}; } return wf::output_config::position_t(x, y); } /** Represent the activator binding as a string. */ template<> std::string wf::option_type::to_string(const output_config::position_t& value) { if (value.is_automatic_position()) { return "auto"; } return to_string(value.get_x()) + ", " + to_string(value.get_y()); } wf-config-0.8.0/src/xml.cpp000066400000000000000000000273061446445615300155150ustar00rootroot00000000000000#include #include #include #include #include #include "section-impl.hpp" #include "option-impl.hpp" static std::optional extract_value(xmlNodePtr node, std::string value_name) { std::optional value_ptr; auto child_ptr = node->children; while (child_ptr != nullptr) { if ((child_ptr->type == XML_ELEMENT_NODE) && (std::string((const char*)child_ptr->name) == value_name)) { auto child_child_ptr = child_ptr->children; if (child_child_ptr == nullptr) { value_ptr = (const xmlChar*)""; } else if ((child_child_ptr->next == nullptr) && (child_child_ptr->type == XML_TEXT_NODE)) { value_ptr = child_child_ptr->content; } } child_ptr = child_ptr->next; } return value_ptr; } /** * Create a new option of type T with the given name and default value. * @return The new option, or nullptr if the default value is invaild. */ template std::shared_ptr> create_option(std::string name, std::string default_value) { auto value = wf::option_type::from_string(default_value); if (!value) { return {}; } return std::make_shared>(name, value.value()); } enum bounds_error_t { BOUNDS_INVALID_MINIMUM, BOUNDS_INVALID_MAXIMUM, BOUNDS_OK, }; template bounds_error_t set_bounds( std::shared_ptr& option, std::optional min_ptr, std::optional max_ptr) { if (!option) { return BOUNDS_OK; // there has been an earlier error } auto typed_option = std::dynamic_pointer_cast>(option); assert(typed_option); if (min_ptr) { auto value = wf::option_type::from_string( (const char*)min_ptr.value()); if (value) { typed_option->set_minimum(value.value()); } else { return BOUNDS_INVALID_MINIMUM; } } if (max_ptr) { std::optional value = wf::option_type::from_string( (const char*)max_ptr.value()); if (value) { typed_option->set_maximum(value.value()); } else { return BOUNDS_INVALID_MAXIMUM; } } return BOUNDS_OK; } #define GET_XML_PROP_OR_BAIL(node, name, str) \ const char *name ## _ptr = (const char*)xmlGetProp(node, (const xmlChar*)(str)); \ if (!name ## _ptr) \ { \ LOGE("Could not parse ", (node)->doc->URL, \ ": XML node at line ", node->line, " is missing \"" #name "\" attribute."); \ return nullptr; \ } \ std::string name = name ## _ptr; #define GET_OPTIONAL_XML_PROP(node, name, str) \ const char *name ## _ptr = (const char*)xmlGetProp(node, (const xmlChar*)(str)); \ if (!name ## _ptr) \ { \ name ## _ptr = ""; \ } \ std::string name = name ## _ptr; template using entry_t = wf::config::compound_option_entry_t; std::shared_ptr parse_compound_option(xmlNodePtr node, const std::string& name) { wf::config::compound_option_t::entries_t entries; GET_OPTIONAL_XML_PROP(node, type_hint, "type-hint"); if (type_hint.empty()) { type_hint = "dict"; } node = node->children; while (node) { if ((node->type == XML_ELEMENT_NODE) && ((const char*)node->name == std::string{"entry"})) { // Found next item GET_XML_PROP_OR_BAIL(node, prefix, "prefix"); GET_XML_PROP_OR_BAIL(node, type, "type"); GET_OPTIONAL_XML_PROP(node, name, "name"); std::optional default_value = std::nullopt; if (const auto& default_value_raw = extract_value(node, "default")) { default_value = (const char*)default_value_raw.value(); } if (type == "int") { entries.push_back(std::make_unique>(prefix, name, default_value)); } else if (type == "double") { entries.push_back(std::make_unique>(prefix, name, default_value)); } else if (type == "bool") { entries.push_back(std::make_unique>(prefix, name, default_value)); } else if (type == "string") { entries.push_back(std::make_unique>(prefix, name, default_value)); } else if (type == "key") { entries.push_back(std::make_unique>(prefix, name, default_value)); } else if (type == "button") { entries.push_back(std::make_unique>( prefix, name, default_value)); } else if (type == "gesture") { entries.push_back(std::make_unique>( prefix, name, default_value)); } else if (type == "color") { entries.push_back(std::make_unique>(prefix, name, default_value)); } else if (type == "activator") { entries.push_back(std::make_unique>( prefix, name, default_value)); } else { LOGE("Could not parse ", node->doc->URL, ": option at line ", node->line, " has invalid type \"", type, "\""); return nullptr; } } node = node->next; } auto opt = new wf::config::compound_option_t{name, std::move(entries), type_hint}; return std::shared_ptr(opt); } std::shared_ptr wf::config::xml::create_option_from_xml_node(xmlNodePtr node) { if ((node->type != XML_ELEMENT_NODE) || ((const char*)node->name != std::string{"option"})) { LOGE("Could not parse ", node->doc->URL, ": line ", node->line, " is not an option element."); return nullptr; } GET_XML_PROP_OR_BAIL(node, name, "name"); GET_XML_PROP_OR_BAIL(node, type, "type"); if (type == "dynamic-list") { auto option = parse_compound_option(node, name); if (option) { option->priv->xml = node; } return option; } auto default_value_ptr = extract_value(node, "default"); if (!default_value_ptr) { LOGE("Could not parse ", node->doc->URL, ": option at line ", node->line, " has no default value specified."); return nullptr; } std::string default_value = (const char*)default_value_ptr.value(); auto min_value_ptr = extract_value(node, "min"); auto max_value_ptr = extract_value(node, "max"); std::shared_ptr option; bounds_error_t bounds_error = BOUNDS_OK; if (type == "int") { option = create_option(name, default_value); bounds_error = set_bounds(option, min_value_ptr, max_value_ptr); } else if (type == "double") { option = create_option(name, default_value); bounds_error = set_bounds(option, min_value_ptr, max_value_ptr); } else if (type == "bool") { option = create_option(name, default_value); } else if (type == "string") { option = create_option(name, default_value); } else if (type == "key") { option = create_option(name, default_value); } else if (type == "button") { option = create_option(name, default_value); } else if (type == "gesture") { option = create_option(name, default_value); } else if (type == "color") { option = create_option(name, default_value); } else if (type == "activator") { option = create_option(name, default_value); } else if (type == "output::mode") { option = create_option(name, default_value); } else if (type == "output::position") { option = create_option(name, default_value); } else { LOGE("Could not parse ", node->doc->URL, ": option at line ", node->line, " has invalid type \"", type, "\""); return nullptr; } if (!option) { /* This can only happen if default value was invalid */ LOGE("Could not parse ", node->doc->URL, ": option at line ", node->line, " has invalid default value \"", default_value, "\" for type ", type); return nullptr; } switch (bounds_error) { case BOUNDS_INVALID_MINIMUM: assert(min_value_ptr); LOGE("Could not parse ", node->doc->URL, ": option at line ", node->line, " has invalid minimum value \"", min_value_ptr.value(), "\"", "for type ", type); return nullptr; case BOUNDS_INVALID_MAXIMUM: assert(max_value_ptr); LOGE("Could not parse ", node->doc->URL, ": option at line ", node->line, " has invalid maximum value \"", max_value_ptr.value(), "\"", "for type ", type); return nullptr; default: break; } option->priv->xml = node; return option; } static void recursively_parse_section_node(xmlNodePtr node, std::shared_ptr section) { auto child_ptr = node->children; while (child_ptr != nullptr) { if ((child_ptr->type == XML_ELEMENT_NODE) && (std::string((const char*)child_ptr->name) == "option")) { auto option = wf::config::xml::create_option_from_xml_node( child_ptr); if (option) { section->register_new_option(option); } } if ((child_ptr->type == XML_ELEMENT_NODE) && (std::string((const char*)child_ptr->name) == "group")) { recursively_parse_section_node(child_ptr, section); } if ((child_ptr->type == XML_ELEMENT_NODE) && (std::string((const char*)child_ptr->name) == "subgroup")) { recursively_parse_section_node(child_ptr, section); } child_ptr = child_ptr->next; } } std::shared_ptr wf::config::xml::create_section_from_xml_node( xmlNodePtr node) { if ((node->type != XML_ELEMENT_NODE) || (((const char*)node->name != std::string{"plugin"}) && ((const char*)node->name != std::string{"object"}))) { LOGE("Could not parse ", node->doc->URL, ": line ", node->line, " is not a plugin/object element."); return nullptr; } GET_XML_PROP_OR_BAIL(node, name, "name"); auto section = std::make_shared(name); section->priv->xml = node; recursively_parse_section_node(node, section); return section; } xmlNodePtr wf::config::xml::get_option_xml_node( std::shared_ptr option) { return option->priv->xml; } xmlNodePtr wf::config::xml::get_section_xml_node( std::shared_ptr section) { return section->priv->xml; } wf-config-0.8.0/test/000077500000000000000000000000001446445615300143715ustar00rootroot00000000000000wf-config-0.8.0/test/config_lock.ini000066400000000000000000000000661446445615300173510ustar00rootroot00000000000000 [section1] option1 = 12 [section2] option2 = opt2 wf-config-0.8.0/test/config_manager_test.cpp000066400000000000000000000063171446445615300211020ustar00rootroot00000000000000#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include #include #include TEST_CASE("wf::config::config_manager_t") { using namespace wf; using namespace wf::config; using namespace option_type; config_manager_t config{}; auto expect_sections = [&] (std::set names) { auto all = config.get_all_sections(); CHECK(all.size() == names.size()); std::set present; std::transform(all.begin(), all.end(), std::inserter(present, present.end()), [] (const auto& section) { return section->get_name(); }); CHECK(present == names); }; expect_sections({}); CHECK(config.get_option("no_such_option") == nullptr); CHECK(config.get_option("section/nonexistent") == nullptr); CHECK(config.get_option("section/nonexist/ent") == nullptr); CHECK(config.get_section("FirstSection") == nullptr); config.merge_section(std::make_shared("FirstSection")); expect_sections({"FirstSection"}); auto section = config.get_section("FirstSection"); REQUIRE(section != nullptr); CHECK(section->get_name() == "FirstSection"); CHECK(section->get_registered_options().empty()); CHECK(config.get_option("FirstSection/FirstOption") == nullptr); auto color = option_type::from_string("#FFFF").value(); auto option = std::make_shared>("ColorOption", color); section->register_new_option(option); CHECK(config.get_option( "FirstSection/ColorOption")->get_value() == color); CHECK(config.get_option("FirstSection/ColorOption") == option); auto section2 = config.get_section("SecondSection"); CHECK(section2 == nullptr); auto section_overwrite = std::make_shared("FirstSection"); section_overwrite->register_new_option( std::make_shared>( "ColorOption", from_string("#CCCC").value())); section_overwrite->register_new_option( std::make_shared>("IntOption", 5)); section2 = std::make_shared("SecondSection"); section2->register_new_option( std::make_shared>("IntOption", 6)); config.merge_section(section_overwrite); CHECK(config.get_section("FirstSection") == section); // do not overwrite expect_sections({"FirstSection"}); auto stored_color_opt = config.get_option("FirstSection/ColorOption"); REQUIRE(stored_color_opt != nullptr); CHECK(stored_color_opt->get_value_str() == "#CCCCCCCC"); auto stored_int_opt = config.get_option("FirstSection/IntOption"); REQUIRE(stored_int_opt); CHECK(stored_int_opt->get_value_str() == "5"); config.merge_section(section2); expect_sections({"FirstSection", "SecondSection"}); stored_int_opt = config.get_option("FirstSection/IntOption"); REQUIRE(stored_int_opt); CHECK(stored_int_opt->get_value_str() == "5"); // remains same stored_int_opt = config.get_option("SecondSection/IntOption"); REQUIRE(stored_int_opt); CHECK(stored_int_opt->get_value_str() == "6"); } wf-config-0.8.0/test/dummy.ini000066400000000000000000000001521446445615300162230ustar00rootroot00000000000000[section1] option1 = 4 option2 = 45 \# 46 \\ [section2] bey_k1 = 1.200000 hey_k1 = 1 option1 = 4.250000 wf-config-0.8.0/test/duration_test.cpp000066400000000000000000000113311446445615300177600ustar00rootroot00000000000000#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include #include #include using namespace wf; using namespace wf::config; using namespace wf::animation; TEST_CASE("wf::animation::duration_t") { auto length = std::make_shared>("length", 100); duration_t duration{length, smoothing::linear}; auto check_lifetime = [&] () { CHECK(duration.running() == false); CHECK(duration.progress() == doctest::Approx{1.0}); duration.start(); CHECK(duration.running()); CHECK(duration.progress() == doctest::Approx{0.0}); usleep(50000); CHECK(duration.progress() == doctest::Approx{0.5}.epsilon(0.1)); CHECK(duration.running()); CHECK(duration.running()); usleep(60000); /* At this point, duration must be finished */ CHECK(duration.progress() == doctest::Approx{1.0}.epsilon(0.01)); CHECK(duration.running()); // one last time CHECK(duration.running() == false); CHECK(duration.running() == false); }; /* Check twice, so that we can test restarting */ check_lifetime(); check_lifetime(); auto check_reverse_duration = [&] () { auto direction = duration.get_direction(); CHECK(duration.running() == false); CHECK(duration.progress() == doctest::Approx{direction ? 1.0 : 0.0}); duration.start(); CHECK(duration.running()); CHECK(duration.progress() == doctest::Approx{direction ? 0.0 : 1.0}); usleep(75000); CHECK(duration.progress() == doctest::Approx{direction ? 0.75 : 0.25}.epsilon( 0.1)); duration.reverse(); usleep(50000); CHECK(duration.progress() == doctest::Approx{direction ? 0.25 : 0.75}.epsilon( 0.1)); CHECK(duration.running()); CHECK(duration.running()); usleep(35000); /* At this point, duration must be finished */ CHECK(duration.progress() == doctest::Approx{direction ? 0.0 : 1.0}.epsilon(0.01)); CHECK(duration.running()); // one last time CHECK(duration.running() == false); CHECK(duration.running() == false); }; /* Check twice, so that we can test direction */ check_reverse_duration(); check_reverse_duration(); } TEST_CASE("wf::animation::timed_transition_t") { const double start = 1.0; const double end = 2.0; const double overend = 3.0; const double middle = (start + end) / 2.0; auto length = std::make_shared>("length", 100); duration_t duration{length, smoothing::linear}; timed_transition_t transition{duration}; timed_transition_t transition2{duration, start, end}; transition.set(start, end); CHECK(transition.start == doctest::Approx(start)); CHECK(transition.end == doctest::Approx(end)); duration.start(); CHECK((double)transition == doctest::Approx(start)); usleep(50000); CHECK((double)transition == doctest::Approx(middle).epsilon(0.1)); CHECK((double)transition2 == doctest::Approx(middle).epsilon(0.1)); transition.restart_with_end(overend); transition2.restart_same_end(); CHECK(transition.start == doctest::Approx(middle).epsilon(0.1)); CHECK(transition2.start == doctest::Approx(middle).epsilon(0.1)); CHECK(transition.end == doctest::Approx(overend)); CHECK(transition2.end == doctest::Approx(end)); usleep(60000); CHECK((double)transition == doctest::Approx(overend).epsilon(0.1)); transition.flip(); CHECK(transition.start == doctest::Approx(3.0).epsilon(0.1)); CHECK(transition.end == doctest::Approx(middle).epsilon(0.1)); } TEST_CASE("wf::animation::simple_animation_t") { auto length = std::make_shared>("length", 10); simple_animation_t anim{length, smoothing::linear}; auto cycle_through = [&] (double s, double e, bool x1, bool x2) { if (!x1 && !x2) { anim.animate(s, e); } else if (!x2) { anim.animate(e); } else if (!x1) { anim.animate(); } CHECK(anim.running()); CHECK((double)anim == doctest::Approx(s)); usleep(5000); CHECK((double)anim == doctest::Approx((s + e) / 2).epsilon(0.1)); CHECK(anim.running()); usleep(5500); CHECK((double)anim == doctest::Approx(e)); CHECK(anim.running()); CHECK(!anim.running()); }; cycle_through(1, 2, false, false); cycle_through(2, 3, true, false); cycle_through(3, 3, false, true); simple_animation_t sa; sa = simple_animation_t{length}; sa.animate(1, 2); CHECK((double)sa == doctest::Approx(1.0)); } wf-config-0.8.0/test/expect_line.hpp000066400000000000000000000007721446445615300174070ustar00rootroot00000000000000auto EXPECT_LINE = [] (std::istream& log, std::string expect) { auto tolower = [] (std::string s) { for (auto& c : s) { c = std::tolower(c); } return s; }; bool found = false; std::string line; while (!found && std::getline(log, line)) { /* Case-insensitive matching */ line = tolower(line); expect = tolower(expect); found |= (line.find(expect) != std::string::npos); } CHECK(found); }; wf-config-0.8.0/test/file_test.cpp000066400000000000000000000307471446445615300170660ustar00rootroot00000000000000#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include #include #include #include #include #include #include #include #include #include "wayfire/config/compound-option.hpp" #include "../src/option-impl.hpp" const std::string contents = R"( illegal_option = value [section1] option1 = value1 option2=3 #Comment option3 = value value value # Comment hey_a = 15 bey_a = 1.2 [section2] option1 = value 4 \ value # Ignore option2 = value \\ # Ignore option3 = \#No way [wrongsection option1 )"; #include "expect_line.hpp" TEST_CASE("wf::config::load_configuration_options_from_string") { std::stringstream log; wf::log::initialize_logging(log, wf::log::LOG_LEVEL_DEBUG, wf::log::LOG_COLOR_MODE_OFF); using wf::config::compound_option_t; using wf::config::compound_option_entry_t; compound_option_t::entries_t entries; entries.push_back(std::make_unique>("hey_")); entries.push_back(std::make_unique>("bey_")); auto opt = new compound_option_t{"option_list", std::move(entries)}; using namespace wf; using namespace wf::config; config_manager_t config; /* Create the first section and add an option there */ auto section = std::make_shared("section1"); section->register_new_option( std::make_shared>("option1", 10)); section->register_new_option( std::make_shared>("option2", 5)); section->register_new_option( std::make_shared>("option4", std::string("option4"))); section->register_new_option(std::shared_ptr(opt)); config.merge_section(section); load_configuration_options_from_string(config, contents, "test"); REQUIRE(config.get_section("section1")); REQUIRE(config.get_section("section2")); CHECK(config.get_section("wrongsection") == nullptr); auto s1 = config.get_section("section1"); auto s2 = config.get_section("section2"); CHECK(s1->get_option("option1")->get_value_str() == "10"); CHECK(s1->get_option("option2")->get_value_str() == "3"); CHECK(s1->get_option("option3")->get_value_str() == "value value value"); CHECK(s1->get_option("option4")->get_value_str() == "option4"); CHECK(s2->get_option("option1")->get_value_str() == "value 4 value"); CHECK(s2->get_option("option2")->get_value_str() == "value \\"); CHECK(s2->get_option("option3")->get_value_str() == "#No way"); CHECK(!s2->get_option_or("Ignored")); CHECK(opt->get_value().size() == 1); EXPECT_LINE(log, "Error in file test:2"); EXPECT_LINE(log, "Error in file test:5"); EXPECT_LINE(log, "Error in file test:20"); EXPECT_LINE(log, "Error in file test:21"); } TEST_CASE("wf::config::load_configuration_options_from_string - " "ignore compound entries not in string") { using namespace wf; using namespace wf::config; // Compound option with prefix `test_` compound_option_t::entries_t entries; entries.push_back(std::make_unique>("test_")); auto opt = new compound_option_t{"option_list", std::move(entries)}; config_manager_t config; // Register compound option auto section = std::make_shared("section"); section->register_new_option(std::shared_ptr(opt)); // Register an option which does not come from the config file section->register_new_option( std::make_shared>("test_nofile", "")); config.merge_section(section); // Update with an emtpy string. The compoud option should remain empty, because // there are no values from the config file. load_configuration_options_from_string(config, ""); CHECK(opt->get_value_untyped().empty()); } const std::string minimal_config_with_opt = R"( [section] option = value )"; TEST_CASE("wf::config::load_configuration_options_from_string - lock & reload") { using namespace wf; using namespace wf::config; config_manager_t cfg; load_configuration_options_from_string(cfg, minimal_config_with_opt); SUBCASE("locked") { cfg.get_option("section/option")->set_locked(); load_configuration_options_from_string(cfg, ""); CHECK(cfg.get_option("section/option")->get_value_str() == "value"); } SUBCASE("unlocked") { load_configuration_options_from_string(cfg, ""); CHECK(cfg.get_option("section/option")->get_value_str() == ""); } } wf::config::config_manager_t build_simple_config() { using namespace wf; using namespace wf::config; auto section1 = std::make_shared("section1"); auto section2 = std::make_shared("section2"); section1->register_new_option(std::make_shared>("option1", 4)); section1->register_new_option(std::make_shared>("option2", std::string("45 # 46 \\"))); section2->register_new_option(std::make_shared>("option1", 4.25)); compound_option_t::entries_t entries; entries.push_back(std::make_unique>("hey_")); entries.push_back(std::make_unique>("bey_")); auto opt = new compound_option_t{"option_list", std::move(entries)}; opt->set_value(compound_list_t{{"k1", 1, 1.2}}); section2->register_new_option(std::shared_ptr(opt)); config_manager_t config; config.merge_section(section1); config.merge_section(section2); return config; } std::string simple_config_source = R"([section1] option1 = 4 option2 = 45 \# 46 \\ [section2] bey_k1 = 1.200000 hey_k1 = 1 option1 = 4.250000 )"; TEST_CASE("wf::config::save_configuration_options_to_string") { auto config = build_simple_config(); auto stringified = save_configuration_options_to_string(config); CHECK(stringified == simple_config_source); } TEST_CASE("wf::config::save_configuration_options_to_string - compound options erase") { using namespace wf; using namespace wf::config; compound_option_t::entries_t entries; entries.push_back(std::make_unique>("hey_")); entries.push_back(std::make_unique>("bey_")); auto opt = new compound_option_t{"option_list", std::move(entries)}; opt->set_value(compound_list_t{{"k1", 1, 1.2}}); auto section = std::make_shared("Section"); section->register_new_option(std::shared_ptr(opt)); // Add the same entries as in the compound option section->register_new_option(std::make_shared>("hey_k1", "1")); section->register_new_option(std::make_shared>("bey_k1", "1.2")); // However, make sure that XML-created options are saved even if they match // the prefix of a compound option. auto special_opt = std::make_shared>("hey_you", 1); special_opt->priv->xml = (xmlNode*)0x123; section->register_new_option(special_opt); config_manager_t cfg; cfg.merge_section(section); // Now, clear the value from the compound option opt->set_value(compound_list_t{}); auto str = save_configuration_options_to_string(cfg); // We expect that after deleting the values from the compound option, // the values for k1 are not saved to the string. const std::string expected = R"([Section] hey_you = 1 )"; CHECK(str == expected); } TEST_CASE("wf::config::load_configuration_options_from_file - no such file") { std::string test_config = std::string("FileDoesNotExist"); wf::config::config_manager_t manager; CHECK(!load_configuration_options_from_file(manager, test_config)); } TEST_CASE("wf::config::load_configuration_options_from_file - locking fails") { std::string test_config = std::string("../test/config_lock.ini"); const int delay = 100e3; /** 100ms */ int pid = fork(); if (pid == 0) { /* Lock config file before parent tries to lock it */ int fd = open(test_config.c_str(), O_RDWR); flock(fd, LOCK_EX); /* Obtained a lock. Now wait until parent tries to lock */ usleep(2 * delay); /* By now, parent should have failed. */ flock(fd, LOCK_UN); close(fd); } /* Wait for other process to lock the file */ usleep(delay); wf::config::config_manager_t manager; CHECK(!load_configuration_options_from_file(manager, test_config)); } void check_int_test_config(const wf::config::config_manager_t& manager, std::string value_opt1 = "12") { auto s1 = manager.get_section("section1"); auto s2 = manager.get_section("section2"); REQUIRE(s1 != nullptr); REQUIRE(s2 != nullptr); auto o1 = manager.get_option("section1/option1"); auto o2 = manager.get_option("section2/option2"); auto o3 = manager.get_option("section2/option3"); REQUIRE(o1); REQUIRE(o2); REQUIRE(o3); CHECK(o1->get_value_str() == value_opt1); CHECK(o2->get_value_str() == "opt2"); CHECK(o3->get_value_str() == "DoesNotExistInXML # \\"); } TEST_CASE("wf::config::load_configuration_options_from_file - success") { std::string test_config = std::string(TEST_SOURCE "/int_test/config.ini"); /* Init with one section */ wf::config::config_manager_t manager; auto s = std::make_shared("section1"); s->register_new_option( std::make_shared>("option1", 1)); manager.merge_section(s); CHECK(load_configuration_options_from_file(manager, test_config)); REQUIRE(manager.get_section("section1") == s); check_int_test_config(manager); } TEST_CASE("wf::config::save_configuration_to_file - success") { std::string test_config = std::string(TEST_SOURCE "/dummy.ini"); { std::ofstream clr(test_config, std::ios::trunc | std::ios::ate); clr << "Dummy"; } wf::config::save_configuration_to_file(build_simple_config(), test_config); /* Read file contents */ std::ifstream infile(test_config); std::string file_contents((std::istreambuf_iterator(infile)), std::istreambuf_iterator()); CHECK(file_contents == simple_config_source); /* Check lock is released */ int fd = open(test_config.c_str(), O_RDWR); CHECK(flock(fd, LOCK_EX | LOCK_NB) == 0); flock(fd, LOCK_UN); close(fd); } TEST_CASE("wf::config::build_configuration") { wf::log::initialize_logging(std::cout, wf::log::LOG_LEVEL_DEBUG, wf::log::LOG_COLOR_MODE_ON); std::string xmldir = std::string(TEST_SOURCE "/int_test/xml"); std::string sysconf = std::string(TEST_SOURCE "/int_test/sys.ini"); std::string userconf = std::string(TEST_SOURCE "/int_test/config.ini"); std::vector xmldirs(1, xmldir); auto config = wf::config::build_configuration(xmldirs, sysconf, userconf); check_int_test_config(config, "10"); auto o1 = config.get_option("section1/option1"); auto o2 = config.get_option("section2/option2"); auto o3 = config.get_option("section2/option3"); auto o4 = config.get_option("section2/option4"); auto o5 = config.get_option("section2/option5"); auto o6 = config.get_option("sectionobj:objtest/option6"); REQUIRE(o4); REQUIRE(o5); using namespace wf; using namespace wf::config; CHECK(std::dynamic_pointer_cast>(o1) != nullptr); CHECK(std::dynamic_pointer_cast>(o2) != nullptr); CHECK(std::dynamic_pointer_cast>(o3) != nullptr); CHECK(std::dynamic_pointer_cast>(o4) != nullptr); CHECK(std::dynamic_pointer_cast>(o5) != nullptr); CHECK(std::dynamic_pointer_cast>(o6) != nullptr); CHECK(o4->get_value_str() == "DoesNotExistInConfig"); CHECK(o5->get_value_str() == "Option5Sys"); CHECK(o6->get_value_str() == "10"); // bounds from xml applied o1->reset_to_default(); o2->reset_to_default(); o3->reset_to_default(); o4->reset_to_default(); o5->reset_to_default(); o6->reset_to_default(); CHECK(o1->get_value_str() == "4"); CHECK(o2->get_value_str() == "XMLDefault"); CHECK(o3->get_value_str() == ""); CHECK(o4->get_value_str() == "DoesNotExistInConfig"); CHECK(o5->get_value_str() == "Option5Sys"); CHECK(o6->get_value_str() == "1"); } wf-config-0.8.0/test/int_test/000077500000000000000000000000001446445615300162225ustar00rootroot00000000000000wf-config-0.8.0/test/int_test/config.ini000066400000000000000000000001741446445615300201720ustar00rootroot00000000000000 [section1] option1 = 12 [section2] option2 = opt2 option3 = DoesNotExistInXML \# \\ [sectionobj:objtest] option6 = 11 wf-config-0.8.0/test/int_test/sys.ini000066400000000000000000000000401446445615300175330ustar00rootroot00000000000000[section2] option5 = Option5Sys wf-config-0.8.0/test/int_test/xml/000077500000000000000000000000001446445615300170225ustar00rootroot00000000000000wf-config-0.8.0/test/int_test/xml/section1.xml000066400000000000000000000003521446445615300212710ustar00rootroot00000000000000 General wf-config-0.8.0/test/int_test/xml/section2.xml000066400000000000000000000006251446445615300212750ustar00rootroot00000000000000 General wf-config-0.8.0/test/int_test/xml/sectionobj.xml000066400000000000000000000003541446445615300217050ustar00rootroot00000000000000 General wf-config-0.8.0/test/log_test.cpp000066400000000000000000000067561446445615300167330ustar00rootroot00000000000000#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include #include struct test_struct { int x, y; }; namespace wf { namespace log { template<> std::string to_string(test_struct str) { return "(" + std::to_string(str.x) + "," + std::to_string(str.y) + ")"; } } } TEST_CASE("wf::log::detail::format_concat()") { using namespace wf::log; CHECK(detail::format_concat("test", 123) == "test123"); CHECK(detail::format_concat("test ", 123, " ", false, true) == "test 123 falsetrue"); int *p = (int*)0xfff; int *null = nullptr; CHECK(detail::format_concat("test ", p) == "test 0xfff"); CHECK(detail::format_concat("test ", null) == "test (null)"); CHECK(detail::format_concat("$", test_struct{1, 2}, "$") == "$(1,2)$"); char *t = nullptr; CHECK(detail::format_concat(t, "$") == "(null)$"); } TEST_CASE("wf::log::log_plain()") { using namespace wf::log; std::stringstream out; auto check_line = [&out] (std::string expect) { std::string line; std::getline(out, line); /* Remove date and current time, because it isn't reproducible. */ int time_start_index = 2; int time_length = 1 + 8 + 1 + 12; /* space + date + space + time */ REQUIRE(line.length() >= time_start_index + time_length); line.erase(time_start_index, time_length); CHECK(line == expect); }; initialize_logging(out, LOG_LEVEL_DEBUG, LOG_COLOR_MODE_OFF, "/test/strip/"); log_plain(LOG_LEVEL_DEBUG, "Test log", "/test/strip/main.cpp", 5); check_line("DD - [main.cpp:5] Test log"); log_plain(LOG_LEVEL_INFO, "Test no source"); check_line("II - Test no source"); log_plain(LOG_LEVEL_INFO, "Test log", "/test/strip/main.cpp", 56789); check_line("II - [main.cpp:56789] Test log"); log_plain(LOG_LEVEL_WARN, "Test log", "test/strip/main.cpp", 5); check_line("WW - [test/strip/main.cpp:5] Test log"); log_plain(LOG_LEVEL_ERROR, "Test error", "/test/strip//test/strip/main.cpp", 5); check_line("EE - [/test/strip/main.cpp:5] Test error"); initialize_logging(out, LOG_LEVEL_ERROR, LOG_COLOR_MODE_OFF, "/test/strip/"); /* Ignore non-error messages */ log_plain(LOG_LEVEL_WARN, "Test log", "test/strip/main.cpp", 5); log_plain(LOG_LEVEL_DEBUG, "Test log", "/test/strip/main.cpp", 5); log_plain(LOG_LEVEL_INFO, "Test log", "/test/strip/main.cpp", 56789); /* Show just errors */ log_plain(LOG_LEVEL_ERROR, "Test error", "main.cpp", 5); check_line("EE - [main.cpp:5] Test error"); /* Stream shouldn't have any more characters */ char dummy; out >> dummy; CHECK(out.eof()); } TEST_CASE("wf::log::log_plain(color_on)") { using namespace wf::log; std::stringstream stream; initialize_logging(stream, LOG_LEVEL_DEBUG, LOG_COLOR_MODE_ON); auto check_line = [&stream] (std::string set_color) { std::string line; std::getline(stream, line); /* Check that line begins with the proper color code and ends with * color reset */ const std::string reset = "\033[0m"; REQUIRE(line.length() >= set_color.length() + reset.length()); CHECK(line.find(set_color) == 0); CHECK(line.rfind(reset) == line.length() - reset.length()); }; LOGD("test"); check_line("\033[0m"); LOGI("test"); check_line("\033[0;34m"); LOGW("test"); check_line("\033[0;33m"); LOGE("test"); check_line("\033[1;31m"); } wf-config-0.8.0/test/meson.build000066400000000000000000000041131446445615300165320ustar00rootroot00000000000000types_test = executable( 'types_test', 'types_test.cpp', dependencies: [wfconfig, doctest], install: false) test('Types test', types_test) option_base_test = executable( 'option_base_test', 'option_base_test.cpp', dependencies: [wfconfig, doctest], install: false) test('OptionBase test', option_base_test) option_test = executable( 'option_test', 'option_test.cpp', dependencies: [wfconfig, doctest, libxml2], install: false) test('Option test', option_test) option_wrapper_test = executable( 'option_wrapper_test', 'option_wrapper_test.cpp', dependencies: [wfconfig, doctest], install: false) test('Option wrapper test', option_wrapper_test) section_test = executable( 'section_test', 'section_test.cpp', dependencies: [wfconfig, doctest, libxml2], install: false) test('Section test', section_test) xml_test = executable( 'xml_test', 'xml_test.cpp', include_directories: wfconfig_inc, dependencies: [wfconfig, doctest, libxml2], install: false) test('XML test', xml_test) config_manager_test = executable( 'config_manager_test', 'config_manager_test.cpp', dependencies: [wfconfig, doctest], install: false) test('ConfigManager test', config_manager_test) file_parse_test = executable( 'file_test', 'file_test.cpp', dependencies: [wfconfig, doctest, libxml2], install: false, cpp_args: '-DTEST_SOURCE="' + meson.current_source_dir() + '"') test('File parsing test', file_parse_test) # Utils log_test = executable( 'log_test', 'log_test.cpp', dependencies: [wfconfig, doctest], install: false) test('Log test', log_test) duration_test = executable( 'duration_test', 'duration_test.cpp', dependencies: [wfconfig, doctest], install: false) test('Duration test', duration_test) number_locale_test = executable( 'number_locale_test', 'number_locale_test.cpp', dependencies: [wfconfig, doctest], install: false) # enable locale test only on request if get_option('locale_test') test('Number locale test', number_locale_test) endif wf-config-0.8.0/test/number_locale_test.cpp000066400000000000000000000110211446445615300207360ustar00rootroot00000000000000#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include #include #include #include #define WF_CONFIG_DOUBLE_EPS 0.01 using namespace wf; using namespace wf::option_type; void setup_test_locale() { // requires glibc-langpack-de // changes std::to_string output std::setlocale(LC_NUMERIC, "fr_FR.UTF-8"); } TEST_CASE("wf::option_type::to_string") { setup_test_locale(); std::string str = wf::option_type::to_string(3.14); CHECK(str == "3.140000"); CHECK(wf::option_type::to_string(3140) == "3140"); } TEST_CASE("wf::bool_wrapper_t locale") { setup_test_locale(); setup_test_locale(); CHECK(from_string("True").value()); setup_test_locale(); CHECK(from_string("true").value()); setup_test_locale(); CHECK(from_string("1").value()); setup_test_locale(); CHECK(from_string("False").value() == false); setup_test_locale(); CHECK(from_string("false").value() == false); setup_test_locale(); CHECK(from_string("0").value() == false); setup_test_locale(); setup_test_locale(); CHECK(!from_string("vrai")); setup_test_locale(); CHECK(!from_string("faux")); setup_test_locale(); setup_test_locale(); CHECK(from_string(to_string(true)).value()); setup_test_locale(); CHECK(from_string(to_string(false)).value() == false); setup_test_locale(); } TEST_CASE("wf::int_wrapper_t locale") { setup_test_locale(); setup_test_locale(); CHECK(from_string("1456").value() == 1456); setup_test_locale(); CHECK(from_string("-89").value() == -89); int32_t max = std::numeric_limits::max(); int32_t min = std::numeric_limits::min(); setup_test_locale(); CHECK(from_string(wf::option_type::to_string(max)).value() == max); setup_test_locale(); CHECK(from_string(wf::option_type::to_string(min)).value() == min); setup_test_locale(); CHECK(!from_string("1e4")); setup_test_locale(); CHECK(!from_string("")); setup_test_locale(); CHECK(!from_string("1234567890000")); setup_test_locale(); CHECK(from_string(to_string(456)).value() == 456); setup_test_locale(); CHECK(from_string(to_string(0)).value() == 0); } TEST_CASE("wf::double_wrapper_t locale") { setup_test_locale(); CHECK(from_string("0.378000").value() == doctest::Approx(0.378)); setup_test_locale(); CHECK(from_string("-89.1847").value() == doctest::Approx(-89.1847)); double max = std::numeric_limits::max(); double min = std::numeric_limits::min(); setup_test_locale(); CHECK(from_string(wf::option_type::to_string( max)).value() == doctest::Approx(max)); setup_test_locale(); CHECK(from_string(wf::option_type::to_string( min)).value() == doctest::Approx(min)); setup_test_locale(); CHECK(!from_string("1u4")); setup_test_locale(); CHECK(!from_string("")); setup_test_locale(); CHECK(!from_string("abc")); setup_test_locale(); CHECK(from_string(to_string(-4.56)).value() == doctest::Approx(-4.56)); setup_test_locale(); CHECK(from_string(to_string(0.0)).value() == doctest::Approx(0)); } /* Test that various wf::color_t constructors work */ TEST_CASE("wf::color_t") { using namespace wf; using namespace option_type; setup_test_locale(); setup_test_locale(); CHECK(!from_string("#FFF")); setup_test_locale(); CHECK(!from_string("0C1A")); setup_test_locale(); CHECK(!from_string("")); setup_test_locale(); CHECK(!from_string("#ZYXUIOPQ")); setup_test_locale(); CHECK(!from_string("#AUIO")); // invalid color setup_test_locale(); CHECK(!from_string("1.0 0.5 0.5 1.0 1.0")); // invalid color setup_test_locale(); CHECK(!from_string("1.0 0.5 0.5 1.0 asdf")); // invalid color setup_test_locale(); CHECK(!from_string("1.0 0.5")); // invalid color setup_test_locale(); CHECK(to_string(color_t{0, 0, 0, 0}) == "#00000000"); setup_test_locale(); CHECK(to_string(color_t{0.4, 0.8, 0.3686274, 0.9686274}) == "#66CC5EF7"); setup_test_locale(); CHECK(to_string(color_t{1, 1, 1, 1}) == "#FFFFFFFF"); } wf-config-0.8.0/test/option_base_test.cpp000066400000000000000000000037421446445615300204440ustar00rootroot00000000000000#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include class option_base_stub_t : public wf::config::option_base_t { public: option_base_stub_t(std::string name) : option_base_t(name) {} std::shared_ptr clone_option() const override { return nullptr; } bool set_default_value_str(const std::string&) override { return true; } bool set_value_str(const std::string&) override { return false; } void reset_to_default() override {} std::string get_value_str() const override { return ""; } std::string get_default_value_str() const override { return ""; } public: void notify_updated() const { option_base_t::notify_updated(); } }; TEST_CASE("wf::option_base_t") { option_base_stub_t option{"string"}; CHECK(option.get_name() == "string"); int callback_called = 0; int callback2_called = 0; wf::config::option_base_t::updated_callback_t callback, callback2; callback = [&] () { callback_called++; }; callback2 = [&] () { callback2_called++; }; option.add_updated_handler(&callback); option.notify_updated(); CHECK(callback_called == 1); CHECK(callback2_called == 0); option.add_updated_handler(&callback); option.add_updated_handler(&callback2); option.notify_updated(); CHECK(callback_called == 3); CHECK(callback2_called == 1); option.rem_updated_handler(&callback); option.notify_updated(); CHECK(callback_called == 3); CHECK(callback2_called == 2); option.rem_updated_handler(&callback2); option.notify_updated(); CHECK(callback_called == 3); CHECK(callback2_called == 2); option.set_locked(); CHECK(option.is_locked()); option.set_locked(); option.set_locked(false); CHECK(option.is_locked()); option.set_locked(false); CHECK(option.is_locked() == false); } wf-config-0.8.0/test/option_test.cpp000066400000000000000000000264241446445615300174540ustar00rootroot00000000000000#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include #include #include #include #include #include "../src/option-impl.hpp" /** * A struct to check whether the maximum and minimum methods are enabled on the * template parameter class. */ template struct are_bounds_enabled { private: template static constexpr bool has_bounds( decltype(&V::template set_maximum<>), decltype(&V::template set_minimum<>), decltype(&V::get_maximum), decltype(&V::get_minimum)) { return true; } template static constexpr bool has_bounds(...) { return false; } public: enum { value = has_bounds(nullptr, nullptr, nullptr, nullptr), }; }; TEST_CASE("wf::config::option_t") { using namespace wf; using namespace wf::config; const wf::keybinding_t binding1{KEYBOARD_MODIFIER_ALT, KEY_E}; const wf::keybinding_t binding2{KEYBOARD_MODIFIER_LOGO, KEY_T}; option_t opt("test123", binding1); CHECK(opt.get_name() == "test123"); CHECK(opt.get_value() == binding1); opt.set_value(binding2); CHECK(opt.get_value() == binding2); opt.set_value(binding1); CHECK(opt.get_value() == binding1); CHECK(opt.set_value_str("KEY_T")); CHECK(opt.get_value() == binding2); CHECK(!opt.set_value_str("garbage")); CHECK(opt.get_value() == binding2); opt.set_value_str("KEY_T"); CHECK(opt.get_value() == binding2); opt.reset_to_default(); CHECK(opt.get_value() == binding1); CHECK(wf::option_type::from_string( opt.get_value_str()).value() == binding1); CHECK(opt.set_default_value_str("KEY_T")); opt.reset_to_default(); CHECK(opt.get_value() == binding2); CHECK(opt.get_default_value() == binding2); CHECK(wf::option_type::from_string( opt.get_default_value_str()) == binding2); CHECK(are_bounds_enabled>::value == false); CHECK(are_bounds_enabled>::value == false); CHECK(are_bounds_enabled>::value == false); CHECK(are_bounds_enabled>::value == false); int callback_called = 0, clone_callback_called = 0; wf::config::option_base_t::updated_callback_t callback = [&] () { callback_called++; }, clone_callback = [&] () { clone_callback_called++; }; opt.add_updated_handler(&callback); opt.priv->xml = (xmlNode*)0x123; auto clone = std::static_pointer_cast>( opt.clone_option()); CHECK(clone->priv->xml == (xmlNode*)0x123); CHECK(clone->get_name() == opt.get_name()); CHECK(clone->get_default_value() == opt.get_default_value()); CHECK(clone->get_value() == opt.get_value()); opt.set_value_str("KEY_F"); CHECK(callback_called == 1); clone->add_updated_handler(&clone_callback); clone->set_value_str("KEY_F"); CHECK(callback_called == 1); CHECK(clone_callback_called == 1); } TEST_CASE("wf::config::option_t") { using namespace wf; using namespace wf::config; option_t iopt{"int123", 5}; CHECK(iopt.get_name() == "int123"); CHECK(iopt.get_value() == 5); CHECK(!iopt.get_minimum()); CHECK(!iopt.get_maximum()); iopt.set_minimum(0); CHECK(!iopt.get_maximum()); CHECK(iopt.get_minimum().value_or(0) == 0); iopt.set_maximum(10); CHECK(iopt.get_maximum().value_or(11) == 10); CHECK(iopt.get_minimum().value_or(1) == 0); CHECK(iopt.get_value() == 5); iopt.set_value(8); CHECK(iopt.get_value() == 8); iopt.set_value(11); CHECK(iopt.get_value() == 10); iopt.set_value(-1); CHECK(iopt.get_value() == 0); iopt.set_minimum(3); CHECK(iopt.get_minimum().value_or(0) == 3); CHECK(iopt.get_value() == 3); iopt.reset_to_default(); CHECK(iopt.get_value() == 5); CHECK(wf::option_type::from_string(iopt.get_value_str()).value_or(0) == 5); option_t dopt{"dbl123", -1.0}; dopt.set_value(-100); CHECK(dopt.get_value() == doctest::Approx(-100.0)); dopt.set_minimum(50); dopt.set_maximum(50); CHECK(dopt.get_value() == doctest::Approx(50)); CHECK(dopt.get_minimum().value_or(60) == doctest::Approx(50)); CHECK(dopt.get_maximum().value_or(60) == doctest::Approx(50)); CHECK(wf::option_type::from_string(dopt.get_value_str()).value_or(0) == doctest::Approx(50)); dopt.set_maximum(60); CHECK(dopt.set_default_value_str("55")); CHECK(!dopt.set_default_value_str("invalid dobule")); dopt.reset_to_default(); CHECK(dopt.get_value() == doctest::Approx(55)); CHECK(dopt.get_default_value() == doctest::Approx(55)); CHECK(dopt.set_default_value_str("75")); // invalid wrt min/max dopt.reset_to_default(); CHECK(dopt.get_value() == doctest::Approx(60)); // not more than max auto clone = std::static_pointer_cast>(dopt.clone_option()); CHECK(clone->get_minimum() == dopt.get_minimum()); CHECK(clone->get_maximum() == dopt.get_maximum()); CHECK(are_bounds_enabled>::value); CHECK(are_bounds_enabled>::value); } TEST_CASE("compound options") { using namespace wf; using namespace wf::config; compound_option_t::entries_t entries; entries.push_back(std::make_unique>("hey_")); entries.push_back(std::make_unique>("bey_")); compound_option_t opt{"Test", std::move(entries)}; auto section = std::make_shared("TestSection"); section->register_new_option(std::make_shared>("hey_k1", 1)); section->register_new_option(std::make_shared>("hey_k2", -12)); section->register_new_option(std::make_shared>("bey_k1", 1.2)); section->register_new_option(std::make_shared>("bey_k2", 3.1415)); // Not fully specified pairs section->register_new_option(std::make_shared>("hey_k3", 3)); // One of the values is a regular option with an associated XML tag, and // needs to be skipped auto xml_opt = std::make_shared>("bey_k3", 5.5); xml_opt->priv->xml = (xmlNode*)0x123; section->register_new_option(xml_opt); section->register_new_option(std::make_shared>("bey_k4", "invalid value")); section->register_new_option(std::make_shared>("bey_k5", 3.5)); // Options which don't match anything section->register_new_option(std::make_shared>("hallo", 3.5)); // Mark all options as coming from the config file, otherwise, they wont' be // parsed for (auto& opt : section->get_registered_options()) { opt->priv->option_in_config_file = true; } update_compound_from_section(opt, section); auto values = opt.get_value(); REQUIRE(values.size() == 2); std::sort(values.begin(), values.end()); CHECK(std::get<0>(values[0]) == "k1"); CHECK(std::get<1>(values[0]) == 1); CHECK(std::get<2>(values[0]) == 1.2); CHECK(std::get<0>(values[1]) == "k2"); CHECK(std::get<1>(values[1]) == -12); CHECK(std::get<2>(values[1]) == 3.1415); std::vector> untyped_values = { {"k1", "1", "1.200000"}, {"k2", "-12", "3.141500"}, }; CHECK(opt.get_value_untyped() == untyped_values); compound_list_t v = { {"k3", 1, 1.23} }; opt.set_value(v); CHECK(v == opt.get_value()); compound_option_t::stored_type_t v2 = { {"k3", "1", "1.23"} }; CHECK(opt.set_value_untyped(v2)); CHECK(v == opt.get_value()); // Fail to set compound_option_t::stored_type_t v3 = { {"k3", "1"} }; CHECK(!opt.set_value_untyped(v3)); compound_option_t::stored_type_t v4 = { {"k3", "1", "invalid double"} }; CHECK(!opt.set_value_untyped(v4)); } TEST_CASE("compound option with default values") { using namespace wf; using namespace wf::config; compound_option_t::entries_t entries; entries.push_back(std::make_unique>("int_", "int", "42")); entries.push_back(std::make_unique>("double_", "double")); entries.push_back(std::make_unique>("str_", "str", "default")); compound_option_t opt("Test", std::move(entries)); auto section = std::make_shared("TestSection"); // k1 -- all entries are scecified // k2 -- double value is unspecified (error) // k3 -- invalid int value, should use the default one // k4 -- only double value is specified section->register_new_option(std::make_shared>("int_k1", 1)); section->register_new_option(std::make_shared>("int_k2", 2)); section->register_new_option(std::make_shared>("int_k3", "invalid")); section->register_new_option(std::make_shared>("double_k1", 1.0)); section->register_new_option(std::make_shared>("double_k3", 3.0)); section->register_new_option(std::make_shared>("double_k4", 4.0)); section->register_new_option(std::make_shared>("str_k1", "s1")); section->register_new_option(std::make_shared>("str_k2", "s2")); section->register_new_option(std::make_shared>("str_k3", "s3")); // Mark all options as coming from the config file, otherwise, they wont' be // parsed for (auto& opt : section->get_registered_options()) { opt->priv->option_in_config_file = true; } update_compound_from_section(opt, section); auto values = opt.get_value(); REQUIRE(values.size() == 3); std::sort(values.begin(), values.end()); CHECK(values[0] == std::tuple{"k1", 1, 1.0, "s1"}); CHECK(values[1] == std::tuple{"k3", 42, 3.0, "s3"}); CHECK(values[2] == std::tuple{"k4", 42, 4.0, "default"}); } TEST_CASE("Plain list compound options") { using namespace wf::config; compound_option_t::entries_t entries; entries.push_back(std::make_unique>("hey_")); entries.push_back(std::make_unique>("bey_")); compound_option_t opt{"Test", std::move(entries)}; simple_list_t simple_list = { {0, 0.0}, {1, -1.5} }; opt.set_value_simple(simple_list); auto with_names = opt.get_value(); compound_list_t compound_list = { {"0", 0, 0.0}, {"1", 1, -1.5} }; CHECK(compound_list == opt.get_value()); compound_list = { {"test", 10, 0.0}, {"blah", 20, 15.6} }; opt.set_value(compound_list); simple_list = { {10, 0.0}, {20, 15.6} }; CHECK(simple_list == opt.get_value_simple()); } wf-config-0.8.0/test/option_wrapper_test.cpp000066400000000000000000000037171446445615300212140ustar00rootroot00000000000000#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include #include static wf::config::config_manager_t config; template class wrapper_t : public wf::base_option_wrapper_t { public: wrapper_t() : wf::base_option_wrapper_t() {} wrapper_t(const std::string& option) : wf::base_option_wrapper_t() { this->load_option(option); } protected: std::shared_ptr load_raw_option( const std::string& name) override { return config.get_option(name); } }; TEST_CASE("wf::base_option_wrapper_t") { using namespace wf; using namespace wf::config; auto section = std::make_shared("Test"); auto opt = std::make_shared>("Option1", 5); compound_option_t::entries_t entries; entries.push_back(std::make_unique>("hey_")); entries.push_back(std::make_unique>("bey_")); auto coptr = new compound_option_t{"Option2", std::move(entries)}; auto copt = std::shared_ptr(coptr); section->register_new_option(opt); section->register_new_option(copt); ::config.merge_section(section); wrapper_t wrapper{"Test/Option1"}; CHECK((option_sptr_t)wrapper == opt); CHECK(wrapper == 5); wrapper_t> wrapper2{"Test/Option2"}; CHECK((std::shared_ptr)wrapper2 == copt); bool value_in_compound_list_is_ok = (wrapper2.value() == compound_list_t{}); CHECK(value_in_compound_list_is_ok); bool updated = false; wrapper.set_callback([&] () { updated = true; }); opt->set_value(6); CHECK(updated); /* Check move operations */ wrapper_t wrapper1{"Test/Option1"}; CHECK((option_sptr_t)wrapper1 == opt); } wf-config-0.8.0/test/section_test.cpp000066400000000000000000000033661446445615300176100ustar00rootroot00000000000000#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include #include #include "../src/section-impl.hpp" TEST_CASE("wf::config::section_t") { using namespace wf; using namespace wf::config; section_t section{"Test_Section-12.34"}; CHECK(section.get_name() == "Test_Section-12.34"); CHECK(section.get_registered_options().empty()); CHECK(section.get_option_or("non_existing") == nullptr); auto intopt = std::make_shared>("IntOption", 123); section.register_new_option(intopt); CHECK(section.get_option("IntOption") == intopt); CHECK(section.get_option_or("IntOption") == intopt); CHECK(section.get_option_or("DoubleOption") == nullptr); auto reg_opts = section.get_registered_options(); REQUIRE(reg_opts.size() == 1); CHECK(reg_opts.back() == intopt); auto intopt2 = std::make_shared>("IntOption", 125); section.register_new_option(intopt2); // overwrite CHECK(section.get_option_or("IntOption") == intopt2); reg_opts = section.get_registered_options(); REQUIRE(reg_opts.size() == 1); CHECK(reg_opts.back() == intopt2); section.unregister_option(intopt2); CHECK(section.get_registered_options().empty()); section.register_new_option(intopt); section.priv->xml = (xmlNode*)0x123; auto clone = section.clone_with_name("Cloned_Section"); CHECK(clone->get_name() == "Cloned_Section"); CHECK(clone->priv->xml == (xmlNode*)0x123); CHECK(clone->get_option_or("IntOption") != intopt); CHECK(clone->get_option_or("IntOption")->get_name() == intopt->get_name()); CHECK(clone->get_option_or( "IntOption")->get_value_str() == intopt->get_value_str()); } wf-config-0.8.0/test/types_test.cpp000066400000000000000000000426451446445615300173130ustar00rootroot00000000000000#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include #include #include #define WF_CONFIG_DOUBLE_EPS 0.01 using namespace wf; using namespace wf::option_type; TEST_CASE("wf::bool_wrapper_t") { CHECK(from_string("True").value()); CHECK(from_string("true").value()); CHECK(from_string("TRUE").value()); CHECK(from_string("TrUe").value()); CHECK(from_string("1").value()); CHECK(from_string("False").value() == false); CHECK(from_string("false").value() == false); CHECK(from_string("FALSE").value() == false); CHECK(from_string("FaLSe").value() == false); CHECK(from_string("0").value() == false); CHECK(!from_string("rip")); CHECK(!from_string("1234")); CHECK(!from_string("")); CHECK(!from_string("1h")); CHECK(!from_string("trueeee")); CHECK(from_string(to_string(true)).value()); CHECK(from_string(to_string(false)).value() == false); } TEST_CASE("wf::int_wrapper_t") { CHECK(from_string("456").value() == 456); CHECK(from_string("-89").value() == -89); int32_t max = std::numeric_limits::max(); int32_t min = std::numeric_limits::min(); CHECK(from_string(std::to_string(max)).value() == max); CHECK(from_string(std::to_string(min)).value() == min); CHECK(!from_string("1e4")); CHECK(!from_string("")); CHECK(!from_string("1234567890000")); CHECK(from_string(to_string(456)).value() == 456); CHECK(from_string(to_string(0)).value() == 0); } TEST_CASE("wf::double_wrapper_t") { CHECK(from_string("0.378000").value() == doctest::Approx(0.378)); CHECK(from_string("-89.1847").value() == doctest::Approx(-89.1847)); double max = std::numeric_limits::max(); double min = std::numeric_limits::min(); CHECK(from_string(std::to_string(max)).value() == doctest::Approx(max)); CHECK(from_string(std::to_string(min)).value() == doctest::Approx(min)); CHECK(!from_string("1u4")); CHECK(!from_string("")); CHECK(!from_string("abc")); CHECK(from_string(to_string(-4.56)).value() == doctest::Approx(-4.56)); CHECK(from_string(to_string(0.0)).value() == doctest::Approx(0)); } static void check_color_equals(const wf::color_t& color, double r, double g, double b, double a) { CHECK(color.r == doctest::Approx(r).epsilon(0.01)); CHECK(color.g == doctest::Approx(g).epsilon(0.01)); CHECK(color.b == doctest::Approx(b).epsilon(0.01)); CHECK(color.a == doctest::Approx(a).epsilon(0.01)); } static void check_color_equals( const std::optional& color, double r, double g, double b, double a) { check_color_equals(color.value(), r, g, b, a); CHECK(color == wf::color_t{r, g, b, a}); } /* Test that various wf::color_t constructors work */ TEST_CASE("wf::color_t") { using namespace wf; using namespace option_type; check_color_equals(color_t{}, 0, 0, 0, 0); check_color_equals(color_t{0.345, 0.127, 0.188, 1.0}, 0.345, 0.127, 0.188, 1.0); check_color_equals(color_t{glm::vec4(0.7)}, 0.7, 0.7, 0.7, 0.7); check_color_equals(from_string("#66CC5ef7"), 0.4, 0.8, 0.3686274, 0.9686274); check_color_equals(from_string("#0F0F"), 0, 1, 0, 1); check_color_equals(from_string("0.34 0.5 0.5 1.0"), 0.34, 0.5, 0.5, 1.0); CHECK(!from_string("#FFF")); CHECK(!from_string("0C1A")); CHECK(!from_string("")); CHECK(!from_string("#ZYXUIOPQ")); CHECK(!from_string("#AUIO")); // invalid color CHECK(!from_string("1.0 0.5 0.5 1.0 1.0")); // invalid color CHECK(!from_string("1.0 0.5 0.5 1.0 asdf")); // invalid color CHECK(!from_string("1.0 0.5")); // invalid color CHECK(to_string(color_t{0, 0, 0, 0}) == "#00000000"); CHECK(to_string(color_t{0.4, 0.8, 0.3686274, 0.9686274}) == "#66CC5EF7"); CHECK(to_string(color_t{1, 1, 1, 1}) == "#FFFFFFFF"); } TEST_CASE("wf::keybinding_t") { /* Test simple constructor */ const uint32_t modifier1 = wf::KEYBOARD_MODIFIER_ALT | wf::KEYBOARD_MODIFIER_LOGO; wf::keybinding_t binding1{modifier1, KEY_L}; CHECK(binding1.get_modifiers() == modifier1); CHECK(binding1.get_key() == KEY_L); /* Test parsing */ auto binding2 = from_string( "KEY_TAB").value(); uint32_t modifier2 = wf::KEYBOARD_MODIFIER_SHIFT | wf::KEYBOARD_MODIFIER_CTRL; CHECK(binding2.get_modifiers() == modifier2); CHECK(binding2.get_key() == KEY_TAB); auto binding3 = from_string("KEY_L").value(); CHECK(binding3.get_modifiers() == modifier1); CHECK(binding3.get_key() == KEY_L); wf::keybinding_t mod_binding = {wf::KEYBOARD_MODIFIER_LOGO, 0}; CHECK(from_string("").value() == mod_binding); auto empty = wf::keybinding_t{0, 0}; CHECK(from_string("none").value() == empty); CHECK(from_string("disabled").value() == empty); /* Test invalid bindings */ CHECK(!from_string("KEY_L")); CHECK(!from_string("")); CHECK(!from_string(" KEY_nonexist")); CHECK(!from_string(" BTN_LEFT")); CHECK(!from_string(" super KEY_L")); CHECK(!from_string((""))); /* Test equality */ CHECK(binding1 == binding3); CHECK(!(binding2 == binding1)); using wk_t = wf::keybinding_t; CHECK(from_string(to_string(binding1)).value() == binding1); CHECK(from_string(to_string(binding2)).value() == binding2); } TEST_CASE("wf::buttonbinding_t") { /* Test simple constructor */ wf::buttonbinding_t binding1{wf::KEYBOARD_MODIFIER_ALT, BTN_LEFT}; CHECK(binding1.get_modifiers() == wf::KEYBOARD_MODIFIER_ALT); CHECK(binding1.get_button() == BTN_LEFT); /* Test parsing */ auto binding2 = from_string("BTN_EXTRA").value(); CHECK(binding2.get_modifiers() == wf::KEYBOARD_MODIFIER_CTRL); CHECK(binding2.get_button() == BTN_EXTRA); auto binding3 = from_string("BTN_LEFT").value(); CHECK(binding3.get_modifiers() == wf::KEYBOARD_MODIFIER_ALT); CHECK(binding3.get_button() == BTN_LEFT); auto empty = wf::buttonbinding_t{0, 0}; CHECK(from_string("none").value() == empty); CHECK(from_string("disabled").value() == empty); /* Test invalid bindings */ CHECK(!from_string(" BTN_inv")); CHECK(!from_string(" KEY_E")); CHECK(!from_string("")); CHECK(!from_string("")); CHECK(!from_string("super BTN_LEFT")); /* Test equality */ CHECK(binding1 == binding3); CHECK(!(binding2 == binding1)); using wb_t = wf::buttonbinding_t; CHECK(from_string(to_string(binding1)).value() == binding1); CHECK(from_string(to_string(binding2)).value() == binding2); CHECK(from_string(to_string(binding3)).value() == binding3); } TEST_CASE("wf::touchgesture_t") { /* Test simple constructor */ wf::touchgesture_t binding1{wf::GESTURE_TYPE_SWIPE, wf::GESTURE_DIRECTION_UP, 3}; CHECK(binding1.get_type() == wf::GESTURE_TYPE_SWIPE); CHECK(binding1.get_finger_count() == 3); CHECK(binding1.get_direction() == wf::GESTURE_DIRECTION_UP); /* Test parsing */ auto binding2 = from_string("swipe up-left 4").value(); uint32_t direction2 = wf::GESTURE_DIRECTION_UP | wf::GESTURE_DIRECTION_LEFT; CHECK(binding2.get_type() == wf::GESTURE_TYPE_SWIPE); CHECK(binding2.get_direction() == direction2); CHECK(binding2.get_finger_count() == 4); auto binding3 = from_string("edge-swipe down 2").value(); CHECK(binding3.get_type() == wf::GESTURE_TYPE_EDGE_SWIPE); CHECK(binding3.get_direction() == wf::GESTURE_DIRECTION_DOWN); CHECK(binding3.get_finger_count() == 2); auto binding4 = from_string("pinch in 3").value(); CHECK(binding4.get_type() == wf::GESTURE_TYPE_PINCH); CHECK(binding4.get_direction() == wf::GESTURE_DIRECTION_IN); CHECK(binding4.get_finger_count() == 3); auto binding5 = from_string("pinch out 2").value(); CHECK(binding5.get_type() == wf::GESTURE_TYPE_PINCH); CHECK(binding5.get_direction() == wf::GESTURE_DIRECTION_OUT); CHECK(binding5.get_finger_count() == 2); auto empty = wf::touchgesture_t{wf::GESTURE_TYPE_NONE, 0, 0}; CHECK(from_string("none").value() == empty); CHECK(from_string("disabled").value() == empty); /* A few bad description cases */ CHECK(!from_string("pinch out")); // missing fingercount CHECK(!from_string("wrong left 5")); // no such type CHECK(!from_string("edge-swipe up-down 3")); // opposite dirs CHECK(!from_string("swipe 3")); // missing dir CHECK(!from_string("pinch 3")); CHECK(!from_string("")); /* Equality */ CHECK(!(binding1 == wf::touchgesture_t{wf::GESTURE_TYPE_PINCH, 0, 3})); CHECK(binding1 == wf::touchgesture_t{wf::GESTURE_TYPE_SWIPE, 0, 3}); CHECK(binding3 == wf::touchgesture_t{ wf::GESTURE_TYPE_EDGE_SWIPE, wf::GESTURE_DIRECTION_DOWN, 2}); CHECK(binding5 == wf::touchgesture_t{ wf::GESTURE_TYPE_PINCH, wf::GESTURE_DIRECTION_OUT, 2}); CHECK(binding5 == wf::touchgesture_t{wf::GESTURE_TYPE_PINCH, 0, 2}); CHECK(!(binding5 == wf::touchgesture_t{ wf::GESTURE_TYPE_PINCH, wf::GESTURE_DIRECTION_IN, 2})); using wt_t = wf::touchgesture_t; CHECK(from_string(to_string(binding1)).value() == binding1); CHECK(from_string(to_string(binding2)).value() == binding2); CHECK(from_string(to_string(binding3)).value() == binding3); CHECK(from_string(to_string(binding4)).value() == binding4); CHECK(from_string(to_string(binding5)).value() == binding5); } TEST_CASE("wf::hotspot_binding_t") { using hs = wf::hotspot_binding_t; const uint32_t tl = OUTPUT_EDGE_TOP | OUTPUT_EDGE_LEFT; // sanity check auto h1 = hs(tl, 100, 20, 150); CHECK(h1.get_edges() == tl); CHECK(h1.get_size_along_edge() == 100); CHECK(h1.get_size_away_from_edge() == 20); CHECK(h1.get_timeout() == 150); // parsing CHECK(from_string("hotspot bottom 20x20 1500") == hs(OUTPUT_EDGE_BOTTOM, 20, 20, 1500)); CHECK(from_string(to_string(h1)) == h1); // check parsing errors CHECK(!from_string("top 10x10 10")); CHECK(!from_string("hotspot topp 10x10 10")); CHECK(!from_string("hotspot top 10x10 10 trailing")); CHECK(!from_string("hotspot top 110 10")); CHECK(!from_string("hotspot top 110")); } TEST_CASE("wf::activatorbinding_t") { using namespace wf; activatorbinding_t empty_binding{}; keybinding_t kb1{KEYBOARD_MODIFIER_ALT, KEY_T}; keybinding_t kb2{KEYBOARD_MODIFIER_LOGO | KEYBOARD_MODIFIER_SHIFT, KEY_TAB}; buttonbinding_t bb1{KEYBOARD_MODIFIER_CTRL, BTN_EXTRA}; buttonbinding_t bb2{0, BTN_SIDE}; touchgesture_t tg1{GESTURE_TYPE_SWIPE, GESTURE_DIRECTION_UP, 3}; touchgesture_t tg2{GESTURE_TYPE_PINCH, GESTURE_DIRECTION_IN, 4}; hotspot_binding_t hs{OUTPUT_EDGE_LEFT, 10, 10, 10}; auto test_binding = [&] (std::string description, bool match_kb1, bool match_kb2, bool match_bb1, bool match_bb2, bool match_tg1, bool match_tg2, bool match_hs) { auto full_binding_opt = from_string(description); REQUIRE(full_binding_opt); auto actbinding = full_binding_opt.value(); CHECK(actbinding.has_match(kb1) == match_kb1); CHECK(actbinding.has_match(kb2) == match_kb2); CHECK(actbinding.has_match(bb1) == match_bb1); CHECK(actbinding.has_match(bb2) == match_bb2); CHECK(actbinding.has_match(tg1) == match_tg1); CHECK(actbinding.has_match(tg2) == match_tg2); if (match_hs) { CHECK(actbinding.get_hotspots() == std::vector{hs}); } }; /** Test all possible combinations */ for (int k1 = 0; k1 <= 1; k1++) { for (int k2 = 0; k2 <= 1; k2++) { for (int b1 = 0; b1 <= 1; b1++) { for (int b2 = 0; b2 <= 1; b2++) { for (auto t1 = 0; t1 <= 1; t1++) { for (auto t2 = 0; t2 <= 1; t2++) { std::string descr; if (k1) { descr += to_string(kb1) + " | "; } if (k2) { descr += to_string(kb2) + " | "; } if (b1) { descr += to_string(bb1) + " | "; } if (b2) { descr += to_string(bb2) + " | "; } if (t1) { descr += to_string(tg1) + " | "; } if (t2) { descr += to_string(tg2) + " | "; } if (descr.length() >= 3) { // remove trailing " | " descr.erase(descr.size() - 3); } test_binding(descr, k1, k2, b1, b2, t1, t2, false); CHECK(to_string( from_string( descr).value()) == descr); } } } } } } test_binding("none", 0, 0, 0, 0, 0, 0, 0); test_binding("disabled | none", 0, 0, 0, 0, 0, 0, 0); test_binding("KEY_T|KEY_T|none|hotspot left 10x10 10", 1, 0, 0, 0, 0, 0, 1); CHECK(!from_string(" KEY_K || KEY_U")); CHECK(!from_string(" KEY_K | thrash")); CHECK(!from_string(" KEY_K |")); } TEST_CASE("wf::output_config::mode_t") { using mt = wf::output_config::mode_t; using namespace wf::output_config; std::vector modes = { mt{true}, mt{true}, mt{false}, mt{1920, 1080, 0}, mt{1920, 1080, 59000}, mt{1920, 1080, 59000}, mt{std::string{"eDP-1"}}, }; std::vector types = { MODE_AUTO, MODE_AUTO, MODE_OFF, MODE_RESOLUTION, MODE_RESOLUTION, MODE_RESOLUTION, MODE_MIRROR, }; std::vector desc = { "auto", "default", "off", "1920 x 1080", "1920x1080@59", "1920x 1080 @ 59000", "mirror eDP-1", }; std::vector invalid = { "mirroredp", "autooo", "192e x 1080", "1920 1080", }; CHECK(modes[3].get_refresh() == 0); CHECK(modes[4].get_refresh() == 59000); CHECK(modes[5].get_refresh() == 59000); CHECK(modes[6].get_mirror_from() == "eDP-1"); for (size_t i = 0; i < modes.size(); i++) { CHECK(modes[i].get_type() == types[i]); CHECK(from_string(desc[i]) == modes[i]); CHECK(from_string(to_string(modes[i])) == modes[i]); if (modes[i].get_type() == MODE_RESOLUTION) { CHECK(modes[i].get_width() == 1920); CHECK(modes[i].get_height() == 1080); } } for (auto& d : invalid) { CHECK(!from_string(d)); } } TEST_CASE("wf::output_config::position_t") { using pt = wf::output_config::position_t; using namespace wf::output_config; pt p1 = {0, 0}; pt p2 = {1920, -1080}; auto p1c = from_string("0, 0"); auto p2c = from_string("1920 , -1080"); CHECK(p2c.value().get_x() == 1920); CHECK(p2c.value().get_y() == -1080); CHECK(!p1c.value().is_automatic_position()); CHECK(!p2c.value().is_automatic_position()); CHECK(p1c == p1); CHECK(p2c == p2); CHECK(from_string(to_string(p1)) == p1); CHECK(from_string(to_string(p2)) == p2); CHECK(from_string("auto").value().is_automatic_position()); CHECK(from_string("default").value().is_automatic_position()); CHECK(!from_string("test")); CHECK(!from_string("129 129")); CHECK(!from_string("129,")); } wf-config-0.8.0/test/xml_test.cpp000066400000000000000000000307331446445615300167420ustar00rootroot00000000000000#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include #include #include #include #include #include #include static const std::string xml_option_int = R"( )"; static const std::string xml_option_string = R"( )"; static const std::string xml_option_key = R"( )"; static const std::string xml_option_dyn_list = R"( )"; static const std::string xml_option_dyn_list_no_prefix = R"( )"; static const std::string xml_option_dyn_list_no_type = R"( )"; static const std::string xml_option_dyn_list_wrong_type = R"( )"; static const std::string xml_option_bad_tag = R"( <super> KEY_E )"; static const std::string xml_option_int_bad_min = R"( )"; static const std::string xml_option_int_bad_max = R"( )"; static const std::string xml_option_bad_type = R"( )"; static const std::string xml_option_bad_default = R"( )"; static const std::string xml_option_missing_name = R"( )"; static const std::string xml_option_missing_type = R"( )"; static const std::string xml_option_missing_default_value = R"( )"; static const std::string xml_option_dyn_list_default = R"( )"; #include "expect_line.hpp" TEST_CASE("wf::config::xml::create_option") { std::stringstream log; wf::log::initialize_logging(log, wf::log::LOG_LEVEL_DEBUG, wf::log::LOG_COLOR_MODE_OFF); namespace wxml = wf::config::xml; namespace wc = wf::config; xmlNodePtr option_node; auto initialize_option = [&] (std::string source) { auto node = xmlParseDoc((const xmlChar*)source.c_str()); REQUIRE(node != nullptr); option_node = xmlDocGetRootElement(node); return wxml::create_option_from_xml_node(option_node); }; SUBCASE("Not an XML option") { auto opt = std::make_shared>("Test", 1); CHECK(wxml::get_option_xml_node(opt) == nullptr); } SUBCASE("IntOption") { auto option = initialize_option(xml_option_int); REQUIRE(option != nullptr); CHECK(option->get_name() == "IntOption"); auto as_int = std::dynamic_pointer_cast>(option); REQUIRE(as_int); REQUIRE(as_int->get_minimum()); REQUIRE(as_int->get_maximum()); CHECK(as_int->get_value() == 3); CHECK(as_int->get_minimum().value() == 0); CHECK(as_int->get_maximum().value() == 10); CHECK(wxml::get_option_xml_node(as_int) == option_node); } SUBCASE("StringOption") { auto option = initialize_option(xml_option_string); REQUIRE(option != nullptr); CHECK(option->get_name() == "StringOption"); auto as_string = std::dynamic_pointer_cast>(option); REQUIRE(as_string); CHECK(as_string->get_value() == ""); } SUBCASE("KeyOption") { auto option = initialize_option(xml_option_key); REQUIRE(option != nullptr); CHECK(option->get_name() == "KeyOption"); auto as_key = std::dynamic_pointer_cast>(option); REQUIRE(as_key); CHECK(as_key->get_value() == wf::keybinding_t{wf::KEYBOARD_MODIFIER_LOGO, KEY_E}); CHECK(wxml::get_option_xml_node(option) == option_node); } SUBCASE("DynamicList") { auto option = initialize_option(xml_option_dyn_list); REQUIRE(option != nullptr); CHECK(option->get_name() == "DynList"); auto as_co = std::dynamic_pointer_cast(option); REQUIRE(as_co != nullptr); CHECK(as_co->get_value() == wf::config::compound_list_t{}); const auto& entries = as_co->get_entries(); REQUIRE(entries.size() == 2); CHECK( dynamic_cast*>(entries[0].get())); CHECK(dynamic_cast*>( entries[1].get())); } SUBCASE("DynamicListDefaultOption") { auto option = initialize_option(xml_option_dyn_list_default); REQUIRE(option != nullptr); auto as_co = std::dynamic_pointer_cast(option); REQUIRE(as_co != nullptr); CHECK(as_co->get_value() == wf::config::compound_list_t{}); const auto& entries = as_co->get_entries(); REQUIRE(entries.size() == 2); CHECK(dynamic_cast*>(entries[0].get())); REQUIRE(entries[0]->get_default_value() == "9"); CHECK( dynamic_cast*>(entries[1].get())); REQUIRE(entries[1]->get_default_value() == std::nullopt); } /* Generate a subcase where the given xml source can't be parsed to an * option, and check that the output in the log is as expected. */ #define SUBCASE_BAD_OPTION(subcase_name, xml_source, expected_log) \ SUBCASE(subcase_name) \ { \ auto option = initialize_option(xml_source); \ CHECK(option == nullptr); \ EXPECT_LINE(log, expected_log); \ } SUBCASE_BAD_OPTION("Invalid xml tag", xml_option_bad_tag, "is not an option element"); SUBCASE_BAD_OPTION("Invalid option type", xml_option_bad_type, "invalid type \"unknown\""); SUBCASE_BAD_OPTION("Invalid default value", xml_option_bad_default, "invalid default value"); SUBCASE_BAD_OPTION("Invalid minimum value", xml_option_int_bad_min, "invalid minimum value"); SUBCASE_BAD_OPTION("Invalid maximum value", xml_option_int_bad_max, "invalid maximum value"); SUBCASE_BAD_OPTION("Missing option name", xml_option_missing_name, "missing \"name\" attribute"); SUBCASE_BAD_OPTION("Missing option type", xml_option_missing_type, "missing \"type\" attribute"); SUBCASE_BAD_OPTION("Missing option default value", xml_option_missing_default_value, "no default value specified"); SUBCASE_BAD_OPTION("Dynamic list without prefix", xml_option_dyn_list_no_prefix, "missing \"prefix\" attribute"); SUBCASE_BAD_OPTION("Dynamic list without type", xml_option_dyn_list_no_type, "missing \"type\" attribute"); SUBCASE_BAD_OPTION("Dynamic list with invalid type", xml_option_dyn_list_wrong_type, "invalid type"); } /* ------------------------- create_section test ---------------------------- */ static const std::string xml_section_empty = R"( )"; static const std::string xml_section_no_plugins = R"( )"; static const std::string xml_section_full = R"( )"; static const std::string xml_section_missing_name = R"( )"; static const std::string xml_section_bad_tag = R"( )"; TEST_CASE("wf::config::xml::create_section") { std::stringstream log; wf::log::initialize_logging(log, wf::log::LOG_LEVEL_DEBUG, wf::log::LOG_COLOR_MODE_OFF); namespace wxml = wf::config::xml; namespace wc = wf::config; xmlNodePtr section_root; auto initialize_section = [&] (std::string xml_source) { auto node = xmlParseDoc((const xmlChar*)xml_source.c_str()); REQUIRE(node != nullptr); section_root = xmlDocGetRootElement(node); return wxml::create_section_from_xml_node(section_root); }; SUBCASE("Section without XML") { auto section = std::make_shared("TestSection"); CHECK(wxml::get_section_xml_node(section) == nullptr); } SUBCASE("Empty section") { auto section = initialize_section(xml_section_empty); REQUIRE(section != nullptr); CHECK(section->get_name() == "TestPluginEmpty"); CHECK(section->get_registered_options().empty()); CHECK(wxml::get_section_xml_node(section) == section_root); } SUBCASE("Empty section - unnecessary data") { auto section = initialize_section(xml_section_no_plugins); REQUIRE(section != nullptr); CHECK(section->get_name() == "TestPluginNoPlugins"); CHECK(section->get_registered_options().empty()); CHECK(wxml::get_section_xml_node(section) == section_root); } SUBCASE("Section with options") { auto section = initialize_section(xml_section_full); REQUIRE(section != nullptr); CHECK(section->get_name() == "TestPluginFull"); auto opts = section->get_registered_options(); std::set opt_names; for (auto& opt : opts) { opt_names.insert(opt->get_name()); } std::set expected_names = { "KeyOption", "ButtonOption", "TouchOption", "ActivatorOption", "IntOption", "DoubleOption", "BoolOption", "StringOption", "OutputModeOption", "OutputPositionOption"}; CHECK(opt_names == expected_names); CHECK(wxml::get_section_xml_node(section) == section_root); } SUBCASE("Missing section name") { auto section = initialize_section(xml_section_missing_name); CHECK(section == nullptr); EXPECT_LINE(log, "missing \"name\" attribute"); } SUBCASE("Invalid section xml tag") { auto section = initialize_section(xml_section_bad_tag); CHECK(section == nullptr); EXPECT_LINE(log, "is not a plugin/object element"); } }