Merge remote-tracking branch 'origin/master' into refactor/generic-tooltip
# Conflicts: # src/modules/wireplumber.cpp
This commit is contained in:
+151
-3
@@ -83,6 +83,68 @@ auto getUcharProperty(GDBusProxy* proxy, const char* property_name) -> unsigned
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto isChildPath(const std::string& child, const std::string& parent) -> bool {
|
||||
return child.starts_with(parent);
|
||||
}
|
||||
|
||||
auto readBatteryCharacteristicValue(GDBusProxy* proxy_char) -> std::optional<unsigned char> {
|
||||
GVariantBuilder builder;
|
||||
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
|
||||
|
||||
GError* error = nullptr;
|
||||
GVariant* gvar =
|
||||
g_dbus_proxy_call_sync(proxy_char, "ReadValue", g_variant_new("(a{sv})", &builder),
|
||||
G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
|
||||
if (error != nullptr) {
|
||||
g_error_free(error);
|
||||
return std::nullopt;
|
||||
}
|
||||
if (gvar == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
GVariant* value_array = g_variant_get_child_value(gvar, 0);
|
||||
gsize n_elements;
|
||||
const auto* data = static_cast<const guchar*>(
|
||||
g_variant_get_fixed_array(value_array, &n_elements, sizeof(guchar)));
|
||||
|
||||
std::optional<unsigned char> result;
|
||||
if (data != nullptr && n_elements > 0) {
|
||||
result = data[0];
|
||||
}
|
||||
|
||||
g_variant_unref(value_array);
|
||||
g_variant_unref(gvar);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto hasUserDescriptionDescriptor(GList* objects, const std::string& char_path,
|
||||
const std::string& user_description_uuid) -> bool {
|
||||
for (GList* n = objects; n != nullptr; n = n->next) {
|
||||
GDBusObject* desc_object = G_DBUS_OBJECT(n->data);
|
||||
std::string desc_path = g_dbus_object_get_object_path(desc_object);
|
||||
|
||||
if (!isChildPath(desc_path, char_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GDBusProxy* proxy_desc =
|
||||
G_DBUS_PROXY(g_dbus_object_get_interface(desc_object, "org.bluez.GattDescriptor1"));
|
||||
if (proxy_desc == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto desc_uuid = getOptionalStringProperty(proxy_desc, "UUID");
|
||||
g_object_unref(proxy_desc);
|
||||
|
||||
if (desc_uuid.has_value() &&
|
||||
desc_uuid.value().find(user_description_uuid) != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config)
|
||||
@@ -232,8 +294,9 @@ auto waybar::modules::Bluetooth::update() -> void {
|
||||
fmt::arg("device_address", cur_focussed_device_.address),
|
||||
fmt::arg("device_address_type", cur_focussed_device_.address_type),
|
||||
fmt::arg("device_alias", cur_focussed_device_.alias), fmt::arg("icon", icon_label),
|
||||
fmt::arg("device_battery_percentage",
|
||||
cur_focussed_device_.battery_percentage.value_or(0))));
|
||||
fmt::arg("device_battery_percentage", cur_focussed_device_.battery_percentage.value_or(0)),
|
||||
fmt::arg("device_battery_percentage_peripheral",
|
||||
cur_focussed_device_.battery_percentage_peripheral.value_or(0))));
|
||||
}
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
@@ -258,7 +321,9 @@ auto waybar::modules::Bluetooth::update() -> void {
|
||||
fmt::runtime(enumerate_format), fmt::arg("device_address", dev.address),
|
||||
fmt::arg("device_address_type", dev.address_type),
|
||||
fmt::arg("device_alias", dev.alias), fmt::arg("icon", enumerate_icon),
|
||||
fmt::arg("device_battery_percentage", dev.battery_percentage.value_or(0)));
|
||||
fmt::arg("device_battery_percentage", dev.battery_percentage.value_or(0)),
|
||||
fmt::arg("device_battery_percentage_peripheral",
|
||||
dev.battery_percentage_peripheral.value_or(0)));
|
||||
}
|
||||
}
|
||||
device_enumerate_ = ss.str();
|
||||
@@ -278,6 +343,8 @@ auto waybar::modules::Bluetooth::update() -> void {
|
||||
fmt::arg("device_address_type", cur_focussed_device_.address_type),
|
||||
fmt::arg("device_alias", cur_focussed_device_.alias), fmt::arg("icon", icon_tooltip),
|
||||
fmt::arg("device_battery_percentage", cur_focussed_device_.battery_percentage.value_or(0)),
|
||||
fmt::arg("device_battery_percentage_peripheral",
|
||||
cur_focussed_device_.battery_percentage_peripheral.value_or(0)),
|
||||
fmt::arg("device_enumerate", device_enumerate_)));
|
||||
}
|
||||
|
||||
@@ -398,6 +465,85 @@ auto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto waybar::modules::Bluetooth::getDeviceGattBatteryLevels(
|
||||
GDBusObject* device_object, std::optional<unsigned char>& central_battery,
|
||||
std::optional<unsigned char>& peripheral_battery) -> void {
|
||||
const std::string BATTERY_SERVICE_UUID = "0000180f-0000-1000-8000-00805f9b34fb";
|
||||
const std::string BATTERY_LEVEL_UUID = "00002a19-0000-1000-8000-00805f9b34fb";
|
||||
const std::string USER_DESCRIPTION_UUID = "00002901-0000-1000-8000-00805f9b34fb";
|
||||
|
||||
GList* objects = g_dbus_object_manager_get_objects(manager_.get());
|
||||
std::string device_path = g_dbus_object_get_object_path(device_object);
|
||||
|
||||
for (GList* l = objects; l != nullptr; l = l->next) {
|
||||
GDBusObject* service_object = G_DBUS_OBJECT(l->data);
|
||||
std::string service_path = g_dbus_object_get_object_path(service_object);
|
||||
|
||||
if (!isChildPath(service_path, device_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GDBusProxy* proxy_service =
|
||||
G_DBUS_PROXY(g_dbus_object_get_interface(service_object, "org.bluez.GattService1"));
|
||||
if (proxy_service == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto service_uuid = getOptionalStringProperty(proxy_service, "UUID");
|
||||
g_object_unref(proxy_service);
|
||||
|
||||
if (!service_uuid.has_value() ||
|
||||
service_uuid.value().find(BATTERY_SERVICE_UUID) == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
processBatteryServiceCharacteristics(objects, service_path, BATTERY_LEVEL_UUID,
|
||||
USER_DESCRIPTION_UUID, central_battery,
|
||||
peripheral_battery);
|
||||
}
|
||||
|
||||
g_list_free_full(objects, g_object_unref);
|
||||
}
|
||||
|
||||
auto waybar::modules::Bluetooth::processBatteryServiceCharacteristics(
|
||||
GList* objects, const std::string& service_path, const std::string& battery_level_uuid,
|
||||
const std::string& user_description_uuid, std::optional<unsigned char>& central_battery,
|
||||
std::optional<unsigned char>& peripheral_battery) -> void {
|
||||
for (GList* m = objects; m != nullptr; m = m->next) {
|
||||
GDBusObject* char_object = G_DBUS_OBJECT(m->data);
|
||||
std::string char_path = g_dbus_object_get_object_path(char_object);
|
||||
|
||||
if (!isChildPath(char_path, service_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GDBusProxy* proxy_char =
|
||||
G_DBUS_PROXY(g_dbus_object_get_interface(char_object, "org.bluez.GattCharacteristic1"));
|
||||
if (proxy_char == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto char_uuid = getOptionalStringProperty(proxy_char, "UUID");
|
||||
if (!char_uuid.has_value() || char_uuid.value().find(battery_level_uuid) == std::string::npos) {
|
||||
g_object_unref(proxy_char);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto battery_value = readBatteryCharacteristicValue(proxy_char);
|
||||
g_object_unref(proxy_char);
|
||||
|
||||
if (!battery_value.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasUserDescriptionDescriptor(objects, char_path, user_description_uuid)) {
|
||||
peripheral_battery = battery_value.value();
|
||||
} else {
|
||||
central_battery = battery_value.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, DeviceInfo& device_info)
|
||||
-> bool {
|
||||
GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1"));
|
||||
@@ -418,6 +564,8 @@ auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, Device
|
||||
g_object_unref(proxy_device);
|
||||
|
||||
device_info.battery_percentage = getDeviceBatteryPercentage(object);
|
||||
getDeviceGattBatteryLevels(object, device_info.battery_percentage,
|
||||
device_info.battery_percentage_peripheral);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
|
||||
#include "util/command.hpp"
|
||||
#include "util/ustring_clen.hpp"
|
||||
|
||||
#ifdef HAVE_LANGINFO_1STDAY
|
||||
@@ -516,6 +517,8 @@ auto waybar::modules::Clock::local_zone() -> const time_zone* {
|
||||
auto waybar::modules::Clock::doAction(const std::string& name) -> void {
|
||||
if (actionMap_[name]) {
|
||||
(this->*actionMap_[name])();
|
||||
} else if (auto key = name.substr(0, name.find(" ")); actionWithArgsMap_[key]) {
|
||||
(this->*actionWithArgsMap_[key])(name);
|
||||
} else
|
||||
spdlog::error("Clock. Unsupported action \"{0}\"", name);
|
||||
}
|
||||
@@ -542,6 +545,10 @@ void waybar::modules::Clock::tz_down() {
|
||||
if (tzSize == 1) return;
|
||||
tzCurrIdx_ = (tzCurrIdx_ == 0) ? tzSize - 1 : tzCurrIdx_ - 1;
|
||||
}
|
||||
void waybar::modules::Clock::action_exec(const std::string& action) {
|
||||
auto cmd = action.substr(strlen("exec "));
|
||||
pid_children_.push_back(util::command::forkExec(cmd));
|
||||
}
|
||||
|
||||
#ifdef HAVE_LANGINFO_1STDAY
|
||||
template <auto fn>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "modules/cpu_graph.hpp"
|
||||
|
||||
#include "modules/cpu_frequency.hpp"
|
||||
#include "modules/cpu_usage.hpp"
|
||||
#include "modules/load.hpp"
|
||||
|
||||
// In the 80000 version of fmt library authors decided to optimize imports
|
||||
// and moved declarations required for fmt::dynamic_format_arg_store in new
|
||||
// header fmt/args.h
|
||||
#if (FMT_VERSION >= 80000)
|
||||
#include <fmt/args.h>
|
||||
#else
|
||||
#include <fmt/core.h>
|
||||
#endif
|
||||
|
||||
waybar::modules::CpuGraph::CpuGraph(const std::string& id, const Json::Value& config)
|
||||
: AGraph(config, "cpu_graph", id, 5) {
|
||||
thread_ = [this] {
|
||||
dp.emit();
|
||||
thread_.sleep_for(interval_);
|
||||
};
|
||||
}
|
||||
|
||||
auto waybar::modules::CpuGraph::update() -> void {
|
||||
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
|
||||
auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_);
|
||||
if (tooltipEnabled()) {
|
||||
graph_.set_tooltip_text(tooltip);
|
||||
}
|
||||
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
|
||||
addValue(total_usage);
|
||||
|
||||
graph_.get_style_context()->remove_class(MODERATE_CLASS);
|
||||
graph_.get_style_context()->remove_class(HIGH_CLASS);
|
||||
graph_.get_style_context()->remove_class(INTENSIVE_CLASS);
|
||||
|
||||
if (total_usage > 90) {
|
||||
graph_.get_style_context()->add_class(INTENSIVE_CLASS);
|
||||
} else if (total_usage > 70) {
|
||||
graph_.get_style_context()->add_class(HIGH_CLASS);
|
||||
} else if (total_usage > 30) {
|
||||
graph_.get_style_context()->add_class(MODERATE_CLASS);
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
AGraph::update();
|
||||
}
|
||||
+26
-3
@@ -8,7 +8,7 @@
|
||||
|
||||
waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
|
||||
const Json::Value& config, const std::string& output_name)
|
||||
: ALabel(config, "custom-" + name, id, "{}"),
|
||||
: AIconLabel(config, "custom-" + name, id, "{}"),
|
||||
name_(name),
|
||||
output_name_(output_name),
|
||||
id_(id),
|
||||
@@ -28,6 +28,15 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
|
||||
} else if (config_["exec"].isString()) {
|
||||
continuousWorker();
|
||||
}
|
||||
if (config_["image-path"].isString()) {
|
||||
image_path_ = config_["image-path"].asString();
|
||||
}
|
||||
if (config_["image-name"].isString()) {
|
||||
image_name_ = config_["image-name"].asString();
|
||||
}
|
||||
if (config["icon-size"].isUInt()) {
|
||||
app_icon_size_ = config["icon-size"].asUInt();
|
||||
}
|
||||
}
|
||||
|
||||
waybar::modules::Custom::~Custom() {
|
||||
@@ -184,7 +193,8 @@ auto waybar::modules::Custom::update() -> void {
|
||||
auto str = fmt::format(fmt::runtime(format_), fmt::arg("text", text_), fmt::arg("alt", alt_),
|
||||
fmt::arg("icon", getIcon(percentage_, alt_)),
|
||||
fmt::arg("percentage", percentage_));
|
||||
if ((config_["hide-empty-text"].asBool() && text_.empty()) || str.empty()) {
|
||||
if ((config_["hide-empty-text"].asBool() && text_.empty()) ||
|
||||
(str.empty() && image_path_.empty() && image_name_.empty())) {
|
||||
event_box_.hide();
|
||||
} else {
|
||||
setLabelMarkup(str);
|
||||
@@ -216,7 +226,19 @@ auto waybar::modules::Custom::update() -> void {
|
||||
style->add_class("flat");
|
||||
style->add_class("text-button");
|
||||
style->add_class(MODULE_CLASS);
|
||||
auto image_style = image_.get_style_context();
|
||||
image_style->add_class("image-button");
|
||||
event_box_.show();
|
||||
if (!image_path_.empty()) {
|
||||
auto pixbuf = Gdk::Pixbuf::create_from_file(image_path_, app_icon_size_, app_icon_size_);
|
||||
image_.set(pixbuf);
|
||||
} else if (!image_name_.empty()) {
|
||||
image_.set_from_icon_name(image_name_, Gtk::ICON_SIZE_INVALID);
|
||||
image_.set_pixel_size(app_icon_size_);
|
||||
}
|
||||
|
||||
image_.set_visible(!image_name_.empty() || !image_path_.empty());
|
||||
label_.set_visible(!str.empty());
|
||||
}
|
||||
} catch (const fmt::format_error& e) {
|
||||
if (std::strcmp(e.what(), "cannot switch from manual to automatic argument indexing") != 0)
|
||||
@@ -228,7 +250,7 @@ auto waybar::modules::Custom::update() -> void {
|
||||
}
|
||||
}
|
||||
// Call parent update
|
||||
ALabel::update();
|
||||
AIconLabel::update();
|
||||
}
|
||||
|
||||
void waybar::modules::Custom::parseOutputRaw() {
|
||||
@@ -294,6 +316,7 @@ void waybar::modules::Custom::parseOutputJson() {
|
||||
class_.push_back(c.asString());
|
||||
}
|
||||
}
|
||||
|
||||
if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) {
|
||||
percentage_ = (int)lround(parsed["percentage"].asFloat());
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
#include "modules/custom_graph.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "util/scope_guard.hpp"
|
||||
|
||||
waybar::modules::CustomGraph::CustomGraph(const std::string& name, const std::string& id,
|
||||
const Json::Value& config, const std::string& output_name)
|
||||
: AGraph(config, "custom-graph-" + name, id),
|
||||
name_(name),
|
||||
output_name_(output_name),
|
||||
id_(id),
|
||||
tooltip_format_enabled_{config_["tooltip-format"].isString()},
|
||||
percentage_(0),
|
||||
fp_(nullptr),
|
||||
pid_(-1) {
|
||||
if (config.isNull()) {
|
||||
spdlog::warn("There is no configuration for 'custom-graph/{}', element will be hidden", name);
|
||||
}
|
||||
dp.emit();
|
||||
if (!config_["signal"].empty() && config_["interval"].empty() &&
|
||||
config_["restart-interval"].empty()) {
|
||||
waitingWorker();
|
||||
} else if (interval_.count() > 0) {
|
||||
delayWorker();
|
||||
} else if (config_["exec"].isString()) {
|
||||
continuousWorker();
|
||||
}
|
||||
}
|
||||
|
||||
waybar::modules::CustomGraph::~CustomGraph() {
|
||||
if (pid_ != -1) {
|
||||
killpg(pid_, SIGTERM);
|
||||
waitpid(pid_, NULL, 0);
|
||||
pid_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::modules::CustomGraph::delayWorker() {
|
||||
thread_ = [this] {
|
||||
for (int i : this->pid_children_) {
|
||||
int status;
|
||||
waitpid(i, &status, 0);
|
||||
}
|
||||
|
||||
this->pid_children_.clear();
|
||||
|
||||
bool can_update = true;
|
||||
if (config_["exec-if"].isString()) {
|
||||
output_ = util::command::execNoRead(config_["exec-if"].asString());
|
||||
if (output_.exit_code != 0) {
|
||||
can_update = false;
|
||||
dp.emit();
|
||||
}
|
||||
}
|
||||
if (can_update) {
|
||||
if (config_["exec"].isString()) {
|
||||
output_ = util::command::exec(config_["exec"].asString(), output_name_);
|
||||
}
|
||||
dp.emit();
|
||||
}
|
||||
thread_.sleep_for(interval_);
|
||||
};
|
||||
}
|
||||
|
||||
void waybar::modules::CustomGraph::continuousWorker() {
|
||||
auto cmd = config_["exec"].asString();
|
||||
pid_ = -1;
|
||||
fp_ = util::command::open(cmd, pid_, output_name_);
|
||||
if (!fp_) {
|
||||
throw std::runtime_error("Unable to open " + cmd);
|
||||
}
|
||||
thread_ = [this, cmd] {
|
||||
char* buff = nullptr;
|
||||
waybar::util::ScopeGuard buff_deleter([&buff]() {
|
||||
if (buff) {
|
||||
free(buff);
|
||||
}
|
||||
});
|
||||
size_t len = 0;
|
||||
if (getline(&buff, &len, fp_) == -1) {
|
||||
int exit_code = 1;
|
||||
if (fp_) {
|
||||
exit_code = WEXITSTATUS(util::command::close(fp_, pid_));
|
||||
fp_ = nullptr;
|
||||
}
|
||||
if (exit_code != 0) {
|
||||
output_ = {exit_code, ""};
|
||||
dp.emit();
|
||||
spdlog::error("{} stopped unexpectedly, is it endless?", name_);
|
||||
}
|
||||
if (config_["restart-interval"].isUInt()) {
|
||||
pid_ = -1;
|
||||
thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt()));
|
||||
fp_ = util::command::open(cmd, pid_, output_name_);
|
||||
if (!fp_) {
|
||||
throw std::runtime_error("Unable to open " + cmd);
|
||||
}
|
||||
} else {
|
||||
thread_.stop();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
std::string output = buff;
|
||||
|
||||
// Remove last newline
|
||||
if (!output.empty() && output[output.length() - 1] == '\n') {
|
||||
output.erase(output.length() - 1);
|
||||
}
|
||||
output_ = {0, output};
|
||||
dp.emit();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void waybar::modules::CustomGraph::waitingWorker() {
|
||||
thread_ = [this] {
|
||||
bool can_update = true;
|
||||
if (config_["exec-if"].isString()) {
|
||||
output_ = util::command::execNoRead(config_["exec-if"].asString());
|
||||
if (output_.exit_code != 0) {
|
||||
can_update = false;
|
||||
dp.emit();
|
||||
}
|
||||
}
|
||||
if (can_update) {
|
||||
if (config_["exec"].isString()) {
|
||||
output_ = util::command::exec(config_["exec"].asString(), output_name_);
|
||||
}
|
||||
dp.emit();
|
||||
}
|
||||
thread_.sleep();
|
||||
};
|
||||
}
|
||||
|
||||
void waybar::modules::CustomGraph::refresh(int sig) {
|
||||
if (sig == SIGRTMIN + config_["signal"].asInt()) {
|
||||
thread_.wake_up();
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::modules::CustomGraph::handleEvent() {
|
||||
if (!config_["exec-on-event"].isBool() || config_["exec-on-event"].asBool()) {
|
||||
thread_.wake_up();
|
||||
}
|
||||
}
|
||||
|
||||
bool waybar::modules::CustomGraph::handleScroll(GdkEventScroll* e) {
|
||||
auto ret = AGraph::handleScroll(e);
|
||||
handleEvent();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool waybar::modules::CustomGraph::handleToggle(GdkEventButton* const& e) {
|
||||
auto ret = AGraph::handleToggle(e);
|
||||
handleEvent();
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto waybar::modules::CustomGraph::update() -> void {
|
||||
// Hide label if output is empty
|
||||
if ((config_["exec"].isString() || config_["exec-if"].isString()) &&
|
||||
(output_.out.empty() || output_.exit_code != 0)) {
|
||||
event_box_.hide();
|
||||
} else {
|
||||
if (config_["return-type"].asString() == "json") {
|
||||
parseOutputJson();
|
||||
} else {
|
||||
parseOutputRaw();
|
||||
}
|
||||
|
||||
try {
|
||||
addValue(percentage_);
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
if (tooltip_format_enabled_) {
|
||||
auto tooltip = config_["tooltip-format"].asString();
|
||||
tooltip = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_),
|
||||
fmt::arg("alt", alt_), fmt::arg("percentage", percentage_));
|
||||
graph_.set_tooltip_markup(tooltip);
|
||||
} else {
|
||||
if (graph_.get_tooltip_markup() != tooltip_) {
|
||||
graph_.set_tooltip_markup(tooltip_);
|
||||
}
|
||||
}
|
||||
}
|
||||
auto style = graph_.get_style_context();
|
||||
auto classes = style->list_classes();
|
||||
for (auto const& c : classes) {
|
||||
if (c == id_) continue;
|
||||
style->remove_class(c);
|
||||
}
|
||||
for (auto const& c : class_) {
|
||||
style->add_class(c);
|
||||
}
|
||||
style->add_class("flat");
|
||||
style->add_class(MODULE_CLASS);
|
||||
event_box_.show();
|
||||
} catch (const fmt::format_error& e) {
|
||||
if (std::strcmp(e.what(), "cannot switch from manual to automatic argument indexing") != 0)
|
||||
throw;
|
||||
|
||||
throw fmt::format_error(
|
||||
"mixing manual and automatic argument indexing is no longer supported; "
|
||||
"try replacing \"{}\" with \"{text}\" in your format specifier");
|
||||
}
|
||||
}
|
||||
// Call parent update
|
||||
AGraph::update();
|
||||
}
|
||||
|
||||
void waybar::modules::CustomGraph::parseOutputRaw() {
|
||||
std::istringstream output(output_.out);
|
||||
std::string line;
|
||||
int i = 0;
|
||||
while (getline(output, line)) {
|
||||
Glib::ustring validated_line = line;
|
||||
if (!validated_line.validate()) {
|
||||
validated_line = validated_line.make_valid();
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
if (config_["escape"].isBool() && config_["escape"].asBool()) {
|
||||
text_ = Glib::Markup::escape_text(validated_line);
|
||||
tooltip_ = Glib::Markup::escape_text(validated_line);
|
||||
} else {
|
||||
text_ = validated_line;
|
||||
tooltip_ = validated_line;
|
||||
}
|
||||
class_.clear();
|
||||
} else if (i == 1) {
|
||||
if (config_["escape"].isBool() && config_["escape"].asBool()) {
|
||||
tooltip_ = Glib::Markup::escape_text(validated_line);
|
||||
} else {
|
||||
tooltip_ = validated_line;
|
||||
}
|
||||
} else if (i == 2) {
|
||||
class_.push_back(validated_line);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::modules::CustomGraph::parseOutputJson() {
|
||||
std::istringstream output(output_.out);
|
||||
std::string line;
|
||||
class_.clear();
|
||||
while (getline(output, line)) {
|
||||
auto parsed = parser_.parse(line);
|
||||
if (config_["escape"].isBool() && config_["escape"].asBool()) {
|
||||
text_ = Glib::Markup::escape_text(parsed["text"].asString());
|
||||
} else {
|
||||
text_ = parsed["text"].asString();
|
||||
}
|
||||
if (config_["escape"].isBool() && config_["escape"].asBool()) {
|
||||
alt_ = Glib::Markup::escape_text(parsed["alt"].asString());
|
||||
} else {
|
||||
alt_ = parsed["alt"].asString();
|
||||
}
|
||||
if (config_["escape"].isBool() && config_["escape"].asBool()) {
|
||||
tooltip_ = Glib::Markup::escape_text(parsed["tooltip"].asString());
|
||||
} else {
|
||||
tooltip_ = parsed["tooltip"].asString();
|
||||
}
|
||||
if (parsed["class"].isString()) {
|
||||
class_.push_back(parsed["class"].asString());
|
||||
} else if (parsed["class"].isArray()) {
|
||||
for (auto const& c : parsed["class"]) {
|
||||
class_.push_back(c.asString());
|
||||
}
|
||||
}
|
||||
if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) {
|
||||
percentage_ = (int)lround(parsed["percentage"].asFloat());
|
||||
} else {
|
||||
percentage_ = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
#include <glibmm/main.h>
|
||||
#include <json/value.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <glibmm/main.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -152,8 +153,7 @@ bool Workspace::pointerInsideButton() {
|
||||
const int buttonHeight = allocation.get_height();
|
||||
|
||||
return pointerRootX >= buttonRootX && pointerRootY >= buttonRootY &&
|
||||
pointerRootX < buttonRootX + buttonWidth &&
|
||||
pointerRootY < buttonRootY + buttonHeight;
|
||||
pointerRootX < buttonRootX + buttonWidth && pointerRootY < buttonRootY + buttonHeight;
|
||||
}
|
||||
|
||||
bool Workspace::syncHoverClass() {
|
||||
@@ -174,9 +174,8 @@ void Workspace::startHoverCheck() {
|
||||
return;
|
||||
}
|
||||
|
||||
m_hoverCheckConnection = Glib::signal_timeout().connect(
|
||||
sigc::mem_fun(*this, &Workspace::syncHoverClass),
|
||||
50);
|
||||
m_hoverCheckConnection =
|
||||
Glib::signal_timeout().connect(sigc::mem_fun(*this, &Workspace::syncHoverClass), 50);
|
||||
}
|
||||
|
||||
void Workspace::stopHoverCheck() {
|
||||
@@ -252,8 +251,7 @@ void Workspace::setActiveWindow(WindowAddress const& addr) {
|
||||
|
||||
auto activeWindowPos = m_workspaceManager.activeWindowPosition();
|
||||
const bool has_active_window =
|
||||
activeIdx.has_value() &&
|
||||
activeWindowPos != Workspaces::ActiveWindowPosition::NONE;
|
||||
activeIdx.has_value() && activeWindowPos != Workspaces::ActiveWindowPosition::NONE;
|
||||
|
||||
if (has_active_window) {
|
||||
auto window = std::move(m_windowMap[*activeIdx]);
|
||||
@@ -270,8 +268,7 @@ void Workspace::insertWindow(WindowCreationPayload create_window_payload) {
|
||||
if (!create_window_payload.isEmpty(m_workspaceManager)) {
|
||||
auto repr = create_window_payload.repr(m_workspaceManager);
|
||||
|
||||
const bool should_display =
|
||||
!repr.empty() || m_workspaceManager.enableTaskbar();
|
||||
const bool should_display = !repr.empty() || m_workspaceManager.enableTaskbar();
|
||||
|
||||
if (should_display) {
|
||||
auto addr = create_window_payload.getAddress();
|
||||
@@ -337,7 +334,7 @@ std::string& Workspace::selectIcon(std::map<std::string, std::string>& icons_map
|
||||
if (specialNamedIconIt != icons_map.end()) {
|
||||
return specialNamedIconIt->second;
|
||||
}
|
||||
|
||||
|
||||
auto specialIconIt = icons_map.find("special");
|
||||
if (specialIconIt != icons_map.end()) {
|
||||
return specialIconIt->second;
|
||||
@@ -378,7 +375,6 @@ std::string& Workspace::selectIcon(std::map<std::string, std::string>& icons_map
|
||||
return m_name;
|
||||
}
|
||||
|
||||
|
||||
void Workspace::update(const std::string& workspace_icon) {
|
||||
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
|
||||
m_button.hide();
|
||||
@@ -436,16 +432,56 @@ void Workspace::update(const std::string& workspace_icon) {
|
||||
// need to compute this if enableTaskbar() is true
|
||||
if (!m_workspaceManager.enableTaskbar()) {
|
||||
auto windowSeparator = m_workspaceManager.getWindowSeparator();
|
||||
auto groupThreshold = m_workspaceManager.windowRewriteGroupThreshold();
|
||||
|
||||
bool isNotFirst = false;
|
||||
auto end_it = m_workspaceManager.maxWindows() == 0 ? m_windowMap.end() : m_windowMap.begin() + m_workspaceManager.maxWindows();
|
||||
auto end_it = m_workspaceManager.maxWindows() == 0
|
||||
? m_windowMap.end()
|
||||
: m_windowMap.begin() + m_workspaceManager.maxWindows();
|
||||
|
||||
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
|
||||
if (isNotFirst) {
|
||||
windows.append(windowSeparator);
|
||||
if (groupThreshold > 0) {
|
||||
// Build ordered counts of each unique icon (including singular ones when threshold set to 1)
|
||||
std::vector<std::pair<std::string, int>> iconCounts;
|
||||
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
|
||||
const auto& window_repr = *it;
|
||||
auto found = std::ranges::find_if(
|
||||
iconCounts, [&](const auto& p) { return p.first == window_repr.repr_rewrite; });
|
||||
if (found != iconCounts.end()) {
|
||||
found->second++;
|
||||
} else {
|
||||
iconCounts.emplace_back(window_repr.repr_rewrite, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Format the group string
|
||||
auto groupFormat = m_workspaceManager.getWindowRewriteGroupFormat();
|
||||
bool isNotFirst = false;
|
||||
for (const auto& [icon, count] : iconCounts) {
|
||||
if (count >= groupThreshold) {
|
||||
if (isNotFirst) windows.append(windowSeparator);
|
||||
isNotFirst = true;
|
||||
try {
|
||||
windows.append(fmt::format(fmt::runtime(groupFormat), fmt::arg("icon", icon),
|
||||
fmt::arg("count", count)));
|
||||
} catch (const fmt::format_error& e) {
|
||||
spdlog::warn("Formatting window-rewrite-group-format error: {}", e.what());
|
||||
windows.append(icon);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (isNotFirst) windows.append(windowSeparator);
|
||||
isNotFirst = true;
|
||||
windows.append(icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Not grouping icons
|
||||
bool isNotFirst = false;
|
||||
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
|
||||
if (isNotFirst) windows.append(windowSeparator);
|
||||
isNotFirst = true;
|
||||
windows.append(it->repr_rewrite);
|
||||
}
|
||||
isNotFirst = true;
|
||||
windows.append(it->repr_rewrite);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,8 +541,7 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) {
|
||||
}
|
||||
if (m_workspaceManager.onClickWindow() != "") {
|
||||
button->signal_button_press_event().connect(
|
||||
sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address),
|
||||
false);
|
||||
sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address), false);
|
||||
}
|
||||
|
||||
auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()),
|
||||
@@ -536,13 +571,17 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) {
|
||||
};
|
||||
|
||||
if (m_workspaceManager.taskbarReverseDirection()) {
|
||||
auto rend_it = m_workspaceManager.maxWindows() == 0 ? m_windowMap.rend() : m_windowMap.rbegin() + m_workspaceManager.maxWindows();
|
||||
auto rend_it = m_workspaceManager.maxWindows() == 0
|
||||
? m_windowMap.rend()
|
||||
: m_windowMap.rbegin() + m_workspaceManager.maxWindows();
|
||||
|
||||
for (auto it = m_windowMap.rbegin(); it != rend_it; ++it) {
|
||||
processWindow(*it);
|
||||
}
|
||||
} else {
|
||||
auto end_it = m_workspaceManager.maxWindows() == 0 ? m_windowMap.end() : m_windowMap.begin() + m_workspaceManager.maxWindows();
|
||||
auto end_it = m_workspaceManager.maxWindows() == 0
|
||||
? m_windowMap.end()
|
||||
: m_windowMap.begin() + m_workspaceManager.maxWindows();
|
||||
|
||||
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
|
||||
processWindow(*it);
|
||||
|
||||
@@ -670,6 +670,16 @@ auto Workspaces::parseConfig(const Json::Value& config) -> void {
|
||||
populateSortByConfig(config);
|
||||
populateIgnoreWorkspacesConfig(config);
|
||||
populateFormatWindowSeparatorConfig(config);
|
||||
|
||||
const auto& groupThreshold = config["window-rewrite-group-threshold"];
|
||||
if (groupThreshold.isInt()) {
|
||||
m_windowRewriteGroupThreshold = groupThreshold.asInt();
|
||||
}
|
||||
const auto& groupFormat = config["window-rewrite-group-format"];
|
||||
if (groupFormat.isString()) {
|
||||
m_windowRewriteGroupFormat = groupFormat.asString();
|
||||
}
|
||||
|
||||
populateWindowRewriteConfig(config);
|
||||
populateMaxWindowsConfig(config);
|
||||
|
||||
|
||||
+148
-15
@@ -1,5 +1,6 @@
|
||||
#include "modules/idle_inhibitor.hpp"
|
||||
|
||||
#include "ext-idle-notify-v1-client-protocol.h"
|
||||
#include "idle-inhibit-unstable-v1-client-protocol.h"
|
||||
#include "util/command.hpp"
|
||||
|
||||
@@ -11,11 +12,24 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar&
|
||||
: ALabel(config, "idle_inhibitor", id, "{status}", 0, false, true),
|
||||
bar_(bar),
|
||||
idle_inhibitor_(nullptr),
|
||||
pid_(-1) {
|
||||
idle_notification_(nullptr),
|
||||
idle_timeout_ms_(0),
|
||||
pid_(-1),
|
||||
wait_for_activity_(false) {
|
||||
if (waybar::Client::inst()->idle_inhibit_manager == nullptr) {
|
||||
throw std::runtime_error("idle-inhibit not available");
|
||||
}
|
||||
|
||||
// Read the wait-for-activity config option
|
||||
if (config_["wait-for-activity"].isBool()) {
|
||||
wait_for_activity_ = config_["wait-for-activity"].asBool();
|
||||
|
||||
// Check if ext-idle-notify protocol is available when wait-for-activity is enabled
|
||||
if (wait_for_activity_ && waybar::Client::inst()->idle_notifier == nullptr) {
|
||||
throw std::runtime_error("wait-for-activity requires ext-idle-notify-v1 protocol support");
|
||||
}
|
||||
}
|
||||
|
||||
if (waybar::modules::IdleInhibitor::modules.empty() && config_["start-activated"].isBool() &&
|
||||
config_["start-activated"].asBool() != status) {
|
||||
toggleStatus();
|
||||
@@ -32,6 +46,8 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar&
|
||||
}
|
||||
|
||||
waybar::modules::IdleInhibitor::~IdleInhibitor() {
|
||||
teardownIdleNotification();
|
||||
|
||||
if (idle_inhibitor_ != nullptr) {
|
||||
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
|
||||
idle_inhibitor_ = nullptr;
|
||||
@@ -70,6 +86,17 @@ auto waybar::modules::IdleInhibitor::update() -> void {
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
auto waybar::modules::IdleInhibitor::refresh(int sig) -> void {
|
||||
if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) {
|
||||
toggleStatus();
|
||||
|
||||
// Make all other idle inhibitor modules update
|
||||
for (auto const& module : waybar::modules::IdleInhibitor::modules) {
|
||||
module->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::modules::IdleInhibitor::toggleStatus() {
|
||||
status = !status;
|
||||
|
||||
@@ -81,21 +108,34 @@ void waybar::modules::IdleInhibitor::toggleStatus() {
|
||||
if (status && config_["timeout"].isNumeric()) {
|
||||
auto timeoutMins = config_["timeout"].asDouble();
|
||||
int timeoutSecs = timeoutMins * 60;
|
||||
idle_timeout_ms_ = timeoutSecs * 1000;
|
||||
|
||||
timeout_ = Glib::signal_timeout().connect_seconds(
|
||||
[]() {
|
||||
/* intentionally not tied to a module instance lifetime
|
||||
* as the output with `this` can be disconnected
|
||||
*/
|
||||
spdlog::info("deactivating idle_inhibitor by timeout");
|
||||
status = false;
|
||||
for (auto const& module : waybar::modules::IdleInhibitor::modules) {
|
||||
module->update();
|
||||
}
|
||||
/* disconnect */
|
||||
return false;
|
||||
},
|
||||
timeoutSecs);
|
||||
// If wait-for-activity is enabled, set up idle notification
|
||||
if (wait_for_activity_) {
|
||||
spdlog::debug("idle_inhibitor: wait-for-activity enabled, timeout: {} ms", idle_timeout_ms_);
|
||||
// Tear down any existing notification first to ensure fresh setup
|
||||
teardownIdleNotification();
|
||||
setupIdleNotification();
|
||||
} else {
|
||||
// Original behavior: simple timeout
|
||||
timeout_ = Glib::signal_timeout().connect_seconds(
|
||||
[]() {
|
||||
/* intentionally not tied to a module instance lifetime
|
||||
* as the output with `this` can be disconnected
|
||||
*/
|
||||
spdlog::info("deactivating idle_inhibitor by timeout");
|
||||
status = false;
|
||||
for (auto const& module : waybar::modules::IdleInhibitor::modules) {
|
||||
module->update();
|
||||
}
|
||||
/* disconnect */
|
||||
return false;
|
||||
},
|
||||
timeoutSecs);
|
||||
}
|
||||
} else {
|
||||
// When deactivated, tear down idle notification
|
||||
teardownIdleNotification();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,3 +154,96 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
|
||||
ALabel::handleToggle(e);
|
||||
return true;
|
||||
}
|
||||
|
||||
void waybar::modules::IdleInhibitor::handleIdled(void* data,
|
||||
ext_idle_notification_v1* /*notification*/) {
|
||||
spdlog::info("deactivating idle_inhibitor due to user inactivity");
|
||||
status = false;
|
||||
|
||||
// Clean up the notification since we're deactivating
|
||||
auto* self = static_cast<IdleInhibitor*>(data);
|
||||
if (self != nullptr) {
|
||||
self->teardownIdleNotification();
|
||||
}
|
||||
|
||||
for (auto const& module : waybar::modules::IdleInhibitor::modules) {
|
||||
module->update();
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::modules::IdleInhibitor::handleResumed(void* data,
|
||||
ext_idle_notification_v1* /*notification*/) {
|
||||
// User became active again - notification will continue monitoring
|
||||
spdlog::debug("user activity detected, idle_inhibitor still active");
|
||||
}
|
||||
|
||||
void waybar::modules::IdleInhibitor::setupIdleNotification() {
|
||||
spdlog::debug("idle_inhibitor: setting up idle notification");
|
||||
|
||||
// Clean up any existing notification first
|
||||
if (idle_notification_ != nullptr) {
|
||||
spdlog::debug("idle_inhibitor: cleaning up existing notification before setup");
|
||||
teardownIdleNotification();
|
||||
}
|
||||
|
||||
auto* client = waybar::Client::inst();
|
||||
if (client->idle_notifier == nullptr) {
|
||||
spdlog::error("ext-idle-notify protocol not available");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the wayland seat from the display
|
||||
auto* gdk_seat = gdk_display_get_default_seat(client->gdk_display->gobj());
|
||||
if (gdk_seat == nullptr) {
|
||||
spdlog::error("failed to get default seat");
|
||||
return;
|
||||
}
|
||||
auto* wl_seat = gdk_wayland_seat_get_wl_seat(gdk_seat);
|
||||
|
||||
// Check protocol version to determine which function to use
|
||||
uint32_t version =
|
||||
wl_proxy_get_version(reinterpret_cast<struct wl_proxy*>(client->idle_notifier));
|
||||
|
||||
spdlog::debug("idle_inhibitor: creating notification with timeout {} ms (protocol version {})",
|
||||
idle_timeout_ms_, version);
|
||||
|
||||
if (version >= 2) {
|
||||
// Version 2+: Use get_input_idle_notification which ignores idle inhibitors
|
||||
// This allows us to detect actual user inactivity even while the inhibitor is active
|
||||
spdlog::debug("idle_inhibitor: using get_input_idle_notification (ignores inhibitors)");
|
||||
idle_notification_ = ext_idle_notifier_v1_get_input_idle_notification(
|
||||
client->idle_notifier, idle_timeout_ms_, wl_seat);
|
||||
} else {
|
||||
// Version 1: Fall back to get_idle_notification
|
||||
// WARNING: This respects idle inhibitors, so it won't fire while inhibitor is active
|
||||
spdlog::warn(
|
||||
"idle_inhibitor: ext-idle-notifier-v1 version {} doesn't support "
|
||||
"get_input_idle_notification, "
|
||||
"wait-for-activity may not work correctly",
|
||||
version);
|
||||
idle_notification_ = ext_idle_notifier_v1_get_idle_notification(client->idle_notifier,
|
||||
idle_timeout_ms_, wl_seat);
|
||||
}
|
||||
|
||||
if (idle_notification_ == nullptr) {
|
||||
spdlog::error("idle_inhibitor: failed to create idle notification");
|
||||
return;
|
||||
}
|
||||
|
||||
static const struct ext_idle_notification_v1_listener idle_notification_listener = {
|
||||
.idled = &IdleInhibitor::handleIdled,
|
||||
.resumed = &IdleInhibitor::handleResumed,
|
||||
};
|
||||
|
||||
ext_idle_notification_v1_add_listener(idle_notification_, &idle_notification_listener, this);
|
||||
wl_display_roundtrip(client->wl_display);
|
||||
spdlog::debug("idle_inhibitor: idle notification setup complete");
|
||||
}
|
||||
|
||||
void waybar::modules::IdleInhibitor::teardownIdleNotification() {
|
||||
if (idle_notification_ != nullptr) {
|
||||
spdlog::debug("idle_inhibitor: tearing down idle notification");
|
||||
ext_idle_notification_v1_destroy(idle_notification_);
|
||||
idle_notification_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
|
||||
port_(config_["port"].isUInt() ? config["port"].asUInt() : 0),
|
||||
password_(config_["password"].empty() ? "" : config_["password"].asString()),
|
||||
timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000),
|
||||
playing_interval_(config_["playing-interval"].isUInt() ? config_["playing-interval"].asUInt()
|
||||
: 1'000),
|
||||
connection_(nullptr, &mpd_connection_free),
|
||||
status_(nullptr, &mpd_status_free),
|
||||
song_(nullptr, &mpd_song_free),
|
||||
@@ -35,6 +37,10 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
|
||||
spdlog::warn("{}: `timeout` configuration should be an unsigned int", module_name_);
|
||||
}
|
||||
|
||||
if (!config_["playing-interval"].isNull() && !config_["playing-interval"].isUInt()) {
|
||||
spdlog::warn("{}: `playing-interval` configuration should be an unsigned int", module_name_);
|
||||
}
|
||||
|
||||
if (!config["server"].isNull()) {
|
||||
if (!config_["server"].isString()) {
|
||||
spdlog::warn("{}:`server` configuration should be a string", module_name_);
|
||||
|
||||
@@ -115,7 +115,7 @@ bool Idle::on_io(Glib::IOCondition const&) {
|
||||
void Playing::entry() noexcept {
|
||||
timer();
|
||||
idle();
|
||||
spdlog::debug("mpd: Playing: enabled 1 second periodic timer.");
|
||||
spdlog::debug("mpd: Playing: enabled {}ms periodic timer.", ctx_->playing_interval());
|
||||
}
|
||||
|
||||
void Playing::exit() noexcept {
|
||||
@@ -126,7 +126,7 @@ void Playing::exit() noexcept {
|
||||
|
||||
if (timer_connection_.connected()) {
|
||||
timer_connection_.disconnect();
|
||||
spdlog::debug("mpd: Playing: disabled 1 second periodic timer.");
|
||||
spdlog::debug("mpd: Playing: disabled {}ms periodic timer.", ctx_->playing_interval());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ void Playing::timer() noexcept {
|
||||
}
|
||||
|
||||
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Playing::on_timer);
|
||||
timer_connection_ = Glib::signal_timeout().connect_seconds(timer_slot, 1);
|
||||
timer_connection_ = Glib::signal_timeout().connect(timer_slot, ctx_->playing_interval());
|
||||
}
|
||||
|
||||
void Playing::idle() noexcept {
|
||||
|
||||
@@ -315,19 +315,26 @@ auto waybar::modules::Network::update() -> void {
|
||||
elapsed_seconds = std::chrono::duration<double>(interval_).count();
|
||||
}
|
||||
|
||||
auto threshold_state = getState(signal_strength_);
|
||||
|
||||
if (!alt_) {
|
||||
auto state = getNetworkState();
|
||||
if (!state_.empty() && label_.get_style_context()->has_class(state_)) {
|
||||
label_.get_style_context()->remove_class(state_);
|
||||
}
|
||||
if (config_["format-" + state].isString()) {
|
||||
if (!threshold_state.empty() && config_["format-" + state + "-" + threshold_state].isString()) {
|
||||
default_format_ = config_["format-" + state + "-" + threshold_state].asString();
|
||||
} else if (config_["format-" + state].isString()) {
|
||||
default_format_ = config_["format-" + state].asString();
|
||||
} else if (config_["format"].isString()) {
|
||||
default_format_ = config_["format"].asString();
|
||||
} else {
|
||||
default_format_ = DEFAULT_FORMAT;
|
||||
}
|
||||
if (config_["tooltip-format-" + state].isString()) {
|
||||
if (!threshold_state.empty() &&
|
||||
config_["tooltip-format-" + state + "-" + threshold_state].isString()) {
|
||||
tooltip_format = config_["tooltip-format-" + state + "-" + threshold_state].asString();
|
||||
} else if (config_["tooltip-format-" + state].isString()) {
|
||||
tooltip_format = config_["tooltip-format-" + state].asString();
|
||||
}
|
||||
if (!label_.get_style_context()->has_class(state)) {
|
||||
@@ -336,7 +343,6 @@ auto waybar::modules::Network::update() -> void {
|
||||
format_ = default_format_;
|
||||
state_ = state;
|
||||
}
|
||||
getState(signal_strength_);
|
||||
|
||||
std::string final_ipaddr_;
|
||||
if (addr_pref_ == ip_addr_pref::IPV4) {
|
||||
|
||||
@@ -46,6 +46,12 @@ Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value&
|
||||
gIPC->registerForIPC("WorkspaceActiveWindowChanged", this);
|
||||
gIPC->registerForIPC("WorkspaceUrgencyChanged", this);
|
||||
|
||||
if (config["enable-bar-scroll"].asBool()) {
|
||||
auto& window = const_cast<Bar&>(bar_).window;
|
||||
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
||||
window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
|
||||
}
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
@@ -123,10 +129,10 @@ void Workspaces::doUpdate() {
|
||||
|
||||
if (config_["format"].isString()) {
|
||||
auto format = config_["format"].asString();
|
||||
name = fmt::format(fmt::runtime(format), fmt::arg("icon", getIcon(name, ws)),
|
||||
fmt::arg("value", name), fmt::arg("name", ws["name"].asString()),
|
||||
fmt::arg("index", ws["idx"].asUInt()),
|
||||
fmt::arg("output", ws["output"].asString()));
|
||||
name = fmt::format(
|
||||
fmt::runtime(format), fmt::arg("icon", getIcon(name, ws)), fmt::arg("value", name),
|
||||
fmt::arg("name", ws["name"].asString()), fmt::arg("index", ws["idx"].asUInt()),
|
||||
fmt::arg("output", ws["output"].asString()), fmt::arg("total", my_workspaces.size()));
|
||||
}
|
||||
if (!config_["disable-markup"].asBool()) {
|
||||
auto* child = gtk_bin_get_child(GTK_BIN(button.gobj()));
|
||||
@@ -225,6 +231,45 @@ std::string Workspaces::getIcon(const std::string& value, const Json::Value& ws)
|
||||
return value;
|
||||
}
|
||||
|
||||
bool Workspaces::handleScroll(GdkEventScroll* e) {
|
||||
if (gdk_event_get_pointer_emulated((GdkEvent*)e) != 0) {
|
||||
/**
|
||||
* Ignore emulated scroll events on window
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
auto dir = AModule::getScrollDir(e);
|
||||
if (dir == SCROLL_DIR::NONE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
Json::Value request(Json::objectValue);
|
||||
auto& action = (request["Action"] = Json::Value(Json::objectValue));
|
||||
|
||||
std::string action_name;
|
||||
|
||||
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) {
|
||||
action_name = "FocusWorkspaceDown";
|
||||
} else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) {
|
||||
action_name = "FocusWorkspaceUp";
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
action[action_name] = Json::Value(Json::objectValue);
|
||||
|
||||
IPC::send(request);
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("Workspaces: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Workspaces::sortWorkspaces(std::vector<Json::Value>& workspaces) const {
|
||||
auto get_name = [](const Json::Value& ws) -> std::string {
|
||||
if (ws["name"]) return ws["name"].asString();
|
||||
|
||||
@@ -7,6 +7,7 @@ waybar::modules::Pulseaudio::Pulseaudio(const std::string& id, const Json::Value
|
||||
|
||||
backend = util::AudioBackend::getInstance([this] { this->dp.emit(); });
|
||||
backend->setIgnoredSinks(config_["ignored-sinks"]);
|
||||
backend->setSinkMapping(config_["sink-mapping"]);
|
||||
|
||||
if (config_["target"].isString() && config_["target"].asString() == "source") {
|
||||
target = util::PulseaudioTarget::Source;
|
||||
|
||||
@@ -6,6 +6,7 @@ PulseaudioSlider::PulseaudioSlider(const std::string& id, const Json::Value& con
|
||||
: ASlider(config, "pulseaudio-slider", id) {
|
||||
backend = util::AudioBackend::getInstance([this] { this->dp.emit(); });
|
||||
backend->setIgnoredSinks(config_["ignored-sinks"]);
|
||||
backend->setSinkMapping(config_["sink-mapping"]);
|
||||
|
||||
if (config_["target"].isString()) {
|
||||
std::string target = config_["target"].asString();
|
||||
|
||||
@@ -35,12 +35,12 @@ static const zriver_output_status_v1_listener output_status_listener_impl{
|
||||
|
||||
static void listen_focused_output(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,
|
||||
struct wl_output* output) {
|
||||
static_cast<Tags *>(data)->handle_focused_output(output);
|
||||
static_cast<Tags*>(data)->handle_focused_output(output);
|
||||
}
|
||||
|
||||
static void listen_unfocused_output(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,
|
||||
struct wl_output* output) {
|
||||
static_cast<Tags *>(data)->handle_unfocused_output(output);
|
||||
static_cast<Tags*>(data)->handle_unfocused_output(output);
|
||||
}
|
||||
|
||||
static void listen_focused_view(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,
|
||||
@@ -183,6 +183,7 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con
|
||||
button.signal_button_press_event().connect(
|
||||
sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), (1 << tag)));
|
||||
}
|
||||
button.get_style_context()->add_class("tag-" + std::to_string(tag + 1));
|
||||
button.show();
|
||||
}
|
||||
|
||||
@@ -306,7 +307,7 @@ void Tags::handle_urgent_tags(uint32_t tags) {
|
||||
}
|
||||
}
|
||||
|
||||
void Tags::handle_focused_output(struct wl_output *output) {
|
||||
void Tags::handle_focused_output(struct wl_output* output) {
|
||||
if (output_ == output) {
|
||||
for (size_t i = 0; i < buttons_.size(); ++i) {
|
||||
buttons_[i].get_style_context()->add_class("output");
|
||||
@@ -314,7 +315,7 @@ void Tags::handle_focused_output(struct wl_output *output) {
|
||||
}
|
||||
}
|
||||
|
||||
void Tags::handle_unfocused_output(struct wl_output *output) {
|
||||
void Tags::handle_unfocused_output(struct wl_output* output) {
|
||||
if (output_ == output) {
|
||||
for (size_t i = 0; i < buttons_.size(); ++i) {
|
||||
buttons_[i].get_style_context()->remove_class("output");
|
||||
|
||||
+67
-10
@@ -28,7 +28,8 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
|
||||
source_node_id_(0),
|
||||
source_muted_(false),
|
||||
source_volume_(0.0),
|
||||
default_source_name_(nullptr) {
|
||||
default_source_name_(nullptr),
|
||||
form_factor_("") {
|
||||
waybar::modules::Wireplumber::modules.push_back(this);
|
||||
|
||||
wp_init(WP_INIT_PIPEWIRE);
|
||||
@@ -111,6 +112,21 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber*
|
||||
: description != nullptr ? description
|
||||
: "Unknown node name";
|
||||
spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, self->node_name_);
|
||||
|
||||
// find form-factor
|
||||
const auto* devid = wp_properties_get(properties, "device.id");
|
||||
spdlog::debug("[{}]: '{}' device.id is {}", self->name_, self->type_, devid);
|
||||
|
||||
auto* dev = static_cast<WpDevice*>(wp_object_manager_lookup(
|
||||
self->om_, WP_TYPE_DEVICE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=s", devid, nullptr));
|
||||
|
||||
if (const auto* ff =
|
||||
wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(dev), "device.form-factor")) {
|
||||
self->form_factor_ = ff;
|
||||
spdlog::debug("[{}]: Updating node form factor to: {}", self->name_, self->form_factor_);
|
||||
} else {
|
||||
self->form_factor_ = "";
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::updateSourceName(waybar::modules::Wireplumber* self,
|
||||
@@ -373,6 +389,8 @@ void waybar::modules::Wireplumber::prepare(waybar::modules::Wireplumber* self) {
|
||||
"=s", self->type_, nullptr);
|
||||
wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
|
||||
"=s", "Audio/Source", nullptr);
|
||||
wp_object_manager_add_interest(om_, WP_TYPE_DEVICE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
|
||||
"=s", "Audio/Device", nullptr);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res,
|
||||
@@ -430,12 +448,54 @@ void waybar::modules::Wireplumber::asyncLoadRequiredApiModules() {
|
||||
this);
|
||||
}
|
||||
|
||||
static const std::array<std::string, 7> ports = {
|
||||
"headphone", "speaker", "headset", "hands-free", "portable", "car", "hifi",
|
||||
};
|
||||
|
||||
std::vector<std::string> waybar::modules::Wireplumber::getWPIcon() {
|
||||
std::vector<std::string> res;
|
||||
if (muted_) {
|
||||
res.emplace_back(node_name_ + "-muted");
|
||||
}
|
||||
res.push_back(node_name_);
|
||||
res.push_back(source_name_);
|
||||
std::transform(form_factor_.begin(), form_factor_.end(), form_factor_.begin(), ::tolower);
|
||||
for (auto const& port : ports) {
|
||||
if (form_factor_.find(port) != std::string::npos) {
|
||||
if (muted_) {
|
||||
res.emplace_back(port + "-muted");
|
||||
}
|
||||
res.push_back(port);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (muted_) {
|
||||
res.emplace_back("default-muted");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
auto waybar::modules::Wireplumber::update() -> void {
|
||||
auto format = format_;
|
||||
std::string format_name = "format";
|
||||
|
||||
// Handle sink bluetooth state
|
||||
const std::string name = default_node_name_ != nullptr ? default_node_name_ : "";
|
||||
|
||||
auto bt = name.find("bluez") != std::string::npos || name.find("a2dp-sink") != std::string::npos;
|
||||
if (bt) {
|
||||
format_name += "-bluetooth";
|
||||
label_.get_style_context()->add_class("bluetooth");
|
||||
} else {
|
||||
label_.get_style_context()->remove_class("bluetooth");
|
||||
}
|
||||
|
||||
// Handle sink mute state
|
||||
if (muted_) {
|
||||
format = config_["format-muted"].isString() ? config_["format-muted"].asString() : format;
|
||||
// Check muted bluetooth format exists, otherwise fall back to default muted format.
|
||||
if (format_name != "format" && !config_[format_name + "-muted"].isString())
|
||||
format_name = "format";
|
||||
format_name += "-muted";
|
||||
label_.get_style_context()->add_class("muted");
|
||||
label_.get_style_context()->add_class("sink-muted");
|
||||
} else {
|
||||
@@ -461,13 +521,10 @@ auto waybar::modules::Wireplumber::update() -> void {
|
||||
|
||||
// Get the state and apply state-specific format if available
|
||||
auto state = getState(vol);
|
||||
if (!state.empty()) {
|
||||
std::string format_name = muted_ ? "format-muted" : "format";
|
||||
std::string state_format_name = format_name + "-" + state;
|
||||
if (config_[state_format_name].isString()) {
|
||||
format = config_[state_format_name].asString();
|
||||
}
|
||||
}
|
||||
if (!state.empty() && config_[format_name + "-" + state].isString())
|
||||
format = config_[format_name + "-" + state].asString();
|
||||
else if (config_[format_name].isString())
|
||||
format = config_[format_name].asString();
|
||||
|
||||
// Prepare source format string (similar to PulseAudio)
|
||||
std::string format_source = "{volume}%";
|
||||
@@ -488,7 +545,7 @@ auto waybar::modules::Wireplumber::update() -> void {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(fmt::arg("node_name", node_name_));
|
||||
store.push_back(fmt::arg("volume", vol));
|
||||
store.push_back(fmt::arg("icon", getIcon(vol)));
|
||||
store.push_back(fmt::arg("icon", getIcon(vol, getWPIcon())));
|
||||
store.push_back(fmt::arg("format_source", formatted_source));
|
||||
store.push_back(fmt::arg("source_volume", source_vol));
|
||||
store.push_back(fmt::arg("source_desc", source_name_));
|
||||
|
||||
Reference in New Issue
Block a user