Merge branch 'master' into issue-1681
This commit is contained in:
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
202
src/modules/cava.cpp
Normal 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);
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
198
src/modules/dwl/tags.cpp
Normal 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, ®istry_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 */
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
63
src/modules/hyprland/submap.cpp
Normal file
63
src/modules/hyprland/submap.cpp
Normal 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
|
@ -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
|
||||
|
199
src/modules/hyprland/workspaces.cpp
Normal file
199
src/modules/hyprland/workspaces.cpp
Normal 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
|
@ -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
88
src/modules/image.cpp
Normal 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++;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
723
src/modules/mpris/mpris.cpp
Normal 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
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
174
src/modules/river/layout.cpp
Normal file
174
src/modules/river/layout.cpp
Normal 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, ®istry_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 */
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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_);
|
||||
|
@ -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() {
|
||||
|
@ -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()) {
|
||||
|
@ -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;
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace waybar::modules::sway {
|
||||
|
||||
Ipc::Ipc() {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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())));
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
355
src/modules/wireplumber.cpp
Normal 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;
|
||||
}
|
@ -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_),
|
||||
|
@ -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 {
|
||||
|
Reference in New Issue
Block a user