diff --git a/include/modules/sway/ipc/ipc.hpp b/include/modules/sway/ipc/ipc.hpp index 2c5a7a6e..5f23d172 100644 --- a/include/modules/sway/ipc/ipc.hpp +++ b/include/modules/sway/ipc/ipc.hpp @@ -29,4 +29,8 @@ enum ipc_command_type { IPC_EVENT_BINDING = ((1 << 31) | 5), IPC_EVENT_SHUTDOWN = ((1 << 31) | 6), IPC_EVENT_TICK = ((1 << 31) | 7), + + // sway-specific event types + IPC_EVENT_BAR_STATE_UPDATE = ((1<<31) | 20), + IPC_EVENT_INPUT = ((1<<31) | 21), }; diff --git a/include/modules/sway/language.hpp b/include/modules/sway/language.hpp index 7cd6bf62..fb4438bb 100644 --- a/include/modules/sway/language.hpp +++ b/include/modules/sway/language.hpp @@ -1,6 +1,11 @@ #pragma once #include +#include + +#include +#include + #include "ALabel.hpp" #include "bar.hpp" #include "client.hpp" @@ -16,13 +21,40 @@ class Language : public ALabel, public sigc::trackable { auto update() -> void; private: + struct Layout { + std::string full_name; + std::string short_name; + std::string variant; + }; + + class XKBContext { + public: + XKBContext(); + ~XKBContext(); + auto next_layout() -> Layout*; + private: + rxkb_context* context_ = nullptr; + rxkb_layout* xkb_layout_ = nullptr; + Layout* layout_ = nullptr; + }; + void onEvent(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&); + + auto set_current_layout(std::string current_layout) -> void; + auto init_layouts_map(const std::vector& used_layouts) -> void; + + const static std::string XKB_LAYOUT_NAMES_KEY; + const static std::string XKB_ACTIVE_LAYOUT_NAME_KEY; - std::string lang_; - util::JsonParser parser_; - std::mutex mutex_; - Ipc ipc_; + Layout layout_; + std::map layouts_map_; + XKBContext xkb_context_; + bool is_variant_displayed; + + util::JsonParser parser_; + std::mutex mutex_; + Ipc ipc_; }; } // namespace waybar::modules::sway diff --git a/include/util/string.hpp b/include/util/string.hpp new file mode 100644 index 00000000..d644b4c4 --- /dev/null +++ b/include/util/string.hpp @@ -0,0 +1,15 @@ +#include + +const std::string WHITESPACE = " \n\r\t\f\v"; + +std::string ltrim(const std::string s) { + size_t begin = s.find_first_not_of(WHITESPACE); + return (begin == std::string::npos) ? "" : s.substr(begin); +} + +std::string rtrim(const std::string s) { + size_t end = s.find_last_not_of(WHITESPACE); + return (end == std::string::npos) ? "" : s.substr(0, end + 1); +} + +std::string trim(const std::string& s) { return rtrim(ltrim(s)); } diff --git a/man/waybar-sway-language.5.scd b/man/waybar-sway-language.5.scd index 769924f0..f7f88304 100644 --- a/man/waybar-sway-language.5.scd +++ b/man/waybar-sway-language.5.scd @@ -15,63 +15,30 @@ Addressed by *sway/language* *format*: ++ typeof: string ++ default: {} ++ - The format, how information should be displayed. On {} data gets inserted. - -*rotate*: ++ - typeof: integer ++ - Positive value to rotate the text label. - -*max-length*: ++ - typeof: integer ++ - The maximum length in character the module should display. - -*min-length*: ++ - typeof: integer ++ - The minimum length in characters the module should take up. - -*align*: ++ - typeof: float ++ - The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. - -*on-click*: ++ - typeof: string ++ - Command to execute when clicked on the module. - -*on-click-middle*: ++ - typeof: string ++ - Command to execute when middle-clicked on the module using mousewheel. - -*on-click-right*: ++ - typeof: string ++ - Command to execute when you right clicked on the module. - -*on-update*: ++ - typeof: string ++ - Command to execute when the module is updated. - -*on-scroll-up*: ++ - typeof: string ++ - Command to execute when scrolling up on the module. - -*on-scroll-down*: ++ - typeof: string ++ - Command to execute when scrolling down on the module. - -*smooth-scrolling-threshold*: ++ - typeof: double ++ - Threshold to be used when scrolling. + The format, how layout should be displayed. *tooltip*: ++ typeof: bool ++ default: true ++ Option to disable tooltip on hover. +# FORMAT REPLACEMENTS + +*{short}*: Short name of layout (e.g. "en"). Equals to {}. + +*{long}*: Long name of layout (e.g. "English (Dvorak)"). + +*{variant}*: Variant of layout (e.g. "Dvorak"). + # EXAMPLES ``` "sway/language": { "format": "{}", - "max-length": 50 +}, + +"sway/language": { + "format": "{short} {variant}", } ``` diff --git a/meson.build b/meson.build index e80448e0..1bd1f027 100644 --- a/meson.build +++ b/meson.build @@ -95,6 +95,7 @@ libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl')) libpulse = dependency('libpulse', required: get_option('pulseaudio')) libudev = dependency('libudev', required: get_option('libudev')) libmpdclient = dependency('libmpdclient', required: get_option('mpd')) +xkbregistry = dependency('xkbregistry') libsndio = compiler.find_library('sndio', required: get_option('sndio')) if libsndio.found() @@ -272,7 +273,8 @@ executable( libmpdclient, gtk_layer_shell, libsndio, - tz_dep + tz_dep, + xkbregistry ], include_directories: [include_directories('include')], install: true, diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp index 4d27fb88..86a8e1eb 100644 --- a/src/modules/sway/language.cpp +++ b/src/modules/sway/language.cpp @@ -1,10 +1,24 @@ #include "modules/sway/language.hpp" + +#include #include +#include + +#include +#include +#include + +#include "modules/sway/ipc/ipc.hpp" +#include "util/string.hpp" namespace waybar::modules::sway { +const std::string Language::XKB_LAYOUT_NAMES_KEY = "xkb_layout_names"; +const std::string Language::XKB_ACTIVE_LAYOUT_NAME_KEY = "xkb_active_layout_name"; + Language::Language(const std::string& id, const Json::Value& config) : ALabel(config, "language", id, "{}", 0, true) { + is_variant_displayed = format_.find("{variant}") != std::string::npos; ipc_.subscribe(R"(["input"])"); ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent)); ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd)); @@ -21,18 +35,31 @@ Language::Language(const std::string& id, const Json::Value& config) } void Language::onCmd(const struct Ipc::ipc_response& res) { + if (res.type != static_cast(IPC_GET_INPUTS)) { + return; + } + try { - auto payload = parser_.parse(res.payload); - //Display current layout of a device with a maximum count of layouts, expecting that all will be OK - Json::Value::ArrayIndex maxId = 0, max = 0; - for(Json::Value::ArrayIndex i = 0; i < payload.size(); i++) { - if(payload[i]["xkb_layout_names"].size() > max) { - max = payload[i]["xkb_layout_names"].size(); - maxId = i; + std::lock_guard lock(mutex_); + auto payload = parser_.parse(res.payload); + std::vector used_layouts; + // Display current layout of a device with a maximum count of layouts, expecting that all will + // be OK + Json::ArrayIndex max_id = 0, max = 0; + for (Json::ArrayIndex i = 0; i < payload.size(); i++) { + auto size = payload[i][XKB_LAYOUT_NAMES_KEY].size(); + if (size > max) { + max = size; + max_id = i; } } - auto layout_name = payload[maxId]["xkb_active_layout_name"].asString().substr(0,2); - lang_ = Glib::Markup::escape_text(layout_name); + + for (const auto& layout : payload[max_id][XKB_LAYOUT_NAMES_KEY]) { + used_layouts.push_back(layout.asString()); + } + + init_layouts_map(used_layouts); + set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString()); dp.emit(); } catch (const std::exception& e) { spdlog::error("Language: {}", e.what()); @@ -40,14 +67,15 @@ void Language::onCmd(const struct Ipc::ipc_response& res) { } void Language::onEvent(const struct Ipc::ipc_response& res) { + if (res.type != static_cast(IPC_EVENT_INPUT)) { + return; + } + try { std::lock_guard lock(mutex_); - auto payload = parser_.parse(res.payload)["input"]; + auto payload = parser_.parse(res.payload)["input"]; if (payload["type"].asString() == "keyboard") { - auto layout_name = payload["xkb_active_layout_name"].asString().substr(0,2); - if (!layout_name.empty()) { - lang_ = Glib::Markup::escape_text(layout_name); - } + set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString()); } dp.emit(); } catch (const std::exception& e) { @@ -56,17 +84,93 @@ void Language::onEvent(const struct Ipc::ipc_response& res) { } auto Language::update() -> void { - if (lang_.empty()) { - event_box_.hide(); - } else { - label_.set_markup(fmt::format(format_, lang_)); - if (tooltipEnabled()) { - label_.set_tooltip_text(lang_); - } - event_box_.show(); + auto display_layout = trim(fmt::format(format_, + fmt::arg("short", layout_.short_name), + fmt::arg("long", layout_.full_name), + fmt::arg("variant", layout_.variant))); + label_.set_markup(display_layout); + if (tooltipEnabled()) { + label_.set_tooltip_markup(display_layout); } + + event_box_.show(); + // Call parent update ALabel::update(); } +auto Language::set_current_layout(std::string current_layout) -> void { + layout_ = layouts_map_[current_layout]; +} + +auto Language::init_layouts_map(const std::vector& used_layouts) -> void { + std::map> found_by_short_names; + auto layout = xkb_context_.next_layout(); + for (; layout != nullptr; layout = xkb_context_.next_layout()) { + if (std::find(used_layouts.begin(), used_layouts.end(), layout->full_name) == + used_layouts.end()) { + continue; + } + + if (!is_variant_displayed) { + auto short_name = layout->short_name; + if (found_by_short_names.count(short_name) > 0) { + found_by_short_names[short_name].push_back(layout); + } else { + found_by_short_names[short_name] = {layout}; + } + } + + layouts_map_.emplace(layout->full_name, *layout); + } + + if (is_variant_displayed || found_by_short_names.size() == 0) { + return; + } + + std::map short_name_to_number_map; + for (const auto& used_layout_name : used_layouts) { + auto used_layout = &layouts_map_.find(used_layout_name)->second; + auto layouts_with_same_name_list = found_by_short_names[used_layout->short_name]; + spdlog::info("SIZE: " + std::to_string(layouts_with_same_name_list.size())); + if (layouts_with_same_name_list.size() < 2) { + continue; + } + + if (short_name_to_number_map.count(used_layout->short_name) == 0) { + short_name_to_number_map[used_layout->short_name] = 1; + } + + used_layout->short_name = + used_layout->short_name + std::to_string(short_name_to_number_map[used_layout->short_name]++); + } +} + +Language::XKBContext::XKBContext() { + context_ = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES); + rxkb_context_include_path_append_default(context_); + rxkb_context_parse_default_ruleset(context_); +} + +auto Language::XKBContext::next_layout() -> Layout* { + if (xkb_layout_ == nullptr) { + xkb_layout_ = rxkb_layout_first(context_); + } else { + xkb_layout_ = rxkb_layout_next(xkb_layout_); + } + + if (xkb_layout_ == nullptr) { + return nullptr; + } + + auto description = std::string(rxkb_layout_get_description(xkb_layout_)); + auto name = std::string(rxkb_layout_get_name(xkb_layout_)); + auto variant_ = rxkb_layout_get_variant(xkb_layout_); + std::string variant = variant_ == nullptr ? "" : std::string(variant_); + + layout_ = new Layout{description, name, variant}; + return layout_; +} + +Language::XKBContext::~XKBContext() { rxkb_context_unref(context_); } } // namespace waybar::modules::sway