diff --git a/Dockerfiles/debian b/Dockerfiles/debian index f7f6e633..026d8fdb 100644 --- a/Dockerfiles/debian +++ b/Dockerfiles/debian @@ -3,5 +3,5 @@ FROM debian:sid RUN apt-get update && \ - apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev libinput-dev wayland-protocols libwayland-client0 libwayland-cursor0 libwayland-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libxkbcommon-dev libudev-dev libpixman-1-dev libgtkmm-3.0-dev libjsoncpp-dev scdoc libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev libpulse-dev libmpdclient-dev gobject-introspection libgirepository1.0-dev libxkbcommon && \ + apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev libinput-dev wayland-protocols libwayland-client0 libwayland-cursor0 libwayland-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libxkbcommon-dev libudev-dev libpixman-1-dev libgtkmm-3.0-dev libjsoncpp-dev scdoc libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev libpulse-dev libmpdclient-dev gobject-introspection libgirepository1.0-dev libxkbcommon-dev libxkbregistry-dev libxkbregistry0 && \ apt-get clean diff --git a/Dockerfiles/opensuse b/Dockerfiles/opensuse index eb231fdb..49dea272 100644 --- a/Dockerfiles/opensuse +++ b/Dockerfiles/opensuse @@ -3,5 +3,7 @@ FROM opensuse/tumbleweed:latest RUN zypper -n up && \ + zypper addrepo https://download.opensuse.org/repositories/X11:Wayland/openSUSE_Tumbleweed/X11:Wayland.repo | echo 'a' && \ + zypper -n refresh && \ zypper -n install -t pattern devel_C_C++ && \ - zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel scdoc libxkbcommon + zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel libxkbregistry-devel scdoc diff --git a/include/modules/sni/item.hpp b/include/modules/sni/item.hpp index 3cbd0b74..1f1503ee 100644 --- a/include/modules/sni/item.hpp +++ b/include/modules/sni/item.hpp @@ -11,8 +11,16 @@ #include #include +#include +#include + namespace waybar::modules::SNI { +struct ToolTip { + Glib::ustring icon_name; + Glib::ustring text; +}; + class Item : public sigc::trackable { public: Item(const std::string&, const std::string&, const Json::Value&); @@ -27,10 +35,8 @@ class Item : public sigc::trackable { Gtk::EventBox event_box; std::string category; std::string id; - std::string status; std::string title; - int32_t window_id; std::string icon_name; Glib::RefPtr icon_pixmap; Glib::RefPtr icon_theme; @@ -39,6 +45,7 @@ class Item : public sigc::trackable { std::string attention_movie_name; std::string icon_theme_path; std::string menu; + ToolTip tooltip; DbusmenuGtkMenu* dbus_menu = nullptr; Gtk::Menu* gtk_menu = nullptr; /** @@ -51,6 +58,7 @@ class Item : public sigc::trackable { private: void proxyReady(Glib::RefPtr& result); void setProperty(const Glib::ustring& name, Glib::VariantBase& value); + void setStatus(const Glib::ustring& value); void getUpdatedProperties(); void processUpdatedProperties(Glib::RefPtr& result); void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, @@ -62,10 +70,18 @@ class Item : public sigc::trackable { static void onMenuDestroyed(Item* self, GObject* old_menu_pointer); void makeMenu(); bool handleClick(GdkEventButton* const& /*ev*/); + bool handleScroll(GdkEventScroll* const&); + + // smooth scrolling threshold + gdouble scroll_threshold_ = 0; + gdouble distance_scrolled_x_ = 0; + gdouble distance_scrolled_y_ = 0; + // visibility of items with Status == Passive + bool show_passive_ = false; Glib::RefPtr proxy_; Glib::RefPtr cancellable_; - bool update_pending_; + std::set update_pending_; }; } // namespace waybar::modules::SNI 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/man/waybar-tray.5.scd b/man/waybar-tray.5.scd index cd0e93f6..c664594d 100644 --- a/man/waybar-tray.5.scd +++ b/man/waybar-tray.5.scd @@ -16,6 +16,15 @@ Addressed by *tray* typeof: integer ++ Defines the size of the tray icons. +*show-passive-items*: ++ + typeof: bool ++ + default: false ++ + Defines visibility of the tray icons with *Passive* status. + +*smooth-scrolling-threshold*: ++ + typeof: double ++ + Threshold to be used when scrolling. + *spacing*: ++ typeof: integer ++ Defines the spacing between the tray icons. @@ -37,3 +46,6 @@ Addressed by *tray* # STYLE - *#tray* +- *#tray > .passive* +- *#tray > .active* +- *#tray > .needs-attention* diff --git a/meson.build b/meson.build index f3b50f7b..1c065f7f 100644 --- a/meson.build +++ b/meson.build @@ -96,6 +96,7 @@ libpulse = dependency('libpulse', required: get_option('pulseaudio')) libudev = dependency('libudev', required: get_option('libudev')) libevdev = dependency('libevdev', required: get_option('libevdev')) libmpdclient = dependency('libmpdclient', required: get_option('mpd')) +xkbregistry = dependency('xkbregistry') libsndio = compiler.find_library('sndio', required: get_option('sndio')) if libsndio.found() @@ -279,7 +280,8 @@ executable( libevdev, gtk_layer_shell, libsndio, - tz_dep + tz_dep, + xkbregistry ], include_directories: [include_directories('include')], install: true, diff --git a/resources/style.css b/resources/style.css index b585e5f1..c0d4d9b9 100644 --- a/resources/style.css +++ b/resources/style.css @@ -195,6 +195,15 @@ label:focus { background-color: #2980b9; } +#tray > .passive { + -gtk-icon-effect: dim; +} + +#tray > .needs-attention { + -gtk-icon-effect: highlight; + background-color: #eb4d4b; +} + #idle_inhibitor { background-color: #2d3436; } diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index abf95560..267cf63f 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -1,7 +1,12 @@ #include "modules/sni/item.hpp" + +#include #include +#include #include + #include +#include template <> struct fmt::formatter : formatter { @@ -39,14 +44,22 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf object_path(op), icon_size(16), effective_icon_size(0), - icon_theme(Gtk::IconTheme::create()), - update_pending_(false) { + icon_theme(Gtk::IconTheme::create()) { if (config["icon-size"].isUInt()) { icon_size = config["icon-size"].asUInt(); } + if (config["smooth-scrolling-threshold"].isNumeric()) { + scroll_threshold_ = config["smooth-scrolling-threshold"].asDouble(); + } + if (config["show-passive-items"].isBool()) { + show_passive_ = config["show-passive-items"].asBool(); + } event_box.add(image); - event_box.add_events(Gdk::BUTTON_PRESS_MASK); + event_box.add_events(Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick)); + event_box.signal_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll)); + // initial visibility + event_box.set_visible(show_passive_); cancellable_ = Gio::Cancellable::create(); @@ -73,12 +86,11 @@ void Item::proxyReady(Glib::RefPtr& result) { this->proxy_->signal_signal().connect(sigc::mem_fun(*this, &Item::onSignal)); - if (this->id.empty() || this->category.empty() || this->status.empty()) { + if (this->id.empty() || this->category.empty()) { spdlog::error("Invalid Status Notifier Item: {}, {}", bus_name, object_path); return; } this->updateImage(); - // this->event_box.set_tooltip_text(this->title); } catch (const Glib::Error& err) { spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); @@ -88,10 +100,24 @@ void Item::proxyReady(Glib::RefPtr& result) { } template -T get_variant(Glib::VariantBase& value) { +T get_variant(const Glib::VariantBase& value) { return Glib::VariantBase::cast_dynamic>(value).get(); } +template <> +ToolTip get_variant(const Glib::VariantBase& value) { + ToolTip result; + // Unwrap (sa(iiay)ss) + auto container = value.cast_dynamic(value); + result.icon_name = get_variant(container.get_child(0)); + result.text = get_variant(container.get_child(2)); + auto description = get_variant(container.get_child(3)); + if (!description.empty()) { + result.text = fmt::format("{}\n{}", result.text, description); + } + return result; +} + void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { try { spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, name, value); @@ -102,10 +128,11 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { id = get_variant(value); } else if (name == "Title") { title = get_variant(value); + if (tooltip.text.empty()) { + event_box.set_tooltip_markup(title); + } } else if (name == "Status") { - status = get_variant(value); - } else if (name == "WindowId") { - window_id = get_variant(value); + setStatus(get_variant(value)); } else if (name == "IconName") { icon_name = get_variant(value); } else if (name == "IconPixmap") { @@ -121,7 +148,10 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { } else if (name == "AttentionMovieName") { attention_movie_name = get_variant(value); } else if (name == "ToolTip") { - // TODO: tooltip + tooltip = get_variant(value); + if (!tooltip.text.empty()) { + event_box.set_tooltip_markup(tooltip.text); + } } else if (name == "IconThemePath") { icon_theme_path = get_variant(value); if (!icon_theme_path.empty()) { @@ -148,9 +178,22 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { } } -void Item::getUpdatedProperties() { - update_pending_ = false; +void Item::setStatus(const Glib::ustring& value) { + Glib::ustring lower = value.lowercase(); + event_box.set_visible(show_passive_ || lower.compare("passive") != 0); + auto style = event_box.get_style_context(); + for (const auto& class_name : style->list_classes()) { + style->remove_class(class_name); + } + if (lower.compare("needsattention") == 0) { + // convert status to dash-case for CSS + lower = "needs-attention"; + } + style->add_class(lower); +} + +void Item::getUpdatedProperties() { auto params = Glib::VariantContainerBase::create_tuple( {Glib::Variant::create(SNI_INTERFACE_NAME)}); proxy_->call("org.freedesktop.DBus.Properties.GetAll", @@ -167,33 +210,48 @@ void Item::processUpdatedProperties(Glib::RefPtr& _result) { auto properties = properties_variant.get(); for (const auto& [name, value] : properties) { - Glib::VariantBase old_value; - proxy_->get_cached_property(old_value, name); - if (!old_value || !value.equal(old_value)) { - proxy_->set_cached_property(name, value); + if (update_pending_.count(name.raw())) { setProperty(name, const_cast(value)); } } this->updateImage(); - // this->event_box.set_tooltip_text(this->title); } catch (const Glib::Error& err) { spdlog::warn("Failed to update properties: {}", err.what()); } catch (const std::exception& err) { spdlog::warn("Failed to update properties: {}", err.what()); } + update_pending_.clear(); } +/** + * Mapping from a signal name to a set of possibly changed properties. + * Commented signals are not handled by the tray module at the moment. + */ +static const std::map> signal2props = { + {"NewTitle", {"Title"}}, + {"NewIcon", {"IconName", "IconPixmap"}}, + // {"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}}, + // {"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}}, + {"NewIconThemePath", {"IconThemePath"}}, + {"NewToolTip", {"ToolTip"}}, + {"NewStatus", {"Status"}}, + // {"XAyatanaNewLabel", {"XAyatanaLabel"}}, +}; + void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, const Glib::VariantContainerBase& arguments) { spdlog::trace("Tray item '{}' got signal {}", id, signal_name); - if (!update_pending_ && signal_name.compare(0, 3, "New") == 0) { - /* Debounce signals and schedule update of all properties. - * Based on behavior of Plasma dataengine for StatusNotifierItem. - */ - update_pending_ = true; - Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties), - UPDATE_DEBOUNCE_TIME); + auto changed = signal2props.find(signal_name.raw()); + if (changed != signal2props.end()) { + if (update_pending_.empty()) { + /* Debounce signals and schedule update of all properties. + * Based on behavior of Plasma dataengine for StatusNotifierItem. + */ + Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties), + UPDATE_DEBOUNCE_TIME); + } + update_pending_.insert(changed->second.begin(), changed->second.end()); } } @@ -252,33 +310,42 @@ Glib::RefPtr Item::extractPixBuf(GVariant* variant) { } void Item::updateImage() { + auto scale_factor = image.get_scale_factor(); + auto scaled_icon_size = icon_size * scale_factor; + image.set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU); - image.set_pixel_size(icon_size); + image.set_pixel_size(scaled_icon_size); if (!icon_name.empty()) { try { // Try to find icons specified by path and filename std::ifstream temp(icon_name); if (temp.is_open()) { auto pixbuf = Gdk::Pixbuf::create_from_file(icon_name); + if (pixbuf->gobj() != nullptr) { // An icon specified by path and filename may be the wrong size for // the tray - // Keep the aspect ratio and scale to make the height equal to icon_size + // Keep the aspect ratio and scale to make the height equal to scaled_icon_size // If people have non square icons, assume they want it to grow in width not height - int width = icon_size * pixbuf->get_width() / pixbuf->get_height(); + int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height(); - pixbuf = pixbuf->scale_simple(width, icon_size, Gdk::InterpType::INTERP_BILINEAR); - image.set(pixbuf); + pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR); + + auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, 0, image.get_window()); + image.set(surface); } } else { - image.set(getIconByName(icon_name, icon_size)); + auto icon_by_name = getIconByName(icon_name, scaled_icon_size); + auto surface = Gdk::Cairo::create_surface_from_pixbuf(icon_by_name, 0, image.get_window()); + image.set(surface); } } catch (Glib::Error& e) { spdlog::error("Item '{}': {}", id, static_cast(e.what())); } } else if (icon_pixmap) { // An icon extracted may be the wrong size for the tray - icon_pixmap = icon_pixmap->scale_simple(icon_size, icon_size, Gdk::InterpType::INTERP_BILINEAR); + icon_pixmap = icon_pixmap->scale_simple(icon_size, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR); + auto surface = Gdk::Cairo::create_surface_from_pixbuf(icon_pixmap, 0, image.get_window()); image.set(icon_pixmap); } } @@ -360,4 +427,52 @@ bool Item::handleClick(GdkEventButton* const& ev) { return false; } +bool Item::handleScroll(GdkEventScroll* const& ev) { + int dx = 0, dy = 0; + switch (ev->direction) { + case GDK_SCROLL_UP: + dy = -1; + break; + case GDK_SCROLL_DOWN: + dy = 1; + break; + case GDK_SCROLL_LEFT: + dx = -1; + break; + case GDK_SCROLL_RIGHT: + dx = 1; + break; + case GDK_SCROLL_SMOOTH: + distance_scrolled_x_ += ev->delta_x; + distance_scrolled_y_ += ev->delta_y; + // check against the configured threshold and ensure that the absolute value >= 1 + if (distance_scrolled_x_ > scroll_threshold_) { + dx = (int)lround(std::max(distance_scrolled_x_, 1.0)); + distance_scrolled_x_ = 0; + } else if (distance_scrolled_x_ < -scroll_threshold_) { + dx = (int)lround(std::min(distance_scrolled_x_, -1.0)); + distance_scrolled_x_ = 0; + } + if (distance_scrolled_y_ > scroll_threshold_) { + dy = (int)lround(std::max(distance_scrolled_y_, 1.0)); + distance_scrolled_y_ = 0; + } else if (distance_scrolled_y_ < -scroll_threshold_) { + dy = (int)lround(std::min(distance_scrolled_y_, -1.0)); + distance_scrolled_y_ = 0; + } + break; + } + if (dx != 0) { + auto parameters = Glib::VariantContainerBase::create_tuple( + {Glib::Variant::create(dx), Glib::Variant::create("horizontal")}); + proxy_->call("Scroll", parameters); + } + if (dy != 0) { + auto parameters = Glib::VariantContainerBase::create_tuple( + {Glib::Variant::create(dy), Glib::Variant::create("vertical")}); + proxy_->call("Scroll", parameters); + } + return true; +} + } // namespace waybar::modules::SNI 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