diff --git a/include/modules/sway/language.hpp b/include/modules/sway/language.hpp index 470b9879..6ababa4f 100644 --- a/include/modules/sway/language.hpp +++ b/include/modules/sway/language.hpp @@ -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 layouts_map_; bool hide_single_; diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp index 7113d9ef..c284e53c 100644 --- a/src/modules/sway/language.cpp +++ b/src/modules/sway/language.cpp @@ -116,6 +116,17 @@ void Language::onEvent(const struct Ipc::ipc_response& res) { auto Language::update() -> void { std::lock_guard 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& used_layouts) -> void {