mirror of
https://github.com/Alexays/Waybar.git
synced 2026-08-18 11:07:06 +00:00
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>
120 lines
3.8 KiB
C++
120 lines
3.8 KiB
C++
#include "AIconLabel.hpp"
|
|
|
|
#include <gdkmm/pixbuf.h>
|
|
#include <spdlog/spdlog.h>
|
|
#include <regex>
|
|
#include <string>
|
|
|
|
namespace waybar {
|
|
|
|
AIconLabel::AIconLabel(const Json::Value& config, const std::string& name, const std::string& id,
|
|
const std::string& format, uint16_t interval, bool ellipsize,
|
|
bool enable_click, bool enable_scroll)
|
|
: ALabel(config, name, id, format, interval, ellipsize, enable_click, enable_scroll) {
|
|
if (config["icon-size"].isUInt()) {
|
|
app_icon_size_ = config["icon-size"].asUInt();
|
|
}
|
|
image_.set_pixel_size(app_icon_size_);
|
|
event_box_.remove();
|
|
label_.unset_name();
|
|
label_.get_style_context()->remove_class(MODULE_CLASS);
|
|
box_.get_style_context()->add_class(MODULE_CLASS);
|
|
if (!id.empty()) {
|
|
label_.get_style_context()->remove_class(id);
|
|
box_.get_style_context()->add_class(id);
|
|
}
|
|
|
|
int rot = 0;
|
|
|
|
if (config_["rotate"].isUInt()) {
|
|
rot = config["rotate"].asUInt() % 360;
|
|
if ((rot % 90) != 00) rot = 0;
|
|
rot /= 90;
|
|
}
|
|
|
|
if ((rot % 2) == 0)
|
|
box_.set_orientation(Gtk::Orientation::ORIENTATION_HORIZONTAL);
|
|
else
|
|
box_.set_orientation(Gtk::Orientation::ORIENTATION_VERTICAL);
|
|
box_.set_name(name);
|
|
|
|
int spacing = config_["icon-spacing"].isInt() ? config_["icon-spacing"].asInt() : 8;
|
|
box_.set_spacing(spacing);
|
|
|
|
bool swap_icon_label = false;
|
|
if (config_["swap-icon-label"].isNull()) {
|
|
} else if (config_["swap-icon-label"].isBool()) {
|
|
swap_icon_label = config_["swap-icon-label"].asBool();
|
|
} else {
|
|
spdlog::warn("'swap-icon-label' must be a bool, found '{}'. Using default value (false).",
|
|
config_["swap-icon-label"].asString());
|
|
}
|
|
|
|
if ((rot == 0 || rot == 3) ^ swap_icon_label) {
|
|
box_.add(image_);
|
|
box_.add(label_);
|
|
} else {
|
|
box_.add(label_);
|
|
box_.add(image_);
|
|
}
|
|
|
|
event_box_.add(box_);
|
|
}
|
|
|
|
std::tuple<std::string, std::string> AIconLabel::extractIcon(const std::string& input) {
|
|
std::string icon_result = "";
|
|
std::string label_result = input;
|
|
try {
|
|
static const std::regex icon_search(R"((?=\\0icon\\1f).+?(?=\\n))");
|
|
std::smatch icon_match;
|
|
if (std::regex_search(input, icon_match, icon_search)) {
|
|
icon_result = icon_match[0].str().substr(9);
|
|
|
|
static const std::regex clean_label_pattern(R"(\\0icon\\1f.+?\\n)");
|
|
label_result = std::regex_replace(input, clean_label_pattern, "");
|
|
}
|
|
} catch (const std::exception& e) {
|
|
spdlog::warn("Error while parsing icon from label. {}", e.what());
|
|
}
|
|
|
|
return std::make_tuple(icon_result, label_result);
|
|
}
|
|
|
|
auto AIconLabel::update() -> void {
|
|
label_contains_icon = false;
|
|
|
|
auto [iconLabel, cleanLabel] = extractIcon(label_.get_label().c_str());
|
|
label_contains_icon = iconLabel.length() > 0;
|
|
|
|
if (label_contains_icon) {
|
|
label_.set_markup(cleanLabel);
|
|
|
|
if (iconLabel.front() == '/') {
|
|
try {
|
|
int scaled_icon_size = app_icon_size_ * image_.get_scale_factor();
|
|
auto pixbuf = Gdk::Pixbuf::create_from_file(iconLabel, scaled_icon_size, scaled_icon_size);
|
|
|
|
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(),
|
|
image_.get_window());
|
|
image_.set(surface);
|
|
image_.set_visible(true);
|
|
} catch (const Glib::Exception& e) {
|
|
spdlog::warn("Failed to load embedded icon {}: {}", iconLabel, std::string(e.what()));
|
|
image_.set_visible(false);
|
|
}
|
|
} else {
|
|
image_.set_from_icon_name(iconLabel, Gtk::ICON_SIZE_INVALID);
|
|
image_.set_visible(true);
|
|
}
|
|
}
|
|
|
|
image_.set_visible(image_.get_visible() && iconEnabled());
|
|
ALabel::update();
|
|
}
|
|
|
|
bool AIconLabel::iconEnabled() const {
|
|
return label_contains_icon || (config_["icon"].isBool() ? config_["icon"].asBool() : false);
|
|
}
|
|
|
|
} // namespace waybar
|