Guard set_current_layout against unknown layout names (wtype, hot-plugged

devices) so transient events stop blanking layout_. Refresh layouts_map_
on "added"/"xkb_keymap" input events and union layouts across all
keyboards so new devices contribute their layouts. Release mutex_ before
the refresh sendCmd to avoid self-deadlock from the synchronous
signal_cmd emit.
This commit is contained in:
Milad Alizadeh
2026-05-15 17:29:21 +01:00
parent 05945748dc
commit 0ec6d06472
+38 -11
View File
@@ -54,21 +54,28 @@ void Language::onCmd(const struct Ipc::ipc_response& res) {
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; std::vector<std::string> used_layouts;
// Display current layout of a device with a maximum count of layouts, expecting that all will // Union layout names across every keyboard input so hot-plugged devices contribute their
// be OK // layouts to the map. Track the device with the most layouts to seed the initially displayed
// layout, matching the previous behaviour at startup.
Json::ArrayIndex max_id = 0, max = 0; Json::ArrayIndex max_id = 0, max = 0;
for (Json::ArrayIndex i = 0; i < payload.size(); i++) { for (Json::ArrayIndex i = 0; i < payload.size(); i++) {
auto size = payload[i][XKB_LAYOUT_NAMES_KEY].size(); if (payload[i]["type"].asString() != "keyboard") continue;
if (size > max) { const auto& names = payload[i][XKB_LAYOUT_NAMES_KEY];
max = size; if (names.size() > max) {
max = names.size();
max_id = i; max_id = i;
} }
for (const auto& layout : names) {
const auto name = layout.asString();
if (std::find(used_layouts.begin(), used_layouts.end(), name) == used_layouts.end()) {
used_layouts.push_back(name);
}
}
} }
for (const auto& layout : payload[max_id][XKB_LAYOUT_NAMES_KEY]) { // Rebuild from scratch so init_layouts_map's duplicate-suffix pass doesn't compound across
used_layouts.push_back(layout.asString()); // refreshes (e.g. "us" -> "us1" -> "us11").
} layouts_map_.clear();
init_layouts_map(used_layouts); init_layouts_map(used_layouts);
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();
@@ -82,13 +89,26 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
return; return;
} }
bool refresh_inputs = false;
try { try {
{
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload)["input"]; auto root = parser_.parse(res.payload);
auto change = root["change"].asString();
auto payload = root["input"];
if (payload["type"].asString() == "keyboard") { if (payload["type"].asString() == "keyboard") {
// A device was added or its keymap changed - the layout set may have grown, so refresh
// layouts_map_ via IPC_GET_INPUTS once we've released mutex_.
refresh_inputs = (change == "added" || change == "xkb_keymap");
set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString()); set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
} }
dp.emit(); dp.emit();
}
// sendCmd is synchronous: it blocks on the IPC reply and then emits signal_cmd on this same
// thread, which lands in onCmd and re-locks mutex_. Must call it with mutex_ released.
if (refresh_inputs) {
ipc_.sendCmd(IPC_GET_INPUTS);
}
} catch (const std::exception& e) { } catch (const std::exception& e) {
spdlog::error("Language: {}", e.what()); spdlog::error("Language: {}", e.what());
} }
@@ -125,8 +145,15 @@ auto Language::update() -> void {
} }
auto Language::set_current_layout(const std::string& current_layout) -> void { auto Language::set_current_layout(const std::string& current_layout) -> void {
// Guard against unknown / empty layout names: transient virtual keyboards (e.g. wtype) and
// hot-plugged devices whose layouts haven't made it into the map yet would otherwise blank out
// layout_ via map::operator[]'s default-construct-on-miss.
auto it = layouts_map_.find(current_layout);
if (it == layouts_map_.end()) {
return;
}
label_.get_style_context()->remove_class(layout_.short_name); label_.get_style_context()->remove_class(layout_.short_name);
layout_ = layouts_map_[current_layout]; layout_ = it->second;
label_.get_style_context()->add_class(layout_.short_name); label_.get_style_context()->add_class(layout_.short_name);
} }