image module reworked and added multiple image rendering capability

This commit is contained in:
VPavliashvili
2024-07-21 21:34:29 +04:00
parent cece04e02f
commit c7f499e372
3 changed files with 414 additions and 30 deletions
+61 -8
View File
@@ -14,6 +14,64 @@
namespace waybar::modules { namespace waybar::modules {
namespace image {
class IStrategy {
public:
virtual ~IStrategy() = default;
virtual void update() = 0;
};
class SingleImageStrategy : public IStrategy {
public:
SingleImageStrategy(const std::string &, const Json::Value &, const std::string &,
Gtk::EventBox &, bool);
~SingleImageStrategy() override = default;
void update() override;
private:
void parseOutputRaw();
util::command::res output_;
Json::Value config_;
Gtk::Image image_;
std::string path_;
std::string tooltip_;
int size_;
Gtk::Box box_;
bool hasTooltip_;
};
class MultipleImageStrategy : public IStrategy {
public:
MultipleImageStrategy(const std::string &, const Json::Value &, const std::string &,
Gtk::EventBox &);
~MultipleImageStrategy() override = default;
void update() override;
private:
struct ImageData {
std::string path;
std::string marker;
std::string tooltip;
std::string on_click;
std::shared_ptr<Gtk::Image> img;
std::shared_ptr<Gtk::Button> btn;
};
void setImagesData(const Json::Value &);
void setupAndDraw();
void resetBoxAndMemory();
void handleClick(const Glib::ustring &data);
Json::Value config_;
int size_;
Gtk::Box box_;
std::vector<ImageData> images_data_;
};
} // namespace image
class Image : public AModule { class Image : public AModule {
public: public:
Image(const std::string &, const Json::Value &); Image(const std::string &, const Json::Value &);
@@ -24,16 +82,11 @@ class Image : public AModule {
private: private:
void delayWorker(); void delayWorker();
void handleEvent(); void handleEvent();
void parseOutputRaw(); static std::unique_ptr<image::IStrategy> getStrategy(const std::string &, const Json::Value &,
const std::string &, Gtk::EventBox &, bool);
Gtk::Box box_;
Gtk::Image image_;
std::string path_;
std::string tooltip_;
int size_;
int interval_; int interval_;
util::command::res output_; std::unique_ptr<image::IStrategy> strategy_;
util::SleeperThread thread_; util::SleeperThread thread_;
}; };
+156 -1
View File
@@ -6,10 +6,19 @@ waybar - image module
# DESCRIPTION # DESCRIPTION
The *image* module displays an image from a path. The *image* module displays container of images(or image) from a provided paths
# REMARK
This module has been rewritten to add multiple image rendering functionality.
To avoid users inconvenience of breaking changes, everything related to old(only
one *image*) implementation has been left untouched including configuration part. For this
reason this wiki page is split into two parts
# CONFIGURATION # CONFIGURATION
For single *image*
*path*: ++ *path*: ++
typeof: string ++ typeof: string ++
The path to the image. The path to the image.
@@ -88,3 +97,149 @@ $path\\n$tooltip
- *#image* - *#image*
- *#image.empty* - *#image.empty*
# CONFIGURATION
For multiple *image*
*size*: ++
typeof: string ++
default: 16 ++
Minumum size of the rendered image in pixels.
*interval*: ++
typeof: interger ++
default: INT_MAX ++
The interval in seconds to redraw module ++
Default value is max value of int and changing it is only recommended ++
if image path or other property is being changed overtime ++
If no *interval* is provided, the module will only be rendered once.
*multiple*: ++
typeof: bool ++
default: false ++
this parameter is used to decide if old *image* implementation should be ++
used(therefore render only one image) or to pick a new implementation to ++
rended multiple images provided from this config. default value of this ++
parameter is false.
*signal*: ++
typeof: interger ++
default: 0 ++
The signal number is used to redraw the module. ++
This is used if *interval* is not provided and module should change ++
regularly depended on external events, e.g when clicking on it. ++
The provided value is valid between 1 and N, where SIGRTMIN+N <= SIGRTMAX. ++
e.g if *signal* = 7, 34 + 7 = 41, which means waybar process should ++
receive signal 41 to redraw this module, since SIGRTMIN = 34 (see 'kill -l').
*entries*: ++
typeof: array ++
default: empty array ++
Json array of objects consisting of several fields ++
this is a main part of configuration where per image properties are defined
*path*: ++
typeof: string ++
default: empty string ++
file path of picture to draw
*marker*: ++
typeof: string ++
default: empty string ++
user defined per image keyword(marker) to identify them, e.g in styles.css ++
e.g if you want to draw 3 pictures and one of them dimmed, you can give ++
*marker*: "dimmed" value and in styles.css write something like this ++
#image .dimmed { opacity: 0.3 }
*tooltip*: ++
typeof: string ++
default: empty string ++
description of the image displayed on hover
*on-click*: ++
typeof: string ++
default: empty string ++
action to perform when clicking on the image. ++
The action can be any system or custom binary/script ++
e.g if { "on-click": "notify-send \"hello world\"" } ++
"hello world" will appear as notification.
*exec*: ++
typeof: string ++
default: empty string ++
same as *entries* but provided from external script which should dynamically ++
return json array of objects consisting of same keys as *entries*. ++
This option is specifically useful in combination with *signal* option without *interval* provided.
# Examples
With entries
```
"image#wentr": {
"entries": [
{
"path": "/home/user/Pictures/idk1.png",
"marker": "dimmed",
"tooltip": "dimmed image",
"on-click": "notify-send \"hello world\""
},
{
"path": "/home/user/Pictures/idk2.png",
"marker": "normal",
"on-click": "myCustomScript.py"
}
],
"size": 32,
"interval": 1 // will redraw every 5 seconds
}
```
With exec
```
"image#wexec": {
"exec": "imageProvider.py",
"size": 32,
"signal": 7 // e.g 'pkill -n waybar --signal 41' will trigger redraw
}
```
where imageProvider.py looks like this
```
#!/usr/bin/python
import json
dir = "/home/user/Pictures/"
out = [
{"path": dir + "idk1.png", "marker": "clickable", "on-click": "echo 'hello world'"},
{"path": dir + "idk2.png", "marker": "normal", "tooltip": "sample tooltip"},
{"path": dir + "idk3.png", "marker": "important"},
]
res = json.dumps(out)
print(res)
```
## Example styles
```
#image .important {
opacity: 0.9;
}
#image button.important {
opacity: 0.2
}
#image .normal {
opacity: 0.3
}
```
# STYLE
- *#image*
- *#image*.empty /\*when image could not be rendered\*/
- *#image*.button /\*when on-click is provided\*/
- *#image*.*marker* /\*where *marker* is parameter from config\*/
- *#image*.button.*marker*
+196 -20
View File
@@ -1,25 +1,13 @@
#include "modules/image.hpp" #include "modules/image.hpp"
#include <json/value.h>
waybar::modules::Image::Image(const std::string& id, const Json::Value& config) waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
: AModule(config, "image", id), box_(Gtk::ORIENTATION_HORIZONTAL, 0) { : AModule(config, "image", id) {
box_.pack_start(image_); strategy_ = getStrategy(id, config, MODULE_CLASS, event_box_, tooltipEnabled());
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();
interval_ = config_["interval"].asInt(); interval_ = config_["interval"].asInt();
if (size_ == 0) {
size_ = 16;
}
if (interval_ == 0) { if (interval_ == 0) {
interval_ = INT_MAX; interval_ = INT_MAX;
} }
@@ -27,6 +15,20 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
delayWorker(); 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() { void waybar::modules::Image::delayWorker() {
thread_ = [this] { thread_ = [this] {
dp.emit(); dp.emit();
@@ -42,6 +44,180 @@ void waybar::modules::Image::refresh(int sig) {
} }
auto waybar::modules::Image::update() -> void { auto waybar::modules::Image::update() -> void {
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() {
Glib::RefPtr<Gdk::Pixbuf> pixbuf; Glib::RefPtr<Gdk::Pixbuf> pixbuf;
if (config_["path"].isString()) { if (config_["path"].isString()) {
path_ = config_["path"].asString(); path_ = config_["path"].asString();
@@ -57,7 +233,7 @@ auto waybar::modules::Image::update() -> void {
pixbuf = {}; pixbuf = {};
if (pixbuf) { if (pixbuf) {
if (tooltipEnabled() && !tooltip_.empty()) { if (hasTooltip_ && !tooltip_.empty()) {
if (box_.get_tooltip_markup() != tooltip_) { if (box_.get_tooltip_markup() != tooltip_) {
box_.set_tooltip_markup(tooltip_); box_.set_tooltip_markup(tooltip_);
} }
@@ -70,11 +246,9 @@ auto waybar::modules::Image::update() -> void {
image_.hide(); image_.hide();
box_.get_style_context()->add_class("empty"); box_.get_style_context()->add_class("empty");
} }
AModule::update();
} }
void waybar::modules::Image::parseOutputRaw() { void SingleImageStrategy::parseOutputRaw() {
std::istringstream output(output_.out); std::istringstream output(output_.out);
std::string line; std::string line;
int i = 0; int i = 0;
@@ -89,3 +263,5 @@ void waybar::modules::Image::parseOutputRaw() {
i++; i++;
} }
} }
} // namespace waybar::modules::image