Merge branch 'master' into issue-1681

This commit is contained in:
Alexis Rouillard
2023-07-04 22:49:35 +02:00
committed by GitHub
174 changed files with 6310 additions and 1503 deletions

View File

@ -48,13 +48,13 @@ struct UdevMonitorDeleter {
void check_eq(int rc, int expected, const char *message = "eq, rc was: ") {
if (rc != expected) {
throw std::runtime_error(fmt::format(message, rc));
throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
}
}
void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") {
if (rc == bad_rc) {
throw std::runtime_error(fmt::format(message, rc));
throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
}
}
@ -62,7 +62,7 @@ void check0(int rc, const char *message = "rc wasn't 0") { check_eq(rc, 0, messa
void check_gte(int rc, int gte, const char *message = "rc was: ") {
if (rc < gte) {
throw std::runtime_error(fmt::format(message, rc));
throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
}
}
@ -73,7 +73,8 @@ void check_nn(const void *ptr, const char *message = "ptr was null") {
}
} // namespace
waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name, int actual, int max, bool powered)
waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name, int actual, int max,
bool powered)
: name_(std::move(name)), actual_(actual), max_(max), powered_(powered) {}
std::string_view waybar::modules::Backlight::BacklightDev::name() const { return name_; }
@ -91,7 +92,7 @@ bool waybar::modules::Backlight::BacklightDev::get_powered() const { return powe
void waybar::modules::Backlight::BacklightDev::set_powered(bool powered) { powered_ = powered; }
waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &config)
: AButton(config, "backlight", id, "{percent}%", 2),
: ALabel(config, "backlight", id, "{percent}%", 2),
preferred_device_(config["device"].isString() ? config["device"].asString() : "") {
// Get initial state
{
@ -105,6 +106,15 @@ waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &
dp.emit();
}
// Set up scroll handler
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Backlight::handleScroll));
// Connect to the login interface
login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1",
"/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session");
udev_thread_ = [this] {
std::unique_ptr<udev, UdevDeleter> udev{udev_new()};
check_nn(udev.get(), "Udev new failed");
@ -180,9 +190,24 @@ auto waybar::modules::Backlight::update() -> void {
event_box_.show();
const uint8_t percent =
best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());
label_->set_markup(fmt::format(format_, fmt::arg("percent", std::to_string(percent)),
fmt::arg("icon", getIcon(percent))));
std::string desc =
fmt::format(fmt::runtime(format_), fmt::arg("percent", std::to_string(percent)),
fmt::arg("icon", getIcon(percent)));
label_.set_markup(desc);
getState(percent);
if (tooltipEnabled()) {
std::string tooltip_format;
if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format),
fmt::arg("percent", std::to_string(percent)),
fmt::arg("icon", getIcon(percent))));
} else {
label_.set_tooltip_text(desc);
}
}
} else {
event_box_.hide();
}
@ -190,12 +215,12 @@ auto waybar::modules::Backlight::update() -> void {
if (!previous_best_.has_value()) {
return;
}
label_->set_markup("");
label_.set_markup("");
}
previous_best_ = best == nullptr ? std::nullopt : std::optional{*best};
previous_format_ = format_;
// Call parent update
AButton::update();
ALabel::update();
}
template <class ForwardIt>
@ -262,3 +287,63 @@ void waybar::modules::Backlight::enumerate_devices(ForwardIt first, ForwardIt la
upsert_device(first, last, inserter, dev.get());
}
}
bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) {
// Check if the user has set a custom command for scrolling
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
return AModule::handleScroll(e);
}
// Fail fast if the proxy could not be initialized
if (!login_proxy_) {
return true;
}
// Check scroll direction
auto dir = AModule::getScrollDir(e);
if (dir == SCROLL_DIR::NONE) {
return true;
}
// Get scroll step
double step = 1;
if (config_["scroll-step"].isDouble()) {
step = config_["scroll-step"].asDouble();
}
// Get the best device
decltype(devices_) devices;
{
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
devices = devices_;
}
const auto best = best_device(devices.cbegin(), devices.cend(), preferred_device_);
if (best == nullptr) {
return true;
}
// Compute the absolute step
const auto abs_step = static_cast<int>(round(step * best->get_max() / 100.0f));
// Compute the new value
int new_value = best->get_actual();
if (dir == SCROLL_DIR::UP) {
new_value += abs_step;
} else if (dir == SCROLL_DIR::DOWN) {
new_value -= abs_step;
}
// Clamp the value
new_value = std::clamp(new_value, 0, best->get_max());
// Set the new value
auto call_args = Glib::VariantContainerBase(
g_variant_new("(ssu)", "backlight", std::string(best->name()).c_str(), new_value));
login_proxy_->call_sync("SetBrightness", call_args);
return true;
}

View File

@ -6,7 +6,7 @@
#include <iostream>
waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config)
: AButton(config, "battery", id, "{capacity}%", 60) {
: ALabel(config, "battery", id, "{capacity}%", 60) {
#if defined(__linux__)
battery_watch_fd_ = inotify_init1(IN_CLOEXEC);
if (battery_watch_fd_ == -1) {
@ -36,7 +36,8 @@ waybar::modules::Battery::~Battery() {
}
close(global_watch_fd_);
for (auto it = batteries_.cbegin(); it != batteries_.cend(); it++) {
for (auto it = batteries_.cbegin(), next_it = it; it != batteries_.cend(); it = next_it) {
++next_it;
auto watch_id = (*it).second;
if (watch_id >= 0) {
inotify_rm_watch(battery_watch_fd_, watch_id);
@ -107,6 +108,15 @@ void waybar::modules::Battery::refreshBatteries() {
std::ifstream(node.path() / "type") >> type;
if (!type.compare("Battery")) {
// Ignore non-system power supplies unless explicitly requested
if (!bat_defined && fs::exists(node.path() / "scope")) {
std::string scope;
std::ifstream(node.path() / "scope") >> scope;
if (g_ascii_strcasecmp(scope.data(), "device") == 0) {
continue;
}
}
check_map[node.path()] = true;
auto search = batteries_.find(node.path());
if (search == batteries_.end()) {
@ -233,6 +243,10 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
bool total_energy_full_design_exists = false;
uint32_t total_capacity = 0;
bool total_capacity_exists = false;
uint32_t time_to_empty_now = 0;
bool time_to_empty_now_exists = false;
uint32_t time_to_full_now = 0;
bool time_to_full_now_exists = false;
std::string status = "Unknown";
for (auto const& item : batteries_) {
@ -260,6 +274,16 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
std::ifstream(bat / "current_avg") >> current_now;
}
if (fs::exists(bat / "time_to_empty_now")) {
time_to_empty_now_exists = true;
std::ifstream(bat / "time_to_empty_now") >> time_to_empty_now;
}
if (fs::exists(bat / "time_to_full_now")) {
time_to_full_now_exists = true;
std::ifstream(bat / "time_to_full_now") >> time_to_full_now;
}
uint32_t voltage_now = 0;
bool voltage_now_exists = false;
if (fs::exists(bat / "voltage_now")) {
@ -481,8 +505,16 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
}
float time_remaining{0.0f};
if (status == "Discharging" && total_power_exists && total_energy_exists) {
if (status == "Discharging" && time_to_empty_now_exists) {
if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 3600.0f;
} else if (status == "Discharging" && total_power_exists && total_energy_exists) {
if (total_power != 0) time_remaining = (float)total_energy / total_power;
} else if (status == "Charging" && time_to_full_now_exists) {
if (time_to_full_now_exists && (time_to_full_now != 0))
time_remaining = -(float)time_to_full_now / 3600.0f;
// If we've turned positive it means the battery is past 100% and so just report that as no
// time remaining
if (time_remaining > 0.0f) time_remaining = 0.0f;
} else if (status == "Charging" && total_energy_exists && total_energy_full_exists &&
total_power_exists) {
if (total_power != 0)
@ -495,7 +527,7 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
float calculated_capacity{0.0f};
if (total_capacity_exists) {
if (total_capacity > 0.0f)
calculated_capacity = (float)total_capacity;
calculated_capacity = (float)total_capacity / batteries_.size();
else if (total_energy_full_exists && total_energy_exists) {
if (total_energy_full > 0.0f)
calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full);
@ -573,7 +605,7 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai
format = config_["format-time"].asString();
}
std::string zero_pad_minutes = fmt::format("{:02d}", minutes);
return fmt::format(format, fmt::arg("H", full_hours), fmt::arg("M", minutes),
return fmt::format(fmt::runtime(format), fmt::arg("H", full_hours), fmt::arg("M", minutes),
fmt::arg("m", zero_pad_minutes));
}
@ -613,14 +645,15 @@ auto waybar::modules::Battery::update() -> void {
} else if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
button_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("timeTo", tooltip_text_default),
fmt::arg("power", power), fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted)));
label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format),
fmt::arg("timeTo", tooltip_text_default),
fmt::arg("power", power), fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted)));
}
if (!old_status_.empty()) {
button_.get_style_context()->remove_class(old_status_);
label_.get_style_context()->remove_class(old_status_);
}
button_.get_style_context()->add_class(status);
label_.get_style_context()->add_class(status);
old_status_ = status;
if (!state.empty() && config_["format-" + status + "-" + state].isString()) {
format = config_["format-" + status + "-" + state].asString();
@ -634,10 +667,10 @@ auto waybar::modules::Battery::update() -> void {
} else {
event_box_.show();
auto icons = std::vector<std::string>{status + "-" + state, status, state};
label_->set_markup(fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("power", power),
fmt::arg("icon", getIcon(capacity, icons)),
fmt::arg("time", time_remaining_formatted)));
label_.set_markup(fmt::format(
fmt::runtime(format), fmt::arg("capacity", capacity), fmt::arg("power", power),
fmt::arg("icon", getIcon(capacity, icons)), fmt::arg("time", time_remaining_formatted)));
}
// Call parent update
AButton::update();
ALabel::update();
}

View File

@ -80,7 +80,7 @@ auto getUcharProperty(GDBusProxy* proxy, const char* property_name) -> unsigned
} // namespace
waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config)
: AButton(config, "bluetooth", id, " {status}", 10),
: ALabel(config, "bluetooth", id, " {status}", 10),
#ifdef WANT_RFKILL
rfkill_{RFKILL_TYPE_BLUETOOTH},
#endif
@ -190,10 +190,10 @@ auto waybar::modules::Bluetooth::update() -> void {
format_.empty() ? event_box_.hide() : event_box_.show();
auto update_style_context = [this](const std::string& style_class, bool in_next_state) {
if (in_next_state && !button_.get_style_context()->has_class(style_class)) {
button_.get_style_context()->add_class(style_class);
} else if (!in_next_state && button_.get_style_context()->has_class(style_class)) {
button_.get_style_context()->remove_class(style_class);
if (in_next_state && !label_.get_style_context()->has_class(style_class)) {
label_.get_style_context()->add_class(style_class);
} else if (!in_next_state && label_.get_style_context()->has_class(style_class)) {
label_.get_style_context()->remove_class(style_class);
}
};
update_style_context("discoverable", cur_controller_.discoverable);
@ -205,8 +205,9 @@ auto waybar::modules::Bluetooth::update() -> void {
update_style_context(state, true);
state_ = state;
label_->set_markup(fmt::format(
format_, fmt::arg("status", state_), fmt::arg("num_connections", connected_devices_.size()),
label_.set_markup(fmt::format(
fmt::runtime(format_), fmt::arg("status", state_),
fmt::arg("num_connections", connected_devices_.size()),
fmt::arg("controller_address", cur_controller_.address),
fmt::arg("controller_address_type", cur_controller_.address_type),
fmt::arg("controller_alias", cur_controller_.alias),
@ -234,7 +235,7 @@ auto waybar::modules::Bluetooth::update() -> void {
enumerate_format = config_["tooltip-format-enumerate-connected"].asString();
}
ss << fmt::format(
enumerate_format, fmt::arg("device_address", dev.address),
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)));
@ -246,8 +247,8 @@ auto waybar::modules::Bluetooth::update() -> void {
device_enumerate_.erase(0, 1);
}
}
button_.set_tooltip_text(fmt::format(
tooltip_format, fmt::arg("status", state_),
label_.set_tooltip_text(fmt::format(
fmt::runtime(tooltip_format), fmt::arg("status", state_),
fmt::arg("num_connections", connected_devices_.size()),
fmt::arg("controller_address", cur_controller_.address),
fmt::arg("controller_address_type", cur_controller_.address_type),
@ -260,7 +261,7 @@ auto waybar::modules::Bluetooth::update() -> void {
}
// Call parent update
AButton::update();
ALabel::update();
}
// NOTE: only for when the org.bluez.Battery1 interface is added/removed after/before a device is

202
src/modules/cava.cpp Normal file
View File

@ -0,0 +1,202 @@
#include "modules/cava.hpp"
#include <spdlog/spdlog.h>
waybar::modules::Cava::Cava(const std::string& id, const Json::Value& config)
: ALabel(config, "cava", id, "{}", 60, false, false, false) {
// Load waybar module config
char cfgPath[PATH_MAX];
cfgPath[0] = '\0';
if (config_["cava_config"].isString()) {
std::string strPath{config_["cava_config"].asString()};
const std::string fnd{"XDG_CONFIG_HOME"};
const std::string::size_type npos{strPath.find("$" + fnd)};
if (npos != std::string::npos) strPath.replace(npos, fnd.length() + 1, getenv(fnd.c_str()));
strcpy(cfgPath, strPath.data());
}
// Load cava config
error_.length = 0;
if (!load_config(cfgPath, &prm_, false, &error_)) {
spdlog::error("Error loading config. {0}", error_.message);
exit(EXIT_FAILURE);
}
// Override cava parameters by the user config
prm_.inAtty = 0;
prm_.output = output_method::OUTPUT_RAW;
strcpy(prm_.data_format, "ascii");
strcpy(prm_.raw_target, "/dev/stdout");
prm_.ascii_range = config_["format-icons"].size() - 1;
prm_.bar_width = 2;
prm_.bar_spacing = 0;
prm_.bar_height = 32;
prm_.bar_width = 1;
prm_.orientation = ORIENT_TOP;
prm_.xaxis = xaxis_scale::NONE;
prm_.mono_opt = AVERAGE;
prm_.autobars = 0;
prm_.gravity = 0;
prm_.integral = 1;
if (config_["framerate"].isInt()) prm_.framerate = config_["framerate"].asInt();
if (config_["autosens"].isInt()) prm_.autosens = config_["autosens"].asInt();
if (config_["sensitivity"].isInt()) prm_.sens = config_["sensitivity"].asInt();
if (config_["bars"].isInt()) prm_.fixedbars = config_["bars"].asInt();
if (config_["lower_cutoff_freq"].isNumeric())
prm_.lower_cut_off = config_["lower_cutoff_freq"].asLargestInt();
if (config_["higher_cutoff_freq"].isNumeric())
prm_.upper_cut_off = config_["higher_cutoff_freq"].asLargestInt();
if (config_["sleep_timer"].isInt()) prm_.sleep_timer = config_["sleep_timer"].asInt();
if (config_["method"].isString())
prm_.input = input_method_by_name(config_["method"].asString().c_str());
if (config_["source"].isString()) prm_.audio_source = config_["source"].asString().data();
if (config_["sample_rate"].isNumeric()) prm_.fifoSample = config_["sample_rate"].asLargestInt();
if (config_["sample_bits"].isInt()) prm_.fifoSampleBits = config_["sample_bits"].asInt();
if (config_["stereo"].isBool()) prm_.stereo = config_["stereo"].asBool();
if (config_["reverse"].isBool()) prm_.reverse = config_["reverse"].asBool();
if (config_["bar_delimiter"].isInt()) prm_.bar_delim = config_["bar_delimiter"].asInt();
if (config_["monstercat"].isBool()) prm_.monstercat = config_["monstercat"].asBool();
if (config_["waves"].isBool()) prm_.waves = config_["waves"].asBool();
if (config_["noise_reduction"].isDouble())
prm_.noise_reduction = config_["noise_reduction"].asDouble();
if (config_["input_delay"].isInt())
fetch_input_delay_ = std::chrono::seconds(config_["input_delay"].asInt());
// Make cava parameters configuration
plan_ = new cava_plan{};
audio_raw_.height = prm_.ascii_range;
audio_data_.format = -1;
audio_data_.source = new char[1 + strlen(prm_.audio_source)];
audio_data_.source[0] = '\0';
strcpy(audio_data_.source, prm_.audio_source);
audio_data_.rate = 0;
audio_data_.samples_counter = 0;
audio_data_.channels = 2;
audio_data_.IEEE_FLOAT = 0;
audio_data_.input_buffer_size = BUFFER_SIZE * audio_data_.channels;
audio_data_.cava_buffer_size = audio_data_.input_buffer_size * 8;
audio_data_.cava_in = new double[audio_data_.cava_buffer_size]{0.0};
audio_data_.terminate = 0;
audio_data_.suspendFlag = false;
input_source_ = get_input(&audio_data_, &prm_);
if (!input_source_) {
spdlog::error("cava API didn't provide input audio source method");
exit(EXIT_FAILURE);
}
// Calculate delay for Update() thread
frame_time_milsec_ = std::chrono::milliseconds((int)(1e3 / prm_.framerate));
// Init cava plan, audio_raw structure
audio_raw_init(&audio_data_, &audio_raw_, &prm_, plan_);
if (!plan_) spdlog::error("cava plan is not provided");
audio_raw_.previous_frame[0] = -1; // For first Update() call need to rePaint text message
// Read audio source trough cava API. Cava orginizes this process via infinity loop
thread_fetch_input_ = [this] {
thread_fetch_input_.sleep_for(fetch_input_delay_);
input_source_(&audio_data_);
};
thread_ = [this] {
dp.emit();
thread_.sleep_for(frame_time_milsec_);
};
}
waybar::modules::Cava::~Cava() {
thread_fetch_input_.stop();
thread_.stop();
delete plan_;
plan_ = nullptr;
}
void upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {
if (delta == std::chrono::seconds{0}) {
delta += std::chrono::seconds{1};
delay += delta;
}
}
void downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {
if (delta > std::chrono::seconds{0}) {
delay -= delta;
delta -= std::chrono::seconds{1};
}
}
auto waybar::modules::Cava::update() -> void {
if (audio_data_.suspendFlag) return;
silence_ = true;
for (int i{0}; i < audio_data_.input_buffer_size; ++i) {
if (audio_data_.cava_in[i]) {
silence_ = false;
sleep_counter_ = 0;
break;
}
}
if (silence_ && prm_.sleep_timer) {
if (sleep_counter_ <=
(int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) {
++sleep_counter_;
silence_ = false;
}
}
if (!silence_) {
downThreadDelay(frame_time_milsec_, suspend_silence_delay_);
// Process: execute cava
pthread_mutex_lock(&audio_data_.lock);
cava_execute(audio_data_.cava_in, audio_data_.samples_counter, audio_raw_.cava_out, plan_);
if (audio_data_.samples_counter > 0) audio_data_.samples_counter = 0;
pthread_mutex_unlock(&audio_data_.lock);
// Do transformation under raw data
audio_raw_fetch(&audio_raw_, &prm_, &rePaint_);
if (rePaint_ == 1) {
text_.clear();
for (int i{0}; i < audio_raw_.number_of_bars; ++i) {
audio_raw_.previous_frame[i] = audio_raw_.bars[i];
text_.append(
getIcon((audio_raw_.bars[i] > prm_.ascii_range) ? prm_.ascii_range : audio_raw_.bars[i],
"", prm_.ascii_range + 1));
if (prm_.bar_delim != 0) text_.push_back(prm_.bar_delim);
}
label_.set_markup(text_);
ALabel::update();
}
} else
upThreadDelay(frame_time_milsec_, suspend_silence_delay_);
}
auto waybar::modules::Cava::doAction(const std::string& name) -> void {
if ((actionMap_[name])) {
(this->*actionMap_[name])();
} else
spdlog::error("Cava. Unsupported action \"{0}\"", name);
}
// Cava actions
void waybar::modules::Cava::pause_resume() {
pthread_mutex_lock(&audio_data_.lock);
if (audio_data_.suspendFlag) {
audio_data_.suspendFlag = false;
pthread_cond_broadcast(&audio_data_.resumeCond);
downThreadDelay(frame_time_milsec_, suspend_silence_delay_);
} else {
audio_data_.suspendFlag = true;
upThreadDelay(frame_time_milsec_, suspend_silence_delay_);
}
pthread_mutex_unlock(&audio_data_.lock);
}

View File

@ -5,45 +5,48 @@
#include <ctime>
#include <iomanip>
#include <regex>
#include <sstream>
#include <type_traits>
#include "util/ustring_clen.hpp"
#include "util/waybar_time.hpp"
#ifdef HAVE_LANGINFO_1STDAY
#include <langinfo.h>
#include <locale.h>
#endif
using waybar::waybar_time;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: AButton(config, "clock", id, "{:%H:%M}", 60, false, false, true),
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true),
current_time_zone_idx_(0),
is_calendar_in_tooltip_(false),
is_timezoned_list_in_tooltip_(false) {
if (config_["timezones"].isArray() && !config_["timezones"].empty()) {
for (const auto& zone_name : config_["timezones"]) {
if (!zone_name.isString() || zone_name.asString().empty()) {
time_zones_.push_back(nullptr);
continue;
}
time_zones_.push_back(date::locate_zone(zone_name.asString()));
if (!zone_name.isString()) continue;
if (zone_name.asString().empty())
time_zones_.push_back(date::current_zone());
else
try {
time_zones_.push_back(date::locate_zone(zone_name.asString()));
} catch (const std::exception& e) {
spdlog::warn("Timezone: {0}. {1}", zone_name.asString(), e.what());
}
}
} else if (config_["timezone"].isString() && !config_["timezone"].asString().empty()) {
time_zones_.push_back(date::locate_zone(config_["timezone"].asString()));
} else if (config_["timezone"].isString()) {
if (config_["timezone"].asString().empty())
time_zones_.push_back(date::current_zone());
else
try {
time_zones_.push_back(date::locate_zone(config_["timezone"].asString()));
} catch (const std::exception& e) {
spdlog::warn("Timezone: {0}. {1}", config_["timezone"].asString(), e.what());
}
}
// If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark
// that local time should be shown.
// If all timezones are parsed and no one is good, add current time zone. nullptr in timezones
// vector means that local time should be shown
if (!time_zones_.size()) {
time_zones_.push_back(nullptr);
}
if (!is_timezone_fixed()) {
spdlog::warn(
"As using a timezone, some format args may be missing as the date library haven't got a "
"release since 2018.");
time_zones_.push_back(date::current_zone());
}
// Check if a particular placeholder is present in the tooltip format, to know what to calculate
@ -61,18 +64,86 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
}
}
// Calendar configuration
if (is_calendar_in_tooltip_) {
if (config_["on-scroll"][kCalendarPlaceholder].isInt()) {
calendar_shift_init_ =
date::months{config_["on-scroll"].get(kCalendarPlaceholder, 0).asInt()};
if (config_[kCalendarPlaceholder]["weeks-pos"].isString()) {
if (config_[kCalendarPlaceholder]["weeks-pos"].asString() == "left") {
cldWPos_ = WeeksSide::LEFT;
} else if (config_[kCalendarPlaceholder]["weeks-pos"].asString() == "right") {
cldWPos_ = WeeksSide::RIGHT;
}
}
if (config_[kCalendarPlaceholder]["format"]["months"].isString())
fmtMap_.insert({0, config_[kCalendarPlaceholder]["format"]["months"].asString()});
else
fmtMap_.insert({0, "{}"});
if (config_[kCalendarPlaceholder]["format"]["days"].isString())
fmtMap_.insert({2, config_[kCalendarPlaceholder]["format"]["days"].asString()});
else
fmtMap_.insert({2, "{}"});
if (config_[kCalendarPlaceholder]["format"]["weeks"].isString() &&
cldWPos_ != WeeksSide::HIDDEN) {
fmtMap_.insert(
{4, std::regex_replace(config_[kCalendarPlaceholder]["format"]["weeks"].asString(),
std::regex("\\{\\}"),
(first_day_of_week() == date::Monday) ? "{:%W}" : "{:%U}")});
Glib::ustring tmp{std::regex_replace(fmtMap_[4], std::regex("</?[^>]+>|\\{.*\\}"), "")};
cldWnLen_ += tmp.size();
} else {
if (cldWPos_ != WeeksSide::HIDDEN)
fmtMap_.insert({4, (first_day_of_week() == date::Monday) ? "{:%W}" : "{:%U}"});
else
cldWnLen_ = 0;
}
if (config_[kCalendarPlaceholder]["format"]["weekdays"].isString())
fmtMap_.insert({1, config_[kCalendarPlaceholder]["format"]["weekdays"].asString()});
else
fmtMap_.insert({1, "{}"});
if (config_[kCalendarPlaceholder]["format"]["today"].isString()) {
fmtMap_.insert({3, config_[kCalendarPlaceholder]["format"]["today"].asString()});
cldBaseDay_ =
date::year_month_day{date::floor<date::days>(std::chrono::system_clock::now())}.day();
} else
fmtMap_.insert({3, "{}"});
if (config_[kCalendarPlaceholder]["mode"].isString()) {
const std::string cfgMode{(config_[kCalendarPlaceholder]["mode"].isString())
? config_[kCalendarPlaceholder]["mode"].asString()
: "month"};
const std::map<std::string, const CldMode&> monthModes{{"month", CldMode::MONTH},
{"year", CldMode::YEAR}};
if (monthModes.find(cfgMode) != monthModes.end())
cldMode_ = monthModes.at(cfgMode);
else
spdlog::warn(
"Clock calendar configuration \"mode\"\"\" \"{0}\" is not recognized. Mode = \"month\" "
"is using instead",
cfgMode);
}
if (config_[kCalendarPlaceholder]["mode-mon-col"].isInt()) {
cldMonCols_ = config_[kCalendarPlaceholder]["mode-mon-col"].asInt();
if (cldMonCols_ == 0u || 12 % cldMonCols_ != 0u) {
cldMonCols_ = 3u;
spdlog::warn(
"Clock calendar configuration \"mode-mon-col\" = {0} must be one of [1, 2, 3, 4, 6, "
"12]. Value 3 is using instead",
cldMonCols_);
}
} else
cldMonCols_ = 1;
if (config_[kCalendarPlaceholder]["on-scroll"].isInt()) {
cldShift_ = date::months{config_[kCalendarPlaceholder]["on-scroll"].asInt()};
event_box_.add_events(Gdk::LEAVE_NOTIFY_MASK);
event_box_.signal_leave_notify_event().connect([this](GdkEventCrossing*) {
cldCurrShift_ = date::months{0};
return false;
});
}
}
if (config_["locale"].isString()) {
if (config_["locale"].isString())
locale_ = std::locale(config_["locale"].asString());
} else {
else
locale_ = std::locale("");
}
thread_ = [this] {
dp.emit();
@ -94,197 +165,295 @@ bool waybar::modules::Clock::is_timezone_fixed() {
}
auto waybar::modules::Clock::update() -> void {
auto time_zone = current_timezone();
const auto* time_zone = current_timezone();
auto now = std::chrono::system_clock::now();
waybar_time wtime = {locale_, date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now) +
calendar_shift_)};
std::string text = "";
auto ztime = date::zoned_time{time_zone, date::floor<std::chrono::seconds>(now)};
auto shifted_date = date::year_month_day{date::floor<date::days>(now)} + cldCurrShift_;
if (cldCurrShift_.count()) {
shifted_date = date::year_month_day(shifted_date.year(), shifted_date.month(), date::day(1));
}
auto now_shifted = date::sys_days{shifted_date} + (now - date::floor<date::days>(now));
auto shifted_ztime = date::zoned_time{time_zone, date::floor<std::chrono::seconds>(now_shifted)};
std::string text{""};
if (!is_timezone_fixed()) {
// As date dep is not fully compatible, prefer fmt
tzset();
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
text = fmt::format(locale_, format_, localtime);
text = fmt::format(locale_, fmt::runtime(format_), localtime);
} else {
text = fmt::format(format_, wtime);
text = fmt::format(locale_, fmt::runtime(format_), ztime);
}
label_->set_markup(text);
label_.set_markup(text);
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
std::string calendar_lines{""};
std::string timezoned_time_lines{""};
if (is_calendar_in_tooltip_) calendar_lines = calendar_text(wtime);
if (is_timezoned_list_in_tooltip_) timezoned_time_lines = timezones_text(&now);
if (is_calendar_in_tooltip_) {
calendar_lines = get_calendar(ztime, shifted_ztime);
}
if (is_timezoned_list_in_tooltip_) {
timezoned_time_lines = timezones_text(&now);
}
auto tooltip_format = config_["tooltip-format"].asString();
text =
fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines),
fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines));
button_.set_tooltip_markup(text);
text = fmt::format(locale_, fmt::runtime(tooltip_format), shifted_ztime,
fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines),
fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines));
label_.set_tooltip_markup(text);
}
}
// Call parent update
AButton::update();
ALabel::update();
}
bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {
// defer to user commands if set
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
return AModule::handleScroll(e);
}
auto dir = AModule::getScrollDir(e);
// Shift calendar date
if (calendar_shift_init_.count() > 0) {
if (dir == SCROLL_DIR::UP)
calendar_shift_ += calendar_shift_init_;
else
calendar_shift_ -= calendar_shift_init_;
} else {
// Change time zone
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
return true;
}
if (time_zones_.size() == 1) {
return true;
}
auto nr_zones = time_zones_.size();
if (dir == SCROLL_DIR::UP) {
size_t new_idx = current_time_zone_idx_ + 1;
current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
} else {
current_time_zone_idx_ =
current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1;
}
}
update();
return true;
}
auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string {
const auto daypoint = date::floor<date::days>(wtime.ztime.get_local_time());
const auto ymd{date::year_month_day{daypoint}};
if (calendar_cached_ymd_ == ymd) return calendar_cached_text_;
const auto curr_day{(calendar_shift_init_.count() > 0 && calendar_shift_.count() != 0)
? date::day{0}
: ymd.day()};
const date::year_month ym{ymd.year(), ymd.month()};
const auto week_format{config_["format-calendar-weekdays"].isString()
? config_["format-calendar-weekdays"].asString()
: ""};
const auto wn_format{config_["format-calendar-weeks"].isString()
? config_["format-calendar-weeks"].asString()
: ""};
std::stringstream os;
const auto first_dow = first_day_of_week();
int ws{0}; // weeks-pos: side(1 - left, 2 - right)
if (config_["calendar-weeks-pos"].isString()) {
if (config_["calendar-weeks-pos"].asString() == "left") {
ws = 1;
// Add paddings before the header
os << std::string(4, ' ');
} else if (config_["calendar-weeks-pos"].asString() == "right") {
ws = 2;
}
}
weekdays_header(first_dow, os);
// First week prefixed with spaces if needed.
auto wd = date::weekday(ym / 1);
auto empty_days = (wd - first_dow).count();
date::sys_days lwd{static_cast<date::sys_days>(ym / 1) + date::days{7 - empty_days}};
if (first_dow == date::Monday) {
lwd -= date::days{1};
}
/* Print weeknumber on the left for the first row*/
if (ws == 1) {
os << fmt::format(wn_format, lwd);
os << ' ';
lwd += date::weeks{1};
}
if (empty_days > 0) {
os << std::string(empty_days * 3 - 1, ' ');
}
auto last_day = (ym / date::literals::last).day();
for (auto d = date::day(1); d <= last_day; ++d, ++wd) {
if (wd != first_dow) {
os << ' ';
} else if (unsigned(d) != 1) {
if (ws == 2) {
os << ' ';
os << fmt::format(wn_format, lwd);
lwd += date::weeks{1};
}
os << '\n';
if (ws == 1) {
os << fmt::format(wn_format, lwd);
os << ' ';
lwd += date::weeks{1};
}
}
if (d == curr_day) {
if (config_["today-format"].isString()) {
auto today_format = config_["today-format"].asString();
os << fmt::format(today_format, date::format("%e", d));
} else {
os << "<b><u>" << date::format("%e", d) << "</u></b>";
}
} else if (config_["format-calendar"].isString()) {
os << fmt::format(config_["format-calendar"].asString(), date::format("%e", d));
} else
os << date::format("%e", d);
/*Print weeks on the right when the endings with spaces*/
if (ws == 2 && d == last_day) {
empty_days = 6 - (wd.c_encoding() - first_dow.c_encoding());
if (empty_days > 0) {
os << std::string(empty_days * 3 + 1, ' ');
os << fmt::format(wn_format, lwd);
}
}
}
auto result = os.str();
calendar_cached_ymd_ = ymd;
calendar_cached_text_ = result;
return result;
}
auto waybar::modules::Clock::weekdays_header(const date::weekday& first_dow, std::ostream& os)
-> void {
std::stringstream res;
auto wd = first_dow;
do {
if (wd != first_dow) res << ' ';
Glib::ustring wd_ustring(date::format(locale_, "%a", wd));
auto clen = ustring_clen(wd_ustring);
auto wd_len = wd_ustring.length();
while (clen > 2) {
wd_ustring = wd_ustring.substr(0, wd_len - 1);
wd_len--;
clen = ustring_clen(wd_ustring);
}
const std::string pad(2 - clen, ' ');
res << pad << wd_ustring;
} while (++wd != first_dow);
res << "\n";
if (config_["format-calendar-weekdays"].isString()) {
os << fmt::format(config_["format-calendar-weekdays"].asString(), res.str());
auto waybar::modules::Clock::doAction(const std::string& name) -> void {
if ((actionMap_[name])) {
(this->*actionMap_[name])();
update();
} else
os << res.str();
spdlog::error("Clock. Unsupported action \"{0}\"", name);
}
// The number of weeks in calendar month layout plus 1 more for calendar titles
unsigned cldRowsInMonth(date::year_month const ym, date::weekday const firstdow) {
using namespace date;
return static_cast<unsigned>(
ceil<weeks>((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count()) +
2;
}
auto cldGetWeekForLine(date::year_month const ym, date::weekday const firstdow, unsigned const line)
-> const date::year_month_weekday {
unsigned index = line - 2;
auto sd = date::sys_days{ym / 1};
if (date::weekday{sd} == firstdow) ++index;
auto ymdw = ym / firstdow[index];
return ymdw;
}
auto getCalendarLine(date::year_month_day const currDate, date::year_month const ym,
unsigned const line, date::weekday const firstdow,
const std::locale* const locale_) -> std::string {
using namespace date::literals;
std::ostringstream res;
switch (line) {
case 0: {
// Output month and year title
res << date::format(*locale_, "%B %Y", ym);
break;
}
case 1: {
// Output weekday names title
auto wd{firstdow};
do {
Glib::ustring wd_ustring{date::format(*locale_, "%a", wd)};
auto clen{ustring_clen(wd_ustring)};
auto wd_len{wd_ustring.length()};
while (clen > 2) {
wd_ustring = wd_ustring.substr(0, wd_len - 1);
--wd_len;
clen = ustring_clen(wd_ustring);
}
const std::string pad(2 - clen, ' ');
if (wd != firstdow) res << ' ';
res << pad << wd_ustring;
} while (++wd != firstdow);
break;
}
case 2: {
// Output first week prefixed with spaces if necessary
auto wd = date::weekday{ym / 1};
res << std::string(static_cast<unsigned>((wd - firstdow).count()) * 3, ' ');
if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / 1_d)
res << date::format("%e", 1_d);
else
res << "{today}";
auto d = 2_d;
while (++wd != firstdow) {
if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / d)
res << date::format(" %e", d);
else
res << " {today}";
++d;
}
break;
}
default: {
// Output a non-first week:
auto ymdw{cldGetWeekForLine(ym, firstdow, line)};
if (ymdw.ok()) {
auto d = date::year_month_day{ymdw}.day();
auto const e = (ym / last).day();
auto wd = firstdow;
if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / d)
res << date::format("%e", d);
else
res << "{today}";
while (++wd != firstdow && ++d <= e) {
if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / d)
res << date::format(" %e", d);
else
res << " {today}";
}
// Append row with spaces if the week did not complete
res << std::string(static_cast<unsigned>((firstdow - wd).count()) * 3, ' ');
}
break;
}
}
return res.str();
}
auto waybar::modules::Clock::get_calendar(const date::zoned_seconds& now,
const date::zoned_seconds& wtime) -> std::string {
auto daypoint = date::floor<date::days>(wtime.get_local_time());
const auto ymd{date::year_month_day{daypoint}};
const auto ym{ymd.year() / ymd.month()};
const auto y{ymd.year()};
const auto d{ymd.day()};
const auto firstdow = first_day_of_week();
const auto maxRows{12 / cldMonCols_};
std::ostringstream os;
std::ostringstream tmp;
// get currdate
daypoint = date::floor<date::days>(now.get_local_time());
const auto currDate{date::year_month_day{daypoint}};
if (cldMode_ == CldMode::YEAR) {
if (y / date::month{1} / 1 == cldYearShift_)
if (d == cldBaseDay_ || (uint)cldBaseDay_ == 0u)
return cldYearCached_;
else
cldBaseDay_ = d;
else
cldYearShift_ = y / date::month{1} / 1;
}
if (cldMode_ == CldMode::MONTH) {
if (ym == cldMonShift_)
if (d == cldBaseDay_ || (uint)cldBaseDay_ == 0u)
return cldMonCached_;
else
cldBaseDay_ = d;
else
cldMonShift_ = ym;
}
// Compute number of lines needed for each calendar month
unsigned ml[12]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
for (auto& m : ml) {
if (cldMode_ == CldMode::YEAR || m == static_cast<unsigned>(ymd.month()))
m = cldRowsInMonth(y / date::month{m}, firstdow);
else
m = 0u;
}
for (auto row{0u}; row < maxRows; ++row) {
const auto lines = *std::max_element(std::begin(ml) + (row * cldMonCols_),
std::begin(ml) + ((row + 1) * cldMonCols_));
for (auto line{0u}; line < lines; ++line) {
for (auto col{0u}; col < cldMonCols_; ++col) {
const auto mon{date::month{row * cldMonCols_ + col + 1}};
if (cldMode_ == CldMode::YEAR || y / mon == ym) {
date::year_month ymTmp{y / mon};
if (col != 0 && cldMode_ == CldMode::YEAR) os << " ";
// Week numbers on the left
if (cldWPos_ == WeeksSide::LEFT && line > 0) {
if (line > 1) {
if (line < ml[static_cast<unsigned>(ymTmp.month()) - 1u])
os << fmt::format(fmt::runtime(fmtMap_[4]),
(line == 2)
? date::sys_days{ymTmp / 1}
: date::sys_days{cldGetWeekForLine(ymTmp, firstdow, line)})
<< ' ';
else
os << std::string(cldWnLen_, ' ');
}
}
os << fmt::format(
fmt::runtime((cldWPos_ != WeeksSide::LEFT || line == 0) ? "{:<{}}" : "{:>{}}"),
getCalendarLine(currDate, ymTmp, line, firstdow, &locale_),
(cldMonColLen_ + ((line < 2) ? cldWnLen_ : 0)));
// Week numbers on the right
if (cldWPos_ == WeeksSide ::RIGHT && line > 0) {
if (line > 1) {
if (line < ml[static_cast<unsigned>(ymTmp.month()) - 1u])
os << ' '
<< fmt::format(fmt::runtime(fmtMap_[4]),
(line == 2)
? date::sys_days{ymTmp / 1}
: date::sys_days{cldGetWeekForLine(ymTmp, firstdow, line)});
else
os << std::string(cldWnLen_, ' ');
}
}
}
}
// Apply user formats to calendar
if (line < 2)
tmp << fmt::format(fmt::runtime(fmtMap_[line]), os.str());
else
tmp << os.str();
// Clear ostringstream
std::ostringstream().swap(os);
if (line + 1u != lines || (row + 1u != maxRows && cldMode_ == CldMode::YEAR)) tmp << '\n';
}
if (row + 1u != maxRows && cldMode_ == CldMode::YEAR) tmp << '\n';
}
os << fmt::format( // Apply days format
fmt::runtime(fmt::format(fmt::runtime(fmtMap_[2]), tmp.str())),
// Apply today format
fmt::arg("today", fmt::format(fmt::runtime(fmtMap_[3]), date::format("%e", ymd.day()))));
if (cldMode_ == CldMode::YEAR)
cldYearCached_ = os.str();
else
cldMonCached_ = os.str();
return os.str();
}
/*Clock actions*/
void waybar::modules::Clock::cldModeSwitch() {
cldMode_ = (cldMode_ == CldMode::YEAR) ? CldMode::MONTH : CldMode::YEAR;
}
void waybar::modules::Clock::cldShift_up() {
cldCurrShift_ += ((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;
}
void waybar::modules::Clock::cldShift_down() {
cldCurrShift_ -= ((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;
}
void waybar::modules::Clock::tz_up() {
auto nr_zones = time_zones_.size();
if (nr_zones == 1) return;
size_t new_idx = current_time_zone_idx_ + 1;
current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
}
void waybar::modules::Clock::tz_down() {
auto nr_zones = time_zones_.size();
if (nr_zones == 1) return;
current_time_zone_idx_ = current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1;
}
auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_point* now)
@ -293,7 +462,6 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin
return "";
}
std::stringstream os;
waybar_time wtime;
for (size_t time_zone_idx = 0; time_zone_idx < time_zones_.size(); ++time_zone_idx) {
if (static_cast<int>(time_zone_idx) == current_time_zone_idx_) {
continue;
@ -302,8 +470,8 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin
if (!timezone) {
timezone = date::current_zone();
}
wtime = {locale_, date::make_zoned(timezone, date::floor<std::chrono::seconds>(*now))};
os << fmt::format(format_, wtime) << "\n";
auto ztime = date::zoned_time{timezone, date::floor<std::chrono::seconds>(*now)};
os << fmt::format(locale_, fmt::runtime(format_), ztime) << '\n';
}
return os.str();
}

View File

@ -10,7 +10,7 @@
#endif
waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config)
: AButton(config, "cpu", id, "{usage}%", 10) {
: ALabel(config, "cpu", id, "{usage}%", 10) {
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
@ -23,7 +23,7 @@ auto waybar::modules::Cpu::update() -> void {
auto [cpu_usage, tooltip] = getCpuUsage();
auto [max_frequency, min_frequency, avg_frequency] = getCpuFrequency();
if (tooltipEnabled()) {
button_.set_tooltip_text(tooltip);
label_.set_tooltip_text(tooltip);
}
auto format = format_;
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
@ -39,7 +39,6 @@ auto waybar::modules::Cpu::update() -> void {
auto icons = std::vector<std::string>{state};
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(fmt::arg("load", cpu_load));
store.push_back(fmt::arg("load", cpu_load));
store.push_back(fmt::arg("usage", total_usage));
store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
store.push_back(fmt::arg("max_frequency", max_frequency));
@ -52,11 +51,11 @@ auto waybar::modules::Cpu::update() -> void {
auto icon_format = fmt::format("icon{}", core_i);
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons)));
}
label_->set_markup(fmt::vformat(format, store));
label_.set_markup(fmt::vformat(format, store));
}
// Call parent update
AButton::update();
ALabel::update();
}
double waybar::modules::Cpu::getCpuLoad() {

View File

@ -4,7 +4,7 @@
waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
const Json::Value& config)
: AButton(config, "custom-" + name, id, "{}"),
: ALabel(config, "custom-" + name, id, "{}"),
name_(name),
id_(id),
percentage_(0),
@ -21,6 +21,7 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
waybar::modules::Custom::~Custom() {
if (pid_ != -1) {
killpg(pid_, SIGTERM);
waitpid(pid_, NULL, 0);
pid_ = -1;
}
}
@ -95,6 +96,7 @@ void waybar::modules::Custom::continuousWorker() {
output_ = {0, output};
dp.emit();
}
free(buff);
};
}
@ -111,13 +113,13 @@ void waybar::modules::Custom::handleEvent() {
}
bool waybar::modules::Custom::handleScroll(GdkEventScroll* e) {
auto ret = AButton::handleScroll(e);
auto ret = ALabel::handleScroll(e);
handleEvent();
return ret;
}
bool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) {
auto ret = AButton::handleToggle(e);
auto ret = ALabel::handleToggle(e);
handleEvent();
return ret;
}
@ -133,39 +135,39 @@ auto waybar::modules::Custom::update() -> void {
} else {
parseOutputRaw();
}
auto str = fmt::format(format_, text_, fmt::arg("alt", alt_),
auto str = fmt::format(fmt::runtime(format_), text_, fmt::arg("alt", alt_),
fmt::arg("icon", getIcon(percentage_, alt_)),
fmt::arg("percentage", percentage_));
if (str.empty()) {
event_box_.hide();
} else {
label_->set_markup(str);
label_.set_markup(str);
if (tooltipEnabled()) {
if (text_ == tooltip_) {
if (button_.get_tooltip_markup() != str) {
button_.set_tooltip_markup(str);
if (label_.get_tooltip_markup() != str) {
label_.set_tooltip_markup(str);
}
} else {
if (button_.get_tooltip_markup() != tooltip_) {
button_.set_tooltip_markup(tooltip_);
if (label_.get_tooltip_markup() != tooltip_) {
label_.set_tooltip_markup(tooltip_);
}
}
}
auto classes = button_.get_style_context()->list_classes();
auto classes = label_.get_style_context()->list_classes();
for (auto const& c : classes) {
if (c == id_) continue;
button_.get_style_context()->remove_class(c);
label_.get_style_context()->remove_class(c);
}
for (auto const& c : class_) {
button_.get_style_context()->add_class(c);
label_.get_style_context()->add_class(c);
}
button_.get_style_context()->add_class("flat");
button_.get_style_context()->add_class("text-button");
label_.get_style_context()->add_class("flat");
label_.get_style_context()->add_class("text-button");
event_box_.show();
}
}
// Call parent update
AButton::update();
ALabel::update();
}
void waybar::modules::Custom::parseOutputRaw() {
@ -216,8 +218,8 @@ void waybar::modules::Custom::parseOutputJson() {
class_.push_back(c.asString());
}
}
if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) {
percentage_ = parsed["percentage"].asUInt();
if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) {
percentage_ = (int)lround(parsed["percentage"].asFloat());
} else {
percentage_ = 0;
}

View File

@ -3,7 +3,7 @@
using namespace waybar::util;
waybar::modules::Disk::Disk(const std::string& id, const Json::Value& config)
: AButton(config, "disk", id, "{}%", 30), path_("/") {
: ALabel(config, "disk", id, "{}%", 30), path_("/") {
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
@ -58,11 +58,11 @@ auto waybar::modules::Disk::update() -> void {
event_box_.hide();
} else {
event_box_.show();
label_->set_markup(
fmt::format(format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks),
fmt::arg("used", used), fmt::arg("percentage_used", percentage_used),
fmt::arg("total", total), fmt::arg("path", path_)));
label_.set_markup(fmt::format(
fmt::runtime(format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),
fmt::arg("path", path_)));
}
if (tooltipEnabled()) {
@ -70,12 +70,12 @@ auto waybar::modules::Disk::update() -> void {
if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
button_.set_tooltip_text(
fmt::format(tooltip_format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks),
fmt::arg("used", used), fmt::arg("percentage_used", percentage_used),
fmt::arg("total", total), fmt::arg("path", path_)));
label_.set_tooltip_text(fmt::format(
fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),
fmt::arg("path", path_)));
}
// Call parent update
AButton::update();
ALabel::update();
}

198
src/modules/dwl/tags.cpp Normal file
View File

@ -0,0 +1,198 @@
#include "modules/dwl/tags.hpp"
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include <spdlog/spdlog.h>
#include <wayland-client.h>
#include <algorithm>
#include "client.hpp"
#include "dwl-ipc-unstable-v2-client-protocol.h"
#define TAG_INACTIVE 0
#define TAG_ACTIVE 1
#define TAG_URGENT 2
namespace waybar::modules::dwl {
/* dwl stuff */
wl_array tags, layouts;
static uint num_tags = 0;
void toggle_visibility(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) {
// Intentionally empty
}
void active(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t active) {
// Intentionally empty
}
static void set_tag(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t tag, uint32_t state,
uint32_t clients, uint32_t focused) {
static_cast<Tags *>(data)->handle_view_tags(tag, state, clients, focused);
num_tags = (state & ZDWL_IPC_OUTPUT_V2_TAG_STATE_ACTIVE) ? num_tags | (1 << tag)
: num_tags & ~(1 << tag);
}
void set_layout_symbol(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *layout) {
// Intentionally empty
}
void title(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *title) {
// Intentionally empty
}
void dwl_frame(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) {
// Intentionally empty
}
static void set_layout(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t layout) {
// Intentionally empty
}
static void appid(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *appid){
// Intentionally empty
};
static const zdwl_ipc_output_v2_listener output_status_listener_impl{
.toggle_visibility = toggle_visibility,
.active = active,
.tag = set_tag,
.layout = set_layout,
.title = title,
.appid = appid,
.layout_symbol = set_layout_symbol,
.frame = dwl_frame,
};
static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) {
if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {
static_cast<Tags *>(data)->status_manager_ = static_cast<struct zdwl_ipc_manager_v2 *>(
(zdwl_ipc_manager_v2 *)wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1));
}
if (std::strcmp(interface, wl_seat_interface.name) == 0) {
version = std::min<uint32_t>(version, 1);
static_cast<Tags *>(data)->seat_ = static_cast<struct wl_seat *>(
wl_registry_bind(registry, name, &wl_seat_interface, version));
}
}
static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {
/* Ignore event */
}
static const wl_registry_listener registry_listener_impl = {.global = handle_global,
.global_remove = handle_global_remove};
Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
: waybar::AModule(config, "tags", id, false, false),
status_manager_{nullptr},
seat_{nullptr},
bar_(bar),
box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
output_status_{nullptr} {
struct wl_display *display = Client::inst()->wl_display;
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener_impl, this);
wl_display_roundtrip(display);
if (!status_manager_) {
spdlog::error("dwl_status_manager_v2 not advertised");
return;
}
if (!seat_) {
spdlog::error("wl_seat not advertised");
}
box_.set_name("tags");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
event_box_.add(box_);
// Default to 9 tags, cap at 32
const uint32_t num_tags =
config["num-tags"].isUInt() ? std::min<uint32_t>(32, config_["num-tags"].asUInt()) : 9;
std::vector<std::string> tag_labels(num_tags);
for (uint32_t tag = 0; tag < num_tags; ++tag) {
tag_labels[tag] = std::to_string(tag + 1);
}
const Json::Value custom_labels = config["tag-labels"];
if (custom_labels.isArray() && !custom_labels.empty()) {
for (uint32_t tag = 0; tag < std::min(num_tags, custom_labels.size()); ++tag) {
tag_labels[tag] = custom_labels[tag].asString();
}
}
uint32_t i = 1;
for (const auto &tag_label : tag_labels) {
Gtk::Button &button = buttons_.emplace_back(tag_label);
button.set_relief(Gtk::RELIEF_NONE);
box_.pack_start(button, false, false, 0);
if (!config_["disable-click"].asBool()) {
button.signal_clicked().connect(
sigc::bind(sigc::mem_fun(*this, &Tags::handle_primary_clicked), i));
button.signal_button_press_event().connect(
sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), i));
}
button.show();
i <<= 1;
}
struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
output_status_ = zdwl_ipc_manager_v2_get_output(status_manager_, output);
zdwl_ipc_output_v2_add_listener(output_status_, &output_status_listener_impl, this);
zdwl_ipc_manager_v2_destroy(status_manager_);
status_manager_ = nullptr;
}
Tags::~Tags() {
if (status_manager_) {
zdwl_ipc_manager_v2_destroy(status_manager_);
}
}
void Tags::handle_primary_clicked(uint32_t tag) {
if (!output_status_) return;
zdwl_ipc_output_v2_set_tags(output_status_, tag, 1);
}
bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) {
if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) {
if (!output_status_) return true;
zdwl_ipc_output_v2_set_tags(output_status_, num_tags ^ tag, 0);
}
return true;
}
void Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused) {
// First clear all occupied state
auto &button = buttons_[tag];
if (clients) {
button.get_style_context()->add_class("occupied");
} else {
button.get_style_context()->remove_class("occupied");
}
if (state & TAG_ACTIVE) {
button.get_style_context()->add_class("focused");
} else {
button.get_style_context()->remove_class("focused");
}
if (state & TAG_URGENT) {
button.get_style_context()->add_class("urgent");
} else {
button.get_style_context()->remove_class("urgent");
}
}
} /* namespace waybar::modules::dwl */

View File

@ -16,9 +16,9 @@
#include "glibmm/ustring.h"
#include "glibmm/variant.h"
#include "glibmm/varianttype.h"
#include "gtkmm/icontheme.h"
#include "gtkmm/label.h"
#include "gtkmm/tooltip.h"
#include "util/gtk_icon.hpp"
namespace waybar::modules {
Gamemode::Gamemode(const std::string& id, const Json::Value& config)
@ -55,7 +55,7 @@ Gamemode::Gamemode(const std::string& id, const Json::Value& config)
}
box_.set_spacing(iconSpacing);
// Wether to use icon or not
// Whether to use icon or not
if (config_["use-icon"].isBool()) {
useIcon = config_["use-icon"].asBool();
}
@ -213,18 +213,18 @@ auto Gamemode::update() -> void {
// Tooltip
if (tooltip) {
std::string text = fmt::format(tooltip_format, fmt::arg("count", gameCount));
std::string text = fmt::format(fmt::runtime(tooltip_format), fmt::arg("count", gameCount));
box_.set_tooltip_text(text);
}
// Label format
std::string str =
fmt::format(showAltText ? format_alt : format, fmt::arg("glyph", useIcon ? "" : glyph),
fmt::arg("count", gameCount > 0 ? std::to_string(gameCount) : ""));
std::string str = fmt::format(fmt::runtime(showAltText ? format_alt : format),
fmt::arg("glyph", useIcon ? "" : glyph),
fmt::arg("count", gameCount > 0 ? std::to_string(gameCount) : ""));
label_.set_markup(str);
if (useIcon) {
if (!Gtk::IconTheme::get_default()->has_icon(iconName)) {
if (!DefaultGtkIconThemeWrapper::has_icon(iconName)) {
iconName = DEFAULT_ICON_NAME;
}
icon_.set_from_icon_name(iconName, Gtk::ICON_SIZE_INVALID);

View File

@ -112,13 +112,13 @@ void IPC::registerForIPC(const std::string& ev, EventHandler* ev_handler) {
}
void IPC::unregisterForIPC(EventHandler* ev_handler) {
if (!ev_handler) {
if (!ev_handler) {
return;
}
callbackMutex.lock();
for(auto it = callbacks.begin(); it != callbacks.end(); ) {
for (auto it = callbacks.begin(); it != callbacks.end();) {
auto it_current = it;
it++;
auto& [eventname, handler] = *it_current;
@ -133,6 +133,8 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) {
std::string IPC::getSocket1Reply(const std::string& rq) {
// basically hyprctl
struct addrinfo ai_hints;
struct addrinfo* ai_res = NULL;
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
if (SERVERSOCKET < 0) {
@ -140,9 +142,11 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
return "";
}
const auto SERVER = gethostbyname("localhost");
memset(&ai_hints, 0, sizeof(struct addrinfo));
ai_hints.ai_family = AF_UNSPEC;
ai_hints.ai_socktype = SOCK_STREAM;
if (!SERVER) {
if (getaddrinfo("localhost", NULL, &ai_hints, &ai_res) != 0) {
spdlog::error("Hyprland IPC: Couldn't get host (2)");
return "";
}
@ -177,17 +181,25 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
}
char buffer[8192] = {0};
std::string response;
sizeWritten = read(SERVERSOCKET, buffer, 8192);
do {
sizeWritten = read(SERVERSOCKET, buffer, 8192);
if (sizeWritten < 0) {
spdlog::error("Hyprland IPC: Couldn't read (5)");
return "";
}
if (sizeWritten < 0) {
spdlog::error("Hyprland IPC: Couldn't read (5)");
close(SERVERSOCKET);
return "";
}
response.append(buffer, sizeWritten);
} while (sizeWritten == 8192);
close(SERVERSOCKET);
return response;
}
return std::string(buffer);
Json::Value IPC::getSocket1JsonReply(const std::string& rq) {
return parser_.parse(getSocket1Reply("j/" + rq));
}
} // namespace waybar::modules::hyprland

View File

@ -6,12 +6,12 @@
#include <util/sanitize_str.hpp>
#include "modules/hyprland/backend.hpp"
#include "util/string.hpp"
namespace waybar::modules::hyprland {
Language::Language(const std::string& id, const Bar& bar, const Json::Value& config)
: AButton(config, "language", id, "{}", 0, true), bar_(bar) {
: ALabel(config, "language", id, "{}", 0, true), bar_(bar) {
modulesReady = true;
if (!gIPC.get()) {
@ -21,8 +21,8 @@ Language::Language(const std::string& id, const Bar& bar, const Json::Value& con
// get the active layout when open
initLanguage();
button_.hide();
AButton::update();
label_.hide();
update();
// register for hyprland ipc
gIPC->registerForIPC("activelayout", this);
@ -37,39 +37,38 @@ Language::~Language() {
auto Language::update() -> void {
std::lock_guard<std::mutex> lg(mutex_);
if (!format_.empty()) {
button_.show();
label_->set_markup(layoutName_);
std::string layoutName = std::string{};
if (config_.isMember("format-" + layout_.short_description)) {
const auto propName = "format-" + layout_.short_description;
layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());
} else {
button_.hide();
layoutName = trim(fmt::format(fmt::runtime(format_), fmt::arg("long", layout_.full_name),
fmt::arg("short", layout_.short_name),
fmt::arg("shortDescription", layout_.short_description),
fmt::arg("variant", layout_.variant)));
}
AButton::update();
if (!format_.empty()) {
label_.show();
label_.set_markup(layoutName);
} else {
label_.hide();
}
ALabel::update();
}
void Language::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lg(mutex_);
auto layoutName = ev.substr(ev.find_last_of(',') + 1);
auto keebName = ev.substr(0, ev.find_last_of(','));
keebName = keebName.substr(keebName.find_first_of('>') + 2);
std::string kbName(begin(ev) + ev.find_last_of('>') + 1, begin(ev) + ev.find_first_of(','));
auto layoutName = ev.substr(ev.find_first_of(',') + 1);
if (config_.isMember("keyboard-name") && keebName != config_["keyboard-name"].asString())
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
return; // ignore
const auto BRIEFNAME = getShortFrom(layoutName);
if (config_.isMember("format-" + BRIEFNAME)) {
const auto PROPNAME = "format-" + BRIEFNAME;
layoutName = fmt::format(format_, config_[PROPNAME].asString());
} else {
layoutName = fmt::format(format_, layoutName);
}
layoutName = waybar::util::sanitize_string(layoutName);
if (layoutName == layoutName_) return;
layoutName_ = layoutName;
layout_ = getLayout(layoutName);
spdlog::debug("hyprland language onevent with {}", layoutName);
@ -77,21 +76,22 @@ void Language::onEvent(const std::string& ev) {
}
void Language::initLanguage() {
const auto INPUTDEVICES = gIPC->getSocket1Reply("devices");
const auto inputDevices = gIPC->getSocket1Reply("devices");
if (!config_.isMember("keyboard-name")) return;
const auto KEEBNAME = config_["keyboard-name"].asString();
const auto kbName = config_["keyboard-name"].asString();
try {
auto searcher = INPUTDEVICES.substr(INPUTDEVICES.find(KEEBNAME) + KEEBNAME.length());
searcher = searcher.substr(searcher.find("Keyboard at"));
searcher = searcher.substr(searcher.find("keymap:") + 7);
auto searcher = kbName.empty()
? inputDevices
: inputDevices.substr(inputDevices.find(kbName) + kbName.length());
searcher = searcher.substr(searcher.find("keymap:") + 8);
searcher = searcher.substr(0, searcher.find_first_of("\n\t"));
layoutName_ = searcher;
searcher = waybar::util::sanitize_string(searcher);
spdlog::debug("hyprland language initLanguage found {}", layoutName_);
layout_ = getLayout(searcher);
spdlog::debug("hyprland language initLanguage found {}", layout_.full_name);
dp.emit();
@ -100,11 +100,10 @@ void Language::initLanguage() {
}
}
std::string Language::getShortFrom(const std::string& fullName) {
auto Language::getLayout(const std::string& fullName) -> Layout {
const auto CONTEXT = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES);
rxkb_context_parse_default_ruleset(CONTEXT);
std::string foundName = "";
rxkb_layout* layout = rxkb_layout_first(CONTEXT);
while (layout) {
std::string nameOfLayout = rxkb_layout_get_description(layout);
@ -114,16 +113,26 @@ std::string Language::getShortFrom(const std::string& fullName) {
continue;
}
std::string briefName = rxkb_layout_get_brief(layout);
auto name = std::string(rxkb_layout_get_name(layout));
auto variant_ = rxkb_layout_get_variant(layout);
std::string variant = variant_ == nullptr ? "" : std::string(variant_);
auto short_description_ = rxkb_layout_get_brief(layout);
std::string short_description =
short_description_ == nullptr ? "" : std::string(short_description_);
Layout info = Layout{nameOfLayout, name, variant, short_description};
rxkb_context_unref(CONTEXT);
return briefName;
return info;
}
rxkb_context_unref(CONTEXT);
return "";
spdlog::debug("hyprland language didn't find matching layout");
return Layout{"", "", "", ""};
}
} // namespace waybar::modules::hyprland

View File

@ -0,0 +1,63 @@
#include "modules/hyprland/submap.hpp"
#include <spdlog/spdlog.h>
#include <util/sanitize_str.hpp>
namespace waybar::modules::hyprland {
Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "submap", id, "{}", 0, true), bar_(bar) {
modulesReady = true;
if (!gIPC.get()) {
gIPC = std::make_unique<IPC>();
}
label_.hide();
ALabel::update();
// register for hyprland ipc
gIPC->registerForIPC("submap", this);
dp.emit();
}
Submap::~Submap() {
gIPC->unregisterForIPC(this);
// wait for possible event handler to finish
std::lock_guard<std::mutex> lg(mutex_);
}
auto Submap::update() -> void {
std::lock_guard<std::mutex> lg(mutex_);
if (submap_.empty()) {
event_box_.hide();
} else {
label_.set_markup(fmt::format(fmt::runtime(format_), submap_));
if (tooltipEnabled()) {
label_.set_tooltip_text(submap_);
}
event_box_.show();
}
// Call parent update
ALabel::update();
}
void Submap::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lg(mutex_);
if (ev.find("submap") == std::string::npos) {
return;
}
auto submapName = ev.substr(ev.find_last_of('>') + 1);
submapName = waybar::util::sanitize_string(submapName);
submap_ = submapName;
spdlog::debug("hyprland submap onevent with {}", submap_);
dp.emit();
}
} // namespace waybar::modules::hyprland

View File

@ -2,30 +2,35 @@
#include <spdlog/spdlog.h>
#include <algorithm>
#include <regex>
#include <util/sanitize_str.hpp>
#include <vector>
#include "modules/hyprland/backend.hpp"
#include "util/command.hpp"
#include "util/json.hpp"
#include "util/rewrite_title.hpp"
#include "util/rewrite_string.hpp"
namespace waybar::modules::hyprland {
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "window", id, "{}", 0, true), bar_(bar) {
modulesReady = true;
separate_outputs = config["separate-outputs"].as<bool>();
separate_outputs = config["separate-outputs"].asBool();
if (!gIPC.get()) {
gIPC = std::make_unique<IPC>();
}
label_.hide();
ALabel::update();
queryActiveWorkspace();
update();
// register for hyprland ipc
gIPC->registerForIPC("activewindow", this);
gIPC->registerForIPC("closewindow", this);
gIPC->registerForIPC("movewindow", this);
gIPC->registerForIPC("changefloatingmode", this);
gIPC->registerForIPC("fullscreen", this);
}
Window::~Window() {
@ -38,63 +43,136 @@ auto Window::update() -> void {
// fix ampersands
std::lock_guard<std::mutex> lg(mutex_);
std::string window_name = waybar::util::sanitize_string(workspace_.last_window_title);
if (window_name != last_title_) {
if (window_name.empty()) {
label_.get_style_context()->add_class("empty");
} else {
label_.get_style_context()->remove_class("empty");
}
last_title_ = window_name;
}
if (!format_.empty()) {
label_.show();
label_.set_markup(
fmt::format(format_, waybar::util::rewriteTitle(lastView, config_["rewrite"])));
label_.set_markup(fmt::format(fmt::runtime(format_),
waybar::util::rewriteString(window_name, config_["rewrite"])));
} else {
label_.hide();
}
setClass("empty", workspace_.windows == 0);
setClass("solo", solo_);
setClass("fullscreen", fullscreen_);
setClass("floating", all_floating_);
if (!last_solo_class_.empty() && solo_class_ != last_solo_class_) {
if (bar_.window.get_style_context()->has_class(last_solo_class_)) {
bar_.window.get_style_context()->remove_class(last_solo_class_);
spdlog::trace("Removing solo class: {}", last_solo_class_);
}
}
if (!solo_class_.empty() && solo_class_ != last_solo_class_) {
bar_.window.get_style_context()->add_class(solo_class_);
spdlog::trace("Adding solo class: {}", solo_class_);
}
last_solo_class_ = solo_class_;
ALabel::update();
}
uint Window::getActiveWorkspaceID(std::string monitorName) {
auto cmd = waybar::util::command::exec("hyprctl monitors -j");
assert(cmd.exit_code == 0);
Json::Value json = parser_.parse(cmd.out);
assert(json.isArray());
auto monitor = std::find_if(json.begin(), json.end(),
[&](Json::Value monitor) { return monitor["name"] == monitorName; });
if(monitor == std::end(json)) {
return 0;
}
return (*monitor)["activeWorkspace"]["id"].as<uint>();
auto Window::getActiveWorkspace() -> Workspace {
const auto workspace = gIPC->getSocket1JsonReply("activeworkspace");
assert(workspace.isObject());
return Workspace::parse(workspace);
}
std::string Window::getLastWindowTitle(uint workspaceID) {
auto cmd = waybar::util::command::exec("hyprctl workspaces -j");
assert(cmd.exit_code == 0);
Json::Value json = parser_.parse(cmd.out);
assert(json.isArray());
auto workspace = std::find_if(json.begin(), json.end(), [&](Json::Value workspace) {
return workspace["id"].as<uint>() == workspaceID;
});
if (workspace == std::end(json)) {
return "";
auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
const auto monitors = gIPC->getSocket1JsonReply("monitors");
assert(monitors.isArray());
auto monitor = std::find_if(monitors.begin(), monitors.end(),
[&](Json::Value monitor) { return monitor["name"] == monitorName; });
if (monitor == std::end(monitors)) {
spdlog::warn("Monitor not found: {}", monitorName);
return Workspace{-1, 0, "", ""};
}
const int id = (*monitor)["activeWorkspace"]["id"].asInt();
const auto workspaces = gIPC->getSocket1JsonReply("workspaces");
assert(workspaces.isArray());
auto workspace = std::find_if(monitors.begin(), monitors.end(),
[&](Json::Value workspace) { return workspace["id"] == id; });
if (workspace == std::end(monitors)) {
spdlog::warn("No workspace with id {}", id);
return Workspace{-1, 0, "", ""};
}
return Workspace::parse(*workspace);
}
auto Window::Workspace::parse(const Json::Value& value) -> Window::Workspace {
return Workspace{value["id"].asInt(), value["windows"].asInt(), value["lastwindow"].asString(),
value["lastwindowtitle"].asString()};
}
void Window::queryActiveWorkspace() {
std::lock_guard<std::mutex> lg(mutex_);
if (separate_outputs) {
workspace_ = getActiveWorkspace(this->bar_.output->name);
} else {
workspace_ = getActiveWorkspace();
}
if (workspace_.windows > 0) {
const auto clients = gIPC->getSocket1Reply("j/clients");
Json::Value json = parser_.parse(clients);
assert(json.isArray());
auto active_window = std::find_if(json.begin(), json.end(), [&](Json::Value window) {
return window["address"] == workspace_.last_window;
});
if (active_window == std::end(json)) {
return;
}
if (workspace_.windows == 1 && !(*active_window)["floating"].asBool()) {
solo_class_ = (*active_window)["class"].asString();
} else {
solo_class_ = "";
}
std::vector<Json::Value> workspace_windows;
std::copy_if(json.begin(), json.end(), std::back_inserter(workspace_windows),
[&](Json::Value window) {
return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool();
});
solo_ = 1 == std::count_if(workspace_windows.begin(), workspace_windows.end(),
[&](Json::Value window) { return !window["floating"].asBool(); });
all_floating_ = std::all_of(workspace_windows.begin(), workspace_windows.end(),
[&](Json::Value window) { return window["floating"].asBool(); });
fullscreen_ = (*active_window)["fullscreen"].asBool();
} else {
solo_class_ = "";
solo_ = false;
all_floating_ = false;
fullscreen_ = false;
}
return (*workspace)["lastwindowtitle"].as<std::string>();
}
void Window::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lg(mutex_);
std::string windowName;
if (separate_outputs) {
windowName = getLastWindowTitle(getActiveWorkspaceID(this->bar_.output->name));
} else {
windowName = ev.substr(ev.find_first_of(',') + 1).substr(0, 256);
}
windowName = waybar::util::sanitize_string(windowName);
if (windowName == lastView) return;
lastView = windowName;
spdlog::debug("hyprland window onevent with {}", windowName);
queryActiveWorkspace();
dp.emit();
}
void Window::setClass(const std::string& classname, bool enable) {
if (enable) {
if (!bar_.window.get_style_context()->has_class(classname)) {
bar_.window.get_style_context()->add_class(classname);
}
} else {
bar_.window.get_style_context()->remove_class(classname);
}
}
} // namespace waybar::modules::hyprland

View File

@ -0,0 +1,199 @@
#include "modules/hyprland/workspaces.hpp"
#include <json/value.h>
#include <spdlog/spdlog.h>
#include <algorithm>
#include <charconv>
#include <memory>
#include <string>
namespace waybar::modules::hyprland {
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
: AModule(config, "workspaces", id, false, false),
bar_(bar),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) {
Json::Value config_format = config["format"];
format_ = config_format.isString() ? config_format.asString() : "{id}";
with_icon_ = format_.find("{icon}") != std::string::npos;
if (with_icon_ && icons_map_.empty()) {
Json::Value format_icons = config["format-icons"];
for (std::string &name : format_icons.getMemberNames()) {
icons_map_.emplace(name, format_icons[name].asString());
}
icons_map_.emplace("", "");
}
box_.set_name("workspaces");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
event_box_.add(box_);
modulesReady = true;
if (!gIPC.get()) {
gIPC = std::make_unique<IPC>();
}
init();
gIPC->registerForIPC("workspace", this);
gIPC->registerForIPC("createworkspace", this);
gIPC->registerForIPC("destroyworkspace", this);
}
auto Workspaces::update() -> void {
for (int &workspace_to_remove : workspaces_to_remove_) {
remove_workspace(workspace_to_remove);
}
workspaces_to_remove_.clear();
for (int &workspace_to_create : workspaces_to_create_) {
create_workspace(workspace_to_create);
}
workspaces_to_create_.clear();
for (std::unique_ptr<Workspace> &workspace : workspaces_) {
workspace->set_active(workspace->id() == active_workspace_id);
std::string &workspace_icon = icons_map_[""];
if (with_icon_) {
workspace_icon = workspace->select_icon(icons_map_);
}
workspace->update(format_, workspace_icon);
}
AModule::update();
}
void Workspaces::onEvent(const std::string &ev) {
std::lock_guard<std::mutex> lock(mutex_);
std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>'));
std::string payload = ev.substr(eventName.size() + 2);
if (eventName == "workspace") {
std::from_chars(payload.data(), payload.data() + payload.size(), active_workspace_id);
} else if (eventName == "destroyworkspace") {
int deleted_workspace_id;
std::from_chars(payload.data(), payload.data() + payload.size(), deleted_workspace_id);
workspaces_to_remove_.push_back(deleted_workspace_id);
} else if (eventName == "createworkspace") {
int new_workspace_id;
std::from_chars(payload.data(), payload.data() + payload.size(), new_workspace_id);
workspaces_to_create_.push_back(new_workspace_id);
}
dp.emit();
}
void Workspaces::create_workspace(int id) {
workspaces_.push_back(std::make_unique<Workspace>(id));
Gtk::Button &new_workspace_button = workspaces_.back()->button();
box_.pack_start(new_workspace_button, false, false);
sort_workspaces();
new_workspace_button.show_all();
}
void Workspaces::remove_workspace(int id) {
auto workspace = std::find_if(workspaces_.begin(), workspaces_.end(),
[&](std::unique_ptr<Workspace> &x) { return x->id() == id; });
if (workspace == workspaces_.end()) {
spdlog::warn("Can't find workspace with id {}", id);
return;
}
box_.remove(workspace->get()->button());
workspaces_.erase(workspace);
}
void Workspaces::init() {
const auto activeWorkspace = WorkspaceDto::parse(gIPC->getSocket1JsonReply("activeworkspace"));
active_workspace_id = activeWorkspace.id;
const Json::Value workspaces_json = gIPC->getSocket1JsonReply("workspaces");
for (const Json::Value &workspace_json : workspaces_json) {
workspaces_.push_back(
std::make_unique<Workspace>(Workspace(WorkspaceDto::parse(workspace_json))));
}
for (auto &workspace : workspaces_) {
box_.pack_start(workspace->button(), false, false);
}
sort_workspaces();
dp.emit();
}
Workspaces::~Workspaces() {
gIPC->unregisterForIPC(this);
// wait for possible event handler to finish
std::lock_guard<std::mutex> lg(mutex_);
}
WorkspaceDto WorkspaceDto::parse(const Json::Value &value) {
return WorkspaceDto{value["id"].asInt()};
}
Workspace::Workspace(WorkspaceDto dto) : Workspace(dto.id){};
Workspace::Workspace(int id) : id_(id) {
button_.set_relief(Gtk::RELIEF_NONE);
content_.set_center_widget(label_);
button_.add(content_);
};
void add_or_remove_class(Glib::RefPtr<Gtk::StyleContext> context, bool condition,
const std::string &class_name) {
if (condition) {
context->add_class(class_name);
} else {
context->remove_class(class_name);
}
}
void Workspace::update(const std::string &format, const std::string &icon) {
Glib::RefPtr<Gtk::StyleContext> style_context = button_.get_style_context();
add_or_remove_class(style_context, active(), "active");
label_.set_markup(
fmt::format(fmt::runtime(format), fmt::arg("id", id()), fmt::arg("icon", icon)));
}
void Workspaces::sort_workspaces() {
std::sort(workspaces_.begin(), workspaces_.end(),
[](std::unique_ptr<Workspace> &lhs, std::unique_ptr<Workspace> &rhs) {
return lhs->id() < rhs->id();
});
for (size_t i = 0; i < workspaces_.size(); ++i) {
box_.reorder_child(workspaces_[i]->button(), i);
}
}
std::string &Workspace::select_icon(std::map<std::string, std::string> &icons_map) {
if (active()) {
auto active_icon_it = icons_map.find("active");
if (active_icon_it != icons_map.end()) {
return active_icon_it->second;
}
}
auto named_icon_it = icons_map.find(std::to_string(id()));
if (named_icon_it != icons_map.end()) {
return named_icon_it->second;
}
auto default_icon_it = icons_map.find("default");
if (default_icon_it != icons_map.end()) {
return default_icon_it->second;
}
return icons_map[""];
}
} // namespace waybar::modules::hyprland

View File

@ -8,7 +8,7 @@ bool waybar::modules::IdleInhibitor::status = false;
waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar,
const Json::Value& config)
: AButton(config, "idle_inhibitor", id, "{status}", 0, false, true),
: ALabel(config, "idle_inhibitor", id, "{status}", 0, false, true),
bar_(bar),
idle_inhibitor_(nullptr),
pid_(-1) {
@ -49,13 +49,13 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() {
auto waybar::modules::IdleInhibitor::update() -> void {
// Check status
if (status) {
button_.get_style_context()->remove_class("deactivated");
label_.get_style_context()->remove_class("deactivated");
if (idle_inhibitor_ == nullptr) {
idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor(
waybar::Client::inst()->idle_inhibit_manager, bar_.surface);
}
} else {
button_.get_style_context()->remove_class("activated");
label_.get_style_context()->remove_class("activated");
if (idle_inhibitor_ != nullptr) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
@ -63,24 +63,18 @@ auto waybar::modules::IdleInhibitor::update() -> void {
}
std::string status_text = status ? "activated" : "deactivated";
label_->set_markup(fmt::format(format_, fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
button_.get_style_context()->add_class(status_text);
label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text);
if (tooltipEnabled()) {
button_.set_tooltip_markup(
status ? fmt::format(config_["tooltip-format-activated"].isString()
? config_["tooltip-format-activated"].asString()
: "{status}",
fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text)))
: fmt::format(config_["tooltip-format-deactivated"].isString()
? config_["tooltip-format-deactivated"].asString()
: "{status}",
fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
auto config = config_[status ? "tooltip-format-activated" : "tooltip-format-deactivated"];
auto tooltip_format = config.isString() ? config.asString() : "{status}";
label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format),
fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
}
// Call parent update
AButton::update();
ALabel::update();
}
void waybar::modules::IdleInhibitor::toggleStatus() {
@ -124,6 +118,6 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
}
}
AButton::handleToggle(e);
ALabel::handleToggle(e);
return true;
}

88
src/modules/image.cpp Normal file
View File

@ -0,0 +1,88 @@
#include "modules/image.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);
}
event_box_.add(box_);
dp.emit();
size_ = config["size"].asInt();
interval_ = config_["interval"].asInt();
if (size_ == 0) {
size_ = 16;
}
if (interval_ == 0) {
interval_ = INT_MAX;
}
delayWorker();
}
void waybar::modules::Image::delayWorker() {
thread_ = [this] {
dp.emit();
auto interval = std::chrono::seconds(interval_);
thread_.sleep_for(interval);
};
}
void waybar::modules::Image::refresh(int sig) {
if (sig == SIGRTMIN + config_["signal"].asInt()) {
thread_.wake_up();
}
}
auto waybar::modules::Image::update() -> void {
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
if (config_["path"].isString()) {
path_ = config_["path"].asString();
} else if (config_["exec"].isString()) {
output_ = util::command::exec(config_["exec"].asString());
parseOutputRaw();
} else {
path_ = "";
}
if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS))
pixbuf = Gdk::Pixbuf::create_from_file(path_, size_, size_);
else
pixbuf = {};
if (pixbuf) {
if (tooltipEnabled() && !tooltip_.empty()) {
if (box_.get_tooltip_markup() != tooltip_) {
box_.set_tooltip_markup(tooltip_);
}
}
image_.set(pixbuf);
image_.show();
} else {
image_.clear();
image_.hide();
}
AModule::update();
}
void waybar::modules::Image::parseOutputRaw() {
std::istringstream output(output_.out);
std::string line;
int i = 0;
while (getline(output, line)) {
if (i == 0) {
path_ = line;
} else if (i == 1) {
tooltip_ = line;
} else {
break;
}
i++;
}
}

View File

@ -98,7 +98,7 @@ auto getInhibitors(const Json::Value& config) -> std::string {
namespace waybar::modules {
Inhibitor::Inhibitor(const std::string& id, const Bar& bar, const Json::Value& config)
: AButton(config, "inhibitor", id, "{status}", true),
: ALabel(config, "inhibitor", id, "{status}", true),
dbus_(::dbus()),
inhibitors_(::getInhibitors(config)) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
@ -117,16 +117,16 @@ auto Inhibitor::activated() -> bool { return handle_ != -1; }
auto Inhibitor::update() -> void {
std::string status_text = activated() ? "activated" : "deactivated";
button_.get_style_context()->remove_class(activated() ? "deactivated" : "activated");
label_->set_markup(fmt::format(format_, fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
button_.get_style_context()->add_class(status_text);
label_.get_style_context()->remove_class(activated() ? "deactivated" : "activated");
label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text);
if (tooltipEnabled()) {
button_.set_tooltip_text(status_text);
label_.set_tooltip_text(status_text);
}
return AButton::update();
return ALabel::update();
}
auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool {
@ -142,7 +142,7 @@ auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool {
}
}
return AButton::handleToggle(e);
return ALabel::handleToggle(e);
}
} // namespace waybar::modules

View File

@ -72,7 +72,7 @@ auto JACK::update() -> void {
} else
format = "{load}%";
label_.set_markup(fmt::format(format, fmt::arg("load", std::round(load_)),
label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("load", std::round(load_)),
fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_),
fmt::arg("latency", fmt::format("{:.2f}", latency)),
fmt::arg("xruns", xruns_)));
@ -81,9 +81,9 @@ auto JACK::update() -> void {
std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms";
if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format(
tooltip_format, fmt::arg("load", std::round(load_)), fmt::arg("bufsize", bufsize_),
fmt::arg("samplerate", samplerate_), fmt::arg("latency", fmt::format("{:.2f}", latency)),
fmt::arg("xruns", xruns_)));
fmt::runtime(tooltip_format), fmt::arg("load", std::round(load_)),
fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_),
fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_)));
}
// Call parent update

View File

@ -278,7 +278,7 @@ auto waybar::modules::KeyboardState::update() -> void {
};
for (auto& label_state : label_states) {
std::string text;
text = fmt::format(label_state.format,
text = fmt::format(fmt::runtime(label_state.format),
fmt::arg("icon", label_state.state ? icon_locked_ : icon_unlocked_),
fmt::arg("name", label_state.name));
label_state.label.set_markup(text);

View File

@ -1,7 +1,7 @@
#include "modules/memory.hpp"
waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config)
: AButton(config, "memory", id, "{}%", 30) {
: ALabel(config, "memory", id, "{}%", 30) {
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
@ -55,8 +55,9 @@ auto waybar::modules::Memory::update() -> void {
} else {
event_box_.show();
auto icons = std::vector<std::string>{state};
label_->set_markup(fmt::format(
format, used_ram_percentage, fmt::arg("icon", getIcon(used_ram_percentage, icons)),
label_.set_markup(fmt::format(
fmt::runtime(format), used_ram_percentage,
fmt::arg("icon", getIcon(used_ram_percentage, icons)),
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
@ -67,20 +68,20 @@ auto waybar::modules::Memory::update() -> void {
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
button_.set_tooltip_text(fmt::format(
tooltip_format, used_ram_percentage, fmt::arg("total", total_ram_gigabytes),
fmt::arg("swapTotal", total_swap_gigabytes),
label_.set_tooltip_text(fmt::format(
fmt::runtime(tooltip_format), used_ram_percentage,
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
fmt::arg("swapAvail", available_swap_gigabytes)));
} else {
button_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
}
}
} else {
event_box_.hide();
}
// Call parent update
AButton::update();
ALabel::update();
}

View File

@ -15,7 +15,7 @@ namespace waybar::modules {
#endif
waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
: AButton(config, "mpd", id, "{album} - {artist} - {title}", 5, false, true),
: ALabel(config, "mpd", id, "{album} - {artist} - {title}", 5, false, true),
module_name_(id.empty() ? "mpd" : "mpd#" + id),
server_(nullptr),
port_(config_["port"].isUInt() ? config["port"].asUInt() : 0),
@ -47,7 +47,7 @@ auto waybar::modules::MPD::update() -> void {
context_.update();
// Call parent update
AButton::update();
ALabel::update();
}
void waybar::modules::MPD::queryMPD() {
@ -88,15 +88,20 @@ std::string waybar::modules::MPD::getFilename() const {
void waybar::modules::MPD::setLabel() {
if (connection_ == nullptr) {
button_.get_style_context()->add_class("disconnected");
button_.get_style_context()->remove_class("stopped");
button_.get_style_context()->remove_class("playing");
button_.get_style_context()->remove_class("paused");
label_.get_style_context()->add_class("disconnected");
label_.get_style_context()->remove_class("stopped");
label_.get_style_context()->remove_class("playing");
label_.get_style_context()->remove_class("paused");
auto format = config_["format-disconnected"].isString()
? config_["format-disconnected"].asString()
: "disconnected";
label_->set_markup(format);
if (format.empty()) {
label_.set_markup(format);
label_.show();
} else {
label_.hide();
}
if (tooltipEnabled()) {
std::string tooltip_format;
@ -104,12 +109,11 @@ void waybar::modules::MPD::setLabel() {
? config_["tooltip-format-disconnected"].asString()
: "MPD (disconnected)";
// Nothing to format
button_.set_tooltip_text(tooltip_format);
label_.set_tooltip_text(tooltip_format);
}
return;
} else {
button_.get_style_context()->remove_class("disconnected");
}
label_.get_style_context()->remove_class("disconnected");
auto format = format_;
Glib::ustring artist, album_artist, album, title;
@ -118,22 +122,24 @@ void waybar::modules::MPD::setLabel() {
std::chrono::seconds elapsedTime, totalTime;
std::string stateIcon = "";
if (stopped()) {
bool no_song = song_.get() == nullptr;
if (stopped() || no_song) {
if (no_song) spdlog::warn("Bug in mpd: no current song but state is not stopped.");
format =
config_["format-stopped"].isString() ? config_["format-stopped"].asString() : "stopped";
button_.get_style_context()->add_class("stopped");
button_.get_style_context()->remove_class("playing");
button_.get_style_context()->remove_class("paused");
label_.get_style_context()->add_class("stopped");
label_.get_style_context()->remove_class("playing");
label_.get_style_context()->remove_class("paused");
} else {
button_.get_style_context()->remove_class("stopped");
label_.get_style_context()->remove_class("stopped");
if (playing()) {
button_.get_style_context()->add_class("playing");
button_.get_style_context()->remove_class("paused");
label_.get_style_context()->add_class("playing");
label_.get_style_context()->remove_class("paused");
} else if (paused()) {
format = config_["format-paused"].isString() ? config_["format-paused"].asString()
: config_["format"].asString();
button_.get_style_context()->add_class("paused");
button_.get_style_context()->remove_class("playing");
label_.get_style_context()->add_class("paused");
label_.get_style_context()->remove_class("playing");
}
stateIcon = getStateIcon();
@ -169,15 +175,21 @@ void waybar::modules::MPD::setLabel() {
if (config_["title-len"].isInt()) title = title.substr(0, config_["title-len"].asInt());
try {
label_->set_markup(fmt::format(
format, fmt::arg("artist", artist.raw()), fmt::arg("albumArtist", album_artist.raw()),
fmt::arg("album", album.raw()), fmt::arg("title", title.raw()), fmt::arg("date", date),
fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos),
fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon),
fmt::arg("filename", filename)));
auto text = fmt::format(
fmt::runtime(format), fmt::arg("artist", artist.raw()),
fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()),
fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume),
fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename));
if (text.empty()) {
label_.hide();
} else {
label_.show();
label_.set_markup(text);
}
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error: {}", e.what());
}
@ -188,7 +200,7 @@ void waybar::modules::MPD::setLabel() {
: "MPD (connected)";
try {
auto tooltip_text =
fmt::format(tooltip_format, fmt::arg("artist", artist.raw()),
fmt::format(fmt::runtime(tooltip_format), fmt::arg("artist", artist.raw()),
fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()),
fmt::arg("title", title.raw()), fmt::arg("date", date),
fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime),
@ -196,7 +208,7 @@ void waybar::modules::MPD::setLabel() {
fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon));
button_.set_tooltip_text(tooltip_text);
label_.set_tooltip_text(tooltip_text);
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error (tooltip): {}", e.what());
}

723
src/modules/mpris/mpris.cpp Normal file
View File

@ -0,0 +1,723 @@
#include "modules/mpris/mpris.hpp"
#include <fmt/core.h>
#include <optional>
#include <sstream>
#include <string>
extern "C" {
#include <playerctl/playerctl.h>
}
#include <glib.h>
#include <spdlog/spdlog.h>
namespace waybar::modules::mpris {
const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}";
Mpris::Mpris(const std::string& id, const Json::Value& config)
: ALabel(config, "mpris", id, DEFAULT_FORMAT, 5, false, true),
tooltip_(DEFAULT_FORMAT),
artist_len_(-1),
album_len_(-1),
title_len_(-1),
dynamic_len_(-1),
dynamic_prio_({"title", "artist", "album", "position", "length"}),
dynamic_order_({"title", "artist", "album", "position", "length"}),
dynamic_separator_(" - "),
truncate_hours_(true),
tooltip_len_limits_(false),
// this character is used in Gnome so it's fine to use it here
ellipsis_("\u2026"),
player_("playerctld"),
manager(),
player(),
last_update_(std::chrono::system_clock::now() - interval_) {
if (config_["format-playing"].isString()) {
format_playing_ = config_["format-playing"].asString();
}
if (config_["format-paused"].isString()) {
format_paused_ = config_["format-paused"].asString();
}
if (config_["format-stopped"].isString()) {
format_stopped_ = config_["format-stopped"].asString();
}
if (config_["ellipsis"].isString()) {
ellipsis_ = config_["ellipsis"].asString();
}
if (config_["dynamic-separator"].isString()) {
dynamic_separator_ = config_["dynamic-separator"].asString();
}
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
tooltip_ = config_["tooltip-format"].asString();
}
if (config_["tooltip-format-playing"].isString()) {
tooltip_playing_ = config_["tooltip-format-playing"].asString();
}
if (config_["tooltip-format-paused"].isString()) {
tooltip_paused_ = config_["tooltip-format-paused"].asString();
}
if (config_["tooltip-format-stopped"].isString()) {
tooltip_stopped_ = config_["tooltip-format-stopped"].asString();
}
if (config_["enable-tooltip-len-limits"].isBool()) {
tooltip_len_limits_ = config["enable-tooltip-len-limits"].asBool();
}
}
if (config["artist-len"].isUInt()) {
artist_len_ = config["artist-len"].asUInt();
}
if (config["album-len"].isUInt()) {
album_len_ = config["album-len"].asUInt();
}
if (config["title-len"].isUInt()) {
title_len_ = config["title-len"].asUInt();
}
if (config["dynamic-len"].isUInt()) {
dynamic_len_ = config["dynamic-len"].asUInt();
}
// "dynamic-priority" has been kept for backward compatibility
if (config_["dynamic-importance-order"].isArray() || config_["dynamic-priority"].isArray()) {
dynamic_prio_.clear();
const auto& dynamic_priority = config_["dynamic-importance-order"].isArray() ? config_["dynamic-importance-order"] : config_["dynamic-priority"];
for (const auto& value : dynamic_priority) {
if (value.isString()) {
dynamic_prio_.push_back(value.asString());
}
}
}
if (config_["dynamic-order"].isArray()) {
dynamic_order_.clear();
for (auto it = config_["dynamic-order"].begin(); it != config_["dynamic-order"].end(); ++it) {
if (it->isString()) {
dynamic_order_.push_back(it->asString());
}
}
}
if (config_["truncate-hours"].isBool()) {
truncate_hours_ = config["truncate-hours"].asBool();
}
if (config_["player"].isString()) {
player_ = config_["player"].asString();
}
if (config_["ignored-players"].isArray()) {
for (auto it = config_["ignored-players"].begin(); it != config_["ignored-players"].end();
++it) {
if (it->isString()) {
ignored_players_.push_back(it->asString());
}
}
}
GError* error = nullptr;
manager = playerctl_player_manager_new(&error);
if (error) {
throw std::runtime_error(fmt::format("unable to create MPRIS client: {}", error->message));
}
g_object_connect(manager, "signal::name-appeared", G_CALLBACK(onPlayerNameAppeared), this, NULL);
g_object_connect(manager, "signal::name-vanished", G_CALLBACK(onPlayerNameVanished), this, NULL);
if (player_ == "playerctld") {
// use playerctld proxy
PlayerctlPlayerName name = {
.instance = (gchar*)player_.c_str(),
.source = PLAYERCTL_SOURCE_DBUS_SESSION,
};
player = playerctl_player_new_from_name(&name, &error);
} else {
GList* players = playerctl_list_players(&error);
if (error) {
auto e = fmt::format("unable to list players: {}", error->message);
g_error_free(error);
throw std::runtime_error(e);
}
for (auto p = players; p != NULL; p = p->next) {
auto pn = static_cast<PlayerctlPlayerName*>(p->data);
if (strcmp(pn->name, player_.c_str()) == 0) {
player = playerctl_player_new_from_name(pn, &error);
break;
}
}
}
if (error) {
throw std::runtime_error(
fmt::format("unable to connect to player {}: {}", player_, error->message));
}
if (player) {
g_object_connect(player, "signal::play", G_CALLBACK(onPlayerPlay), this, "signal::pause",
G_CALLBACK(onPlayerPause), this, "signal::stop", G_CALLBACK(onPlayerStop),
this, "signal::stop", G_CALLBACK(onPlayerStop), this, "signal::metadata",
G_CALLBACK(onPlayerMetadata), this, NULL);
}
// allow setting an interval count that triggers periodic refreshes
if (interval_.count() > 0) {
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
}
// trigger initial update
dp.emit();
}
Mpris::~Mpris() {
if (manager != NULL) g_object_unref(manager);
if (player != NULL) g_object_unref(player);
}
auto Mpris::getIconFromJson(const Json::Value& icons, const std::string& key) -> std::string {
if (icons.isObject()) {
if (icons[key].isString()) {
return icons[key].asString();
} else if (icons["default"].isString()) {
return icons["default"].asString();
}
}
return "";
}
// Wide characters count as two, zero-width characters count as zero
// Modifies str in-place (unless width = std::string::npos)
// Returns the total width of the string pre-truncating
size_t utf8_truncate(std::string& str, size_t width = std::string::npos) {
if (str.length() == 0) return 0;
const gchar* trunc_end = nullptr;
size_t total_width = 0;
for (gchar *data = str.data(), *end = data + str.size(); data;) {
gunichar c = g_utf8_get_char_validated(data, end - data);
if (c == -1U || c == -2U) {
// invalid unicode, treat string as ascii
if (width != std::string::npos && str.length() > width) str.resize(width);
return str.length();
} else if (g_unichar_iswide(c)) {
total_width += 2;
} else if (!g_unichar_iszerowidth(c) && c != 0xAD) { // neither zero-width nor soft hyphen
total_width += 1;
}
data = g_utf8_find_next_char(data, end);
if (width != std::string::npos && total_width <= width && !g_unichar_isspace(c))
trunc_end = data;
}
if (trunc_end) str.resize(trunc_end - str.data());
return total_width;
}
size_t utf8_width(const std::string& str) { return utf8_truncate(const_cast<std::string&>(str)); }
void truncate(std::string& s, const std::string& ellipsis, size_t max_len) {
if (max_len == 0) {
s.resize(0);
return;
}
size_t len = utf8_truncate(s, max_len);
if (len > max_len) {
size_t ellipsis_len = utf8_width(ellipsis);
if (max_len >= ellipsis_len) {
if (ellipsis_len) utf8_truncate(s, max_len - ellipsis_len);
s += ellipsis;
} else {
s.resize(0);
}
}
}
auto Mpris::getArtistStr(const PlayerInfo& info, bool truncated) -> std::string {
auto artist = info.artist.value_or(std::string());
if (truncated && artist_len_ >= 0) truncate(artist, ellipsis_, artist_len_);
return artist;
}
auto Mpris::getAlbumStr(const PlayerInfo& info, bool truncated) -> std::string {
auto album = info.album.value_or(std::string());
if (truncated && album_len_ >= 0) truncate(album, ellipsis_, album_len_);
return album;
}
auto Mpris::getTitleStr(const PlayerInfo& info, bool truncated) -> std::string {
auto title = info.title.value_or(std::string());
if (truncated && title_len_ >= 0) truncate(title, ellipsis_, title_len_);
return title;
}
auto Mpris::getLengthStr(const PlayerInfo& info, bool truncated) -> std::string {
if (info.length.has_value()) {
auto length = info.length.value();
return (truncated && length.substr(0, 3) == "00:") ? length.substr(3) : length;
}
return std::string();
}
auto Mpris::getPositionStr(const PlayerInfo& info, bool truncated) -> std::string {
if (info.position.has_value()) {
auto position = info.position.value();
return (truncated && position.substr(0, 3) == "00:") ? position.substr(3) : position;
}
return std::string();
}
auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) -> std::string {
auto artist = getArtistStr(info, truncated);
auto album = getAlbumStr(info, truncated);
auto title = getTitleStr(info, truncated);
auto length = getLengthStr(info, truncated && truncate_hours_);
// keep position format same as length format
auto position = getPositionStr(info, truncated && truncate_hours_ && length.length() < 6);
size_t artistLen = utf8_width(artist);
size_t albumLen = utf8_width(album);
size_t titleLen = utf8_width(title);
size_t lengthLen = length.length();
size_t posLen = position.length();
bool showArtist = (artistLen != 0) && (std::find(dynamic_order_.begin(), dynamic_order_.end(),
"artist") != dynamic_order_.end());
bool showAlbum = (albumLen != 0) && (std::find(dynamic_order_.begin(), dynamic_order_.end(),
"album") != dynamic_order_.end());
bool showTitle = (titleLen != 0) && (std::find(dynamic_order_.begin(), dynamic_order_.end(),
"title") != dynamic_order_.end());
bool showLength = (lengthLen != 0) && (std::find(dynamic_order_.begin(), dynamic_order_.end(),
"length") != dynamic_order_.end());
bool showPos = (posLen != 0) && (std::find(dynamic_order_.begin(), dynamic_order_.end(),
"position") != dynamic_order_.end());
if (truncated && dynamic_len_ >= 0) {
//Since the first element doesn't present a separator and we don't know a priori which one
//it will be, we add a "virtual separatorLen" to the dynamicLen, since we are adding the
//separatorLen to all the other lengths.
size_t separatorLen = utf8_width(dynamic_separator_);
size_t dynamicLen = dynamic_len_ + separatorLen;
if (showArtist) artistLen += separatorLen;
if (showAlbum) albumLen += separatorLen;
if (showTitle) albumLen += separatorLen;
if (showLength) lengthLen += separatorLen;
if (showPos) posLen += separatorLen;
size_t totalLen = 0;
for (auto it = dynamic_prio_.begin(); it != dynamic_prio_.end(); ++it) {
if (*it == "artist") {
if (totalLen + artistLen > dynamicLen) {
showArtist = false;
} else if (showArtist) {
totalLen += artistLen;
}
} else if (*it == "album") {
if (totalLen + albumLen > dynamicLen) {
showAlbum = false;
} else if (showAlbum) {
totalLen += albumLen;
}
} else if (*it == "title") {
if (totalLen + titleLen > dynamicLen) {
showTitle = false;
} else if (showTitle) {
totalLen += titleLen;
}
} else if (*it == "length") {
if (totalLen + lengthLen > dynamicLen) {
showLength = false;
} else if (showLength) {
totalLen += lengthLen;
posLen = std::max((size_t)2, posLen) - 2;
}
} else if (*it == "position") {
if (totalLen + posLen > dynamicLen) {
showPos = false;
} else if (showPos) {
totalLen += posLen;
lengthLen = std::max((size_t)2, lengthLen) - 2;
}
}
}
}
std::stringstream dynamic;
if (html) {
artist = Glib::Markup::escape_text(artist);
album = Glib::Markup::escape_text(album);
title = Glib::Markup::escape_text(title);
}
bool lengthOrPositionShown = false;
bool previousShown = false;
std::string previousOrder = "";
for (const std::string& order : dynamic_order_) {
if ((order == "artist" && showArtist) ||
(order == "album" && showAlbum) ||
(order == "title" && showTitle)) {
if (previousShown &&
previousOrder != "length" &&
previousOrder != "position") {
dynamic << dynamic_separator_;
}
if (order == "artist") {
dynamic << artist;
} else if (order == "album") {
dynamic << album;
} else if (order == "title") {
dynamic << title;
}
previousShown = true;
} else if (order == "length" || order == "position") {
if (!lengthOrPositionShown && (showLength || showPos)) {
if (html) dynamic << "<small>";
if (previousShown) dynamic << ' ';
dynamic << '[';
if (showPos) {
dynamic << position;
if (showLength) dynamic << '/';
}
if (showLength) dynamic << length;
dynamic << ']';
if (!dynamic.str().empty()) dynamic << ' ';
if (html) dynamic << "</small>";
lengthOrPositionShown = true;
}
}
previousOrder = order;
}
return dynamic.str();
}
auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name,
gpointer data) -> void {
Mpris* mpris = static_cast<Mpris*>(data);
if (!mpris) return;
spdlog::debug("mpris: name-appeared callback: {}", player_name->name);
if (std::string(player_name->name) != mpris->player_) {
return;
}
GError* error = nullptr;
mpris->player = playerctl_player_new_from_name(player_name, &error);
g_object_connect(mpris->player, "signal::play", G_CALLBACK(onPlayerPlay), mpris, "signal::pause",
G_CALLBACK(onPlayerPause), mpris, "signal::stop", G_CALLBACK(onPlayerStop),
mpris, "signal::stop", G_CALLBACK(onPlayerStop), mpris, "signal::metadata",
G_CALLBACK(onPlayerMetadata), mpris, NULL);
mpris->dp.emit();
}
auto Mpris::onPlayerNameVanished(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name,
gpointer data) -> void {
Mpris* mpris = static_cast<Mpris*>(data);
if (!mpris) return;
spdlog::debug("mpris: player-vanished callback: {}", player_name->name);
if (std::string(player_name->name) == mpris->player_) {
mpris->player = nullptr;
mpris->dp.emit();
}
}
auto Mpris::onPlayerPlay(PlayerctlPlayer* player, gpointer data) -> void {
Mpris* mpris = static_cast<Mpris*>(data);
if (!mpris) return;
spdlog::debug("mpris: player-play callback");
// update widget
mpris->dp.emit();
}
auto Mpris::onPlayerPause(PlayerctlPlayer* player, gpointer data) -> void {
Mpris* mpris = static_cast<Mpris*>(data);
if (!mpris) return;
spdlog::debug("mpris: player-pause callback");
// update widget
mpris->dp.emit();
}
auto Mpris::onPlayerStop(PlayerctlPlayer* player, gpointer data) -> void {
Mpris* mpris = static_cast<Mpris*>(data);
if (!mpris) return;
spdlog::debug("mpris: player-stop callback");
// hide widget
mpris->event_box_.set_visible(false);
// update widget
mpris->dp.emit();
}
auto Mpris::onPlayerMetadata(PlayerctlPlayer* player, GVariant* metadata, gpointer data) -> void {
Mpris* mpris = static_cast<Mpris*>(data);
if (!mpris) return;
spdlog::debug("mpris: player-metadata callback");
// update widget
mpris->dp.emit();
}
auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
if (!player) {
return std::nullopt;
}
GError* error = nullptr;
char* player_status = nullptr;
auto player_playback_status = PLAYERCTL_PLAYBACK_STATUS_STOPPED;
g_object_get(player, "status", &player_status, "playback-status", &player_playback_status, NULL);
std::string player_name = player_;
if (player_name == "playerctld") {
GList* players = playerctl_list_players(&error);
if (error) {
auto e = fmt::format("unable to list players: {}", error->message);
g_error_free(error);
throw std::runtime_error(e);
}
// > get the list of players [..] in order of activity
// https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249
players = g_list_first(players);
if (players) player_name = static_cast<PlayerctlPlayerName*>(players->data)->name;
}
if (std::any_of(ignored_players_.begin(), ignored_players_.end(),
[&](const std::string& pn) { return player_name == pn; })) {
spdlog::warn("mpris[{}]: ignoring player update", player_name);
return std::nullopt;
}
// make status lowercase
player_status[0] = std::tolower(player_status[0]);
PlayerInfo info = {
.name = player_name,
.status = player_playback_status,
.status_string = player_status,
.artist = std::nullopt,
.album = std::nullopt,
.title = std::nullopt,
.length = std::nullopt,
};
if (auto artist_ = playerctl_player_get_artist(player, &error)) {
spdlog::debug("mpris[{}]: artist = {}", info.name, artist_);
info.artist = artist_;
g_free(artist_);
}
if (error) goto errorexit;
if (auto album_ = playerctl_player_get_album(player, &error)) {
spdlog::debug("mpris[{}]: album = {}", info.name, album_);
info.album = album_;
g_free(album_);
}
if (error) goto errorexit;
if (auto title_ = playerctl_player_get_title(player, &error)) {
spdlog::debug("mpris[{}]: title = {}", info.name, title_);
info.title = title_;
g_free(title_);
}
if (error) goto errorexit;
if (auto length_ = playerctl_player_print_metadata_prop(player, "mpris:length", &error)) {
spdlog::debug("mpris[{}]: mpris:length = {}", info.name, length_);
std::chrono::microseconds len = std::chrono::microseconds(std::strtol(length_, nullptr, 10));
auto len_h = std::chrono::duration_cast<std::chrono::hours>(len);
auto len_m = std::chrono::duration_cast<std::chrono::minutes>(len - len_h);
auto len_s = std::chrono::duration_cast<std::chrono::seconds>(len - len_h - len_m);
info.length = fmt::format("{:02}:{:02}:{:02}", len_h.count(), len_m.count(), len_s.count());
g_free(length_);
}
if (error) goto errorexit;
{
auto position_ = playerctl_player_get_position(player, &error);
if (error) {
// it's fine to have an error here because not all players report a position
g_error_free(error);
error = nullptr;
} else {
spdlog::debug("mpris[{}]: position = {}", info.name, position_);
std::chrono::microseconds len = std::chrono::microseconds(position_);
auto len_h = std::chrono::duration_cast<std::chrono::hours>(len);
auto len_m = std::chrono::duration_cast<std::chrono::minutes>(len - len_h);
auto len_s = std::chrono::duration_cast<std::chrono::seconds>(len - len_h - len_m);
info.position = fmt::format("{:02}:{:02}:{:02}", len_h.count(), len_m.count(), len_s.count());
}
}
return info;
errorexit:
spdlog::error("mpris[{}]: {}", info.name, error->message);
g_error_free(error);
return std::nullopt;
}
bool Mpris::handleToggle(GdkEventButton* const& e) {
GError* error = nullptr;
auto info = getPlayerInfo();
if (!info) return false;
if (e->type == GdkEventType::GDK_BUTTON_PRESS) {
switch (e->button) {
case 1: // left-click
if (config_["on-click"].isString()) {
return ALabel::handleToggle(e);
}
playerctl_player_play_pause(player, &error);
break;
case 2: // middle-click
if (config_["on-middle-click"].isString()) {
return ALabel::handleToggle(e);
}
playerctl_player_previous(player, &error);
break;
case 3: // right-click
if (config_["on-right-click"].isString()) {
return ALabel::handleToggle(e);
}
playerctl_player_next(player, &error);
break;
}
}
if (error) {
spdlog::error("mpris[{}]: error running builtin on-click action: {}", (*info).name,
error->message);
g_error_free(error);
return false;
}
return true;
}
auto Mpris::update() -> void {
const auto now = std::chrono::system_clock::now();
if (now - last_update_ < interval_) return;
last_update_ = now;
auto opt = getPlayerInfo();
if (!opt) {
event_box_.set_visible(false);
ALabel::update();
return;
}
auto info = *opt;
if (info.status == PLAYERCTL_PLAYBACK_STATUS_STOPPED) {
spdlog::debug("mpris[{}]: player stopped, skipping update", info.name);
return;
}
spdlog::debug("mpris[{}]: running update", info.name);
// set css class for player status
if (!lastStatus.empty() && label_.get_style_context()->has_class(lastStatus)) {
label_.get_style_context()->remove_class(lastStatus);
}
if (!label_.get_style_context()->has_class(info.status_string)) {
label_.get_style_context()->add_class(info.status_string);
}
lastStatus = info.status_string;
// set css class for player name
if (!lastPlayer.empty() && label_.get_style_context()->has_class(lastPlayer)) {
label_.get_style_context()->remove_class(lastPlayer);
}
if (!label_.get_style_context()->has_class(info.name)) {
label_.get_style_context()->add_class(info.name);
}
lastPlayer = info.name;
auto formatstr = format_;
auto tooltipstr = tooltip_;
switch (info.status) {
case PLAYERCTL_PLAYBACK_STATUS_PLAYING:
if (!format_playing_.empty()) formatstr = format_playing_;
if (!tooltip_playing_.empty()) tooltipstr = tooltip_playing_;
break;
case PLAYERCTL_PLAYBACK_STATUS_PAUSED:
if (!format_paused_.empty()) formatstr = format_paused_;
if (!tooltip_paused_.empty()) tooltipstr = tooltip_paused_;
break;
case PLAYERCTL_PLAYBACK_STATUS_STOPPED:
if (!format_stopped_.empty()) formatstr = format_stopped_;
if (!tooltip_stopped_.empty()) tooltipstr = tooltip_stopped_;
break;
}
std::string length = getLengthStr(info, truncate_hours_);
std::string tooltipLength =
(tooltip_len_limits_ || length.length() > 5) ? length : getLengthStr(info, false);
// keep position format same as length format
std::string position = getPositionStr(info, truncate_hours_ && length.length() < 6);
std::string tooltipPosition =
(tooltip_len_limits_ || position.length() > 5) ? position : getPositionStr(info, false);
try {
auto label_format = fmt::format(
fmt::runtime(formatstr),
fmt::arg("player", std::string(Glib::Markup::escape_text(info.name))),
fmt::arg("status", info.status_string),
fmt::arg("artist", std::string(Glib::Markup::escape_text(getArtistStr(info, true)))),
fmt::arg("title", std::string(Glib::Markup::escape_text(getTitleStr(info, true)))),
fmt::arg("album", std::string(Glib::Markup::escape_text(getAlbumStr(info, true)))),
fmt::arg("length", length), fmt::arg("position", position),
fmt::arg("dynamic", getDynamicStr(info, true, true)),
fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)),
fmt::arg("status_icon", getIconFromJson(config_["status-icons"], info.status_string)));
if (label_format.empty()) {
label_.hide();
} else {
label_.set_markup(label_format);
label_.show();
}
} catch (fmt::format_error const& e) {
spdlog::warn("mpris: format error: {}", e.what());
}
if (tooltipEnabled()) {
try {
auto tooltip_text = fmt::format(
fmt::runtime(tooltipstr), fmt::arg("player", info.name),
fmt::arg("status", info.status_string),
fmt::arg("artist", getArtistStr(info, tooltip_len_limits_)),
fmt::arg("title", getTitleStr(info, tooltip_len_limits_)),
fmt::arg("album", getAlbumStr(info, tooltip_len_limits_)),
fmt::arg("length", tooltipLength), fmt::arg("position", tooltipPosition),
fmt::arg("dynamic", getDynamicStr(info, tooltip_len_limits_, false)),
fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)),
fmt::arg("status_icon", getIconFromJson(config_["status-icons"], info.status_string)));
label_.set_tooltip_text(tooltip_text);
} catch (fmt::format_error const& e) {
spdlog::warn("mpris: format error (tooltip): {}", e.what());
}
}
event_box_.set_visible(true);
// call parent update
ALabel::update();
}
} // namespace waybar::modules::mpris

View File

@ -78,7 +78,7 @@ waybar::modules::Network::readBandwidthUsage() {
}
waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
: AButton(config, "network", id, DEFAULT_FORMAT, 60),
: ALabel(config, "network", id, DEFAULT_FORMAT, 60),
ifid_(-1),
family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET),
efd_(-1),
@ -87,6 +87,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
want_link_dump_(false),
want_addr_dump_(false),
dump_in_progress_(false),
is_p2p_(false),
cidr_(0),
signal_strength_dbm_(0),
signal_strength_(0),
@ -95,11 +96,11 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
#endif
frequency_(0.0) {
// Start with some "text" in the module's label_-> update() will then
// Start with some "text" in the module's label_. update() will then
// update it. Since the text should be different, update() will be able
// to show or hide the event_box_. This is to work around the case where
// the module start with no text, but the the event_box_ is shown.
label_->set_markup("<s></s>");
// the module start with no text, but the event_box_ is shown.
label_.set_markup("<s></s>");
auto bandwidth = readBandwidthUsage();
if (bandwidth.has_value()) {
@ -187,7 +188,7 @@ void waybar::modules::Network::createEventSocket() {
throw std::runtime_error("Can't create epoll");
}
{
ev_fd_ = eventfd(0, EFD_NONBLOCK);
ev_fd_ = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
struct epoll_event event;
memset(&event, 0, sizeof(event));
event.events = EPOLLIN | EPOLLET;
@ -309,8 +310,8 @@ auto waybar::modules::Network::update() -> void {
if (!alt_) {
auto state = getNetworkState();
if (!state_.empty() && button_.get_style_context()->has_class(state_)) {
button_.get_style_context()->remove_class(state_);
if (!state_.empty() && label_.get_style_context()->has_class(state_)) {
label_.get_style_context()->remove_class(state_);
}
if (config_["format-" + state].isString()) {
default_format_ = config_["format-" + state].asString();
@ -322,8 +323,8 @@ auto waybar::modules::Network::update() -> void {
if (config_["tooltip-format-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + state].asString();
}
if (!button_.get_style_context()->has_class(state)) {
button_.get_style_context()->add_class(state);
if (!label_.get_style_context()->has_class(state)) {
label_.get_style_context()->add_class(state);
}
format_ = default_format_;
state_ = state;
@ -331,7 +332,7 @@ auto waybar::modules::Network::update() -> void {
getState(signal_strength_);
auto text = fmt::format(
format_, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_),
fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_),
fmt::arg("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_),
@ -349,8 +350,8 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")),
fmt::arg("bandwidthTotalBytes",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s")));
if (text.compare(label_->get_label()) != 0) {
label_->set_markup(text);
if (text.compare(label_.get_label()) != 0) {
label_.set_markup(text);
if (text.empty()) {
event_box_.hide();
} else {
@ -363,8 +364,8 @@ auto waybar::modules::Network::update() -> void {
}
if (!tooltip_format.empty()) {
auto tooltip_text = fmt::format(
tooltip_format, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_),
fmt::arg("signalStrength", signal_strength_),
fmt::runtime(tooltip_format), fmt::arg("essid", essid_),
fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_),
fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
@ -382,16 +383,16 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")),
fmt::arg("bandwidthTotalBytes",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s")));
if (button_.get_tooltip_text() != tooltip_text) {
button_.set_tooltip_markup(tooltip_text);
if (label_.get_tooltip_text() != tooltip_text) {
label_.set_tooltip_markup(tooltip_text);
}
} else if (button_.get_tooltip_text() != text) {
button_.set_tooltip_markup(text);
} else if (label_.get_tooltip_text() != text) {
label_.set_tooltip_markup(text);
}
}
// Call parent update
AButton::update();
ALabel::update();
}
bool waybar::modules::Network::checkInterface(std::string name) {
@ -456,6 +457,8 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
case IFLA_IFNAME:
ifname = static_cast<const char *>(RTA_DATA(ifla));
ifname_len = RTA_PAYLOAD(ifla) - 1; // minus \0
if (ifi->ifi_flags & IFF_POINTOPOINT && net->checkInterface(ifname))
net->is_p2p_ = true;
break;
case IFLA_CARRIER: {
carrier = *(char *)RTA_DATA(ifla) == 1;
@ -494,6 +497,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
net->ifname_ = new_ifname;
net->ifid_ = ifi->ifi_index;
if (ifi->ifi_flags & IFF_POINTOPOINT) net->is_p2p_ = true;
if (carrier.has_value()) {
net->carrier_ = carrier.value();
}
@ -537,7 +541,9 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) {
switch (ifa_rta->rta_type) {
case IFA_ADDRESS: {
case IFA_ADDRESS:
if (net->is_p2p_) continue;
case IFA_LOCAL:
char ipaddr[INET6_ADDRSTRLEN];
if (!is_del_event) {
net->ipaddr_ = inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
@ -570,7 +576,6 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
}
net->dp.emit();
break;
}
}
}
break;

View File

@ -1,7 +1,7 @@
#include "modules/pulseaudio.hpp"
waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value &config)
: AButton(config, "pulseaudio", id, "{volume}%"),
: ALabel(config, "pulseaudio", id, "{volume}%"),
mainloop_(nullptr),
mainloop_api_(nullptr),
context_(nullptr),
@ -81,13 +81,6 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
if (dir == SCROLL_DIR::NONE) {
return true;
}
if (config_["reverse-scrolling"].asInt() == 1) {
if (dir == SCROLL_DIR::UP) {
dir = SCROLL_DIR::DOWN;
} else if (dir == SCROLL_DIR::DOWN) {
dir = SCROLL_DIR::UP;
}
}
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_volume_t change = volume_tick;
pa_cvolume pa_volume = pa_volume_;
@ -260,11 +253,12 @@ auto waybar::modules::Pulseaudio::update() -> void {
if (!alt_) {
std::string format_name = "format";
if (monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio
monitor_.find("a2dp-sink") != std::string::npos) { // PipeWire
monitor_.find("a2dp-sink") != std::string::npos || // PipeWire
monitor_.find("bluez") != std::string::npos) {
format_name = format_name + "-bluetooth";
button_.get_style_context()->add_class("bluetooth");
label_.get_style_context()->add_class("bluetooth");
} else {
button_.get_style_context()->remove_class("bluetooth");
label_.get_style_context()->remove_class("bluetooth");
}
if (muted_) {
// Check muted bluetooth format exist, otherwise fallback to default muted format
@ -272,49 +266,59 @@ auto waybar::modules::Pulseaudio::update() -> void {
format_name = "format";
}
format_name = format_name + "-muted";
button_.get_style_context()->add_class("muted");
button_.get_style_context()->add_class("sink-muted");
label_.get_style_context()->add_class("muted");
label_.get_style_context()->add_class("sink-muted");
} else {
button_.get_style_context()->remove_class("muted");
button_.get_style_context()->remove_class("sink-muted");
label_.get_style_context()->remove_class("muted");
label_.get_style_context()->remove_class("sink-muted");
}
auto state = getState(volume_, true);
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();
}
format = config_[format_name].isString() ? config_[format_name].asString() : format;
}
// TODO: find a better way to split source/sink
std::string format_source = "{volume}%";
if (source_muted_) {
button_.get_style_context()->add_class("source-muted");
label_.get_style_context()->add_class("source-muted");
if (config_["format-source-muted"].isString()) {
format_source = config_["format-source-muted"].asString();
}
} else {
button_.get_style_context()->remove_class("source-muted");
label_.get_style_context()->remove_class("source-muted");
if (config_["format-source-muted"].isString()) {
format_source = config_["format-source"].asString();
}
}
format_source = fmt::format(format_source, fmt::arg("volume", source_volume_));
label_->set_markup(fmt::format(
format, fmt::arg("desc", desc_), fmt::arg("volume", volume_),
format_source = fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_volume_));
auto text = fmt::format(
fmt::runtime(format), fmt::arg("desc", desc_), fmt::arg("volume", volume_),
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
getState(volume_);
fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())));
if (text.empty()) {
label_.hide();
} else {
label_.set_markup(text);
label_.show();
}
if (tooltipEnabled()) {
if (tooltip_format.empty() && config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
button_.set_tooltip_text(fmt::format(
tooltip_format, fmt::arg("desc", desc_), fmt::arg("volume", volume_),
label_.set_tooltip_text(fmt::format(
fmt::runtime(tooltip_format), fmt::arg("desc", desc_), fmt::arg("volume", volume_),
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_),
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
} else {
button_.set_tooltip_text(desc_);
label_.set_tooltip_text(desc_);
}
}
// Call parent update
AButton::update();
ALabel::update();
}

View File

@ -0,0 +1,174 @@
#include "modules/river/layout.hpp"
#include <spdlog/spdlog.h>
#include <wayland-client.h>
#include "client.hpp"
namespace waybar::modules::river {
static void listen_focused_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
uint32_t tags) {
// Intentionally empty
}
static void listen_view_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
struct wl_array *tags) {
// Intentionally empty
}
static void listen_urgent_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
uint32_t tags) {
// Intentionally empty
}
static void listen_layout_name(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
const char *layout) {
static_cast<Layout *>(data)->handle_name(layout);
}
static void listen_layout_name_clear(void *data,
struct zriver_output_status_v1 *zriver_output_status_v1) {
static_cast<Layout *>(data)->handle_clear();
}
static void listen_focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
struct wl_output *output) {
static_cast<Layout *>(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<Layout *>(data)->handle_unfocused_output(output);
}
static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
const char *title) {
// Intentionally empty
}
static void listen_mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
const char *mode) {
// Intentionally empty
}
static const zriver_output_status_v1_listener output_status_listener_impl{
.focused_tags = listen_focused_tags,
.view_tags = listen_view_tags,
.urgent_tags = listen_urgent_tags,
.layout_name = listen_layout_name,
.layout_name_clear = listen_layout_name_clear,
};
static const zriver_seat_status_v1_listener seat_status_listener_impl{
.focused_output = listen_focused_output,
.unfocused_output = listen_unfocused_output,
.focused_view = listen_focused_view,
.mode = listen_mode,
};
static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) {
if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {
version = std::min<uint32_t>(version, 4);
// implies ZRIVER_OUTPUT_STATUS_V1_LAYOUT_NAME_CLEAR_SINCE_VERSION
if (version < ZRIVER_OUTPUT_STATUS_V1_LAYOUT_NAME_SINCE_VERSION) {
spdlog::error(
"river server does not support the \"layout_name\" and \"layout_clear\" events; the "
"module will be disabled" +
std::to_string(version));
return;
}
static_cast<Layout *>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1 *>(
wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version));
}
if (std::strcmp(interface, wl_seat_interface.name) == 0) {
version = std::min<uint32_t>(version, 1);
static_cast<Layout *>(data)->seat_ = static_cast<struct wl_seat *>(
wl_registry_bind(registry, name, &wl_seat_interface, version));
}
}
static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {
// Nobody cares
}
static const wl_registry_listener registry_listener_impl = {.global = handle_global,
.global_remove = handle_global_remove};
Layout::Layout(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
: waybar::ALabel(config, "layout", id, "{}"),
status_manager_{nullptr},
seat_{nullptr},
bar_(bar),
output_status_{nullptr} {
struct wl_display *display = Client::inst()->wl_display;
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener_impl, this);
wl_display_roundtrip(display);
output_ = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
if (!status_manager_) {
spdlog::error("river_status_manager_v1 not advertised");
return;
}
if (!seat_) {
spdlog::error("wl_seat not advertised");
}
label_.hide();
ALabel::update();
seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_);
zriver_seat_status_v1_add_listener(seat_status_, &seat_status_listener_impl, this);
output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output_);
zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this);
zriver_status_manager_v1_destroy(status_manager_);
}
Layout::~Layout() {
if (output_status_) {
zriver_output_status_v1_destroy(output_status_);
}
if (seat_status_) {
zriver_seat_status_v1_destroy(seat_status_);
}
}
void Layout::handle_name(const char *name) {
if (std::strcmp(name, "") == 0 || format_.empty()) {
label_.hide(); // hide empty labels or labels with empty format
} else {
label_.show();
label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(name).raw()));
}
ALabel::update();
}
void Layout::handle_clear() {
label_.hide();
ALabel::update();
}
void Layout::handle_focused_output(struct wl_output *output) {
if (output_ == output) { // if we focused the output this bar belongs to
label_.get_style_context()->add_class("focused");
ALabel::update();
}
focused_output_ = output;
}
void Layout::handle_unfocused_output(struct wl_output *output) {
if (output_ == output) { // if we unfocused the output this bar belongs to
label_.get_style_context()->remove_class("focused");
ALabel::update();
}
}
} /* namespace waybar::modules::river */

View File

@ -103,7 +103,7 @@ void Mode::handle_mode(const char *mode) {
}
label_.get_style_context()->add_class(mode);
label_.set_markup(fmt::format(format_, Glib::Markup::escape_text(mode).raw()));
label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(mode).raw()));
label_.show();
}

View File

@ -53,7 +53,7 @@ static const zriver_command_callback_v1_listener command_callback_listener_impl{
static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) {
if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {
version = std::min<uint32_t>(version, 2);
version = std::min(version, 2u);
if (version < ZRIVER_OUTPUT_STATUS_V1_URGENT_TAGS_SINCE_VERSION) {
spdlog::warn("river server does not support urgent tags");
}
@ -62,13 +62,13 @@ static void handle_global(void *data, struct wl_registry *registry, uint32_t nam
}
if (std::strcmp(interface, zriver_control_v1_interface.name) == 0) {
version = std::min<uint32_t>(version, 1);
version = std::min(version, 1u);
static_cast<Tags *>(data)->control_ = static_cast<struct zriver_control_v1 *>(
wl_registry_bind(registry, name, &zriver_control_v1_interface, version));
}
if (std::strcmp(interface, wl_seat_interface.name) == 0) {
version = std::min<uint32_t>(version, 1);
version = std::min(version, 1u);
static_cast<Tags *>(data)->seat_ = static_cast<struct wl_seat *>(
wl_registry_bind(registry, name, &wl_seat_interface, version));
}
@ -114,33 +114,39 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
event_box_.add(box_);
// Default to 9 tags, cap at 32
const uint32_t num_tags =
config["num-tags"].isUInt() ? std::min<uint32_t>(32, config_["num-tags"].asUInt()) : 9;
const int num_tags =
config["num-tags"].isUInt() ? std::min<int>(32, config_["num-tags"].asUInt()) : 9;
std::vector<std::string> tag_labels(num_tags);
for (uint32_t tag = 0; tag < num_tags; ++tag) {
tag_labels[tag] = std::to_string(tag + 1);
}
const Json::Value custom_labels = config["tag-labels"];
if (custom_labels.isArray() && !custom_labels.empty()) {
for (uint32_t tag = 0; tag < std::min(num_tags, custom_labels.size()); ++tag) {
tag_labels[tag] = custom_labels[tag].asString();
const auto tag_labels = config["tag-labels"];
const auto set_tags = config["set-tags"];
const auto toggle_tags = config["toggle-tags"];
for (int tag = 0; tag < num_tags; ++tag) {
if (tag_labels.isArray() && !tag_labels.empty()) {
buttons_.emplace_back(tag_labels[tag].asString());
} else {
// default name is the tag value
buttons_.emplace_back(std::to_string(tag + 1));
}
}
uint32_t i = 1;
for (const auto &tag_label : tag_labels) {
Gtk::Button &button = buttons_.emplace_back(tag_label);
auto &button = buttons_[tag];
button.set_relief(Gtk::RELIEF_NONE);
box_.pack_start(button, false, false, 0);
if (!config_["disable-click"].asBool()) {
button.signal_clicked().connect(
sigc::bind(sigc::mem_fun(*this, &Tags::handle_primary_clicked), i));
button.signal_button_press_event().connect(
sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), i));
if (set_tags.isArray() && !set_tags.empty())
button.signal_clicked().connect(sigc::bind(
sigc::mem_fun(*this, &Tags::handle_primary_clicked), set_tags[tag].asUInt()));
else
button.signal_clicked().connect(
sigc::bind(sigc::mem_fun(*this, &Tags::handle_primary_clicked), (1 << tag)));
if (toggle_tags.isArray() && !toggle_tags.empty())
button.signal_button_press_event().connect(sigc::bind(
sigc::mem_fun(*this, &Tags::handle_button_press), toggle_tags[tag].asUInt()));
else
button.signal_button_press_event().connect(
sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), (1 << tag)));
}
button.show();
i <<= 1;
}
struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
@ -182,45 +188,38 @@ bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) {
}
void Tags::handle_focused_tags(uint32_t tags) {
uint32_t i = 0;
for (auto &button : buttons_) {
for (size_t i = 0; i < buttons_.size(); ++i) {
if ((1 << i) & tags) {
button.get_style_context()->add_class("focused");
buttons_[i].get_style_context()->add_class("focused");
} else {
button.get_style_context()->remove_class("focused");
buttons_[i].get_style_context()->remove_class("focused");
}
++i;
}
}
void Tags::handle_view_tags(struct wl_array *view_tags) {
// First clear all occupied state
for (auto &button : buttons_) {
button.get_style_context()->remove_class("occupied");
uint32_t tags = 0;
auto view_tag = reinterpret_cast<uint32_t *>(view_tags->data);
auto end = view_tag + (view_tags->size / sizeof(uint32_t));
for (; view_tag < end; ++view_tag) {
tags |= *view_tag;
}
// Set tags with a view to occupied
uint32_t *start = static_cast<uint32_t *>(view_tags->data);
for (uint32_t *tags = start; tags < start + view_tags->size / sizeof(uint32_t); ++tags) {
uint32_t i = 0;
for (auto &button : buttons_) {
if (*tags & (1 << i)) {
button.get_style_context()->add_class("occupied");
}
++i;
for (size_t i = 0; i < buttons_.size(); ++i) {
if ((1 << i) & tags) {
buttons_[i].get_style_context()->add_class("occupied");
} else {
buttons_[i].get_style_context()->remove_class("occupied");
}
}
}
void Tags::handle_urgent_tags(uint32_t tags) {
uint32_t i = 0;
for (auto &button : buttons_) {
for (size_t i = 0; i < buttons_.size(); ++i) {
if ((1 << i) & tags) {
button.get_style_context()->add_class("urgent");
buttons_[i].get_style_context()->add_class("urgent");
} else {
button.get_style_context()->remove_class("urgent");
buttons_[i].get_style_context()->remove_class("urgent");
}
++i;
}
}

View File

@ -106,7 +106,11 @@ void Window::handle_focused_view(const char *title) {
label_.hide(); // hide empty labels or labels with empty format
} else {
label_.show();
label_.set_markup(fmt::format(format_, Glib::Markup::escape_text(title).raw()));
auto text = fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(title).raw());
label_.set_markup(text);
if (tooltipEnabled()) {
label_.set_tooltip_markup(text);
}
}
ALabel::update();

View File

@ -3,7 +3,7 @@
#include <time.h>
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: AButton(config, "clock", id, "{:%H:%M}", 60) {
: ALabel(config, "clock", id, "{:%H:%M}", 60) {
thread_ = [this] {
dp.emit();
auto now = std::chrono::system_clock::now();
@ -19,17 +19,17 @@ auto waybar::modules::Clock::update() -> void {
auto now = std::chrono::system_clock::now();
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
auto text = fmt::format(format_, localtime);
label_->set_markup(text);
label_.set_markup(text);
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
auto tooltip_text = fmt::format(tooltip_format, localtime);
button_.set_tooltip_text(tooltip_text);
label_.set_tooltip_text(tooltip_text);
} else {
button_.set_tooltip_text(text);
label_.set_tooltip_text(text);
}
}
// Call parent update
AButton::update();
ALabel::update();
}

View File

@ -41,7 +41,7 @@ auto Sndio::connect_to_sndio() -> void {
}
Sndio::Sndio(const std::string &id, const Json::Value &config)
: AButton(config, "sndio", id, "{volume}%", 1, false, true),
: ALabel(config, "sndio", id, "{volume}%", 1, false, true),
hdl_(nullptr),
pfds_(0),
addr_(0),
@ -105,14 +105,21 @@ auto Sndio::update() -> void {
unsigned int vol = 100. * static_cast<double>(volume_) / static_cast<double>(maxval_);
if (volume_ == 0) {
button_.get_style_context()->add_class("muted");
label_.get_style_context()->add_class("muted");
} else {
button_.get_style_context()->remove_class("muted");
label_.get_style_context()->remove_class("muted");
}
label_->set_markup(fmt::format(format, fmt::arg("volume", vol), fmt::arg("raw_value", volume_)));
auto text =
fmt::format(fmt::runtime(format), fmt::arg("volume", vol), fmt::arg("raw_value", volume_));
if (text.empty()) {
label_.hide();
} else {
label_.set_markup(text);
label_.show();
}
AButton::update();
ALabel::update();
}
auto Sndio::set_desc(struct sioctl_desc *d, unsigned int val) -> void {

View File

@ -22,10 +22,6 @@ Host::~Host() {
Gio::DBus::unwatch_name(bus_name_id_);
bus_name_id_ = 0;
}
if (watcher_id_ > 0) {
Gio::DBus::unwatch_name(watcher_id_);
watcher_id_ = 0;
}
g_cancellable_cancel(cancellable_);
g_clear_object(&cancellable_);
g_clear_object(&watcher_);

View File

@ -9,6 +9,7 @@
#include <map>
#include "util/format.hpp"
#include "util/gtk_icon.hpp"
template <>
struct fmt::formatter<Glib::VariantBase> : formatter<std::string> {
@ -379,10 +380,8 @@ Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int reque
return icon_theme->load_icon(name.c_str(), tmp_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
}
Glib::RefPtr<Gtk::IconTheme> default_theme = Gtk::IconTheme::get_default();
default_theme->rescan_if_needed();
return default_theme->load_icon(name.c_str(), tmp_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
return DefaultGtkIconThemeWrapper::load_icon(name.c_str(), tmp_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
}
double Item::getScaledIconSize() {

View File

@ -10,9 +10,6 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
watcher_(SNI::Watcher::getInstance()),
host_(nb_hosts_, config, bar, std::bind(&Tray::onAdd, this, std::placeholders::_1),
std::bind(&Tray::onRemove, this, std::placeholders::_1)) {
spdlog::warn(
"For a functional tray you must have libappindicator-* installed and export "
"XDG_CURRENT_DESKTOP=Unity");
box_.set_name("tray");
event_box_.add(box_);
if (!id.empty()) {

View File

@ -14,11 +14,6 @@ Watcher::Watcher()
watcher_(sn_watcher_skeleton_new()) {}
Watcher::~Watcher() {
if (hosts_ != nullptr) {
g_slist_free_full(hosts_, gfWatchFree);
hosts_ = nullptr;
}
if (items_ != nullptr) {
g_slist_free_full(items_, gfWatchFree);
items_ = nullptr;

View File

@ -2,6 +2,8 @@
#include <fcntl.h>
#include <stdexcept>
namespace waybar::modules::sway {
Ipc::Ipc() {

View File

@ -18,7 +18,7 @@ const std::string Language::XKB_LAYOUT_NAMES_KEY = "xkb_layout_names";
const std::string Language::XKB_ACTIVE_LAYOUT_NAME_KEY = "xkb_active_layout_name";
Language::Language(const std::string& id, const Json::Value& config)
: AButton(config, "language", id, "{}", 0, true) {
: ALabel(config, "language", id, "{}", 0, true) {
is_variant_displayed = format_.find("{variant}") != std::string::npos;
if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) {
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortName);
@ -96,33 +96,33 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
auto Language::update() -> void {
std::lock_guard<std::mutex> lock(mutex_);
auto display_layout = trim(fmt::format(
format_, fmt::arg("short", layout_.short_name),
fmt::runtime(format_), fmt::arg("short", layout_.short_name),
fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name),
fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag())));
label_->set_markup(display_layout);
label_.set_markup(display_layout);
if (tooltipEnabled()) {
if (tooltip_format_ != "") {
auto tooltip_display_layout = trim(
fmt::format(tooltip_format_, fmt::arg("short", layout_.short_name),
fmt::format(fmt::runtime(tooltip_format_), fmt::arg("short", layout_.short_name),
fmt::arg("shortDescription", layout_.short_description),
fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant),
fmt::arg("flag", layout_.country_flag())));
button_.set_tooltip_markup(tooltip_display_layout);
label_.set_tooltip_markup(tooltip_display_layout);
} else {
button_.set_tooltip_markup(display_layout);
label_.set_tooltip_markup(display_layout);
}
}
event_box_.show();
// Call parent update
AButton::update();
ALabel::update();
}
auto Language::set_current_layout(std::string current_layout) -> void {
button_.get_style_context()->remove_class(layout_.short_name);
label_.get_style_context()->remove_class(layout_.short_name);
layout_ = layouts_map_[current_layout];
button_.get_style_context()->add_class(layout_.short_name);
label_.get_style_context()->add_class(layout_.short_name);
}
auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> void {

View File

@ -5,7 +5,7 @@
namespace waybar::modules::sway {
Mode::Mode(const std::string& id, const Json::Value& config)
: AButton(config, "mode", id, "{}", 0, true) {
: ALabel(config, "mode", id, "{}", 0, true) {
ipc_.subscribe(R"(["mode"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &Mode::onEvent));
// Launch worker
@ -42,14 +42,14 @@ auto Mode::update() -> void {
if (mode_.empty()) {
event_box_.hide();
} else {
label_->set_markup(fmt::format(format_, mode_));
label_.set_markup(fmt::format(fmt::runtime(format_), mode_));
if (tooltipEnabled()) {
button_.set_tooltip_text(mode_);
label_.set_tooltip_text(mode_);
}
event_box_.show();
}
// Call parent update
AButton::update();
ALabel::update();
}
} // namespace waybar::modules::sway

View File

@ -32,7 +32,8 @@ auto Scratchpad::update() -> void {
if (count_ || show_empty_) {
event_box_.show();
label_.set_markup(
fmt::format(format_, fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())),
fmt::format(fmt::runtime(format_),
fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())),
fmt::arg("count", count_)));
if (tooltip_enabled_) {
label_.set_tooltip_markup(tooltip_text_);
@ -64,7 +65,7 @@ auto Scratchpad::onCmd(const struct Ipc::ipc_response& res) -> void {
if (tooltip_enabled_) {
tooltip_text_.clear();
for (const auto& window : tree["nodes"][0]["nodes"][0]["floating_nodes"]) {
tooltip_text_.append(fmt::format(tooltip_format_ + '\n',
tooltip_text_.append(fmt::format(fmt::runtime(tooltip_format_ + '\n'),
fmt::arg("app", window["app_id"].asString()),
fmt::arg("title", window["name"].asString())));
}

View File

@ -5,19 +5,19 @@
#include <glibmm/keyfile.h>
#include <glibmm/miscutils.h>
#include <gtkmm/enums.h>
#include <gtkmm/icontheme.h>
#include <spdlog/spdlog.h>
#include <filesystem>
#include <regex>
#include <string>
#include "util/rewrite_title.hpp"
#include "util/gtk_icon.hpp"
#include "util/rewrite_string.hpp"
namespace waybar::modules::sway {
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: AIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), windowId_(-1) {
: AIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) {
// Icon size
if (config_["icon-size"].isUInt()) {
app_icon_size_ = config["icon-size"].asUInt();
@ -35,6 +35,7 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
ipc_.handleEvent();
} catch (const std::exception& e) {
spdlog::error("Window: {}", e.what());
spdlog::trace("Window::Window exception");
}
});
}
@ -46,12 +47,13 @@ void Window::onCmd(const struct Ipc::ipc_response& res) {
std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload);
auto output = payload["output"].isString() ? payload["output"].asString() : "";
std::tie(app_nb_, windowId_, window_, app_id_, app_class_, shell_) =
std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_) =
getFocusedNode(payload["nodes"], output);
updateAppIconName();
dp.emit();
} catch (const std::exception& e) {
spdlog::error("Window: {}", e.what());
spdlog::trace("Window::onCmd exception");
}
}
@ -79,13 +81,12 @@ std::optional<Glib::ustring> getIconName(const std::string& app_id, const std::s
if (!desktop_file_path.has_value()) {
// Try some heuristics to find a matching icon
const auto default_icon_theme = Gtk::IconTheme::get_default();
if (default_icon_theme->has_icon(app_id)) {
if (DefaultGtkIconThemeWrapper::has_icon(app_id)) {
return app_id;
}
const auto app_id_desktop = app_id + "-desktop";
if (default_icon_theme->has_icon(app_id_desktop)) {
if (DefaultGtkIconThemeWrapper::has_icon(app_id_desktop)) {
return app_id_desktop;
}
@ -99,7 +100,7 @@ std::optional<Glib::ustring> getIconName(const std::string& app_id, const std::s
const auto first_space = app_id.find_first_of(' ');
if (first_space != std::string::npos) {
const auto first_word = to_lower(app_id.substr(0, first_space));
if (default_icon_theme->has_icon(first_word)) {
if (DefaultGtkIconThemeWrapper::has_icon(first_word)) {
return first_word;
}
}
@ -107,7 +108,7 @@ std::optional<Glib::ustring> getIconName(const std::string& app_id, const std::s
const auto first_dash = app_id.find_first_of('-');
if (first_dash != std::string::npos) {
const auto first_word = to_lower(app_id.substr(0, first_dash));
if (default_icon_theme->has_icon(first_word)) {
if (DefaultGtkIconThemeWrapper::has_icon(first_word)) {
return first_word;
}
}
@ -156,30 +157,52 @@ void Window::updateAppIcon() {
}
auto Window::update() -> void {
if (!old_app_id_.empty()) {
bar_.window.get_style_context()->remove_class(old_app_id_);
}
spdlog::trace("workspace layout {}, tiled count {}, floating count {}", layout_, app_nb_,
floating_count_);
int mode = 0;
if (app_nb_ == 0) {
bar_.window.get_style_context()->remove_class("solo");
if (!bar_.window.get_style_context()->has_class("empty")) {
bar_.window.get_style_context()->add_class("empty");
if (floating_count_ == 0) {
mode += 1;
} else {
mode += 4;
}
} else if (app_nb_ == 1) {
bar_.window.get_style_context()->remove_class("empty");
if (!bar_.window.get_style_context()->has_class("solo")) {
bar_.window.get_style_context()->add_class("solo");
}
if (!app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) {
bar_.window.get_style_context()->add_class(app_id_);
old_app_id_ = app_id_;
}
mode += 2;
} else {
bar_.window.get_style_context()->remove_class("solo");
bar_.window.get_style_context()->remove_class("empty");
if (layout_ == "tabbed") {
mode += 8;
} else if (layout_ == "stacked") {
mode += 16;
} else {
mode += 32;
}
}
label_.set_markup(fmt::format(
format_, fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])),
fmt::arg("app_id", app_id_), fmt::arg("shell", shell_)));
if (!old_app_id_.empty() && ((mode & 2) == 0 || old_app_id_ != app_id_) &&
bar_.window.get_style_context()->has_class(old_app_id_)) {
spdlog::trace("Removing app_id class: {}", old_app_id_);
bar_.window.get_style_context()->remove_class(old_app_id_);
old_app_id_ = "";
}
setClass("empty", ((mode & 1) > 0));
setClass("solo", ((mode & 2) > 0));
setClass("floating", ((mode & 4) > 0));
setClass("tabbed", ((mode & 8) > 0));
setClass("stacked", ((mode & 16) > 0));
setClass("tiled", ((mode & 32) > 0));
if ((mode & 2) > 0 && !app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) {
spdlog::trace("Adding app_id class: {}", app_id_);
bar_.window.get_style_context()->add_class(app_id_);
old_app_id_ = app_id_;
}
label_.set_markup(waybar::util::rewriteString(
fmt::format(fmt::runtime(format_), fmt::arg("title", window_), fmt::arg("app_id", app_id_),
fmt::arg("shell", shell_)),
config_["rewrite"]));
if (tooltipEnabled()) {
label_.set_tooltip_text(window_);
}
@ -190,71 +213,143 @@ auto Window::update() -> void {
AIconLabel::update();
}
int leafNodesInWorkspace(const Json::Value& node) {
void Window::setClass(std::string classname, bool enable) {
if (enable) {
if (!bar_.window.get_style_context()->has_class(classname)) {
bar_.window.get_style_context()->add_class(classname);
}
} else {
bar_.window.get_style_context()->remove_class(classname);
}
}
std::pair<int, int> leafNodesInWorkspace(const Json::Value& node) {
auto const& nodes = node["nodes"];
auto const& floating_nodes = node["floating_nodes"];
if (nodes.empty() && floating_nodes.empty()) {
if (node["type"] == "workspace")
return 0;
else
return 1;
if (node["type"].asString() == "workspace")
return {0, 0};
else if (node["type"].asString() == "floating_con") {
return {0, 1};
} else {
return {1, 0};
}
}
int sum = 0;
if (!nodes.empty()) {
for (auto const& node : nodes) sum += leafNodesInWorkspace(node);
}
if (!floating_nodes.empty()) {
for (auto const& node : floating_nodes) sum += leafNodesInWorkspace(node);
}
return sum;
}
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string> gfnWithWorkspace(
const Json::Value& nodes, std::string& output, const Json::Value& config_, const Bar& bar_,
Json::Value& parentWorkspace) {
int floating_sum = 0;
for (auto const& node : nodes) {
if (node["output"].isString()) {
output = node["output"].asString();
}
// found node
if (node["focused"].asBool() && (node["type"] == "con" || node["type"] == "floating_con")) {
if ((!config_["all-outputs"].asBool() && output == bar_.output->name) ||
config_["all-outputs"].asBool()) {
auto app_id = node["app_id"].isString() ? node["app_id"].asString()
: node["window_properties"]["instance"].asString();
const auto app_class = node["window_properties"]["class"].isString()
? node["window_properties"]["class"].asString()
: "";
const auto shell = node["shell"].isString() ? node["shell"].asString() : "";
int nb = node.size();
if (parentWorkspace != 0) nb = leafNodesInWorkspace(parentWorkspace);
return {nb, node["id"].asInt(), Glib::Markup::escape_text(node["name"].asString()),
app_id, app_class, shell};
}
}
// iterate
if (node["type"] == "workspace") parentWorkspace = node;
auto [nb, id, name, app_id, app_class, shell] =
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace);
if (id > -1 && !name.empty()) {
return {nb, id, name, app_id, app_class, shell};
}
// Search for floating node
std::tie(nb, id, name, app_id, app_class, shell) =
gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace);
if (id > -1 && !name.empty()) {
return {nb, id, name, app_id, app_class, shell};
}
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
sum += all_leaf_nodes.first;
floating_sum += all_leaf_nodes.second;
}
return {0, -1, "", "", "", ""};
for (auto const& node : floating_nodes) {
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
sum += all_leaf_nodes.first;
floating_sum += all_leaf_nodes.second;
}
return {sum, floating_sum};
}
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string>
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Value& config_,
const Bar& bar_, Json::Value& parentWorkspace,
const Json::Value& immediateParent) {
for (auto const& node : nodes) {
if (node["type"].asString() == "output") {
if ((!config_["all-outputs"].asBool() || config_["offscreen-css"].asBool()) &&
(node["name"].asString() != bar_.output->name)) {
continue;
}
output = node["name"].asString();
} else if (node["type"].asString() == "workspace") {
// needs to be a string comparison, because filterWorkspace is the current_workspace
if (node["name"].asString() != immediateParent["current_workspace"].asString()) {
continue;
}
if (node["focused"].asBool()) {
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
return {all_leaf_nodes.first,
all_leaf_nodes.second,
node["id"].asInt(),
(((all_leaf_nodes.first > 0) || (all_leaf_nodes.second > 0)) &&
(config_["show-focused-workspace-name"].asBool()))
? node["name"].asString()
: "",
"",
"",
"",
node["layout"].asString()};
}
parentWorkspace = node;
} else if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") &&
(node["focused"].asBool())) {
// found node
spdlog::trace("actual output {}, output found {}, node (focused) found {}", bar_.output->name,
output, node["name"].asString());
auto app_id = node["app_id"].isString() ? node["app_id"].asString()
: node["window_properties"]["instance"].asString();
const auto app_class = node["window_properties"]["class"].isString()
? node["window_properties"]["class"].asString()
: "";
const auto shell = node["shell"].isString() ? node["shell"].asString() : "";
int nb = node.size();
int floating_count = 0;
std::string workspace_layout = "";
if (!parentWorkspace.isNull()) {
std::pair all_leaf_nodes = leafNodesInWorkspace(parentWorkspace);
nb = all_leaf_nodes.first;
floating_count = all_leaf_nodes.second;
workspace_layout = parentWorkspace["layout"].asString();
}
return {nb,
floating_count,
node["id"].asInt(),
Glib::Markup::escape_text(node["name"].asString()),
app_id,
app_class,
shell,
workspace_layout};
}
// iterate
auto [nb, f, id, name, app_id, app_class, shell, workspace_layout] =
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace, node);
auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2] =
gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace, node);
// if ((id > 0 || ((id2 < 0 || name2.empty()) && id > -1)) && !name.empty()) {
if ((id > 0) || (id2 < 0 && id > -1)) {
return {nb, f, id, name, app_id, app_class, shell, workspace_layout};
} else if (id2 > 0 && !name2.empty()) {
return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2};
}
}
// this only comes into effect when no focused children are present
if (config_["all-outputs"].asBool() && config_["offscreen-css"].asBool() &&
immediateParent["type"].asString() == "workspace") {
std::pair all_leaf_nodes = leafNodesInWorkspace(immediateParent);
// using an empty string as default ensures that no window depending styles are set due to the
// checks above for !name.empty()
return {all_leaf_nodes.first,
all_leaf_nodes.second,
0,
(all_leaf_nodes.first > 0 || all_leaf_nodes.second > 0)
? config_["offscreen-css-text"].asString()
: "",
"",
"",
"",
immediateParent["layout"].asString()};
}
return {0, 0, -1, "", "", "", "", ""};
}
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
Window::getFocusedNode(const Json::Value& nodes, std::string& output) {
Json::Value placeholder = 0;
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder);
Json::Value placeholder = Json::Value::null;
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder);
}
void Window::getTree() {
@ -262,6 +357,7 @@ void Window::getTree() {
ipc_.sendCmd(IPC_GET_TREE);
} catch (const std::exception& e) {
spdlog::error("Window: {}", e.what());
spdlog::trace("Window::getTree exception");
}
}

View File

@ -130,6 +130,10 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
// In a first pass, the maximum "num" value is computed to enqueue
// unnumbered workspaces behind numbered ones when computing the sort
// attribute.
//
// Note: if the 'alphabetical_sort' option is true, the user is in
// agreement that the "workspace prev/next" commands may not follow
// the order displayed in Waybar.
int max_num = -1;
for (auto &workspace : workspaces_) {
max_num = std::max(workspace["num"].asInt(), max_num);
@ -143,16 +147,19 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
}
}
std::sort(workspaces_.begin(), workspaces_.end(),
[](const Json::Value &lhs, const Json::Value &rhs) {
[this](const Json::Value &lhs, const Json::Value &rhs) {
auto lname = lhs["name"].asString();
auto rname = rhs["name"].asString();
int l = lhs["sort"].asInt();
int r = rhs["sort"].asInt();
if (l == r) {
if (l == r || config_["alphabetical_sort"].asBool()) {
// In case both integers are the same, lexicographical
// sort. The code above already ensure that this will only
// happend in case of explicitly numbered workspaces.
// happened in case of explicitly numbered workspaces.
//
// Additionally, if the config specifies to sort workspaces
// alphabetically do this here.
return lname < rname;
}
@ -226,9 +233,10 @@ auto Workspaces::update() -> void {
std::string output = (*it)["name"].asString();
if (config_["format"].isString()) {
auto format = config_["format"].asString();
output = fmt::format(format, fmt::arg("icon", getIcon(output, *it)),
output = fmt::format(fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)),
fmt::arg("value", output), fmt::arg("name", trimWorkspaceName(output)),
fmt::arg("index", (*it)["num"].asString()));
fmt::arg("index", (*it)["num"].asString()),
fmt::arg("output", (*it)["output"].asString()));
}
if (!config_["disable-markup"].asBool()) {
static_cast<Gtk::Label *>(button.get_children()[0])->set_markup(output);
@ -252,11 +260,9 @@ Gtk::Button &Workspaces::addButton(const Json::Value &node) {
try {
if (node["target_output"].isString()) {
ipc_.sendCmd(IPC_COMMAND,
fmt::format(workspace_switch_cmd_ + "; move workspace to output \"{}\"; " +
workspace_switch_cmd_,
"--no-auto-back-and-forth", node["name"].asString(),
node["target_output"].asString(), "--no-auto-back-and-forth",
node["name"].asString()));
fmt::format(persistent_workspace_switch_cmd_, "--no-auto-back-and-forth",
node["name"].asString(), node["target_output"].asString(),
"--no-auto-back-and-forth", node["name"].asString()));
} else {
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace {} \"{}\"",
config_["disable-auto-back-and-forth"].asBool()
@ -273,7 +279,7 @@ Gtk::Button &Workspaces::addButton(const Json::Value &node) {
}
std::string Workspaces::getIcon(const std::string &name, const Json::Value &node) {
std::vector<std::string> keys = {name, "urgent", "focused", "visible", "default"};
std::vector<std::string> keys = {"urgent", "focused", name, "visible", "default"};
for (auto const &key : keys) {
if (key == "focused" || key == "visible" || key == "urgent") {
if (config_["format-icons"][key].isString() && node[key].asBool()) {
@ -321,11 +327,17 @@ bool Workspaces::handleScroll(GdkEventScroll *e) {
return true;
}
}
if (!config_["warp-on-scroll"].asBool()) {
ipc_.sendCmd(IPC_COMMAND, fmt::format("mouse_warping none"));
}
try {
ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, "--no-auto-back-and-forth", name));
} catch (const std::exception &e) {
spdlog::error("Workspaces: {}", e.what());
}
if (!config_["warp-on-scroll"].asBool()) {
ipc_.sendCmd(IPC_COMMAND, fmt::format("mouse_warping container"));
}
return true;
}

View File

@ -7,12 +7,22 @@
#endif
waybar::modules::Temperature::Temperature(const std::string& id, const Json::Value& config)
: AButton(config, "temperature", id, "{temperatureC}°C", 10) {
: ALabel(config, "temperature", id, "{temperatureC}°C", 10) {
#if defined(__FreeBSD__)
// try to read sysctl?
#else
if (config_["hwmon-path"].isString()) {
file_path_ = config_["hwmon-path"].asString();
auto& hwmon_path = config_["hwmon-path"];
if (hwmon_path.isString()) {
file_path_ = hwmon_path.asString();
} else if (hwmon_path.isArray()) {
// if hwmon_path is an array, loop to find first valid item
for (auto& item : hwmon_path) {
auto path = item.asString();
if (std::filesystem::exists(path)) {
file_path_ = path;
break;
}
}
} else if (config_["hwmon-path-abs"].isString() && config_["input-filename"].isString()) {
file_path_ = (*std::filesystem::directory_iterator(config_["hwmon-path-abs"].asString()))
.path()
@ -42,9 +52,9 @@ auto waybar::modules::Temperature::update() -> void {
auto format = format_;
if (critical) {
format = config_["format-critical"].isString() ? config_["format-critical"].asString() : format;
button_.get_style_context()->add_class("critical");
label_.get_style_context()->add_class("critical");
} else {
button_.get_style_context()->remove_class("critical");
label_.get_style_context()->remove_class("critical");
}
if (format.empty()) {
@ -55,21 +65,21 @@ auto waybar::modules::Temperature::update() -> void {
}
auto max_temp = config_["critical-threshold"].isInt() ? config_["critical-threshold"].asInt() : 0;
label_->set_markup(fmt::format(format, fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k),
fmt::arg("icon", getIcon(temperature_c, "", max_temp))));
label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k),
fmt::arg("icon", getIcon(temperature_c, "", max_temp))));
if (tooltipEnabled()) {
std::string tooltip_format = "{temperatureC}°C";
if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
button_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k)));
label_.set_tooltip_text(fmt::format(
fmt::runtime(tooltip_format), fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k)));
}
// Call parent update
AButton::update();
ALabel::update();
}
float waybar::modules::Temperature::getTemperature() {

View File

@ -5,10 +5,8 @@
#include <cstring>
#include <string>
#include "gtkmm/icontheme.h"
#include "gtkmm/label.h"
#include "gtkmm/tooltip.h"
#include "modules/upower/upower_tooltip.hpp"
#include "util/gtk_icon.hpp"
namespace waybar::modules::upower {
UPower::UPower(const std::string& id, const Json::Value& config)
@ -25,6 +23,8 @@ UPower::UPower(const std::string& id, const Json::Value& config)
box_.set_name(name_);
event_box_.add(box_);
// Device user wants
if (config_["native-path"].isString()) nativePath_ = config_["native-path"].asString();
// Icon Size
if (config_["icon-size"].isUInt()) {
iconSize = config_["icon-size"].asUInt();
@ -195,8 +195,26 @@ void UPower::addDevice(UpDevice* device) {
void UPower::setDisplayDevice() {
std::lock_guard<std::mutex> guard(m_Mutex);
displayDevice = up_client_get_display_device(client);
g_signal_connect(displayDevice, "notify", G_CALLBACK(deviceNotify_cb), this);
if (nativePath_.empty())
displayDevice = up_client_get_display_device(client);
else {
g_ptr_array_foreach(
up_client_get_devices2(client),
[](gpointer data, gpointer user_data) {
UpDevice* device{static_cast<UpDevice*>(data)};
UPower* thisPtr{static_cast<UPower*>(user_data)};
gchar* nativePath;
if (!thisPtr->displayDevice) {
g_object_get(device, "native-path", &nativePath, NULL);
if (!std::strcmp(nativePath, thisPtr->nativePath_.c_str()))
thisPtr->displayDevice = device;
}
},
this);
}
if (displayDevice) g_signal_connect(displayDevice, "notify", G_CALLBACK(deviceNotify_cb), this);
}
void UPower::removeDevices() {
@ -252,8 +270,7 @@ const std::string UPower::getDeviceStatus(UpDeviceState& state) {
bool UPower::handleToggle(GdkEventButton* const& event) {
std::lock_guard<std::mutex> guard(m_Mutex);
showAltText = !showAltText;
dp.emit();
return true;
return AModule::handleToggle(event);
}
std::string UPower::timeToString(gint64 time) {
@ -279,14 +296,22 @@ auto UPower::update() -> void {
double percentage;
gint64 time_empty;
gint64 time_full;
gchar* icon_name;
gchar* icon_name{(char*)'\0'};
std::string percentString{""};
std::string time_format{""};
g_object_get(displayDevice, "kind", &kind, "state", &state, "percentage", &percentage,
"icon-name", &icon_name, "time-to-empty", &time_empty, "time-to-full", &time_full,
NULL);
bool displayDeviceValid{false};
bool displayDeviceValid =
kind == UpDeviceKind::UP_DEVICE_KIND_BATTERY || kind == UpDeviceKind::UP_DEVICE_KIND_UPS;
if (displayDevice) {
g_object_get(displayDevice, "kind", &kind, "state", &state, "percentage", &percentage,
"icon-name", &icon_name, "time-to-empty", &time_empty, "time-to-full", &time_full,
NULL);
/* Every Device which is handled by Upower and which is not
* UP_DEVICE_KIND_UNKNOWN (0) or UP_DEVICE_KIND_LINE_POWER (1) is a Battery
*/
displayDeviceValid = (kind != UpDeviceKind::UP_DEVICE_KIND_UNKNOWN &&
kind != UpDeviceKind::UP_DEVICE_KIND_LINE_POWER);
}
// CSS status class
const std::string status = getDeviceStatus(state);
@ -309,36 +334,34 @@ auto UPower::update() -> void {
event_box_.set_visible(true);
// Tooltip
if (tooltip_enabled) {
uint tooltipCount = upower_tooltip->updateTooltip(devices);
// Disable the tooltip if there aren't any devices in the tooltip
box_.set_has_tooltip(!devices.empty() && tooltipCount > 0);
}
// Set percentage
std::string percentString = "";
if (displayDeviceValid) {
percentString = std::to_string(int(percentage + 0.5)) + "%";
}
// Tooltip
if (tooltip_enabled) {
uint tooltipCount = upower_tooltip->updateTooltip(devices);
// Disable the tooltip if there aren't any devices in the tooltip
box_.set_has_tooltip(!devices.empty() && tooltipCount > 0);
}
// Label format
std::string time_format = "";
switch (state) {
case UP_DEVICE_STATE_CHARGING:
case UP_DEVICE_STATE_PENDING_CHARGE:
time_format = timeToString(time_full);
break;
case UP_DEVICE_STATE_DISCHARGING:
case UP_DEVICE_STATE_PENDING_DISCHARGE:
time_format = timeToString(time_empty);
break;
default:
break;
// Set percentage
percentString = std::to_string(int(percentage + 0.5)) + "%";
// Label format
switch (state) {
case UP_DEVICE_STATE_CHARGING:
case UP_DEVICE_STATE_PENDING_CHARGE:
time_format = timeToString(time_full);
break;
case UP_DEVICE_STATE_DISCHARGING:
case UP_DEVICE_STATE_PENDING_DISCHARGE:
time_format = timeToString(time_empty);
break;
default:
break;
}
}
std::string label_format =
fmt::format(showAltText ? format_alt : format, fmt::arg("percentage", percentString),
fmt::arg("time", time_format));
fmt::format(fmt::runtime(showAltText ? format_alt : format),
fmt::arg("percentage", percentString), fmt::arg("time", time_format));
// Only set the label text if it doesn't only contain spaces
bool onlySpaces = true;
for (auto& character : label_format) {
@ -349,7 +372,7 @@ auto UPower::update() -> void {
label_.set_markup(onlySpaces ? "" : label_format);
// Set icon
if (icon_name == NULL || !Gtk::IconTheme::get_default()->has_icon(icon_name)) {
if (icon_name == NULL || !DefaultGtkIconThemeWrapper::has_icon(icon_name)) {
icon_name = (char*)"battery-missing-symbolic";
}
icon_.set_from_icon_name(icon_name, Gtk::ICON_SIZE_INVALID);

View File

@ -2,9 +2,9 @@
#include "gtkmm/box.h"
#include "gtkmm/enums.h"
#include "gtkmm/icontheme.h"
#include "gtkmm/image.h"
#include "gtkmm/label.h"
#include "util/gtk_icon.hpp"
namespace waybar::modules::upower {
UPowerTooltip::UPowerTooltip(uint iconSize_, uint tooltipSpacing_, uint tooltipPadding_)
@ -29,7 +29,7 @@ UPowerTooltip::~UPowerTooltip() {}
uint UPowerTooltip::updateTooltip(Devices& devices) {
// Removes all old devices
for (auto child : contentBox->get_children()) {
child->~Widget();
delete child;
}
uint deviceCount = 0;
@ -62,7 +62,7 @@ uint UPowerTooltip::updateTooltip(Devices& devices) {
std::string deviceIconName = getDeviceIcon(kind);
Gtk::Image* deviceIcon = new Gtk::Image();
deviceIcon->set_pixel_size(iconSize);
if (!Gtk::IconTheme::get_default()->has_icon(deviceIconName)) {
if (!DefaultGtkIconThemeWrapper::has_icon(deviceIconName)) {
deviceIconName = "battery-missing-symbolic";
}
deviceIcon->set_from_icon_name(deviceIconName, Gtk::ICON_SIZE_INVALID);
@ -79,7 +79,7 @@ uint UPowerTooltip::updateTooltip(Devices& devices) {
// Set icon
Gtk::Image* icon = new Gtk::Image();
icon->set_pixel_size(iconSize);
if (icon_name == NULL || !Gtk::IconTheme::get_default()->has_icon(icon_name)) {
if (icon_name == NULL || !DefaultGtkIconThemeWrapper::has_icon(icon_name)) {
icon_name = (char*)"battery-missing-symbolic";
}
icon->set_from_icon_name(icon_name, Gtk::ICON_SIZE_INVALID);

View File

@ -6,7 +6,13 @@
#include <algorithm>
#include <chrono>
#include <iostream>
#include "gdkmm/cursor.h"
#include "gdkmm/event.h"
#include "gdkmm/types.h"
#include "glibmm/fileutils.h"
#include "sigc++/functors/mem_fun.h"
#include "sigc++/functors/ptr_fun.h"
#if HAVE_CPU_LINUX
#include <sys/sysinfo.h>
@ -16,15 +22,34 @@
#include <time.h>
#endif
const static int LEFT_MOUSE_BUTTON_CODE = 1;
namespace waybar::modules {
User::User(const std::string& id, const Json::Value& config)
: AIconLabel(config, "user", id, "{user} {work_H}:{work_M}", 60, false, false, true) {
: AIconLabel(config, "user", id, "{user} {work_H}:{work_M}", 60, false, true, true) {
AIconLabel::box_.set_spacing(0);
if (AIconLabel::iconEnabled()) {
this->init_avatar(AIconLabel::config_);
}
this->init_update_worker();
}
bool User::handleToggle(GdkEventButton* const& e) {
if (AIconLabel::config_["open-on-click"].isBool() &&
AIconLabel::config_["open-on-click"].asBool() && e->button == LEFT_MOUSE_BUTTON_CODE) {
std::string openPath = this->get_user_home_dir();
if (AIconLabel::config_["open-path"].isString()) {
std::string customPath = AIconLabel::config_["open-path"].asString();
if (!customPath.empty()) {
openPath = std::move(customPath);
}
}
Gio::AppInfo::launch_default_for_uri("file:///" + openPath);
}
return true;
}
long User::uptime_as_seconds() {
long uptime = 0;
@ -45,9 +70,9 @@ long User::uptime_as_seconds() {
return uptime;
}
std::string User::get_user_login() { return Glib::get_user_name(); }
std::string User::get_user_login() const { return Glib::get_user_name(); }
std::string User::get_user_home_dir() { return Glib::get_home_dir(); }
std::string User::get_user_home_dir() const { return Glib::get_home_dir(); }
void User::init_update_worker() {
this->thread_ = [this] {
@ -74,7 +99,7 @@ void User::init_avatar(const Json::Value& config) {
this->init_default_user_avatar(width, width);
}
std::string User::get_default_user_avatar_path() {
std::string User::get_default_user_avatar_path() const {
return this->get_user_home_dir() + "/" + ".face";
}
@ -83,8 +108,12 @@ void User::init_default_user_avatar(int width, int height) {
}
void User::init_user_avatar(const std::string& path, int width, int height) {
this->pixbuf_ = Gdk::Pixbuf::create_from_file(path, width, height);
AIconLabel::image_.set(this->pixbuf_);
if (Glib::file_test(path, Glib::FILE_TEST_EXISTS)) {
Glib::RefPtr<Gdk::Pixbuf> pixbuf_ = Gdk::Pixbuf::create_from_file(path, width, height);
AIconLabel::image_.set(pixbuf_);
} else {
AIconLabel::box_.remove(AIconLabel::image_);
}
}
auto User::update() -> void {
@ -98,17 +127,17 @@ auto User::update() -> void {
auto startSystemTime = currentSystemTime - workSystemTimeSeconds;
long workSystemDays = uptimeSeconds / 86400;
auto label = fmt::format(ALabel::format_, fmt::arg("up_H", fmt::format("{:%H}", startSystemTime)),
fmt::arg("up_M", fmt::format("{:%M}", startSystemTime)),
fmt::arg("up_d", fmt::format("{:%d}", startSystemTime)),
fmt::arg("up_m", fmt::format("{:%m}", startSystemTime)),
fmt::arg("up_Y", fmt::format("{:%Y}", startSystemTime)),
fmt::arg("work_d", workSystemDays),
fmt::arg("work_H", fmt::format("{:%H}", workSystemTimeSeconds)),
fmt::arg("work_M", fmt::format("{:%M}", workSystemTimeSeconds)),
fmt::arg("work_S", fmt::format("{:%S}", workSystemTimeSeconds)),
fmt::arg("user", systemUser));
auto label = fmt::format(
fmt::runtime(ALabel::format_), fmt::arg("up_H", fmt::format("{:%H}", startSystemTime)),
fmt::arg("up_M", fmt::format("{:%M}", startSystemTime)),
fmt::arg("up_d", fmt::format("{:%d}", startSystemTime)),
fmt::arg("up_m", fmt::format("{:%m}", startSystemTime)),
fmt::arg("up_Y", fmt::format("{:%Y}", startSystemTime)), fmt::arg("work_d", workSystemDays),
fmt::arg("work_H", fmt::format("{:%H}", workSystemTimeSeconds)),
fmt::arg("work_M", fmt::format("{:%M}", workSystemTimeSeconds)),
fmt::arg("work_S", fmt::format("{:%S}", workSystemTimeSeconds)),
fmt::arg("user", systemUser));
ALabel::label_.set_markup(label);
ALabel::update();
AIconLabel::update();
}
}; // namespace waybar::modules

355
src/modules/wireplumber.cpp Normal file
View File

@ -0,0 +1,355 @@
#include "modules/wireplumber.hpp"
#include <spdlog/spdlog.h>
bool isValidNodeId(uint32_t id) { return id > 0 && id < G_MAXUINT32; }
waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config)
: ALabel(config, "wireplumber", id, "{volume}%"),
wp_core_(nullptr),
apis_(nullptr),
om_(nullptr),
mixer_api_(nullptr),
def_nodes_api_(nullptr),
default_node_name_(nullptr),
pending_plugins_(0),
muted_(false),
volume_(0.0),
min_step_(0.0),
node_id_(0) {
wp_init(WP_INIT_PIPEWIRE);
wp_core_ = wp_core_new(NULL, NULL);
apis_ = g_ptr_array_new_with_free_func(g_object_unref);
om_ = wp_object_manager_new();
prepare();
loadRequiredApiModules();
spdlog::debug("[{}]: connecting to pipewire...", this->name_);
if (!wp_core_connect(wp_core_)) {
spdlog::error("[{}]: Could not connect to PipeWire", this->name_);
throw std::runtime_error("Could not connect to PipeWire\n");
}
spdlog::debug("[{}]: connected!", this->name_);
g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this);
activatePlugins();
dp.emit();
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Wireplumber::handleScroll));
}
waybar::modules::Wireplumber::~Wireplumber() {
g_clear_pointer(&apis_, g_ptr_array_unref);
g_clear_object(&om_);
g_clear_object(&wp_core_);
g_clear_object(&mixer_api_);
g_clear_object(&def_nodes_api_);
g_free(default_node_name_);
}
void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self, uint32_t id) {
spdlog::debug("[{}]: updating node name with node.id {}", self->name_, id);
if (!isValidNodeId(id)) {
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node name update.", self->name_, id);
return;
}
auto proxy = static_cast<WpProxy*>(wp_object_manager_lookup(
self->om_, WP_TYPE_GLOBAL_PROXY, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, NULL));
if (!proxy) {
auto err = fmt::format("Object '{}' not found\n", id);
spdlog::error("[{}]: {}", self->name_, err);
throw std::runtime_error(err);
}
g_autoptr(WpProperties) properties =
WP_IS_PIPEWIRE_OBJECT(proxy) ? wp_pipewire_object_get_properties(WP_PIPEWIRE_OBJECT(proxy))
: wp_properties_new_empty();
g_autoptr(WpProperties) global_p = wp_global_proxy_get_global_properties(WP_GLOBAL_PROXY(proxy));
properties = wp_properties_ensure_unique_owner(properties);
wp_properties_add(properties, global_p);
wp_properties_set(properties, "object.id", NULL);
auto nick = wp_properties_get(properties, "node.nick");
auto description = wp_properties_get(properties, "node.description");
self->node_name_ = nick ? nick : description;
spdlog::debug("[{}]: Updating node name to: {}", self->name_, self->node_name_);
}
void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self, uint32_t id) {
spdlog::debug("[{}]: updating volume", self->name_);
GVariant* variant = NULL;
if (!isValidNodeId(id)) {
spdlog::error("[{}]: '{}' is not a valid node ID. Ignoring volume update.", self->name_, id);
return;
}
g_signal_emit_by_name(self->mixer_api_, "get-volume", id, &variant);
if (!variant) {
auto err = fmt::format("Node {} does not support volume\n", id);
spdlog::error("[{}]: {}", self->name_, err);
throw std::runtime_error(err);
}
g_variant_lookup(variant, "volume", "d", &self->volume_);
g_variant_lookup(variant, "step", "d", &self->min_step_);
g_variant_lookup(variant, "mute", "b", &self->muted_);
g_clear_pointer(&variant, g_variant_unref);
self->dp.emit();
}
void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id) {
spdlog::debug("[{}]: (onMixerChanged) - id: {}", self->name_, id);
g_autoptr(WpNode) node = static_cast<WpNode*>(wp_object_manager_lookup(
self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, NULL));
if (!node) {
spdlog::warn("[{}]: (onMixerChanged) - Object with id {} not found", self->name_, id);
return;
}
const gchar* name = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name");
if (self->node_id_ != id) {
spdlog::debug(
"[{}]: (onMixerChanged) - ignoring mixer update for node: id: {}, name: {} as it is not "
"the default node: {} with id: {}",
self->name_, id, name, self->default_node_name_, self->node_id_);
return;
}
spdlog::debug("[{}]: (onMixerChanged) - Need to update volume for node with id {} and name {}",
self->name_, id, name);
updateVolume(self, id);
}
void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wireplumber* self) {
spdlog::debug("[{}]: (onDefaultNodesApiChanged)", self->name_);
uint32_t default_node_id;
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &default_node_id);
if (!isValidNodeId(default_node_id)) {
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node change.", self->name_,
default_node_id);
return;
}
g_autoptr(WpNode) node = static_cast<WpNode*>(
wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id",
"=u", default_node_id, NULL));
if (!node) {
spdlog::warn("[{}]: (onDefaultNodesApiChanged) - Object with id {} not found", self->name_,
default_node_id);
return;
}
const gchar* default_node_name =
wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name");
spdlog::debug(
"[{}]: (onDefaultNodesApiChanged) - got the following default node: Node(name: {}, id: {})",
self->name_, default_node_name, default_node_id);
if (g_strcmp0(self->default_node_name_, default_node_name) == 0) {
spdlog::debug(
"[{}]: (onDefaultNodesApiChanged) - Default node has not changed. Node(name: {}, id: {}). "
"Ignoring.",
self->name_, self->default_node_name_, default_node_id);
return;
}
spdlog::debug(
"[{}]: (onDefaultNodesApiChanged) - Default node changed to -> Node(name: {}, id: {})",
self->name_, default_node_name, default_node_id);
g_free(self->default_node_name_);
self->default_node_name_ = g_strdup(default_node_name);
self->node_id_ = default_node_id;
updateVolume(self, default_node_id);
updateNodeName(self, default_node_id);
}
void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wireplumber* self) {
spdlog::debug("[{}]: onObjectManagerInstalled", self->name_);
self->def_nodes_api_ = wp_plugin_find(self->wp_core_, "default-nodes-api");
if (!self->def_nodes_api_) {
spdlog::error("[{}]: default nodes api is not loaded.", self->name_);
throw std::runtime_error("Default nodes API is not loaded\n");
}
self->mixer_api_ = wp_plugin_find(self->wp_core_, "mixer-api");
if (!self->mixer_api_) {
spdlog::error("[{}]: mixer api is not loaded.", self->name_);
throw std::runtime_error("Mixer api is not loaded\n");
}
g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", "Audio/Sink",
&self->default_node_name_);
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &self->node_id_);
if (self->default_node_name_) {
spdlog::debug("[{}]: (onObjectManagerInstalled) - default configured node name: {} and id: {}",
self->name_, self->default_node_name_, self->node_id_);
}
updateVolume(self, self->node_id_);
updateNodeName(self, self->node_id_);
g_signal_connect_swapped(self->mixer_api_, "changed", (GCallback)onMixerChanged, self);
g_signal_connect_swapped(self->def_nodes_api_, "changed", (GCallback)onDefaultNodesApiChanged,
self);
}
void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult* res,
waybar::modules::Wireplumber* self) {
auto plugin_name = wp_plugin_get_name(WP_PLUGIN(p));
spdlog::debug("[{}]: onPluginActivated: {}", self->name_, plugin_name);
g_autoptr(GError) error = NULL;
if (!wp_object_activate_finish(p, res, &error)) {
spdlog::error("[{}]: error activating plugin: {}", self->name_, error->message);
throw std::runtime_error(error->message);
}
if (--self->pending_plugins_ == 0) {
wp_core_install_object_manager(self->wp_core_, self->om_);
}
}
void waybar::modules::Wireplumber::activatePlugins() {
spdlog::debug("[{}]: activating plugins", name_);
for (uint16_t i = 0; i < apis_->len; i++) {
WpPlugin* plugin = static_cast<WpPlugin*>(g_ptr_array_index(apis_, i));
pending_plugins_++;
wp_object_activate(WP_OBJECT(plugin), WP_PLUGIN_FEATURE_ENABLED, NULL,
(GAsyncReadyCallback)onPluginActivated, this);
}
}
void waybar::modules::Wireplumber::prepare() {
spdlog::debug("[{}]: preparing object manager", name_);
wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
"=s", "Audio/Sink", NULL);
}
void waybar::modules::Wireplumber::loadRequiredApiModules() {
spdlog::debug("[{}]: loading required modules", name_);
g_autoptr(GError) error = NULL;
if (!wp_core_load_component(wp_core_, "libwireplumber-module-default-nodes-api", "module", NULL,
&error)) {
throw std::runtime_error(error->message);
}
if (!wp_core_load_component(wp_core_, "libwireplumber-module-mixer-api", "module", NULL,
&error)) {
throw std::runtime_error(error->message);
}
g_ptr_array_add(apis_, wp_plugin_find(wp_core_, "default-nodes-api"));
g_ptr_array_add(apis_, ({
WpPlugin* p = wp_plugin_find(wp_core_, "mixer-api");
g_object_set(G_OBJECT(p), "scale", 1 /* cubic */, NULL);
p;
}));
}
auto waybar::modules::Wireplumber::update() -> void {
auto format = format_;
std::string tooltip_format;
if (muted_) {
format = config_["format-muted"].isString() ? config_["format-muted"].asString() : format;
label_.get_style_context()->add_class("muted");
} else {
label_.get_style_context()->remove_class("muted");
}
int vol = round(volume_ * 100.0);
std::string markup = fmt::format(fmt::runtime(format), fmt::arg("node_name", node_name_),
fmt::arg("volume", vol), fmt::arg("icon", getIcon(vol)));
label_.set_markup(markup);
getState(vol);
if (tooltipEnabled()) {
if (tooltip_format.empty() && config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format),
fmt::arg("node_name", node_name_),
fmt::arg("volume", vol), fmt::arg("icon", getIcon(vol))));
} else {
label_.set_tooltip_text(node_name_);
}
}
// Call parent update
ALabel::update();
}
bool waybar::modules::Wireplumber::handleScroll(GdkEventScroll* e) {
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
return AModule::handleScroll(e);
}
auto dir = AModule::getScrollDir(e);
if (dir == SCROLL_DIR::NONE) {
return true;
}
if (config_["reverse-scrolling"].asInt() == 1) {
if (dir == SCROLL_DIR::UP) {
dir = SCROLL_DIR::DOWN;
} else if (dir == SCROLL_DIR::DOWN) {
dir = SCROLL_DIR::UP;
}
}
double max_volume = 1;
double step = 1.0 / 100.0;
if (config_["scroll-step"].isDouble()) {
step = config_["scroll-step"].asDouble() / 100.0;
}
if (config_["max-volume"].isDouble()) {
max_volume = config_["max-volume"].asDouble() / 100.0;
}
if (step < min_step_) step = min_step_;
double new_vol = volume_;
if (dir == SCROLL_DIR::UP) {
if (volume_ < max_volume) {
new_vol = volume_ + step;
if (new_vol > max_volume) new_vol = max_volume;
}
} else if (dir == SCROLL_DIR::DOWN) {
if (volume_ > 0) {
new_vol = volume_ - step;
if (new_vol < 0) new_vol = 0;
}
}
if (new_vol != volume_) {
GVariant* variant = g_variant_new_double(new_vol);
gboolean ret;
g_signal_emit_by_name(mixer_api_, "set-volume", node_id_, variant, &ret);
}
return true;
}

View File

@ -20,6 +20,7 @@
#include "glibmm/fileutils.h"
#include "glibmm/refptr.h"
#include "util/format.hpp"
#include "util/rewrite_string.hpp"
#include "util/string.hpp"
namespace waybar::modules::wlr {
@ -102,8 +103,11 @@ Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id
desktop_file = desktop_list[0][i];
} else {
auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]);
auto startup_class = tmp_info->get_startup_wm_class();
if (!tmp_info)
// see https://github.com/Alexays/Waybar/issues/1446
continue;
auto startup_class = tmp_info->get_startup_wm_class();
if (startup_class == app_id) {
desktop_file = desktop_list[0][i];
break;
@ -265,14 +269,14 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} {
zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this);
button_.set_relief(Gtk::RELIEF_NONE);
button.set_relief(Gtk::RELIEF_NONE);
content_.add(text_before_);
content_.add(icon_);
content_.add(text_after_);
content_.show();
button_.add(content_);
button.add(content_);
format_before_.clear();
format_after_.clear();
@ -314,20 +318,20 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
config_["on-click-right"].isString()) {
}
button_.add_events(Gdk::BUTTON_PRESS_MASK);
button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Task::handle_clicked), false);
button_.signal_button_release_event().connect(sigc::mem_fun(*this, &Task::handle_button_release),
false);
button_.signal_motion_notify_event().connect(sigc::mem_fun(*this, &Task::handle_motion_notify),
button.add_events(Gdk::BUTTON_PRESS_MASK);
button.signal_button_press_event().connect(sigc::mem_fun(*this, &Task::handle_clicked), false);
button.signal_button_release_event().connect(sigc::mem_fun(*this, &Task::handle_button_release),
false);
button_.drag_source_set(target_entries, Gdk::BUTTON1_MASK, Gdk::ACTION_MOVE);
button_.drag_dest_set(target_entries, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_MOVE);
button.signal_motion_notify_event().connect(sigc::mem_fun(*this, &Task::handle_motion_notify),
false);
button_.signal_drag_data_get().connect(sigc::mem_fun(*this, &Task::handle_drag_data_get), false);
button_.signal_drag_data_received().connect(
sigc::mem_fun(*this, &Task::handle_drag_data_received), false);
button.drag_source_set(target_entries, Gdk::BUTTON1_MASK, Gdk::ACTION_MOVE);
button.drag_dest_set(target_entries, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_MOVE);
button.signal_drag_data_get().connect(sigc::mem_fun(*this, &Task::handle_drag_data_get), false);
button.signal_drag_data_received().connect(sigc::mem_fun(*this, &Task::handle_drag_data_received),
false);
}
Task::~Task() {
@ -336,7 +340,7 @@ Task::~Task() {
handle_ = nullptr;
}
if (button_visible_) {
tbar_->remove_button(button_);
tbar_->remove_button(button);
button_visible_ = false;
}
}
@ -435,8 +439,8 @@ void Task::handle_output_enter(struct wl_output *output) {
if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) {
/* The task entered the output of the current bar make the button visible */
tbar_->add_button(button_);
button_.show();
tbar_->add_button(button);
button.show();
button_visible_ = true;
spdlog::debug("{} now visible on {}", repr(), bar_.output->name);
}
@ -447,8 +451,8 @@ void Task::handle_output_leave(struct wl_output *output) {
if (button_visible_ && !tbar_->all_outputs() && tbar_->show_output(output)) {
/* The task left the output of the current bar, make the button invisible */
tbar_->remove_button(button_);
button_.hide();
tbar_->remove_button(button);
button.hide();
button_visible_ = false;
spdlog::debug("{} now invisible on {}", repr(), bar_.output->name);
}
@ -470,31 +474,31 @@ void Task::handle_done() {
spdlog::debug("{} changed", repr());
if (state_ & MAXIMIZED) {
button_.get_style_context()->add_class("maximized");
button.get_style_context()->add_class("maximized");
} else if (!(state_ & MAXIMIZED)) {
button_.get_style_context()->remove_class("maximized");
button.get_style_context()->remove_class("maximized");
}
if (state_ & MINIMIZED) {
button_.get_style_context()->add_class("minimized");
button.get_style_context()->add_class("minimized");
} else if (!(state_ & MINIMIZED)) {
button_.get_style_context()->remove_class("minimized");
button.get_style_context()->remove_class("minimized");
}
if (state_ & ACTIVE) {
button_.get_style_context()->add_class("active");
button.get_style_context()->add_class("active");
} else if (!(state_ & ACTIVE)) {
button_.get_style_context()->remove_class("active");
button.get_style_context()->remove_class("active");
}
if (state_ & FULLSCREEN) {
button_.get_style_context()->add_class("fullscreen");
button.get_style_context()->add_class("fullscreen");
} else if (!(state_ & FULLSCREEN)) {
button_.get_style_context()->remove_class("fullscreen");
button.get_style_context()->remove_class("fullscreen");
}
if (config_["active-first"].isBool() && config_["active-first"].asBool() && active())
tbar_->move_button(button_, 0);
tbar_->move_button(button, 0);
tbar_->dp.emit();
}
@ -503,11 +507,11 @@ void Task::handle_closed() {
spdlog::debug("{} closed", repr());
zwlr_foreign_toplevel_handle_v1_destroy(handle_);
handle_ = nullptr;
tbar_->remove_task(id_);
if (button_visible_) {
tbar_->remove_button(button_);
tbar_->remove_button(button);
button_visible_ = false;
}
tbar_->remove_task(id_);
}
bool Task::handle_clicked(GdkEventButton *bt) {
@ -560,12 +564,12 @@ bool Task::handle_button_release(GdkEventButton *bt) {
bool Task::handle_motion_notify(GdkEventMotion *mn) {
if (drag_start_button == -1) return false;
if (button_.drag_check_threshold(drag_start_x, drag_start_y, mn->x, mn->y)) {
if (button.drag_check_threshold(drag_start_x, drag_start_y, mn->x, mn->y)) {
/* start drag in addition to other assigned action */
auto target_list = Gtk::TargetList::create(target_entries);
auto refptr = Glib::RefPtr<Gtk::TargetList>(target_list);
auto drag_context =
button_.drag_begin(refptr, Gdk::DragAction::ACTION_MOVE, drag_start_button, (GdkEvent *)mn);
button.drag_begin(refptr, Gdk::DragAction::ACTION_MOVE, drag_start_button, (GdkEvent *)mn);
}
return false;
@ -574,7 +578,7 @@ bool Task::handle_motion_notify(GdkEventMotion *mn) {
void Task::handle_drag_data_get(const Glib::RefPtr<Gdk::DragContext> &context,
Gtk::SelectionData &selection_data, guint info, guint time) {
spdlog::debug("drag_data_get");
void *button_addr = (void *)&this->button_;
void *button_addr = (void *)&this->button;
selection_data.set("WAYBAR_TOPLEVEL", 32, (const guchar *)&button_addr, sizeof(gpointer));
}
@ -585,16 +589,16 @@ void Task::handle_drag_data_received(const Glib::RefPtr<Gdk::DragContext> &conte
gpointer handle = *(gpointer *)selection_data.get_data();
auto dragged_button = (Gtk::Button *)handle;
if (dragged_button == &this->button_) return;
if (dragged_button == &this->button) return;
auto parent_of_dragged = dragged_button->get_parent();
auto parent_of_dest = this->button_.get_parent();
auto parent_of_dest = this->button.get_parent();
if (parent_of_dragged != parent_of_dest) return;
auto box = (Gtk::Box *)parent_of_dragged;
auto position_prop = box->child_property_position(this->button_);
auto position_prop = box->child_property_position(this->button);
auto position = position_prop.get_value();
box->reorder_child(*dragged_button, position);
@ -615,9 +619,13 @@ void Task::update() {
app_id = Glib::Markup::escape_text(app_id);
}
if (!format_before_.empty()) {
auto txt = fmt::format(format_before_, fmt::arg("title", title), fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)));
auto txt =
fmt::format(fmt::runtime(format_before_), fmt::arg("title", title), fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)));
txt = waybar::util::rewriteString(txt, config_["rewrite"]);
if (markup)
text_before_.set_markup(txt);
else
@ -625,9 +633,13 @@ void Task::update() {
text_before_.show();
}
if (!format_after_.empty()) {
auto txt = fmt::format(format_after_, fmt::arg("title", title), fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)));
auto txt =
fmt::format(fmt::runtime(format_after_), fmt::arg("title", title), fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)));
txt = waybar::util::rewriteString(txt, config_["rewrite"]);
if (markup)
text_after_.set_markup(txt);
else
@ -636,13 +648,14 @@ void Task::update() {
}
if (!format_tooltip_.empty()) {
auto txt = fmt::format(format_tooltip_, fmt::arg("title", title), fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)));
auto txt =
fmt::format(fmt::runtime(format_tooltip_), fmt::arg("title", title), fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)));
if (markup)
button_.set_tooltip_markup(txt);
button.set_tooltip_markup(txt);
else
button_.set_tooltip_text(txt);
button.set_tooltip_text(txt);
}
}
@ -704,6 +717,7 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
box_.get_style_context()->add_class("empty");
event_box_.add(box_);
struct wl_display *display = Client::inst()->wl_display;
@ -785,6 +799,17 @@ void Taskbar::update() {
t->update();
}
if (config_["sort-by-app-id"].asBool()) {
std::stable_sort(tasks_.begin(), tasks_.end(),
[](const std::unique_ptr<Task> &a, const std::unique_ptr<Task> &b) {
return a->app_id() < b->app_id();
});
for (unsigned long i = 0; i < tasks_.size(); i++) {
move_button(tasks_[i]->button, i);
}
}
AModule::update();
}
@ -845,11 +870,19 @@ void Taskbar::handle_finished() {
manager_ = nullptr;
}
void Taskbar::add_button(Gtk::Button &bt) { box_.pack_start(bt, false, false); }
void Taskbar::add_button(Gtk::Button &bt) {
box_.pack_start(bt, false, false);
box_.get_style_context()->remove_class("empty");
}
void Taskbar::move_button(Gtk::Button &bt, int pos) { box_.reorder_child(bt, pos); }
void Taskbar::remove_button(Gtk::Button &bt) { box_.remove(bt); }
void Taskbar::remove_button(Gtk::Button &bt) {
box_.remove(bt);
if (tasks_.empty()) {
box_.get_style_context()->add_class("empty");
}
}
void Taskbar::remove_task(uint32_t id) {
auto it = std::find_if(std::begin(tasks_), std::end(tasks_),

View File

@ -6,8 +6,10 @@
#include <algorithm>
#include <iterator>
#include <stdexcept>
#include <vector>
#include "client.hpp"
#include "gtkmm/widget.h"
#include "modules/wlr/workspace_manager_binding.hpp"
@ -62,14 +64,17 @@ WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar
auto WorkspaceManager::workspace_comparator() const
-> std::function<bool(std::unique_ptr<Workspace> &, std::unique_ptr<Workspace> &)> {
return [=](std::unique_ptr<Workspace> &lhs, std::unique_ptr<Workspace> &rhs) {
return [=, this](std::unique_ptr<Workspace> &lhs, std::unique_ptr<Workspace> &rhs) {
auto is_name_less = lhs->get_name() < rhs->get_name();
auto is_name_eq = lhs->get_name() == rhs->get_name();
auto is_coords_less = lhs->get_coords() < rhs->get_coords();
auto is_number_less = std::stoi(lhs->get_name()) < std::stoi(rhs->get_name());
if (sort_by_number_) {
return is_number_less;
try {
auto is_number_less = std::stoi(lhs->get_name()) < std::stoi(rhs->get_name());
return is_number_less;
} catch (const std::invalid_argument &) {
}
}
if (sort_by_name_) {
@ -162,8 +167,20 @@ WorkspaceManager::~WorkspaceManager() {
return;
}
zext_workspace_manager_v1_destroy(workspace_manager_);
workspace_manager_ = nullptr;
wl_display *display = Client::inst()->wl_display;
// Send `stop` request and wait for one roundtrip. This is not quite correct as
// the protocol encourages us to wait for the .finished event, but it should work
// with wlroots workspace manager implementation.
zext_workspace_manager_v1_stop(workspace_manager_);
wl_display_roundtrip(display);
// If the .finished handler is still not executed, destroy the workspace manager here.
if (workspace_manager_) {
spdlog::warn("Foreign toplevel manager destroyed before .finished event");
zext_workspace_manager_v1_destroy(workspace_manager_);
workspace_manager_ = nullptr;
}
}
auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void {
@ -191,6 +208,38 @@ WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value
add_workspace_group_listener(workspace_group_handle, this);
}
auto WorkspaceGroup::fill_persistent_workspaces() -> void {
if (config_["persistent_workspaces"].isObject() && !workspace_manager_.all_outputs()) {
const Json::Value &p_workspaces = config_["persistent_workspaces"];
const std::vector<std::string> p_workspaces_names = p_workspaces.getMemberNames();
for (const std::string &p_w_name : p_workspaces_names) {
const Json::Value &p_w = p_workspaces[p_w_name];
if (p_w.isArray() && !p_w.empty()) {
// Adding to target outputs
for (const Json::Value &output : p_w) {
if (output.asString() == bar_.output->name) {
persistent_workspaces_.push_back(p_w_name);
break;
}
}
} else {
// Adding to all outputs
persistent_workspaces_.push_back(p_w_name);
}
}
}
}
auto WorkspaceGroup::create_persistent_workspaces() -> void {
for (const std::string &p_w_name : persistent_workspaces_) {
auto new_id = ++workspace_global_id;
workspaces_.push_back(
std::make_unique<Workspace>(bar_, config_, *this, nullptr, new_id, p_w_name));
spdlog::debug("Workspace {} created", new_id);
}
}
auto WorkspaceGroup::active_only() const -> bool { return workspace_manager_.active_only(); }
auto WorkspaceGroup::creation_delayed() const -> bool {
return workspace_manager_.creation_delayed();
@ -211,8 +260,13 @@ WorkspaceGroup::~WorkspaceGroup() {
auto WorkspaceGroup::handle_workspace_create(zext_workspace_handle_v1 *workspace) -> void {
auto new_id = ++workspace_global_id;
workspaces_.push_back(std::make_unique<Workspace>(bar_, config_, *this, workspace, new_id));
workspaces_.push_back(std::make_unique<Workspace>(bar_, config_, *this, workspace, new_id, ""));
spdlog::debug("Workspace {} created", new_id);
if (!persistent_created_) {
fill_persistent_workspaces();
create_persistent_workspaces();
persistent_created_ = true;
}
}
auto WorkspaceGroup::handle_remove() -> void {
@ -311,13 +365,18 @@ auto WorkspaceGroup::sort_workspaces() -> void {
auto WorkspaceGroup::remove_button(Gtk::Button &button) -> void { box_.remove(button); }
Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group,
zext_workspace_handle_v1 *workspace, uint32_t id)
zext_workspace_handle_v1 *workspace, uint32_t id, std::string name)
: bar_(bar),
config_(config),
workspace_group_(workspace_group),
workspace_handle_(workspace),
id_(id) {
add_workspace_listener(workspace, this);
id_(id),
name_(name) {
if (workspace) {
add_workspace_listener(workspace, this);
} else {
state_ = (uint32_t)State::EMPTY;
}
auto config_format = config["format"];
@ -362,7 +421,7 @@ Workspace::~Workspace() {
}
auto Workspace::update() -> void {
label_.set_markup(fmt::format(format_, fmt::arg("name", name_),
label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_),
fmt::arg("icon", with_icon_ ? get_icon() : "")));
}
@ -384,9 +443,15 @@ auto Workspace::handle_state(const std::vector<uint32_t> &state) -> void {
}
auto Workspace::handle_remove() -> void {
zext_workspace_handle_v1_destroy(workspace_handle_);
workspace_handle_ = nullptr;
workspace_group_.remove_workspace(id_);
if (workspace_handle_) {
zext_workspace_handle_v1_destroy(workspace_handle_);
workspace_handle_ = nullptr;
}
if (!persistent_) {
workspace_group_.remove_workspace(id_);
} else {
state_ = (uint32_t)State::EMPTY;
}
}
auto add_or_remove_class(Glib::RefPtr<Gtk::StyleContext> context, bool condition,
@ -404,6 +469,7 @@ auto Workspace::handle_done() -> void {
add_or_remove_class(style_context, is_active(), "active");
add_or_remove_class(style_context, is_urgent(), "urgent");
add_or_remove_class(style_context, is_hidden(), "hidden");
add_or_remove_class(style_context, is_empty(), "persistent");
if (workspace_group_.creation_delayed()) {
return;
@ -429,6 +495,13 @@ auto Workspace::get_icon() -> std::string {
return named_icon_it->second;
}
if (is_empty()) {
auto persistent_icon_it = icons_map_.find("persistent");
if (persistent_icon_it != icons_map_.end()) {
return persistent_icon_it->second;
}
}
auto default_icon_it = icons_map_.find("default");
if (default_icon_it != icons_map_.end()) {
return default_icon_it->second;
@ -470,6 +543,29 @@ auto Workspace::handle_name(const std::string &name) -> void {
workspace_group_.set_need_to_sort();
}
name_ = name;
spdlog::debug("Workspace {} added to group {}", name, workspace_group_.id());
make_persistent();
handle_duplicate();
}
auto Workspace::make_persistent() -> void {
auto p_workspaces = workspace_group_.persistent_workspaces();
if (std::find(p_workspaces.begin(), p_workspaces.end(), name_) != p_workspaces.end()) {
persistent_ = true;
}
}
auto Workspace::handle_duplicate() -> void {
auto duplicate =
std::find_if(workspace_group_.workspaces().begin(), workspace_group_.workspaces().end(),
[this](const std::unique_ptr<Workspace> &g) {
return g->get_name() == name_ && g->id() != id_;
});
if (duplicate != workspace_group_.workspaces().end()) {
workspace_group_.remove_workspace(duplicate->get()->id());
}
}
auto Workspace::handle_coordinates(const std::vector<uint32_t> &coordinates) -> void {