From 98dffc292a36fb07b65d5760824de62bbefff613 Mon Sep 17 00:00:00 2001
From: dvermd <315743+dvermd@users.noreply.github.com>
Date: Tue, 5 Oct 2021 12:12:47 +0200
Subject: [PATCH] feat(xkeyboard): Icon matching using variant (#2521)
* feat(string_util): add contains_nocase
* feat(xkeyboard): Enable icon by variant
* Cleanup
* string_util: add some cases to string test
* string_util: rename contains_nocase -> contains_ignore_case
* layouticonset: use contains_ignore_case
* layouticonset: apply renamings and remove dead code
* remove VARIANT_NONE and use empty string instead
* use emplace_back and add assert
* layouticonset: precompute condition
* xkeyboard: restore missing continue
* Cleanup parse_icons
* Always choose the first case-insensitive match
* Cleanup layouticonset.get
* update the changelog
Co-authored-by: patrick96
---
CHANGELOG.md | 2 +
include/drawtypes/layouticonset.hpp | 33 +++++++
include/modules/xkeyboard.hpp | 6 +-
include/utils/string.hpp | 4 +-
src/CMakeLists.txt | 1 +
src/drawtypes/layouticonset.cpp | 99 ++++++++++++++++++++
src/modules/xkeyboard.cpp | 58 ++++++++++--
src/utils/string.cpp | 16 +++-
tests/CMakeLists.txt | 1 +
tests/unit_tests/drawtypes/layouticonset.cpp | 76 +++++++++++++++
tests/unit_tests/utils/string.cpp | 22 +++++
11 files changed, 302 insertions(+), 16 deletions(-)
create mode 100644 include/drawtypes/layouticonset.hpp
create mode 100644 src/drawtypes/layouticonset.cpp
create mode 100644 tests/unit_tests/drawtypes/layouticonset.cpp
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