Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package wf-config for openSUSE:Factory 
checked in at 2026-08-18 16:38:01
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/wf-config (Old)
 and      /work/SRC/openSUSE:Factory/.wf-config.new.1258 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "wf-config"

Tue Aug 18 16:38:01 2026 rev:3 rq:1371738 version:0.11.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/wf-config/wf-config.changes      2025-09-04 
18:01:43.404626237 +0200
+++ /work/SRC/openSUSE:Factory/.wf-config.new.1258/wf-config.changes    
2026-08-18 16:38:49.201948332 +0200
@@ -1,0 +2,14 @@
+Tue Jul 28 04:36:45 UTC 2026 - Dead Mozay <[email protected]>
+
+- Update to version 0.11.0:
+  * Refactor load_xml_files: suppress per-file logging, summarize 
+    loaded ….
+  * ini-parser: emit errors for options not part of the XML spec.
+  * option-wrapper: add option to load via section+option name.
+  * support cubic-bezier as easing.
+  * Don't copy accessed values in getters.
+  * Add extra AUTO options.
+  * files: add support for reloading XMLs files after building 
+    the initial configuration.
+
+-------------------------------------------------------------------

Old:
----
  wf-config-0.10.0.tar.xz
  wf-config-0.10.0.tar.xz.sha256sum

New:
----
  wf-config-0.11.0.tar.xz
  wf-config-0.11.0.tar.xz.sha256sum

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ wf-config.spec ++++++
--- /var/tmp/diff_new_pack.sgkVfQ/_old  2026-08-18 16:38:49.857971806 +0200
+++ /var/tmp/diff_new_pack.sgkVfQ/_new  2026-08-18 16:38:49.859971878 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package wf-config
 #
-# Copyright (c) 2025 SUSE LLC and contributors
+# Copyright (c) 2026 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -16,10 +16,10 @@
 #
 
 
-%define so_ver 0.10
+%define so_ver 0.11
 %define libname libwf-config1
 Name:           wf-config
-Version:        0.10.0
+Version:        0.11.0
 Release:        0
 Summary:        A library for managing configuration files
 License:        MIT
@@ -33,7 +33,7 @@
 BuildRequires:  pkgconfig(glm)
 BuildRequires:  pkgconfig(libevdev)
 BuildRequires:  pkgconfig(libxml-2.0)
-BuildRequires:  pkgconfig(wlroots) >= 0.17.0
+BuildRequires:  pkgconfig(wlroots-0.20)
 
 %description
 A library for managing configuration files, written for wayfire.

++++++ wf-config-0.10.0.tar.xz -> wf-config-0.11.0.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wf-config-0.10.0/.github/workflows/ci.yaml 
new/wf-config-0.11.0/.github/workflows/ci.yaml
--- old/wf-config-0.10.0/.github/workflows/ci.yaml      2025-06-22 
08:30:31.000000000 +0200
+++ new/wf-config-0.11.0/.github/workflows/ci.yaml      2026-07-20 
21:40:27.000000000 +0200
@@ -12,7 +12,9 @@
         - 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: git ls-files | grep "hpp$\|cpp$" | xargs 
./uncrustify/build/uncrustify -c uncrustify.ini --no-backup --replace
+        - run: git diff
+        - run: git diff | diff - /dev/null &> /dev/null
   run_tests:
     name: "Check that tests do not break"
     runs-on: ubuntu-latest
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wf-config-0.10.0/.gitignore 
new/wf-config-0.11.0/.gitignore
--- old/wf-config-0.10.0/.gitignore     1970-01-01 01:00:00.000000000 +0100
+++ new/wf-config-0.11.0/.gitignore     2026-07-20 21:40:27.000000000 +0200
@@ -0,0 +1 @@
+uncrustify
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/wf-config-0.10.0/include/wayfire/config/compound-option.hpp 
new/wf-config-0.11.0/include/wayfire/config/compound-option.hpp
--- old/wf-config-0.10.0/include/wayfire/config/compound-option.hpp     
2025-06-22 08:30:31.000000000 +0200
+++ new/wf-config-0.11.0/include/wayfire/config/compound-option.hpp     
2026-07-20 21:40:27.000000000 +0200
@@ -47,19 +47,19 @@
     virtual ~compound_option_entry_base_t() = default;
 
     /** @return The prefix of the tuple entry. */
-    std::string get_prefix() const
+    const std::string& get_prefix() const WF_LIFETIMEBOUND
     {
         return prefix;
     }
 
     /** @return The name of the tuple entry. */
-    std::string get_name() const
+    const std::string& get_name() const WF_LIFETIMEBOUND
     {
         return name;
     }
 
     /** @return The untyped default value of the tuple entry. */
-    std::optional<std::string> get_default_value() const
+    const std::optional<std::string>& get_default_value() const 
WF_LIFETIMEBOUND
     {
         return default_value;
     }
@@ -224,12 +224,12 @@
     /**
      * Get the type information about entries in the option.
      */
-    const entries_t& get_entries() const;
+    const entries_t& get_entries() const WF_LIFETIMEBOUND;
 
     /**
      * Check if this compound option has named tuples.
      */
-    std::string get_type_hint() const
+    const std::string& get_type_hint() const WF_LIFETIMEBOUND
     {
         return list_type_hint;
     }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wf-config-0.10.0/include/wayfire/config/file.hpp 
new/wf-config-0.11.0/include/wayfire/config/file.hpp
--- old/wf-config-0.10.0/include/wayfire/config/file.hpp        2025-06-22 
08:30:31.000000000 +0200
+++ new/wf-config-0.11.0/include/wayfire/config/file.hpp        2026-07-20 
21:40:27.000000000 +0200
@@ -86,5 +86,13 @@
  */
 config_manager_t build_configuration(const std::vector<std::string>& xmldirs,
     const std::string& sysconf, const std::string& userconf);
+
+/**
+ * Load XML option metadata from @xmldirs and merge any newly discovered
+ * sections/options into @manager. Existing options are left untouched so that
+ * active option wrappers keep their values and callbacks.
+ */
+void load_configuration_options_from_xml_dirs(config_manager_t& manager,
+    const std::vector<std::string>& xmldirs);
 }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/wf-config-0.10.0/include/wayfire/config/option-wrapper.hpp 
new/wf-config-0.11.0/include/wayfire/config/option-wrapper.hpp
--- old/wf-config-0.10.0/include/wayfire/config/option-wrapper.hpp      
2025-06-22 08:30:31.000000000 +0200
+++ new/wf-config-0.11.0/include/wayfire/config/option-wrapper.hpp      
2026-07-20 21:40:27.000000000 +0200
@@ -2,6 +2,7 @@
 
 #include <stdexcept>
 #include <wayfire/config/option.hpp>
+#include <wayfire/config/section.hpp>
 #include <wayfire/config/compound-option.hpp>
 
 namespace wf
@@ -77,25 +78,12 @@
      */
     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<OptionType>(untyped_option);
-        if (raw_option == nullptr)
-        {
-            throw std::runtime_error("Bad option type: " + std::string(name));
-        }
+        _load_option(load_raw_option(name), name);
+    }
 
-        raw_option->add_updated_handler(&option_update_listener);
+    void load_option(std::shared_ptr<wf::config::section_t> section, const 
std::string& name)
+    {
+        _load_option(section->get_option_or(name), section->get_name() + "/" + 
name);
     }
 
     virtual ~base_option_wrapper_t()
@@ -107,12 +95,12 @@
     }
 
     /** Implicitly convertible to the value of the option */
-    operator Type() const
+    operator decltype(auto)() const WF_LIFETIMEBOUND
     {
         return this->value();
     }
 
-    Type value() const
+    decltype(auto) value() const WF_LIFETIMEBOUND
     {
         if constexpr (is_std_vector<Type>::value)
         {
@@ -162,5 +150,27 @@
      */
     virtual std::shared_ptr<wf::config::option_base_t> load_raw_option(
         const std::string& name) = 0;
+
+    void _load_option(std::shared_ptr<wf::config::option_base_t> _option, 
const std::string& name)
+    {
+        if (raw_option)
+        {
+            throw std::logic_error(
+                "Loading an option into option wrapper twice!");
+        }
+
+        if (_option == nullptr)
+        {
+            throw std::runtime_error("No such option: " + std::string(name));
+        }
+
+        raw_option = std::dynamic_pointer_cast<OptionType>(_option);
+        if (raw_option == nullptr)
+        {
+            throw std::runtime_error("Bad option type: " + name);
+        }
+
+        raw_option->add_updated_handler(&option_update_listener);
+    }
 };
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wf-config-0.10.0/include/wayfire/config/option.hpp 
new/wf-config-0.11.0/include/wayfire/config/option.hpp
--- old/wf-config-0.10.0/include/wayfire/config/option.hpp      2025-06-22 
08:30:31.000000000 +0200
+++ new/wf-config-0.11.0/include/wayfire/config/option.hpp      2026-07-20 
21:40:27.000000000 +0200
@@ -6,6 +6,12 @@
 
 #include <memory>
 
+#ifdef __clang__
+    #define WF_LIFETIMEBOUND [[clang::lifetimebound]]
+#else
+    #define WF_LIFETIMEBOUND
+#endif
+
 namespace wf
 {
 namespace config
@@ -21,7 +27,7 @@
     option_base_t& operator =(const option_base_t& other) = delete;
 
     /** @return The name of the option */
-    std::string get_name() const;
+    const std::string& get_name() const WF_LIFETIMEBOUND;
 
     /** @return A copy of the option */
     virtual std::shared_ptr<option_base_t> clone_option() const = 0;
@@ -253,12 +259,12 @@
         }
     }
 
-    Type get_value() const
+    const Type& get_value() const WF_LIFETIMEBOUND
     {
         return value;
     }
 
-    Type get_default_value() const
+    const Type& get_default_value() const WF_LIFETIMEBOUND
     {
         return default_value;
     }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wf-config-0.10.0/include/wayfire/config/types.hpp 
new/wf-config-0.11.0/include/wayfire/config/types.hpp
--- old/wf-config-0.10.0/include/wayfire/config/types.hpp       2025-06-22 
08:30:31.000000000 +0200
+++ new/wf-config-0.11.0/include/wayfire/config/types.hpp       2026-07-20 
21:40:27.000000000 +0200
@@ -487,6 +487,10 @@
 {
     /** Output was configured in automatic mode. */
     MODE_AUTO,
+    /** Output was configured with the biggest refresh rate. */
+    MODE_HIGHRR,
+    /** Output was configured with the biggest resolution. */
+    MODE_HIGHRES,
     /** Output was configured to be turned off. */
     MODE_OFF,
     /** Output was configured with a given resolution. */
@@ -502,11 +506,13 @@
 struct mode_t
 {
     /**
-     * Initialize an OFF or AUTO mode.
+     * Initialize a mode.
      *
-     * @param auto_on If true, the created mode will be an AUTO mode.
+     * @param mode One of: MODE_AUTO, MODE_HIGHRR, MODE_HIGHRES, MODE_OFF. 
MODE_HIGHRR prioritizes refresh
+     * rate, MODE_HIGHRES prioritises resolution and MODE_AUTO chooses 
whatever the display tells it to use.
+     * @throws std::invalid_argument if the mode isn't MODE_AUTO, MODEHIGHRR, 
MODEHIGHRES or MODE_OFF.
      */
-    mode_t(bool auto_on = false);
+    mode_t(output_config::mode_type_t mode);
 
     /**
      * Initialize the mode with source self.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wf-config-0.10.0/include/wayfire/util/duration.hpp 
new/wf-config-0.11.0/include/wayfire/util/duration.hpp
--- old/wf-config-0.10.0/include/wayfire/util/duration.hpp      2025-06-22 
08:30:31.000000000 +0200
+++ new/wf-config-0.11.0/include/wayfire/util/duration.hpp      2026-07-20 
21:40:27.000000000 +0200
@@ -11,9 +11,8 @@
 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.
+ * 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<double (double)>;
 
@@ -23,6 +22,8 @@
 extern smooth_function circle;
 /** "sigmoid" smoothing function, i.e x -> 1.0 / (1 + exp(-12 * x + 6)) */
 extern smooth_function sigmoid;
+/** custom cubic-bezier as in CSS */
+extern smooth_function get_cubic_bezier(double x1, double y1, double x2, 
double y2);
 
 std::vector<std::string> get_available_smooth_functions();
 }
@@ -34,10 +35,7 @@
     animation::smoothing::smooth_function easing;
     std::string easing_name;
 
-    bool operator ==(const animation_description_t& other) const
-    {
-        return (length_ms == other.length_ms) && (easing_name == 
other.easing_name);
-    }
+    bool operator ==(const animation_description_t& other) const;
 };
 
 namespace option_type
@@ -66,15 +64,13 @@
 };
 
 /**
- * duration_t is a class which can be used to track progress over a specific
- * time interval.
+ * 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.
+     * 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.
@@ -95,40 +91,36 @@
     duration_t& operator =(duration_t&& other) = default;
 
     /**
-     * Start the duration.
-     * This means that the progress will get reset to 0.
+     * 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.
+     * 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.
+     * @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.
+     * 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.
+     * 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
+     *  0: reverse 1: forward
      */
     int get_direction();
 
@@ -138,17 +130,14 @@
 };
 
 /**
- * A timed transition is a transition between two states which happens
- * over a period of time.
+ * 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.
+ * 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.
+     * Construct a new timed transition using the given duration to measure 
progress.
      *
      * @duration The duration to use for time measurement
      * @start The start state.
@@ -204,14 +193,12 @@
     void animate(double start, double end);
 
     /**
-     * Animate from the current progress to the given end, and start the
-     * duration.
+     * 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.
+     * Animate from the current progress to the current end, and start the 
duration.
      */
     void animate();
 };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wf-config-0.10.0/meson.build 
new/wf-config-0.11.0/meson.build
--- old/wf-config-0.10.0/meson.build    2025-06-22 08:30:31.000000000 +0200
+++ new/wf-config-0.11.0/meson.build    2026-07-20 21:40:27.000000000 +0200
@@ -1,7 +1,7 @@
 project(
        'wf-config',
        'cpp',
-       version: '0.10.0',
+       version: '0.11.0',
        license: 'MIT',
        meson_version: '>=0.47.0',
        default_options: [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wf-config-0.10.0/src/compound-option.cpp 
new/wf-config-0.11.0/src/compound-option.cpp
--- old/wf-config-0.10.0/src/compound-option.cpp        2025-06-22 
08:30:31.000000000 +0200
+++ new/wf-config-0.11.0/src/compound-option.cpp        2026-07-20 
21:40:27.000000000 +0200
@@ -60,10 +60,10 @@
         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);
+            if (const auto & entry_option = 
section->get_option_or(entries[i]->get_prefix() + suffix);
                 entry_option && !should_ignore_option(entry_option))
             {
+                entry_option->priv->could_be_compound = true;
                 if (entries[i]->is_parsable(entry_option->get_value_str()))
                 {
                     value[i + 1] = entry_option->get_value_str();
@@ -83,9 +83,6 @@
                 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;
             }
@@ -94,6 +91,14 @@
         if (!value.empty())
         {
             stored_value.push_back(std::move(value));
+            for (size_t i = 0; i < entries.size(); ++i)
+            {
+                if (auto entry_option = 
section->get_option_or(entries[i]->get_prefix() + suffix))
+                {
+                    // The option was used as part of the compound option, do 
not issue warning for it!
+                    entry_option->priv->is_part_compound = true;
+                }
+            }
         }
     }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wf-config-0.10.0/src/duration.cpp 
new/wf-config-0.11.0/src/duration.cpp
--- old/wf-config-0.10.0/src/duration.cpp       2025-06-22 08:30:31.000000000 
+0200
+++ new/wf-config-0.11.0/src/duration.cpp       2026-07-20 21:40:27.000000000 
+0200
@@ -4,7 +4,20 @@
 #include <algorithm>
 #include <chrono>
 #include <cmath>
+#include <limits>
 #include <map>
+#include <sstream>
+
+double bezier_helper(double t, double p0, double p1, double p2, double p3)
+{
+    const double u = 1 - t;
+    return u * u * u * p0 + 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * 
t * p3;
+}
+
+inline bool epsilon_comparison(double a, double b)
+{
+    return std::fabs(a - b) <= std::numeric_limits<double>::epsilon() * 
std::fabs(a + b);
+}
 
 namespace wf
 {
@@ -20,10 +33,59 @@
 const double sigmoid_max = 1 + std::exp(-6);
 smooth_function sigmoid  =
     [] (double x) -> double { return sigmoid_max / (1 + exp(-12 * x + 6)); };
+
+smooth_function get_cubic_bezier(double x1, double y1, double x2, double y2)
+{
+    // https://en.wikipedia.org/wiki/Newton%27s_method
+    return [=] (double x)
+    {
+        double t = x;
+        for (int i = 0; i < 10; ++i)
+        {
+            const double f  = bezier_helper(t, 0, x1, x2, 1) - x;
+            const double df = 3 * (1 - t) * (1 - t) * x1 + 6 * (1 - t) * t * 
(x2 - x1) + 3 * t * t * (1 - x2);
+            if (std::abs(f) < 1e-6)
+            {
+                break;
+            }
+
+            t -= f / df;
+        }
+
+        return bezier_helper(t, 0, y1, y2, 1);
+    };
+}
 }
 } // namespace animation
 }
 
+bool wf::animation_description_t::operator ==(const animation_description_t & 
other) const
+{
+    if (easing_name == other.easing_name)
+    {
+        return (length_ms == other.length_ms);
+    }
+
+    // Cubic-bezier easings need parsing to handle epsilon
+    std::stringstream easing_a(easing_name);
+    std::stringstream easing_b(easing_name);
+    std::string easing_type_a, easing_type_b;
+    easing_a >> easing_type_a;
+    easing_b >> easing_type_b;
+    if ((easing_type_a != "cubic-bezier") || (easing_type_b != "cubic-bezier"))
+    {
+        return false;
+    }
+
+    double x1_a, y1_a, x2_a, y2_a, x1_b, y1_b, x2_b, y2_b;
+    easing_a >> x1_a >> y1_a >> x2_a >> y2_a;
+    easing_b >> x1_b >> y1_b >> x2_b >> y2_b;
+    return epsilon_comparison(x1_a, x1_b) &&
+           epsilon_comparison(y1_a, y1_b) &&
+           epsilon_comparison(x2_a, x2_b) &&
+           epsilon_comparison(y2_b, y2_b);
+}
+
 class wf::animation::duration_t::impl
 {
   public:
@@ -297,7 +359,7 @@
         return animation_description_t{
             .length_ms = *val,
             .easing    = animation::smoothing::circle,
-            .easing_name = "circle",
+            .easing_name = "circle"
         };
     }
 
@@ -321,7 +383,20 @@
         result.easing_name = "circle";
     }
 
-    if (!animation::smoothing::easing_map.count(result.easing_name))
+    if (animation::smoothing::easing_map.count(result.easing_name))
+    {
+        result.easing = 
animation::smoothing::easing_map.at(result.easing_name);
+    } else if (result.easing_name == "cubic-bezier")
+    {
+        double x1 = 0, y1 = 0, x2 = 1, y2 = 1;
+        stream >> x1 >> y1 >> x2 >> y2;
+        result.easing = animation::smoothing::get_cubic_bezier(x1, y1, x2, y2);
+        result.easing_name = "cubic-bezier " +
+            to_string(x1) +
+            " " + to_string(y1) +
+            " " + to_string(x2) +
+            " " + to_string(y2);
+    } else
     {
         return {};
     }
@@ -333,7 +408,6 @@
         return {};
     }
 
-    result.easing = animation::smoothing::easing_map.at(result.easing_name);
     if (suffix == "s")
     {
         result.length_ms = N * 1000;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wf-config-0.10.0/src/file.cpp 
new/wf-config-0.11.0/src/file.cpp
--- old/wf-config-0.10.0/src/file.cpp   2025-06-22 08:30:31.000000000 +0200
+++ new/wf-config-0.11.0/src/file.cpp   2026-07-20 21:40:27.000000000 +0200
@@ -40,6 +40,7 @@
 
     size_t source_line_number;
 };
+
 using lines_t = std::vector<line_t>;
 
 static lines_t split_to_lines(const std::string& source)
@@ -214,9 +215,8 @@
 };
 
 /**
- * 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.
+ * 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.
  */
@@ -252,9 +252,8 @@
 }
 
 /**
- * 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.
+ * 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.
  */
@@ -354,6 +353,10 @@
         for (auto opt : section->get_registered_options())
         {
             opt->priv->option_in_config_file = (reloaded.count(opt) > 0);
+
+            opt->priv->is_part_compound  = false; // will be re-set when 
updating compound options
+            opt->priv->could_be_compound = false; // will be re-set when 
updating compound options
+
             if (!opt->priv->option_in_config_file && !opt->is_locked())
             {
                 opt->reset_to_default();
@@ -374,6 +377,27 @@
             }
         }
     }
+
+    for (auto section : config.get_all_sections())
+    {
+        for (auto opt : section->get_registered_options())
+        {
+            if (!opt->priv->xml && !opt->priv->is_part_compound)
+            {
+                if (opt->priv->could_be_compound)
+                {
+                    LOGW("Option ", section->get_name(), "/", opt->get_name(),
+                        " could not be parsed as part of a compound option: 
missing entries or wrong type!");
+                } else
+                {
+                    LOGW("Loaded option ", section->get_name(), "/", 
opt->get_name(),
+                        ", which does not belong to any registered plugin, nor 
could be parsed as a part of ",
+                        "a compound list option. Make sure all the relevant 
XML files are installed and "
+                        "that the option name is spelled correctly!");
+                }
+            }
+        }
+    }
 }
 
 std::string wf::config::save_configuration_options_to_string(
@@ -518,16 +542,13 @@
     flock(fd, LOCK_UN);
     close(fd);
 
-    /* Modify the file one last time. Now programs waiting for updates can
-     * acquire a shared lock. */
+    /* 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)
@@ -562,23 +583,23 @@
     // xmlFreeDoc(doc); - May clear the XML nodes before they are used
 }
 
-static wf::config::config_manager_t load_xml_files(
-    const std::vector<std::string>& xmldirs)
+static wf::config::config_manager_t load_xml_files(const 
std::vector<std::string>& xmldirs)
 {
     wf::config::config_manager_t manager;
 
     for (auto& xmldir : xmldirs)
     {
         auto xmld = opendir(xmldir.c_str());
-        if (NULL == xmld)
+        if (!xmld)
         {
             LOGW("Failed to open XML directory ", xmldir);
             continue;
         }
 
-        LOGI("Reading XML configuration options from directory ", xmldir);
+        std::vector<std::string> loaded_files;
+
         struct dirent *entry;
-        while ((entry = readdir(xmld)) != NULL)
+        while ((entry = readdir(xmld)) != nullptr)
         {
             if ((entry->d_type != DT_LNK) && (entry->d_type != DT_REG) &&
                 (entry->d_type != DT_UNKNOWN))
@@ -591,10 +612,29 @@
                 (filename.rfind(".xml") == filename.length() - 4))
             {
                 process_xml_file(manager, filename);
+                loaded_files.push_back(entry->d_name);
             }
         }
 
         closedir(xmld);
+
+        if (!loaded_files.empty())
+        {
+            LOGI("Loaded XML configuration options from ", loaded_files.size(),
+                " files in ", xmldir, ":");
+
+            std::string list;
+            for (size_t i = 0; i < loaded_files.size(); ++i)
+            {
+                list += loaded_files[i];
+                if (i + 1 != loaded_files.size())
+                {
+                    list += ", ";
+                }
+            }
+
+            LOGI(list);
+        }
     }
 
     return manager;
@@ -643,3 +683,32 @@
     load_configuration_options_from_file(manager, userconf);
     return manager;
 }
+
+void wf::config::load_configuration_options_from_xml_dirs(
+    wf::config::config_manager_t& manager,
+    const std::vector<std::string>& xmldirs)
+{
+    auto loaded = load_xml_files(xmldirs);
+    for (auto& section : loaded.get_all_sections())
+    {
+        auto existing = manager.get_section(section->get_name());
+        if (!existing)
+        {
+            manager.merge_section(section);
+            continue;
+        }
+
+        for (auto& option : section->get_registered_options())
+        {
+            auto existing_option = existing->get_option_or(option->get_name());
+            if (!existing_option)
+            {
+                existing->register_new_option(option);
+            } else if (!xml::get_option_xml_node(existing_option))
+            {
+                option->set_value_str(existing_option->get_value_str());
+                existing->register_new_option(option);
+            }
+        }
+    }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wf-config-0.10.0/src/option-impl.hpp 
new/wf-config-0.11.0/src/option-impl.hpp
--- old/wf-config-0.10.0/src/option-impl.hpp    2025-06-22 08:30:31.000000000 
+0200
+++ new/wf-config-0.11.0/src/option-impl.hpp    2026-07-20 21:40:27.000000000 
+0200
@@ -6,21 +6,6 @@
 #include <libxml/tree.h>
 #include <stdint.h>
 
-struct wf::config::option_base_t::impl
-{
-    std::string name;
-    wf::safe_list_t<updated_callback_t*> 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
@@ -37,3 +22,23 @@
     const std::shared_ptr<section_t>& section);
 }
 }
+
+struct wf::config::option_base_t::impl
+{
+    std::string name;
+    wf::safe_list_t<updated_callback_t*> updated_handlers;
+
+    // Number of times the option has been locked
+    int32_t lock_count = 0;
+
+    // Associated XML node
+    xmlNode *xml = nullptr;
+
+    // Is option in config file?
+    bool option_in_config_file = false;
+
+    // Is option part of a successfully parsed compound option?
+    bool is_part_compound = false;
+    // Does this option match a compound option in part at least?
+    bool could_be_compound = false;
+};
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wf-config-0.10.0/src/option.cpp 
new/wf-config-0.11.0/src/option.cpp
--- old/wf-config-0.10.0/src/option.cpp 2025-06-22 08:30:31.000000000 +0200
+++ new/wf-config-0.11.0/src/option.cpp 2026-07-20 21:40:27.000000000 +0200
@@ -5,7 +5,7 @@
 #include "option-impl.hpp"
 #include "wayfire/util/log.hpp"
 
-std::string wf::config::option_base_t::get_name() const
+const std::string& wf::config::option_base_t::get_name() const
 {
     return this->priv->name;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wf-config-0.10.0/src/types.cpp 
new/wf-config-0.11.0/src/types.cpp
--- old/wf-config-0.10.0/src/types.cpp  2025-06-22 08:30:31.000000000 +0200
+++ new/wf-config-0.11.0/src/types.cpp  2026-07-20 21:40:27.000000000 +0200
@@ -1067,9 +1067,14 @@
 }
 
 /* ------------------------- Output config types ---------------------------- 
*/
-wf::output_config::mode_t::mode_t(bool auto_on)
+wf::output_config::mode_t::mode_t(output_config::mode_type_t mode)
 {
-    this->type = auto_on ? MODE_AUTO : MODE_OFF;
+    if ((mode == MODE_RESOLUTION) || (mode == MODE_MIRROR))
+    {
+        throw std::invalid_argument("Invalid mode definition");
+    }
+
+    this->type = mode;
 }
 
 wf::output_config::mode_t::mode_t(int32_t width, int32_t height, int32_t 
refresh)
@@ -1132,6 +1137,8 @@
         return mirror_from == other.mirror_from;
 
       case MODE_AUTO:
+      case MODE_HIGHRR:
+      case MODE_HIGHRES:
       case MODE_OFF:
         return true;
     }
@@ -1145,12 +1152,22 @@
 {
     if (string == "off")
     {
-        return wf::output_config::mode_t{false};
+        return 
wf::output_config::mode_t{wf::output_config::mode_type_t::MODE_OFF};
     }
 
     if ((string == "auto") || (string == "default"))
     {
-        return wf::output_config::mode_t{true};
+        return 
wf::output_config::mode_t{wf::output_config::mode_type_t::MODE_AUTO};
+    }
+
+    if ((string == "highres"))
+    {
+        return 
wf::output_config::mode_t{wf::output_config::mode_type_t::MODE_HIGHRES};
+    }
+
+    if ((string == "highrr"))
+    {
+        return 
wf::output_config::mode_t{wf::output_config::mode_type_t::MODE_HIGHRR};
     }
 
     if (string.substr(0, 6) == "mirror")
@@ -1204,6 +1221,12 @@
       case output_config::MODE_AUTO:
         return "auto";
 
+      case output_config::MODE_HIGHRR:
+        return "highrr";
+
+      case output_config::MODE_HIGHRES:
+        return "highres";
+
       case output_config::MODE_OFF:
         return "off";
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wf-config-0.10.0/test/file_test.cpp 
new/wf-config-0.11.0/test/file_test.cpp
--- old/wf-config-0.10.0/test/file_test.cpp     2025-06-22 08:30:31.000000000 
+0200
+++ new/wf-config-0.11.0/test/file_test.cpp     2026-07-20 21:40:27.000000000 
+0200
@@ -94,6 +94,10 @@
     EXPECT_LINE(log, "Error in file test:5");
     EXPECT_LINE(log, "Error in file test:20");
     EXPECT_LINE(log, "Error in file test:21");
+
+    // reset logging state for subsequent tests
+    wf::log::initialize_logging(std::cout, wf::log::LOG_LEVEL_DEBUG,
+        wf::log::LOG_COLOR_MODE_OFF);
 }
 
 TEST_CASE("wf::config::load_configuration_options_from_string - "
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wf-config-0.10.0/test/types_test.cpp 
new/wf-config-0.11.0/test/types_test.cpp
--- old/wf-config-0.10.0/test/types_test.cpp    2025-06-22 08:30:31.000000000 
+0200
+++ new/wf-config-0.11.0/test/types_test.cpp    2026-07-20 21:40:27.000000000 
+0200
@@ -416,9 +416,11 @@
     using namespace wf::output_config;
 
     std::vector<mt> modes = {
-        mt{true},
-        mt{true},
-        mt{false},
+        mt{MODE_HIGHRES},
+        mt{MODE_HIGHRR},
+        mt{MODE_AUTO},
+        mt{MODE_AUTO},
+        mt{MODE_OFF},
         mt{1920, 1080, 0},
         mt{1920, 1080, 59000},
         mt{1920, 1080, 59000},
@@ -426,6 +428,8 @@
     };
 
     std::vector<mode_type_t> types = {
+        MODE_HIGHRES,
+        MODE_HIGHRR,
         MODE_AUTO,
         MODE_AUTO,
         MODE_OFF,
@@ -436,6 +440,8 @@
     };
 
     std::vector<std::string> desc = {
+        "highres",
+        "highrr",
         "auto",
         "default",
         "off",
@@ -452,10 +458,10 @@
         "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");
+    CHECK(modes[5].get_refresh() == 0);
+    CHECK(modes[6].get_refresh() == 59000);
+    CHECK(modes[7].get_refresh() == 59000);
+    CHECK(modes[8].get_mirror_from() == "eDP-1");
 
     for (size_t i = 0; i < modes.size(); i++)
     {
@@ -536,9 +542,26 @@
     };
     std::string sigmoid250ms_str = "250ms sigmoid";
 
+    adt custom4s = {
+        .length_ms = 4000,
+        .easing    = wf::animation::smoothing::get_cubic_bezier(0.25, 0.6, 
0.75, 0.4),
+        .easing_name = "cubic-bezier 0.25 0.6 0.75 0.4",
+    };
+    std::string custom4s_str = "4s cubic-bezier 0.25 0.6 0.75 0.4";
+
+    adt custom333ms = {
+        .length_ms = 333,
+        .easing    = wf::animation::smoothing::get_cubic_bezier(0.16, 1, 0.3, 
1),
+        .easing_name = "cubic-bezier 0.1600 1.0000 0.3000 1.0000",
+    };
+    std::string custom333ms_str = "333ms cubic-bezier 0.16 1 0.3 1";
+
     CHECK(from_string<adt>(circle100_str) == circle100);
     CHECK(from_string<adt>(circle100_str_2) == circle100);
     CHECK(from_string<adt>(linear8s_str) == linear8s);
     CHECK(from_string<adt>(sigmoid250ms_str) == sigmoid250ms);
+    CHECK(from_string<adt>(custom4s_str) == custom4s);
+    CHECK(from_string<adt>(custom333ms_str) == custom333ms);
+    CHECK(from_string<adt>(to_string<adt>(custom333ms)) == 
from_string<adt>(custom333ms_str));
     CHECK(to_string<adt>(sigmoid250ms) == sigmoid250ms_str);
 }

++++++ wf-config-0.10.0.tar.xz.sha256sum -> wf-config-0.11.0.tar.xz.sha256sum 
++++++
--- /work/SRC/openSUSE:Factory/wf-config/wf-config-0.10.0.tar.xz.sha256sum      
2025-09-04 18:01:43.388625562 +0200
+++ 
/work/SRC/openSUSE:Factory/.wf-config.new.1258/wf-config-0.11.0.tar.xz.sha256sum
    2026-08-18 16:38:49.192948009 +0200
@@ -1 +1 @@
-9676f08248aaf83b91ecce5c953326c4341084b6efa00d3757a936617a51e487 
*wf-config-0.10.0.tar.xz
+b7721326ade8d42b25ecd2d572e5deb853b1327672608472854c6473bc8d2514 
*wf-config-0.11.0.tar.xz

Reply via email to