The item-ordering feature made Host::reorderItems() re-run the full remove/add path over items_ via std::ranges::for_each(on_remove_/on_add_). This caused two confirmed bugs: BUG 1 (iterator invalidation / UAF): on_add_ (Tray::onAdd) calls Host::checkIgnoreList, which erases from items_ while for_each is still iterating items_, invalidating iterators/pointers. Triggered by a non-empty ignore-list matching an item with >=2 items present. BUG 2 (double add): reorderItems runs while an item's Id is resolved in proxyReady, i.e. before setReady(). It added the not-yet-ready item (re-parenting its event_box, pushing into Tray::items_, connecting signal_show/hide), then setReady() -> itemReady -> onAdd added it again: GTK 'widget already has a parent' critical, duplicate Item* and signal handlers that accumulated unbounded. Fixes: - reorderItems() now only reorders already-added GTK box children via a dedicated on_reorder_ callback (Tray::reorderBox), never re-adding or removing. reorderBox stable-sorts items_ by order_ and repositions children with gtk_box_reorder_child (honouring reverse-direction). - Tray::onAdd is idempotent (guards against an already-added item) and positions the new widget via reorderBox before the ignore-list check. - signal_show/signal_hide connections are stored per item and disconnected in Tray::onRemove; onRemove is a no-op for items that were never added.
69 lines
2.4 KiB
C++
69 lines
2.4 KiB
C++
#pragma once
|
|
|
|
#include <dbus-status-notifier-watcher.h>
|
|
#include <giomm.h>
|
|
#include <glibmm/refptr.h>
|
|
#include <json/json.h>
|
|
|
|
#include <tuple>
|
|
|
|
#include "bar.hpp"
|
|
#include "modules/sni/item.hpp"
|
|
|
|
namespace waybar::modules::SNI {
|
|
|
|
class Host {
|
|
public:
|
|
Host(std::size_t id, const Json::Value&, const Bar&, const std::vector<std::string>&,
|
|
const std::function<void(std::unique_ptr<Item>&)>&,
|
|
const std::function<void(std::unique_ptr<Item>&)>&, const std::function<void()>&,
|
|
const std::function<void()>&);
|
|
~Host();
|
|
|
|
void checkIgnoreList(const std::vector<std::string>& ignore_list,
|
|
const std::function<void(std::unique_ptr<Item>&)>& on_remove);
|
|
|
|
void reorderItems();
|
|
|
|
private:
|
|
void busAcquired(const Glib::RefPtr<Gio::DBus::Connection>&, const Glib::ustring&);
|
|
void nameAppeared(const Glib::RefPtr<Gio::DBus::Connection>&, const Glib::ustring&,
|
|
const Glib::ustring&);
|
|
void nameVanished(const Glib::RefPtr<Gio::DBus::Connection>&, const Glib::ustring&);
|
|
static void proxyReady(GObject*, GAsyncResult*, gpointer);
|
|
static void registerHost(GObject*, GAsyncResult*, gpointer);
|
|
static void itemRegistered(SnWatcher*, const gchar*, gpointer);
|
|
static void itemUnregistered(SnWatcher*, const gchar*, gpointer);
|
|
void itemReady(Item&);
|
|
void itemInvalidated(Item&);
|
|
void removeItem(std::vector<std::unique_ptr<Item>>::iterator);
|
|
void clearItems();
|
|
|
|
static std::tuple<std::string, std::string> getBusNameAndObjectPath(const std::string&);
|
|
void addRegisteredItem(const std::string& service);
|
|
|
|
std::vector<std::unique_ptr<Item>> items_;
|
|
const std::string bus_name_;
|
|
const std::string object_path_;
|
|
std::size_t bus_name_id_;
|
|
std::size_t watcher_id_;
|
|
GCancellable* cancellable_ = nullptr;
|
|
SnWatcher* watcher_ = nullptr;
|
|
sigc::connection retry_connection_;
|
|
unsigned retry_count_ = 0;
|
|
const Json::Value& config_;
|
|
const Bar& bar_;
|
|
const std::vector<std::string> ignore_list_;
|
|
const std::function<void(std::unique_ptr<Item>&)> on_add_;
|
|
const std::function<void(std::unique_ptr<Item>&)> on_remove_;
|
|
// Re-applies the configured ordering to the already-added tray widgets. This
|
|
// must NOT re-run the add path (which would re-parent widgets and reconnect
|
|
// signals); it only reorders existing children.
|
|
const std::function<void()> on_reorder_;
|
|
|
|
ItemOrderMap orders_;
|
|
const std::function<void()> on_update_;
|
|
};
|
|
|
|
} // namespace waybar::modules::SNI
|