diff --git a/CHANGELOG.md b/CHANGELOG.md index bc0b3589..01e7f2c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -122,6 +122,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `double-click-interval` setting to the bar section to control the time interval in which a double-click is recognized. Defaults to 400 (ms) ([`#1441`](https://github.com/polybar/polybar/issues/1441)) +- `internal/xkeyboard`: Allow configuring icons using variant + ([`#2414`](https://github.com/polybar/polybar/issues/2414)) ### Changed - We rewrote polybar's main event loop. This shouldn't change any behavior for diff --git a/include/drawtypes/layouticonset.hpp b/include/drawtypes/layouticonset.hpp new file mode 100644 index 00000000..f3b42308 --- /dev/null +++ b/include/drawtypes/layouticonset.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include "common.hpp" +#include "drawtypes/label.hpp" +#include "utils/mixins.hpp" + +using std::tuple; + +POLYBAR_NS + +namespace drawtypes { + class layouticonset : public non_copyable_mixin { + public: + explicit layouticonset(label_t&& default_icon); + + bool add(const string& layout, const string& variant, label_t&& icon); + label_t get(const string& layout, const string& variant) const; + bool contains(const string& layout, const string& variant) const; + + static constexpr const char* VARIANT_ANY = "_"; + + protected: + label_t m_default_icon; + vector> m_layout_icons; + }; + + using layouticonset_t = shared_ptr; +} // namespace drawtypes + +POLYBAR_NS_END diff --git a/include/modules/xkeyboard.hpp b/include/modules/xkeyboard.hpp index d8d11926..2014b65c 100644 --- a/include/modules/xkeyboard.hpp +++ b/include/modules/xkeyboard.hpp @@ -3,6 +3,7 @@ #include "common.hpp" #include "components/config.hpp" #include "components/types.hpp" +#include "drawtypes/layouticonset.hpp" #include "modules/meta/event_handler.hpp" #include "modules/meta/static_module.hpp" #include "x11/extensions/xkb.hpp" @@ -40,6 +41,9 @@ namespace modules { void action_switch(); + void define_layout_icon(const string& entry, const string& layout, const string& variant, label_t&& icon); + void parse_icons(); + private: static constexpr const char* TAG_LABEL_LAYOUT{""}; static constexpr const char* TAG_LABEL_INDICATOR{""}; @@ -61,7 +65,7 @@ namespace modules { map m_indicator_off_labels; vector m_blacklist; - iconset_t m_layout_icons; + layouticonset_t m_layout_icons; iconset_t m_indicator_icons_on; iconset_t m_indicator_icons_off; }; diff --git a/include/utils/string.hpp b/include/utils/string.hpp index 53b9b9ae..17ff895c 100644 --- a/include/utils/string.hpp +++ b/include/utils/string.hpp @@ -62,6 +62,7 @@ namespace string_util { using hash_type = unsigned long; bool contains(const string& haystack, const string& needle); + bool contains_ignore_case(const string& haystack, const string& needle); string upper(const string& s); string lower(const string& s); bool compare(const string& s1, const string& s2); @@ -96,7 +97,8 @@ namespace string_util { string floating_point(double value, size_t precision, bool fixed = false, const string& locale = ""); string filesize_mib(unsigned long long kibibytes, size_t precision = 0, const string& locale = ""); string filesize_gib(unsigned long long kibibytes, size_t precision = 0, const string& locale = ""); - string filesize_gib_mib(unsigned long long kibibytes, size_t precision_mib = 0, size_t precision_gib = 0, const string& locale = ""); + string filesize_gib_mib( + unsigned long long kibibytes, size_t precision_mib = 0, size_t precision_gib = 0, const string& locale = ""); string filesize(unsigned long long kbytes, size_t precision = 0, bool fixed = false, const string& locale = ""); hash_type hash(const string& src); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c671ce2c..9d5c0ff5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -72,6 +72,7 @@ if(BUILD_LIBPOLY) ${src_dir}/drawtypes/animation.cpp ${src_dir}/drawtypes/iconset.cpp + ${src_dir}/drawtypes/layouticonset.cpp ${src_dir}/drawtypes/label.cpp ${src_dir}/drawtypes/progressbar.cpp ${src_dir}/drawtypes/ramp.cpp diff --git a/src/drawtypes/layouticonset.cpp b/src/drawtypes/layouticonset.cpp new file mode 100644 index 00000000..badf0676 --- /dev/null +++ b/src/drawtypes/layouticonset.cpp @@ -0,0 +1,99 @@ +#include "drawtypes/layouticonset.hpp" + +POLYBAR_NS + +namespace drawtypes { + layouticonset::layouticonset(label_t&& default_icon) : m_default_icon(default_icon) {} + + bool layouticonset::add(const string& layout, const string& variant, label_t&& icon) { + if (layout == VARIANT_ANY && variant == VARIANT_ANY) { + return false; + } + m_layout_icons.emplace_back(layout, variant, icon); + return true; + } + + label_t layouticonset::get(const string& layout, const string& variant) const { + // The layout, variant are matched against defined icons in that order: + // 1. perfect match on layout and perfect match on variant (ex: us;Colemak;) + // 2. perfect match on layout and case insensitive search on variant (ex: us;coLEmAk;) + // 3. perfect match on layout and the any variant '_' (ex: us; or us;_;) + // 4. any layout for icon and perfect match on variant (ex: _;Colemak;) + // 5. any layout for icon and case insensitive search on variant (ex: _;coLEmAk;) + // 6. no match at all => default icon if defined + + /* + * The minimal case that was matched. + * Once a case is matched, this is updated and no case with the same or higher number can be matched again. + */ + int min_case = 6; + + // Case 6: initializing with default + label_t icon = m_default_icon; + + for (auto it : m_layout_icons) { + const string& icon_layout = std::get<0>(it); + const string& icon_variant = std::get<1>(it); + label_t icon_label = std::get<2>(it); + + bool is_variant_match = icon_variant == variant; + + bool is_variant_any = icon_variant == VARIANT_ANY; + + bool is_variant_match_fuzzy = + !is_variant_any && !icon_variant.empty() && string_util::contains_ignore_case(variant, icon_variant); + + // Which of the 6 match cases is matched here. + int current_case = 6; + + if (icon_layout == layout) { + if (is_variant_match) { + // Case 1 + current_case = 1; + } else if (is_variant_match_fuzzy) { + // Case 2 + current_case = 2; + } else if (is_variant_any) { + // Case 3 + current_case = 3; + } + } else if (icon_layout == VARIANT_ANY) { + if (is_variant_match) { + // Case 4 + current_case = 4; + } else if (is_variant_match_fuzzy) { + // Case 5 + current_case = 5; + } + } + + /* + * We matched with a higher priority than before -> update icon. + */ + if (current_case < min_case) { + icon = icon_label; + min_case = current_case; + } + + if (current_case == 1) { + // Case 1: perfect match, we can break early + break; + } + } + + return icon; + } + + bool layouticonset::contains(const string& layout, const string& variant) const { + for (auto it : m_layout_icons) { + const string& icon_layout = std::get<0>(it); + const string& icon_variant = std::get<1>(it); + if (icon_layout == layout && icon_variant == variant) { + return true; + } + } + return false; + } +} // namespace drawtypes + +POLYBAR_NS_END diff --git a/src/modules/xkeyboard.cpp b/src/modules/xkeyboard.cpp index c07941ed..c50343f7 100644 --- a/src/modules/xkeyboard.cpp +++ b/src/modules/xkeyboard.cpp @@ -44,15 +44,7 @@ namespace modules { m_blacklist = m_conf.get_list(name(), "blacklist", {}); // load layout icons - m_layout_icons = std::make_shared(); - m_layout_icons->add(DEFAULT_LAYOUT_ICON, load_optional_label(m_conf, name(), DEFAULT_LAYOUT_ICON, ""s)); - - for (const auto& it : m_conf.get_list(name(), "layout-icon", {})) { - auto vec = string_util::tokenize(it, ';'); - if (vec.size() == 2) { - m_layout_icons->add(vec[0], std::make_shared