mirror of
https://github.com/rad4day/Waybar.git
synced 2023-12-21 10:22:59 +01:00
2b735f44bc
Mouse-over tooltips set on the label only appear once the mouse hovers over exactly the label. Other apps (e.g. firefox) show the tooltip once the pointer hovers the button. Not solely its label. With this commit we get the same behaviour.
228 lines
7.5 KiB
C++
228 lines
7.5 KiB
C++
#include "modules/sway/language.hpp"
|
|
|
|
#include <fmt/core.h>
|
|
#include <json/json.h>
|
|
#include <spdlog/spdlog.h>
|
|
#include <xkbcommon/xkbregistry.h>
|
|
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#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)
|
|
: AButton(config, "language", id, "{}", 0, true) {
|
|
is_variant_displayed = format_.find("{variant}") != std::string::npos;
|
|
if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) {
|
|
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortName);
|
|
}
|
|
if (format_.find("{shortDescription}") != std::string::npos) {
|
|
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortDescription);
|
|
}
|
|
if (config.isMember("tooltip-format")) {
|
|
tooltip_format_ = config["tooltip-format"].asString();
|
|
}
|
|
ipc_.subscribe(R"(["input"])");
|
|
ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent));
|
|
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd));
|
|
ipc_.sendCmd(IPC_GET_INPUTS);
|
|
// Launch worker
|
|
ipc_.setWorker([this] {
|
|
try {
|
|
ipc_.handleEvent();
|
|
} catch (const std::exception& e) {
|
|
spdlog::error("Language: {}", e.what());
|
|
}
|
|
});
|
|
dp.emit();
|
|
}
|
|
|
|
void Language::onCmd(const struct Ipc::ipc_response& res) {
|
|
if (res.type != IPC_GET_INPUTS) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
auto payload = parser_.parse(res.payload);
|
|
std::vector<std::string> 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;
|
|
}
|
|
}
|
|
|
|
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());
|
|
}
|
|
}
|
|
|
|
void Language::onEvent(const struct Ipc::ipc_response& res) {
|
|
if (res.type != IPC_EVENT_INPUT) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
auto payload = parser_.parse(res.payload)["input"];
|
|
if (payload["type"].asString() == "keyboard") {
|
|
set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
|
|
}
|
|
dp.emit();
|
|
} catch (const std::exception& e) {
|
|
spdlog::error("Language: {}", e.what());
|
|
}
|
|
}
|
|
|
|
auto Language::update() -> void {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
auto display_layout = trim(fmt::format(
|
|
format_, fmt::arg("short", layout_.short_name),
|
|
fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name),
|
|
fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag())));
|
|
label_->set_markup(display_layout);
|
|
if (tooltipEnabled()) {
|
|
if (tooltip_format_ != "") {
|
|
auto tooltip_display_layout = trim(
|
|
fmt::format(tooltip_format_, fmt::arg("short", layout_.short_name),
|
|
fmt::arg("shortDescription", layout_.short_description),
|
|
fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant),
|
|
fmt::arg("flag", layout_.country_flag())));
|
|
button_.set_tooltip_markup(tooltip_display_layout);
|
|
} else {
|
|
button_.set_tooltip_markup(display_layout);
|
|
}
|
|
}
|
|
|
|
event_box_.show();
|
|
|
|
// Call parent update
|
|
AButton::update();
|
|
}
|
|
|
|
auto Language::set_current_layout(std::string current_layout) -> void {
|
|
button_.get_style_context()->remove_class(layout_.short_name);
|
|
layout_ = layouts_map_[current_layout];
|
|
button_.get_style_context()->add_class(layout_.short_name);
|
|
}
|
|
|
|
auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> void {
|
|
std::map<std::string, std::vector<Layout*>> found_by_short_names;
|
|
XKBContext xkb_context;
|
|
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<std::string, int> short_name_to_number_map;
|
|
for (const auto& used_layout_name : used_layouts) {
|
|
auto found = layouts_map_.find(used_layout_name);
|
|
if (found == layouts_map_.end()) continue;
|
|
auto used_layout = &found->second;
|
|
auto layouts_with_same_name_list = found_by_short_names[used_layout->short_name];
|
|
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;
|
|
}
|
|
|
|
if (displayed_short_flag != static_cast<std::byte>(0)) {
|
|
int& number = short_name_to_number_map[used_layout->short_name];
|
|
used_layout->short_name = used_layout->short_name + std::to_string(number);
|
|
used_layout->short_description = used_layout->short_description + std::to_string(number);
|
|
++number;
|
|
}
|
|
}
|
|
}
|
|
|
|
Language::XKBContext::XKBContext() {
|
|
context_ = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES);
|
|
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_);
|
|
auto short_description_ = rxkb_layout_get_brief(xkb_layout_);
|
|
std::string short_description;
|
|
if (short_description_ != nullptr) {
|
|
short_description = std::string(short_description_);
|
|
base_layouts_by_name_.emplace(name, xkb_layout_);
|
|
} else {
|
|
auto base_layout = base_layouts_by_name_[name];
|
|
short_description =
|
|
base_layout == nullptr ? "" : std::string(rxkb_layout_get_brief(base_layout));
|
|
}
|
|
delete layout_;
|
|
layout_ = new Layout{description, name, variant, short_description};
|
|
return layout_;
|
|
}
|
|
|
|
Language::XKBContext::~XKBContext() {
|
|
rxkb_context_unref(context_);
|
|
delete layout_;
|
|
}
|
|
|
|
std::string Language::Layout::country_flag() const {
|
|
if (short_name.size() != 2) return "";
|
|
unsigned char result[] = "\xf0\x9f\x87\x00\xf0\x9f\x87\x00";
|
|
result[3] = short_name[0] + 0x45;
|
|
result[7] = short_name[1] + 0x45;
|
|
// Check if both emojis are in A-Z symbol bounds
|
|
if (result[3] < 0xa6 || result[3] > 0xbf) return "";
|
|
if (result[7] < 0xa6 || result[7] > 0xbf) return "";
|
|
return std::string{reinterpret_cast<char*>(result)};
|
|
}
|
|
} // namespace waybar::modules::sway
|