Merge pull request #3151 from VPavliashvili/module-images

feat: add new module 'images' to render multiple images under one gtk container
This commit is contained in:
Alexis Rouillard
2026-07-04 01:10:43 +02:00
committed by GitHub
3 changed files with 417 additions and 37 deletions
+201 -28
View File
@@ -1,20 +1,12 @@
#include "modules/image.hpp"
#include <json/value.h>
#include <config.hpp>
waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
: AModule(config, "image", id), box_(Gtk::ORIENTATION_HORIZONTAL, 0) {
box_.pack_start(image_);
box_.set_name("image");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
box_.get_style_context()->add_class(MODULE_CLASS);
event_box_.add(box_);
dp.emit();
size_ = config["size"].asInt();
: AModule(config, "image", id) {
strategy_ = getStrategy(id, config, MODULE_CLASS, event_box_, tooltipEnabled());
const auto once = std::chrono::milliseconds::max();
if (!config_.isMember("interval") || config_["interval"].isNull() ||
@@ -33,20 +25,24 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
interval_ = once;
}
if (size_ == 0) {
size_ = 16;
}
if (config_["path"].isString()) {
auto result = Config::tryExpandPath(config_["path"].asString(), "");
path_ = result.empty() ? "" : result.front();
} else {
path_.clear();
}
delayWorker();
}
auto waybar::modules::Image::getStrategy(const std::string& id, const Json::Value& cfg,
const std::string& module, Gtk::EventBox& evbox,
bool hasTooltip)
-> std::unique_ptr<waybar::modules::image::IStrategy> {
std::unique_ptr<waybar::modules::image::IStrategy> strat;
if (!cfg["multiple"].empty() && cfg["multiple"].asBool()) {
strat = std::make_unique<waybar::modules::image::MultipleImageStrategy>(id, cfg, module, evbox);
} else {
strat = std::make_unique<waybar::modules::image::SingleImageStrategy>(id, cfg, module, evbox,
hasTooltip);
}
return strat;
}
void waybar::modules::Image::delayWorker() {
thread_ = [this] {
dp.emit();
@@ -63,7 +59,184 @@ void waybar::modules::Image::refresh(int sig) {
}
auto waybar::modules::Image::update() -> void {
if (config_["exec"].isString()) {
strategy_->update();
AModule::update();
}
namespace waybar::modules::image {
MultipleImageStrategy::MultipleImageStrategy(const std::string& id, const Json::Value& config,
const std::string& module, Gtk::EventBox& evbox)
: IStrategy(), box_(Gtk::ORIENTATION_HORIZONTAL, 0) {
config_ = config;
box_.set_name("image");
box_.get_style_context()->add_class(id);
box_.get_style_context()->add_class(module);
evbox.add(box_);
size_ = config["size"].asInt();
if (size_ == 0) {
size_ = 16;
}
}
void MultipleImageStrategy::update() {
// spdlog::info("update function run");
// clear box_, previous css classes and memory
if (box_.get_children().size() > 0) {
resetBoxAndMemory();
}
// set new images from config script
if (!config_["entries"].empty()) {
setImagesData(config_["entries"]);
} else if (!config_["exec"].empty()) {
auto exec = util::command::exec(config_["exec"].asString(), "");
Json::Value as_json;
Json::Reader reader;
if (!reader.parse(exec.out, as_json)) {
spdlog::error("invalid json from exec {}", exec.out);
return;
}
setImagesData(as_json);
} else {
spdlog::error("no image files provded in config");
return;
}
setupAndDraw();
}
void MultipleImageStrategy::setupAndDraw() {
for (unsigned int i = 0; i < images_data_.size(); i++) {
images_data_[i].img = std::make_shared<Gtk::Image>();
images_data_[i].btn = std::make_shared<Gtk::Button>();
auto img = images_data_[i].img;
auto data = images_data_[i];
auto path = data.path;
auto marker = data.marker;
auto tooltip = data.tooltip;
bool has_onclick = !data.on_click.empty();
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
pixbuf = Gdk::Pixbuf::create_from_file(path, size_, size_);
if (has_onclick) {
auto btn = images_data_[i].btn;
btn->set_name("button_" + path);
btn->get_style_context()->add_class(marker);
btn->set_tooltip_text(tooltip);
btn->set_image(*img);
box_.pack_start(*btn);
btn->add_events(Gdk::BUTTON_PRESS_MASK);
btn->signal_clicked().connect(
sigc::bind(sigc::mem_fun(*this, &MultipleImageStrategy::handleClick), data.on_click));
if (pixbuf) {
btn->show_all();
img->set(pixbuf);
box_.get_style_context()->remove_class("empty");
} else {
btn->hide();
img->clear();
img->hide();
box_.get_style_context()->add_class("empty");
}
} else {
img->set_name(path);
img->get_style_context()->add_class(marker);
img->set_tooltip_text(tooltip);
box_.pack_start(*img);
// spdlog::info("added image -> {}:{}", marker, path);
if (pixbuf) {
img->set(pixbuf);
img->show();
box_.get_style_context()->remove_class("empty");
} else {
img->clear();
img->hide();
box_.get_style_context()->add_class("empty");
}
}
}
}
void MultipleImageStrategy::setImagesData(const Json::Value& entries) {
for (unsigned int i = 0; i < entries.size(); i++) {
auto path = entries[i]["path"];
auto marker = entries[i]["marker"];
auto tooltip = entries[i]["tooltip"];
auto onclick = entries[i]["on-click"];
bool has_tooltip_err = !tooltip.empty() && !tooltip.isString();
bool has_onclick_err = !onclick.empty() && !onclick.isString();
if (!path.isString() || !marker.isString() || has_tooltip_err || has_onclick_err ||
!Glib::file_test(path.asString(), Glib::FILE_TEST_EXISTS)) {
spdlog::error("invalid input in images config -> {}", entries[i]);
return;
}
ImageData data;
data.path = path.asString();
data.marker = marker.asString();
data.tooltip = !tooltip.empty() ? tooltip.asString() : "";
data.on_click = onclick.asString();
images_data_.push_back(data);
}
}
void MultipleImageStrategy::resetBoxAndMemory() {
auto children = box_.get_children();
for (auto child : children) {
box_.remove(*child);
// spdlog::info("child removed with name -> {}", std::string(child->get_name()));
}
images_data_.clear();
}
void MultipleImageStrategy::handleClick(const Glib::ustring& data) {
auto msg = std::string(data);
auto exec = util::command::exec(data, "");
}
SingleImageStrategy::SingleImageStrategy(const std::string& id, const Json::Value& config,
const std::string& module, Gtk::EventBox& evbox,
bool tooltipEnabled)
: IStrategy(), box_(Gtk::ORIENTATION_HORIZONTAL, 0) {
config_ = config;
hasTooltip_ = tooltipEnabled;
box_.pack_start(image_);
box_.set_name("image");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
box_.get_style_context()->add_class(module);
evbox.add(box_);
size_ = config["size"].asInt();
if (size_ == 0) {
size_ = 16;
}
}
void SingleImageStrategy::update() {
if (config_["path"].isString()) {
auto result = Config::tryExpandPath(config_["path"].asString(), "");
path_ = result.empty() ? "" : result.front();
} else if (config_["exec"].isString()) {
output_ = util::command::exec(config_["exec"].asString(), "");
parseOutputRaw();
// expand path if "~" or "$HOME" is present in original path
@@ -82,7 +255,7 @@ auto waybar::modules::Image::update() -> void {
image_.set(surface);
image_.show();
if (tooltipEnabled() && !tooltip_.empty()) {
if (hasTooltip_ && !tooltip_.empty()) {
if (box_.get_tooltip_markup() != tooltip_) {
box_.set_tooltip_markup(tooltip_);
}
@@ -94,11 +267,9 @@ auto waybar::modules::Image::update() -> void {
image_.hide();
box_.get_style_context()->add_class("empty");
}
AModule::update();
}
void waybar::modules::Image::parseOutputRaw() {
void SingleImageStrategy::parseOutputRaw() {
std::istringstream output(output_.out);
std::string line;
int i = 0;
@@ -113,3 +284,5 @@ void waybar::modules::Image::parseOutputRaw() {
i++;
}
}
} // namespace waybar::modules::image