fix(hyprland/workspaces): own debounce timer on main thread, fix UAF
The debounce timer added for flicker prevention was armed from the IPC listener thread via Glib::signal_timeout().connect(), while its timeout lambda and the m_updatePending flag ran on the GTK main thread — an unsynchronized cross-thread data race on GLib timer/main-loop state. Additionally ~Workspaces() never disconnected the timer, so a pending timeout could fire on a freed 'this' (use-after-free). Restore the pre-refactor threading model: onEvent now only mutates state under m_mutex on the IPC thread and calls dp.emit() (Glib::Dispatcher is thread-safe). The debounce timer is owned entirely by the main-thread update() path, which arms/re-arms it on each dispatch and coalesces bursts into a single refresh. ~Workspaces() disconnects the timer (guarded) so none outlives the object. Debounce behavior is preserved.
This commit is contained in:
@@ -227,8 +227,9 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
sigc::connection m_scrollEventConnection_;
|
sigc::connection m_scrollEventConnection_;
|
||||||
IPC& m_ipc;
|
IPC& m_ipc;
|
||||||
|
|
||||||
|
// Coalesces bursts of Hyprland events into a single UI refresh. Armed and
|
||||||
|
// disconnected only on the GTK main thread (see Workspaces::update).
|
||||||
sigc::connection m_debounceTimer;
|
sigc::connection m_debounceTimer;
|
||||||
bool m_updatePending = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar::modules::hyprland
|
} // namespace waybar::modules::hyprland
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ Workspaces::~Workspaces() {
|
|||||||
if (m_scrollEventConnection_.connected()) {
|
if (m_scrollEventConnection_.connected()) {
|
||||||
m_scrollEventConnection_.disconnect();
|
m_scrollEventConnection_.disconnect();
|
||||||
}
|
}
|
||||||
|
// Cancel any pending debounce timeout so it cannot fire on a freed `this`.
|
||||||
|
// Runs on the main thread, same as where the timer is armed.
|
||||||
|
if (m_debounceTimer.connected()) {
|
||||||
|
m_debounceTimer.disconnect();
|
||||||
|
}
|
||||||
m_ipc.unregisterForIPC(this);
|
m_ipc.unregisterForIPC(this);
|
||||||
// wait for possible event handler to finish
|
// wait for possible event handler to finish
|
||||||
std::lock_guard<std::mutex> lg(m_mutex);
|
std::lock_guard<std::mutex> lg(m_mutex);
|
||||||
@@ -332,23 +337,11 @@ void Workspaces::onEvent(const std::string& ev) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_debounceTimer.connected()) {
|
// Notify the main thread. dp (Glib::Dispatcher) is the only thread-safe way to
|
||||||
m_debounceTimer.disconnect();
|
// hand off to the GTK main loop; GLib timer state must never be touched from the
|
||||||
m_updatePending = false;
|
// IPC listener thread. The debounce timer is owned entirely by the main-thread
|
||||||
}
|
// update() path (see Workspaces::update).
|
||||||
|
|
||||||
m_updatePending = true;
|
|
||||||
m_debounceTimer = Glib::signal_timeout().connect(
|
|
||||||
[this]() {
|
|
||||||
if (!m_updatePending) return false;
|
|
||||||
std::lock_guard<std::mutex> lock(m_mutex);
|
|
||||||
if (m_updatePending) {
|
|
||||||
dp.emit();
|
dp.emit();
|
||||||
m_updatePending = false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
7);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Workspaces::onWorkspaceActivated(std::string const& payload) {
|
void Workspaces::onWorkspaceActivated(std::string const& payload) {
|
||||||
@@ -1041,8 +1034,20 @@ void Workspaces::setUrgentWorkspace(std::string const& windowaddress) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Workspaces::update() -> void {
|
auto Workspaces::update() -> void {
|
||||||
|
// Debounce rapid events (e.g. out-of-order create/destroy workspace events from
|
||||||
|
// Hyprland) to prevent workspace button flicker. This runs on the GTK main thread
|
||||||
|
// (invoked via the dp dispatcher), so arming/disconnecting the GLib timer here is
|
||||||
|
// thread-safe. Each event re-arms the timer, coalescing bursts into one refresh.
|
||||||
|
if (m_debounceTimer.connected()) {
|
||||||
|
m_debounceTimer.disconnect();
|
||||||
|
}
|
||||||
|
m_debounceTimer = Glib::signal_timeout().connect(
|
||||||
|
[this]() {
|
||||||
doUpdate();
|
doUpdate();
|
||||||
AModule::update();
|
AModule::update();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
7);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Workspaces::updateWindowCount() {
|
void Workspaces::updateWindowCount() {
|
||||||
|
|||||||
Reference in New Issue
Block a user