fix(sway/language): process input configuration changes
Input change type `xkb_keymap` should update the list of available layouts and unhide the module if necessary. Only addition of new layouts is implemented for now.pull/2962/head
parent
6b45e21eb1
commit
17a483442f
|
@ -47,27 +47,39 @@ class Language : public ALabel, public sigc::trackable {
|
|||
public:
|
||||
XKBContext();
|
||||
~XKBContext();
|
||||
auto next_layout() -> Layout*;
|
||||
|
||||
void initLayouts(const std::vector<std::string>& names, bool want_unique_names);
|
||||
/*
|
||||
* Get layout info by full name (description).
|
||||
* The reference is guaranteed to be valid until the XKBContext is destroyed.
|
||||
*/
|
||||
const Layout& getLayout(const std::string& name);
|
||||
|
||||
private:
|
||||
static const Layout fallback_;
|
||||
|
||||
bool want_unique_names_ = false;
|
||||
rxkb_context* context_ = nullptr;
|
||||
rxkb_layout* xkb_layout_ = nullptr;
|
||||
std::unique_ptr<Layout> layout_;
|
||||
|
||||
std::map<std::string, Layout> cached_layouts_;
|
||||
std::map<std::string, rxkb_layout*> base_layouts_by_name_;
|
||||
std::multimap<std::string, Layout&> layouts_by_short_name_;
|
||||
|
||||
Layout& newCachedEntry(const std::string& name, rxkb_layout* xkb_layout);
|
||||
};
|
||||
|
||||
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<std::string>& used_layouts) -> void;
|
||||
|
||||
const static std::string XKB_LAYOUT_NAMES_KEY;
|
||||
const static std::string XKB_ACTIVE_LAYOUT_NAME_KEY;
|
||||
|
||||
Layout layout_;
|
||||
XKBContext xkb_context_;
|
||||
std::string tooltip_format_ = "";
|
||||
std::map<std::string, Layout> layouts_map_;
|
||||
std::vector<std::string> layouts_;
|
||||
bool hide_single_;
|
||||
std::underlying_type_t<VisibleFields> visible_fields = VisibleFields::None;
|
||||
|
||||
|
|
|
@ -55,7 +55,6 @@ void Language::onCmd(const struct Ipc::ipc_response& res) {
|
|||
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;
|
||||
|
@ -68,10 +67,20 @@ void Language::onCmd(const struct Ipc::ipc_response& res) {
|
|||
}
|
||||
|
||||
for (const auto& layout : payload[max_id][XKB_LAYOUT_NAMES_KEY]) {
|
||||
used_layouts.push_back(layout.asString());
|
||||
if (std::find(layouts_.begin(), layouts_.end(), layout.asString()) == layouts_.end()) {
|
||||
layouts_.push_back(layout.asString());
|
||||
}
|
||||
}
|
||||
|
||||
init_layouts_map(used_layouts);
|
||||
spdlog::debug("Language: layouts from the compositor: {}",
|
||||
fmt::join(layouts_.begin(), layouts_.end(), ", "));
|
||||
|
||||
// The configured format string does not allow distinguishing layout variants.
|
||||
// Add numeric suffixes to the layouts with the same short name.
|
||||
bool want_unique_names = ((visible_fields & VisibleFields::Variant) == 0) &&
|
||||
((visible_fields & ~VisibleFields::Variant) != 0);
|
||||
|
||||
xkb_context_.initLayouts(layouts_, want_unique_names);
|
||||
set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
|
||||
dp.emit();
|
||||
} catch (const std::exception& e) {
|
||||
|
@ -86,10 +95,26 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
|
|||
|
||||
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());
|
||||
auto payload = parser_.parse(res.payload);
|
||||
|
||||
const auto& input = payload["input"];
|
||||
if (input["type"] != "keyboard") {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& change = payload["change"];
|
||||
if (change == "added" || change == "xkb_keymap") {
|
||||
// Update list of configured layouts
|
||||
for (const auto& name : input[XKB_LAYOUT_NAMES_KEY]) {
|
||||
if (std::find(layouts_.begin(), layouts_.end(), name.asString()) == layouts_.end()) {
|
||||
layouts_.push_back(name.asString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto& layout = input[XKB_ACTIVE_LAYOUT_NAME_KEY].asString();
|
||||
spdlog::trace("Language: set layout {} for device {}", layout, input["identifier"].asString());
|
||||
set_current_layout(layout);
|
||||
dp.emit();
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("Language: {}", e.what());
|
||||
|
@ -98,7 +123,7 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
|
|||
|
||||
auto Language::update() -> void {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (hide_single_ && layouts_map_.size() <= 1) {
|
||||
if (hide_single_ && layouts_.size() <= 1) {
|
||||
event_box_.hide();
|
||||
return;
|
||||
}
|
||||
|
@ -128,77 +153,87 @@ auto Language::update() -> void {
|
|||
|
||||
auto Language::set_current_layout(std::string current_layout) -> void {
|
||||
label_.get_style_context()->remove_class(layout_.short_name);
|
||||
layout_ = layouts_map_[current_layout];
|
||||
layout_ = xkb_context_.getLayout(current_layout);
|
||||
label_.get_style_context()->add_class(layout_.short_name);
|
||||
}
|
||||
|
||||
auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> void {
|
||||
// First layout entry with this short name
|
||||
std::map<std::string_view, Layout&> layout_by_short_names;
|
||||
// Current number of layout entries with this short name
|
||||
std::map<std::string_view, int> count_by_short_names;
|
||||
XKBContext xkb_context;
|
||||
|
||||
bool want_unique_names = ((visible_fields & VisibleFields::Variant) == 0) &&
|
||||
((visible_fields & ~VisibleFields::Variant) != 0);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
auto [it, added] = layouts_map_.emplace(layout->full_name, *layout);
|
||||
if (!added) continue;
|
||||
|
||||
if (want_unique_names) {
|
||||
auto prev = layout_by_short_names.emplace(it->second.short_name, it->second).first;
|
||||
switch (int number = ++count_by_short_names[it->second.short_name]; number) {
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
// First duplicate appeared, add suffix to the original entry
|
||||
prev->second.addShortNameSuffix("1");
|
||||
G_GNUC_FALLTHROUGH;
|
||||
default:
|
||||
it->second.addShortNameSuffix(std::to_string(number));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spdlog::debug("Language: new layout '{}' short='{}' variant='{}' shortDescription='{}'",
|
||||
it->second.full_name, it->second.short_name, it->second.variant,
|
||||
it->second.short_description);
|
||||
}
|
||||
}
|
||||
const Language::Layout Language::XKBContext::fallback_;
|
||||
|
||||
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_);
|
||||
void Language::XKBContext::initLayouts(const std::vector<std::string>& names,
|
||||
bool want_unique_names) {
|
||||
want_unique_names_ = want_unique_names;
|
||||
|
||||
for (auto* xkb_layout = rxkb_layout_first(context_); xkb_layout != nullptr;
|
||||
xkb_layout = rxkb_layout_next(xkb_layout)) {
|
||||
const auto* short_name = rxkb_layout_get_name(xkb_layout);
|
||||
/* Assume that we see base layout first and remember it */
|
||||
if (const auto* brief = rxkb_layout_get_brief(xkb_layout); brief != nullptr) {
|
||||
base_layouts_by_name_.emplace(short_name, xkb_layout);
|
||||
}
|
||||
|
||||
const auto* full_name = rxkb_layout_get_description(xkb_layout);
|
||||
if (std::find(names.begin(), names.end(), full_name) == names.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
newCachedEntry(full_name, xkb_layout);
|
||||
}
|
||||
}
|
||||
|
||||
const Language::Layout& Language::XKBContext::getLayout(const std::string& name) {
|
||||
if (auto it = cached_layouts_.find(name); it != cached_layouts_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
if (xkb_layout_ == nullptr) {
|
||||
return nullptr;
|
||||
for (auto* xkb_layout = rxkb_layout_first(context_); xkb_layout != nullptr;
|
||||
xkb_layout = rxkb_layout_next(xkb_layout)) {
|
||||
if (name == rxkb_layout_get_description(xkb_layout)) {
|
||||
return newCachedEntry(name, xkb_layout);
|
||||
}
|
||||
}
|
||||
|
||||
layout_ = std::make_unique<Layout>(xkb_layout_);
|
||||
return fallback_;
|
||||
}
|
||||
|
||||
if (!layout_->short_description.empty()) {
|
||||
base_layouts_by_name_.emplace(layout_->short_name, xkb_layout_);
|
||||
} else if (auto base = base_layouts_by_name_.find(layout_->short_name);
|
||||
base != base_layouts_by_name_.end()) {
|
||||
layout_->short_description = rxkb_layout_get_brief(base->second);
|
||||
Language::Layout& Language::XKBContext::newCachedEntry(const std::string& name,
|
||||
rxkb_layout* xkb_layout) {
|
||||
auto [it, added] = cached_layouts_.try_emplace(name, xkb_layout);
|
||||
/* Existing entry (shouldn't happen) */
|
||||
if (!added) return it->second;
|
||||
|
||||
Layout& layout = it->second;
|
||||
|
||||
if (layout.short_description.empty()) {
|
||||
if (auto base = base_layouts_by_name_.find(layout.short_name);
|
||||
base != base_layouts_by_name_.end()) {
|
||||
layout.short_description = rxkb_layout_get_brief(base->second);
|
||||
}
|
||||
}
|
||||
|
||||
return layout_.get();
|
||||
if (want_unique_names_) {
|
||||
// Fetch and count already cached layouts with this short name
|
||||
auto [first, last] = layouts_by_short_name_.equal_range(layout.short_name);
|
||||
auto count = std::distance(first, last);
|
||||
// Add the new layout before we modify its short name
|
||||
layouts_by_short_name_.emplace(layout.short_name, layout);
|
||||
|
||||
if (count > 0) {
|
||||
if (count == 1) {
|
||||
// First duplicate appeared, add suffix to the original entry
|
||||
first->second.addShortNameSuffix("1");
|
||||
}
|
||||
layout.addShortNameSuffix(std::to_string(count + 1));
|
||||
}
|
||||
}
|
||||
|
||||
spdlog::debug("Language: new layout '{}' short='{}' variant='{}' shortDescription='{}'",
|
||||
layout.full_name, layout.short_name, layout.variant, layout.short_description);
|
||||
return layout;
|
||||
}
|
||||
|
||||
Language::XKBContext::~XKBContext() { rxkb_context_unref(context_); }
|
||||
|
|
Loading…
Reference in New Issue