fix(sway/language): apply CSS classes on the main thread to stop SIGSEGV

set_current_layout() mutated label_'s GTK style context (remove_class/
add_class) while being called from the sway IPC worker thread via
onEvent(). Off-main-thread GTK widget mutation caused a SIGSEGV.

Record only the target layout in set_current_layout() and apply the
matching CSS class in update(), which the dispatcher runs on the GTK
main thread. A new applied_class_ member tracks the currently applied
class so update() can swap it. The shared layout_/applied_class_ state
is guarded by the existing mutex_.

Fixes #3702.
This commit is contained in:
Alex
2026-07-04 03:14:13 +02:00
parent c29ed5f972
commit c0a26104a5
2 changed files with 18 additions and 2 deletions
+3
View File
@@ -54,6 +54,9 @@ class Language : public ALabel, public sigc::trackable {
const static std::string XKB_ACTIVE_LAYOUT_NAME_KEY;
Layout layout_;
// CSS class currently applied to label_. Tracked so update() (main thread) can swap classes
// instead of set_current_layout() mutating the widget from the IPC worker thread (#3702).
std::string applied_class_;
std::string tooltip_format_ = "";
std::map<std::string, Layout> layouts_map_;
bool hide_single_;
+15 -2
View File
@@ -116,6 +116,17 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
auto Language::update() -> void {
std::lock_guard<std::mutex> lock(mutex_);
// Apply the CSS class here, on the GTK main thread. set_current_layout() runs on the IPC worker
// thread, so mutating label_'s style context there would crash (#3702).
if (layout_.short_name != applied_class_) {
if (!applied_class_.empty()) {
label_.get_style_context()->remove_class(applied_class_);
}
if (!layout_.short_name.empty()) {
label_.get_style_context()->add_class(layout_.short_name);
}
applied_class_ = layout_.short_name;
}
if (hide_single_ && layouts_map_.size() <= 1) {
event_box_.hide();
return;
@@ -145,6 +156,10 @@ auto Language::update() -> void {
}
auto Language::set_current_layout(const std::string& current_layout) -> void {
// Runs on the IPC worker thread (via onEvent) as well as the main thread (via onCmd), so it must
// not touch GTK widgets - off-main-thread widget mutation caused SIGSEGV (#3702). Only record the
// target layout here; update() applies the matching CSS class on the main thread.
//
// 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.
@@ -152,9 +167,7 @@ auto Language::set_current_layout(const std::string& current_layout) -> void {
if (it == layouts_map_.end()) {
return;
}
label_.get_style_context()->remove_class(layout_.short_name);
layout_ = it->second;
label_.get_style_context()->add_class(layout_.short_name);
}
auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> void {