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:
|
public:
|
||||||
XKBContext();
|
XKBContext();
|
||||||
~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:
|
private:
|
||||||
|
static const Layout fallback_;
|
||||||
|
|
||||||
|
bool want_unique_names_ = false;
|
||||||
rxkb_context* context_ = nullptr;
|
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::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 onEvent(const struct Ipc::ipc_response&);
|
||||||
void onCmd(const struct Ipc::ipc_response&);
|
void onCmd(const struct Ipc::ipc_response&);
|
||||||
|
|
||||||
auto set_current_layout(std::string current_layout) -> void;
|
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_LAYOUT_NAMES_KEY;
|
||||||
const static std::string XKB_ACTIVE_LAYOUT_NAME_KEY;
|
const static std::string XKB_ACTIVE_LAYOUT_NAME_KEY;
|
||||||
|
|
||||||
Layout layout_;
|
Layout layout_;
|
||||||
|
XKBContext xkb_context_;
|
||||||
std::string tooltip_format_ = "";
|
std::string tooltip_format_ = "";
|
||||||
std::map<std::string, Layout> layouts_map_;
|
std::vector<std::string> layouts_;
|
||||||
bool hide_single_;
|
bool hide_single_;
|
||||||
std::underlying_type_t<VisibleFields> visible_fields = VisibleFields::None;
|
std::underlying_type_t<VisibleFields> visible_fields = VisibleFields::None;
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,6 @@ void Language::onCmd(const struct Ipc::ipc_response& res) {
|
||||||
try {
|
try {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
auto payload = parser_.parse(res.payload);
|
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
|
// Display current layout of a device with a maximum count of layouts, expecting that all will
|
||||||
// be OK
|
// be OK
|
||||||
Json::ArrayIndex max_id = 0, max = 0;
|
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]) {
|
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());
|
set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
|
||||||
dp.emit();
|
dp.emit();
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
|
@ -86,10 +95,26 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
auto payload = parser_.parse(res.payload)["input"];
|
auto payload = parser_.parse(res.payload);
|
||||||
if (payload["type"].asString() == "keyboard") {
|
|
||||||
set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
|
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();
|
dp.emit();
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
spdlog::error("Language: {}", e.what());
|
spdlog::error("Language: {}", e.what());
|
||||||
|
@ -98,7 +123,7 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
|
||||||
|
|
||||||
auto Language::update() -> void {
|
auto Language::update() -> void {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
if (hide_single_ && layouts_map_.size() <= 1) {
|
if (hide_single_ && layouts_.size() <= 1) {
|
||||||
event_box_.hide();
|
event_box_.hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -128,77 +153,87 @@ auto Language::update() -> void {
|
||||||
|
|
||||||
auto Language::set_current_layout(std::string current_layout) -> void {
|
auto Language::set_current_layout(std::string current_layout) -> void {
|
||||||
label_.get_style_context()->remove_class(layout_.short_name);
|
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);
|
label_.get_style_context()->add_class(layout_.short_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> void {
|
const Language::Layout Language::XKBContext::fallback_;
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Language::XKBContext::XKBContext() {
|
Language::XKBContext::XKBContext() {
|
||||||
context_ = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES);
|
context_ = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES);
|
||||||
rxkb_context_parse_default_ruleset(context_);
|
rxkb_context_parse_default_ruleset(context_);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Language::XKBContext::next_layout() -> Layout* {
|
void Language::XKBContext::initLayouts(const std::vector<std::string>& names,
|
||||||
if (xkb_layout_ == nullptr) {
|
bool want_unique_names) {
|
||||||
xkb_layout_ = rxkb_layout_first(context_);
|
want_unique_names_ = want_unique_names;
|
||||||
} else {
|
|
||||||
xkb_layout_ = rxkb_layout_next(xkb_layout_);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (xkb_layout_ == nullptr) {
|
const auto* full_name = rxkb_layout_get_description(xkb_layout);
|
||||||
return nullptr;
|
if (std::find(names.begin(), names.end(), full_name) == names.end()) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
layout_ = std::make_unique<Layout>(xkb_layout_);
|
newCachedEntry(full_name, xkb_layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!layout_->short_description.empty()) {
|
const Language::Layout& Language::XKBContext::getLayout(const std::string& name) {
|
||||||
base_layouts_by_name_.emplace(layout_->short_name, xkb_layout_);
|
if (auto it = cached_layouts_.find(name); it != cached_layouts_.end()) {
|
||||||
} else if (auto base = base_layouts_by_name_.find(layout_->short_name);
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback_;
|
||||||
|
}
|
||||||
|
|
||||||
|
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()) {
|
base != base_layouts_by_name_.end()) {
|
||||||
layout_->short_description = rxkb_layout_get_brief(base->second);
|
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_); }
|
Language::XKBContext::~XKBContext() { rxkb_context_unref(context_); }
|
||||||
|
|
Loading…
Reference in New Issue