Post-0.15.0 review of the 0.15.0..HEAD range surfaced regressions and
bugs. This restores backward compatibility for existing configs/CSS,
fixes confirmed defects, and repairs the scdoc man-page build break on
master. Pango-markup tooltips are intentional and were kept.
Backward-compat restorations:
- AModule: honor legacy numeric Gdk::CursorType cursor values (int overload)
- memory: correct GiB divisor (was ~2.3% low); round bare {} placeholders
- wireplumber: scale max-volume into the linear domain so the cap works again
- idle_inhibitor: gate right/middle-click deactivate & scroll on dynamic-timeouts;
accept both dynamic-timeout(s); widen timeout to double (no fractional truncation)
- custom: keep #custom-<name>.<class> CSS selectors working (classes on box_)
- image: don't wordexp-split a single path; fall back to the literal path
- niri/window: restore hide-when-empty (new show-empty opt-in); escape tooltip
- wlr/taskbar: plain-text tooltip when markup is disabled
Bug fixes:
- tray: fix use-after-free in onAdd; guard the watcher retry timeout
- hyprland: clamp max-windows iterator (OOB); drop duplicate language tooltip block
- niri/window: supply {col}/{max_col} args in the empty branch (fmt::format_error)
- mpris: escape {dynamic}/{player} tooltip; fix dangling player; albumArtist source
- mango: fix use-after-free race (dispatch under callback_mutex_)
- mpd: contain throwing checkErrors in noexcept idle paths (no std::terminate/UAF)
- keyboard_state: always render every lock label, with guarded defaults
- bluetooth: bound GATT ReadValue timeout, opt-in + services-resolved gating,
preserve authoritative Battery1 percentage
- wireplumber: fix WpDevice reference leak / NULL handling
- battery, clock, dwl, wayfire, graph, custom_graph, transform, river: assorted
crash/logic fixes
Man page / build:
- niri-workspaces: fix scdoc "indented by an amount greater than 1"
(workspace-taskbar sub-options were mis-indented; breaks man-page build)
- document new show-empty (niri/window); correct network {txBitrate}/{rxBitrate}
Not compiled locally (no gtkmm on this host); C++ build relies on CI.
Man pages validated with scdoc 1.11.4.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
106 lines
3.6 KiB
C++
106 lines
3.6 KiB
C++
#include "modules/sni/tray.hpp"
|
|
|
|
#include <spdlog/spdlog.h>
|
|
|
|
#include <algorithm>
|
|
|
|
#include "modules/sni/icon_manager.hpp"
|
|
|
|
namespace waybar::modules::SNI {
|
|
|
|
static void initIconsConfig(const Json::Value& config) {
|
|
if (config["icons"].isObject()) {
|
|
IconManager::instance().setIconsConfig(config["icons"]);
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> Tray::parseIgnoreList(const Json::Value& config) {
|
|
std::vector<std::string> ignore_list;
|
|
if (config["ignore-list"].isArray()) {
|
|
spdlog::info("Tray: Found ignore-list with {} items", config["ignore-list"].size());
|
|
for (const auto& item : config["ignore-list"]) {
|
|
if (item.isString()) {
|
|
ignore_list.push_back(item.asString());
|
|
spdlog::info("Tray: Adding to ignore list: {}", item.asString());
|
|
}
|
|
}
|
|
} else {
|
|
spdlog::info("Tray: No ignore-list configured");
|
|
}
|
|
return ignore_list;
|
|
}
|
|
|
|
Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
|
|
: AModule(config, "tray", id),
|
|
box_(bar.orientation, 0),
|
|
watcher_(SNI::Watcher::getInstance()),
|
|
ignore_list_(parseIgnoreList(config)),
|
|
host_((initIconsConfig(config), nb_hosts_), config, bar, ignore_list_,
|
|
std::bind(&Tray::onAdd, this, std::placeholders::_1),
|
|
std::bind(&Tray::onRemove, this, std::placeholders::_1),
|
|
std::bind(&Tray::queueUpdate, this)) {
|
|
box_.set_name("tray");
|
|
event_box_.add(box_);
|
|
if (!id.empty()) {
|
|
box_.get_style_context()->add_class(id);
|
|
}
|
|
box_.get_style_context()->add_class(MODULE_CLASS);
|
|
if (config_["spacing"].isUInt()) {
|
|
box_.set_spacing(config_["spacing"].asUInt());
|
|
}
|
|
nb_hosts_ += 1;
|
|
dp.emit();
|
|
}
|
|
|
|
void Tray::checkIgnoreList(std::unique_ptr<Item>* item_ptr) {
|
|
// Delegate to Host's checkIgnoreList method
|
|
host_.checkIgnoreList(ignore_list_, std::bind(&Tray::onRemove, this, std::placeholders::_1));
|
|
}
|
|
|
|
void Tray::queueUpdate() { dp.emit(); }
|
|
|
|
void Tray::onAdd(std::unique_ptr<Item>& item) {
|
|
spdlog::info("Tray::onAdd - item bus_name='{}', category='{}', icon_name='{}', title='{}'",
|
|
item->bus_name, item->category, item->icon_name, item->title);
|
|
|
|
if (config_["reverse-direction"].isBool() && config_["reverse-direction"].asBool()) {
|
|
box_.pack_end(item->event_box);
|
|
} else {
|
|
box_.pack_start(item->event_box);
|
|
}
|
|
items_.push_back(item.get());
|
|
|
|
item->event_box.signal_show().connect([this] { dp.emit(); });
|
|
item->event_box.signal_hide().connect([this] { dp.emit(); });
|
|
|
|
// After this point `item` may be erased/invalidated by the ignore-list check;
|
|
// do not touch it again below.
|
|
spdlog::debug("Tray::onAdd deferred check - checking ignore list");
|
|
host_.checkIgnoreList(ignore_list_, std::bind(&Tray::onRemove, this, std::placeholders::_1));
|
|
|
|
dp.emit();
|
|
}
|
|
|
|
void Tray::onRemove(std::unique_ptr<Item>& item) {
|
|
box_.remove(item->event_box);
|
|
items_.erase(std::remove(items_.begin(), items_.end(), item.get()), items_.end());
|
|
dp.emit();
|
|
}
|
|
|
|
auto Tray::update() -> void {
|
|
// Check if any items should be ignored now that properties have loaded
|
|
if (!ignore_list_.empty()) {
|
|
spdlog::debug("Tray::update() - checking ignore list");
|
|
host_.checkIgnoreList(ignore_list_, std::bind(&Tray::onRemove, this, std::placeholders::_1));
|
|
}
|
|
|
|
// Show tray only when items are visible. Iterate the managed items_ list
|
|
// instead of box_.get_children() to avoid a use-after-free on raw widget
|
|
// pointers that may dangle after items are destroyed asynchronously.
|
|
event_box_.set_visible(std::any_of(items_.begin(), items_.end(),
|
|
[](Item* item) { return item->event_box.get_visible(); }));
|
|
AModule::update();
|
|
}
|
|
|
|
} // namespace waybar::modules::SNI
|