Merge branch 'Alexays:master' into master

This commit is contained in:
hecate cantus
2025-10-16 18:40:58 -07:00
committed by GitHub
162 changed files with 5420 additions and 2447 deletions
+12 -2
View File
@@ -38,10 +38,20 @@ 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());
std::string desc = fmt::format(fmt::runtime(format_), fmt::arg("percent", percent),
// Get the state and apply state-specific format if available
auto state = getState(percent);
std::string current_format = format_;
if (!state.empty()) {
std::string state_format_name = "format-" + state;
if (config_[state_format_name].isString()) {
current_format = config_[state_format_name].asString();
}
}
std::string desc = fmt::format(fmt::runtime(current_format), fmt::arg("percent", percent),
fmt::arg("icon", getIcon(percent)));
label_.set_markup(desc);
getState(percent);
if (tooltipEnabled()) {
std::string tooltip_format;
if (config_["tooltip-format"].isString()) {
+36 -7
View File
@@ -1,14 +1,16 @@
#include "modules/battery.hpp"
#include <algorithm>
#include <cctype>
#include "util/command.hpp"
#if defined(__FreeBSD__)
#include <sys/sysctl.h>
#endif
#include <spdlog/spdlog.h>
#include <iostream>
waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "battery", id, "{capacity}%", 60), bar_(bar) {
: ALabel(config, "battery", id, "{capacity}%", 60), last_event_(""), bar_(bar) {
#if defined(__linux__)
battery_watch_fd_ = inotify_init1(IN_CLOEXEC);
if (battery_watch_fd_ == -1) {
@@ -26,6 +28,7 @@ waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const J
throw std::runtime_error("Could not watch for battery plug/unplug");
}
#endif
spdlog::debug("battery: worker interval is {}", interval_.count());
worker();
}
@@ -677,18 +680,22 @@ auto waybar::modules::Battery::update() -> void {
}
auto status_pretty = status;
// Transform to lowercase and replace space with dash
std::transform(status.begin(), status.end(), status.begin(),
[](char ch) { return ch == ' ' ? '-' : std::tolower(ch); });
std::ranges::transform(status.begin(), status.end(), status.begin(),
[](char ch) { return ch == ' ' ? '-' : std::tolower(ch); });
auto format = format_;
auto state = getState(capacity, true);
processEvents(state, status, capacity);
setBarClass(state);
auto time_remaining_formatted = formatTimeRemaining(time_remaining);
if (tooltipEnabled()) {
std::string tooltip_text_default;
std::string tooltip_format = "{timeTo}";
if (time_remaining != 0) {
std::string time_to = std::string("Time to ") + ((time_remaining > 0) ? "empty" : "full");
tooltip_text_default = time_to + ": " + time_remaining_formatted;
if (time_remaining > 0) {
tooltip_text_default = std::string("Empty in ") + time_remaining_formatted;
} else {
tooltip_text_default = std::string("Full in ") + time_remaining_formatted;
}
} else {
tooltip_text_default = status_pretty;
}
@@ -701,7 +708,7 @@ auto waybar::modules::Battery::update() -> void {
} else if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
label_.set_tooltip_text(
label_.set_tooltip_markup(
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), fmt::arg("cycles", cycles),
@@ -767,3 +774,25 @@ void waybar::modules::Battery::setBarClass(std::string& state) {
bar_.window.get_style_context()->add_class(new_class);
}
}
void waybar::modules::Battery::processEvents(std::string& state, std::string& status,
uint8_t capacity) {
// There are no events specified, skip
auto events = config_["events"];
if (!events.isObject() || events.empty()) {
return;
}
std::string event_name = fmt::format("on-{}-{}", status == "discharging" ? status : "charging",
state.empty() ? std::to_string(capacity) : state);
if (last_event_ != event_name) {
spdlog::debug("battery: triggering event {}", event_name);
if (events[event_name].isString()) {
std::string exec = events[event_name].asString();
// Execute the command if it is not empty
if (!exec.empty()) {
util::command::exec(exec, "");
}
}
last_event_ = event_name;
}
}
-211
View File
@@ -1,211 +0,0 @@
#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()) strcpy(cfgPath, config_["cava_config"].asString().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 = cava::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 = cava::ORIENT_TOP;
prm_.xaxis = cava::xaxis_scale::NONE;
prm_.mono_opt = cava::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 = cava::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_.samplerate = config_["sample_rate"].asLargestInt();
if (config_["sample_bits"].isInt()) prm_.samplebits = 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());
if (config_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool();
if (config_["format_silent"].isString()) format_silent_ = config_["format_silent"].asString();
// Make cava parameters configuration
plan_ = new cava::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 != 0) {
if (sleep_counter_ <=
(int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) {
++sleep_counter_;
silence_ = false;
}
}
if (!silence_ || prm_.sleep_timer == 0) {
downThreadDelay(frame_time_milsec_, suspend_silence_delay_);
// Process: execute cava
pthread_mutex_lock(&audio_data_.lock);
cava::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_, plan_);
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_);
label_.show();
ALabel::update();
label_.get_style_context()->add_class("updated");
}
label_.get_style_context()->remove_class("silent");
} else {
upThreadDelay(frame_time_milsec_, suspend_silence_delay_);
if (hide_on_silence_)
label_.hide();
else if (config_["format_silent"].isString())
label_.set_markup(format_silent_);
label_.get_style_context()->add_class("silent");
label_.get_style_context()->remove_class("updated");
}
}
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);
}
+51
View File
@@ -0,0 +1,51 @@
#include "modules/cava/cava.hpp"
#include <spdlog/spdlog.h>
waybar::modules::cava::Cava::Cava(const std::string& id, const Json::Value& config)
: ALabel(config, "cava", id, "{}", 60, false, false, false),
backend_{waybar::modules::cava::CavaBackend::inst(config)} {
if (config_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool();
if (config_["format_silent"].isString()) format_silent_ = config_["format_silent"].asString();
ascii_range_ = backend_->getAsciiRange();
backend_->signal_update().connect(sigc::mem_fun(*this, &Cava::onUpdate));
backend_->signal_silence().connect(sigc::mem_fun(*this, &Cava::onSilence));
backend_->Update();
}
auto waybar::modules::cava::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::Cava::pause_resume() { backend_->doPauseResume(); }
auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void {
if (silence_) {
label_.get_style_context()->remove_class("silent");
label_.get_style_context()->add_class("updated");
}
label_text_.clear();
for (auto& ch : input)
label_text_.append(getIcon((ch > ascii_range_) ? ascii_range_ : ch, "", ascii_range_ + 1));
label_.set_markup(label_text_);
label_.show();
ALabel::update();
silence_ = false;
}
auto waybar::modules::cava::Cava::onSilence() -> void {
if (!silence_) {
label_.get_style_context()->remove_class("updated");
if (hide_on_silence_)
label_.hide();
else if (config_["format_silent"].isString())
label_.set_markup(format_silent_);
silence_ = true;
label_.get_style_context()->add_class("silent");
}
}
+223
View File
@@ -0,0 +1,223 @@
#include "modules/cava/cava_backend.hpp"
#include <spdlog/spdlog.h>
std::shared_ptr<waybar::modules::cava::CavaBackend> waybar::modules::cava::CavaBackend::inst(
const Json::Value& config) {
static auto* backend = new CavaBackend(config);
static std::shared_ptr<CavaBackend> backend_ptr{backend};
return backend_ptr;
}
waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) {
// Load waybar module config
char cfgPath[PATH_MAX];
cfgPath[0] = '\0';
if (config["cava_config"].isString()) strcpy(cfgPath, config["cava_config"].asString().data());
// Load cava config
error_.length = 0;
if (!load_config(cfgPath, &prm_, false, &error_)) {
spdlog::error("cava backend. Error loading config. {0}", error_.message);
exit(EXIT_FAILURE);
}
// Override cava parameters by the user config
prm_.inAtty = 0;
prm_.output = ::cava::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 = ::cava::ORIENT_TOP;
prm_.xaxis = ::cava::xaxis_scale::NONE;
prm_.mono_opt = ::cava::AVERAGE;
prm_.autobars = 0;
prm_.gravity = 0;
prm_.integral = 1;
if (config["framerate"].isInt()) prm_.framerate = config["framerate"].asInt();
// Calculate delay for Update() thread
frame_time_milsec_ = std::chrono::milliseconds((int)(1e3 / prm_.framerate));
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 = ::cava::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_.samplerate = config["sample_rate"].asLargestInt();
if (config["sample_bits"].isInt()) prm_.samplebits = 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::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 backend API didn't provide input audio source method");
exit(EXIT_FAILURE);
}
// Init cava plan, audio_raw structure
audio_raw_init(&audio_data_, &audio_raw_, &prm_, plan_);
if (!plan_) spdlog::error("cava backend 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
read_thread_ = [this] {
try {
input_source_(&audio_data_);
} catch (const std::runtime_error& e) {
spdlog::warn("Cava backend. Read source error: {0}", e.what());
}
read_thread_.sleep_for(fetch_input_delay_);
};
thread_ = [this] {
doUpdate();
thread_.sleep_for(frame_time_milsec_);
};
}
waybar::modules::cava::CavaBackend::~CavaBackend() {
thread_.stop();
read_thread_.stop();
delete plan_;
plan_ = nullptr;
}
static void upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {
if (delta == std::chrono::seconds{0}) {
delta += std::chrono::seconds{1};
delay += delta;
}
}
static void downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {
if (delta > std::chrono::seconds{0}) {
delay -= delta;
delta -= std::chrono::seconds{1};
}
}
bool waybar::modules::cava::CavaBackend::isSilence() {
for (int i{0}; i < audio_data_.input_buffer_size; ++i) {
if (audio_data_.cava_in[i]) {
return false;
}
}
return true;
}
int waybar::modules::cava::CavaBackend::getAsciiRange() { return prm_.ascii_range; }
// Process: execute cava
void waybar::modules::cava::CavaBackend::invoke() {
pthread_mutex_lock(&audio_data_.lock);
::cava::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
void waybar::modules::cava::CavaBackend::execute() {
invoke();
audio_raw_fetch(&audio_raw_, &prm_, &re_paint_, plan_);
if (re_paint_ == 1) {
output_.clear();
for (int i{0}; i < audio_raw_.number_of_bars; ++i) {
audio_raw_.previous_frame[i] = audio_raw_.bars[i];
output_.push_back(audio_raw_.bars[i]);
if (prm_.bar_delim != 0) output_.push_back(prm_.bar_delim);
}
}
}
void waybar::modules::cava::CavaBackend::doPauseResume() {
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);
}
waybar::modules::cava::CavaBackend::type_signal_update
waybar::modules::cava::CavaBackend::signal_update() {
return m_signal_update_;
}
waybar::modules::cava::CavaBackend::type_signal_silence
waybar::modules::cava::CavaBackend::signal_silence() {
return m_signal_silence_;
}
void waybar::modules::cava::CavaBackend::Update() { doUpdate(true); }
void waybar::modules::cava::CavaBackend::doUpdate(bool force) {
if (audio_data_.suspendFlag && !force) return;
silence_ = isSilence();
if (!silence_) sleep_counter_ = 0;
if (silence_ && prm_.sleep_timer != 0) {
if (sleep_counter_ <=
(int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) {
++sleep_counter_;
silence_ = false;
}
}
if (!silence_ || prm_.sleep_timer == 0) {
downThreadDelay(frame_time_milsec_, suspend_silence_delay_);
execute();
if (re_paint_ == 1 || force) m_signal_update_.emit(output_);
} else {
upThreadDelay(frame_time_milsec_, suspend_silence_delay_);
if (silence_ != silence_prev_ || force) m_signal_silence_.emit();
}
silence_prev_ = silence_;
}
+74 -27
View File
@@ -1,5 +1,6 @@
#include "modules/clock.hpp"
#include <glib.h>
#include <gtkmm/tooltip.h>
#include <spdlog/spdlog.h>
@@ -16,6 +17,7 @@
#include <clocale>
#endif
using namespace date;
namespace fmt_lib = waybar::util::date::format;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
@@ -25,8 +27,12 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
m_tooltip_{new Gtk::Label()},
cldInTooltip_{m_tlpFmt_.find("{" + kCldPlaceholder + "}") != std::string::npos},
cldYearShift_{January / 1 / 1900},
cldMonShift_{year(1900) / January},
tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos},
tzCurrIdx_{0},
tzTooltipFormat_{config_["timezone-tooltip-format"].isString()
? config_["timezone-tooltip-format"].asString()
: ""},
ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} {
m_tlpText_ = m_tlpFmt_;
@@ -60,8 +66,8 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
if (cldInTooltip_) {
if (config_[kCldPlaceholder]["mode"].isString()) {
const std::string cfgMode{config_[kCldPlaceholder]["mode"].asString()};
const std::map<std::string_view, const CldMode&> monthModes{{"month", CldMode::MONTH},
{"year", CldMode::YEAR}};
const std::map<std::string, CldMode> monthModes{{"month", CldMode::MONTH},
{"year", CldMode::YEAR}};
if (monthModes.find(cfgMode) != monthModes.end())
cldMode_ = monthModes.at(cfgMode);
else
@@ -70,6 +76,11 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
"using instead",
cfgMode);
}
if (config_[kCldPlaceholder]["iso8601"].isBool()) {
iso8601Calendar_ = config_[kCldPlaceholder]["iso8601"].asBool();
}
if (config_[kCldPlaceholder]["weeks-pos"].isString()) {
if (config_[kCldPlaceholder]["weeks-pos"].asString() == "left") cldWPos_ = WS::LEFT;
if (config_[kCldPlaceholder]["weeks-pos"].asString() == "right") cldWPos_ = WS::RIGHT;
@@ -89,23 +100,25 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
fmtMap_.insert({2, "{}"});
if (config_[kCldPlaceholder]["format"]["today"].isString()) {
fmtMap_.insert({3, config_[kCldPlaceholder]["format"]["today"].asString()});
cldBaseDay_ =
year_month_day{
floor<days>(zoned_time{local_zone(), system_clock::now()}.get_local_time())}
.day();
auto local_time = zoned_time{local_zone(), system_clock::now()}.get_local_time();
cldBaseDay_ = year_month_day{floor<days>(local_time)}.day();
} else
fmtMap_.insert({3, "{}"});
if (config_[kCldPlaceholder]["format"]["weeks"].isString() && cldWPos_ != WS::HIDDEN) {
const auto defaultFmt =
iso8601Calendar_ ? "{:%V}" : ((first_day_of_week() == Monday) ? "{:%W}" : "{:%U}");
fmtMap_.insert({4, std::regex_replace(config_[kCldPlaceholder]["format"]["weeks"].asString(),
std::regex("\\{\\}"),
(first_day_of_week() == Monday) ? "{:%W}" : "{:%U}")});
std::regex("\\{\\}"), defaultFmt)});
Glib::ustring tmp{std::regex_replace(fmtMap_[4], std::regex("</?[^>]+>|\\{.*\\}"), "")};
cldWnLen_ += tmp.size();
} else {
if (cldWPos_ != WS::HIDDEN)
fmtMap_.insert({4, (first_day_of_week() == Monday) ? "{:%W}" : "{:%U}"});
else
if (cldWPos_ != WS::HIDDEN) {
const auto defaultFmt =
iso8601Calendar_ ? "{:%V}" : ((first_day_of_week() == Monday) ? "{:%W}" : "{:%U}");
fmtMap_.insert({4, defaultFmt});
} else {
cldWnLen_ = 0;
}
}
if (config_[kCldPlaceholder]["mode-mon-col"].isInt()) {
cldMonCols_ = config_[kCldPlaceholder]["mode-mon-col"].asInt();
@@ -185,11 +198,26 @@ auto waybar::modules::Clock::getTZtext(sys_seconds now) -> std::string {
if (tzList_.size() == 1) return "";
std::stringstream os;
bool first = true;
for (size_t tz_idx{0}; tz_idx < tzList_.size(); ++tz_idx) {
if (static_cast<int>(tz_idx) == tzCurrIdx_) continue;
const auto* tz = tzList_[tz_idx] != nullptr ? tzList_[tz_idx] : local_zone();
// Skip local timezone (nullptr) - never show it in tooltip
if (tzList_[tz_idx] == nullptr) continue;
// Skip current timezone unless timezone-tooltip-format is specified
if (static_cast<int>(tz_idx) == tzCurrIdx_ && tzTooltipFormat_.empty()) continue;
const auto* tz = tzList_[tz_idx];
auto zt{zoned_time{tz, now}};
os << fmt_lib::vformat(m_locale_, format_, fmt_lib::make_format_args(zt)) << '\n';
// Add newline before each entry except the first
if (!first) {
os << '\n';
}
first = false;
// Use timezone-tooltip-format if specified, otherwise use format_
const std::string& fmt = tzTooltipFormat_.empty() ? format_ : tzTooltipFormat_;
os << fmt_lib::vformat(m_locale_, fmt, fmt_lib::make_format_args(zt));
}
return os.str();
@@ -201,9 +229,11 @@ const unsigned cldRowsInMonth(const year_month& ym, const weekday& firstdow) {
auto cldGetWeekForLine(const year_month& ym, const weekday& firstdow, const unsigned line)
-> const year_month_weekday {
unsigned index{line - 2};
if (weekday{ym / 1} == firstdow) ++index;
return ym / firstdow[index];
const unsigned idx = line - 2;
const std::chrono::weekday_indexed indexed_first_day_of_week =
weekday{ym / 1} == firstdow ? firstdow[idx + 1] : firstdow[idx];
return ym / indexed_first_day_of_week;
}
auto getCalendarLine(const year_month_day& currDate, const year_month ym, const unsigned line,
@@ -262,7 +292,7 @@ auto getCalendarLine(const year_month_day& currDate, const year_month ym, const
}
// Print non-first week
default: {
auto ymdTmp{cldGetWeekForLine(ym, firstdow, line)};
const auto ymdTmp{cldGetWeekForLine(ym, firstdow, line)};
if (ymdTmp.ok()) {
auto d{year_month_day{ymdTmp}.day()};
const auto dlast{(ym / last).day()};
@@ -348,20 +378,34 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
m_locale_, fmtMap_[4],
fmt_lib::make_format_args(
(line == 2)
? static_cast<const date::zoned_seconds&&>(
? static_cast<const zoned_seconds&&>(
zoned_seconds{tz, local_days{ymTmp / 1}})
: static_cast<const date::zoned_seconds&&>(zoned_seconds{
: static_cast<const zoned_seconds&&>(zoned_seconds{
tz, local_days{cldGetWeekForLine(ymTmp, firstdow, line)}})))
<< ' ';
} else
} else {
os << pads;
}
}
}
os << Glib::ustring::format((cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right,
std::setfill(L' '),
std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ : 0)),
getCalendarLine(today, ymTmp, line, firstdow, &m_locale_));
// Count wide characters to avoid extra padding
size_t wideCharCount = 0;
std::string calendarLine = getCalendarLine(today, ymTmp, line, firstdow, &m_locale_);
if (line < 2) {
for (gchar *data = calendarLine.data(), *end = data + calendarLine.size();
data != nullptr;) {
gunichar c = g_utf8_get_char_validated(data, end - data);
if (g_unichar_iswide(c)) {
wideCharCount++;
}
data = g_utf8_find_next_char(data, end);
}
}
os << Glib::ustring::format(
(cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right, std::setfill(L' '),
std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ - wideCharCount : 0)),
calendarLine);
// Week numbers on the right
if (cldWPos_ == WS::RIGHT && line > 0) {
@@ -371,9 +415,9 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
<< fmt_lib::vformat(
m_locale_, fmtMap_[4],
fmt_lib::make_format_args(
(line == 2) ? static_cast<const date::zoned_seconds&&>(
(line == 2) ? static_cast<const zoned_seconds&&>(
zoned_seconds{tz, local_days{ymTmp / 1}})
: static_cast<const date::zoned_seconds&&>(
: static_cast<const zoned_seconds&&>(
zoned_seconds{tz, local_days{cldGetWeekForLine(
ymTmp, firstdow, line)}})));
else
@@ -465,6 +509,9 @@ using deleting_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>;
// Computations done similarly to Linux cal utility.
auto waybar::modules::Clock::first_day_of_week() -> weekday {
if (iso8601Calendar_) {
return Monday;
}
#ifdef HAVE_LANGINFO_1STDAY
deleting_unique_ptr<std::remove_pointer<locale_t>::type, freelocale> posix_locale{
newlocale(LC_ALL, m_locale_.name().c_str(), nullptr)};
+9 -2
View File
@@ -5,11 +5,12 @@
std::vector<float> waybar::modules::CpuFrequency::parseCpuFrequencies() {
std::vector<float> frequencies;
char buffer[256];
size_t len;
int32_t freq;
uint32_t i = 0;
#ifndef __OpenBSD__
char buffer[256];
uint32_t i = 0;
while (true) {
len = 4;
snprintf(buffer, 256, "dev.cpu.%u.freq", i);
@@ -17,6 +18,12 @@ std::vector<float> waybar::modules::CpuFrequency::parseCpuFrequencies() {
frequencies.push_back(freq);
++i;
}
#else
int getMhz[] = {CTL_HW, HW_CPUSPEED};
len = sizeof(freq);
sysctl(getMhz, 2, &freq, &len, NULL, 0);
frequencies.push_back((float)freq);
#endif
if (frequencies.empty()) {
spdlog::warn("cpu/bsd: parseCpuFrequencies failed, not found in sysctl");
+15 -3
View File
@@ -14,6 +14,9 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
percentage_(0),
fp_(nullptr),
pid_(-1) {
if (config.isNull()) {
spdlog::warn("There is no configuration for 'custom/{}', element will be hidden", name);
}
dp.emit();
if (!config_["signal"].empty() && config_["interval"].empty() &&
config_["restart-interval"].empty()) {
@@ -35,6 +38,13 @@ waybar::modules::Custom::~Custom() {
void waybar::modules::Custom::delayWorker() {
thread_ = [this] {
for (int i : this->pid_children_) {
int status;
waitpid(i, &status, 0);
}
this->pid_children_.clear();
bool can_update = true;
if (config_["exec-if"].isString()) {
output_ = util::command::execNoRead(config_["exec-if"].asString());
@@ -62,7 +72,7 @@ void waybar::modules::Custom::continuousWorker() {
}
thread_ = [this, cmd] {
char* buff = nullptr;
waybar::util::ScopeGuard buff_deleter([buff]() {
waybar::util::ScopeGuard buff_deleter([&buff]() {
if (buff) {
free(buff);
}
@@ -79,9 +89,11 @@ void waybar::modules::Custom::continuousWorker() {
dp.emit();
spdlog::error("{} stopped unexpectedly, is it endless?", name_);
}
if (config_["restart-interval"].isUInt()) {
if (config_["restart-interval"].isNumeric()) {
pid_ = -1;
thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt()));
thread_.sleep_for(std::chrono::milliseconds(
std::max(1L, // Minimum 1ms due to millisecond precision
static_cast<long>(config_["restart-interval"].asDouble() * 1000))));
fp_ = util::command::open(cmd, pid_, output_name_);
if (!fp_) {
throw std::runtime_error("Unable to open " + cmd);
+504
View File
@@ -0,0 +1,504 @@
#include "modules/ext/workspace_manager.hpp"
#include <gdk/gdkwayland.h>
#include <gtkmm.h>
#include <spdlog/spdlog.h>
#include <algorithm>
#include <iostream>
#include <vector>
#include "client.hpp"
#include "gtkmm/widget.h"
#include "modules/ext/workspace_manager_binding.hpp"
namespace waybar::modules::ext {
// WorkspaceManager
uint32_t WorkspaceManager::group_global_id = 0;
uint32_t WorkspaceManager::workspace_global_id = 0;
std::map<std::string, std::string> Workspace::icon_map_;
WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar,
const Json::Value &config)
: waybar::AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) {
add_registry_listener(this);
// parse configuration
const auto config_sort_by_number = config_["sort-by-number"];
if (config_sort_by_number.isBool()) {
spdlog::warn("[ext/workspaces]: Prefer sort-by-id instead of sort-by-number");
sort_by_id_ = config_sort_by_number.asBool();
}
const auto config_sort_by_id = config_["sort-by-id"];
if (config_sort_by_id.isBool()) {
sort_by_id_ = config_sort_by_id.asBool();
}
const auto config_sort_by_name = config_["sort-by-name"];
if (config_sort_by_name.isBool()) {
sort_by_name_ = config_sort_by_name.asBool();
}
const auto config_sort_by_coordinates = config_["sort-by-coordinates"];
if (config_sort_by_coordinates.isBool()) {
sort_by_coordinates_ = config_sort_by_coordinates.asBool();
}
const auto config_all_outputs = config_["all-outputs"];
if (config_all_outputs.isBool()) {
all_outputs_ = config_all_outputs.asBool();
}
// setup UI
box_.set_name("workspaces");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
box_.get_style_context()->add_class(MODULE_CLASS);
event_box_.add(box_);
spdlog::debug("[ext/workspaces]: Workspace manager created");
}
WorkspaceManager::~WorkspaceManager() {
workspaces_.clear();
groups_.clear();
if (ext_manager_ != nullptr) {
auto *display = Client::inst()->wl_display;
// Send `stop` request and wait for one roundtrip.
ext_workspace_manager_v1_stop(ext_manager_);
wl_display_roundtrip(display);
}
if (ext_manager_ != nullptr) {
spdlog::warn("[ext/workspaces]: Destroying workspace manager before .finished event");
ext_workspace_manager_v1_destroy(ext_manager_);
}
spdlog::debug("[ext/workspaces]: Workspace manager destroyed");
}
void WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version) {
if (ext_manager_ != nullptr) {
spdlog::warn("[ext/workspaces]: Register workspace manager again although already registered!");
return;
}
if (version != 1) {
spdlog::warn("[ext/workspaces]: Using different workspace manager protocol version: {}",
version);
}
ext_manager_ = workspace_manager_bind(registry, name, version, this);
}
void WorkspaceManager::remove_workspace_group(uint32_t id) {
const auto it =
std::find_if(groups_.begin(), groups_.end(), [id](const auto &g) { return g->id() == id; });
if (it == groups_.end()) {
spdlog::warn("[ext/workspaces]: Can't find workspace group with id {}", id);
return;
}
groups_.erase(it);
}
void WorkspaceManager::remove_workspace(uint32_t id) {
const auto it = std::find_if(workspaces_.begin(), workspaces_.end(),
[id](const auto &w) { return w->id() == id; });
if (it == workspaces_.end()) {
spdlog::warn("[ext/workspaces]: Can't find workspace with id {}", id);
return;
}
workspaces_.erase(it);
}
void WorkspaceManager::handle_workspace_group(ext_workspace_group_handle_v1 *handle) {
const auto new_id = ++group_global_id;
groups_.push_back(std::make_unique<WorkspaceGroup>(*this, handle, new_id));
spdlog::debug("[ext/workspaces]: Workspace group {} created", new_id);
}
void WorkspaceManager::handle_workspace(ext_workspace_handle_v1 *handle) {
const auto new_id = ++workspace_global_id;
const auto new_name = std::to_string(++workspace_name);
workspaces_.push_back(std::make_unique<Workspace>(config_, *this, handle, new_id, new_name));
set_needs_sorting();
spdlog::debug("[ext/workspaces]: Workspace {} created", new_id);
}
void WorkspaceManager::handle_done() { dp.emit(); }
void WorkspaceManager::handle_finished() {
spdlog::debug("[ext/workspaces]: Finishing workspace manager");
ext_workspace_manager_v1_destroy(ext_manager_);
ext_manager_ = nullptr;
}
void WorkspaceManager::commit() const { ext_workspace_manager_v1_commit(ext_manager_); }
void WorkspaceManager::update() {
spdlog::debug("[ext/workspaces]: Updating state");
if (needs_sorting_) {
clear_buttons();
sort_workspaces();
needs_sorting_ = false;
}
update_buttons();
AModule::update();
}
bool WorkspaceManager::has_button(const Gtk::Button *button) {
const auto buttons = box_.get_children();
return std::find(buttons.begin(), buttons.end(), button) != buttons.end();
}
void WorkspaceManager::sort_workspaces() {
// determine if workspace ID's and names can be sort numerically or literally
auto is_numeric = [](const std::string &s) {
return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit);
};
auto sort_by_workspace_id_numerically =
std::all_of(workspaces_.begin(), workspaces_.end(),
[&](const auto &w) { return is_numeric(w->workspace_id()); });
auto sort_by_name_numerically = std::all_of(workspaces_.begin(), workspaces_.end(),
[&](const auto &w) { return is_numeric(w->name()); });
// sort based on configuration setting with sort-by-id as fallback
std::sort(workspaces_.begin(), workspaces_.end(), [&](const auto &w1, const auto &w2) {
if (sort_by_id_ || (!sort_by_name_ && !sort_by_coordinates_)) {
if (w1->workspace_id() == w2->workspace_id()) {
return w1->id() < w2->id();
}
if (sort_by_workspace_id_numerically) {
// the idea is that phonetic compare can be applied just to numbers
// with same number of digits
return w1->workspace_id().size() < w2->workspace_id().size() ||
(w1->workspace_id().size() == w2->workspace_id().size() &&
w1->workspace_id() < w2->workspace_id());
}
return w1->workspace_id() < w2->workspace_id();
}
if (sort_by_name_) {
if (w1->name() == w2->name()) {
return w1->id() < w2->id();
}
if (sort_by_name_numerically) {
// see above about numeric sorting
return w1->name().size() < w2->name().size() ||
(w1->name().size() == w2->name().size() && w1->name() < w2->name());
}
return w1->name() < w2->name();
}
if (sort_by_coordinates_) {
if (w1->coordinates() == w2->coordinates()) {
return w1->id() < w2->id();
}
return w1->coordinates() < w2->coordinates();
}
return w1->id() < w2->id();
});
}
void WorkspaceManager::clear_buttons() {
for (const auto &workspace : workspaces_) {
if (has_button(&workspace->button())) {
box_.remove(workspace->button());
}
}
}
void WorkspaceManager::update_buttons() {
const auto *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
// go through all workspace
for (const auto &workspace : workspaces_) {
const bool workspace_on_any_group_for_output =
std::any_of(groups_.begin(), groups_.end(), [&](const auto &group) {
const bool group_on_output = group->has_output(output) || all_outputs_;
const bool workspace_on_group = group->has_workspace(workspace->handle());
return group_on_output && workspace_on_group;
});
const bool bar_contains_button = has_button(&workspace->button());
// add or remove buttons if needed, update button state
if (workspace_on_any_group_for_output) {
if (!bar_contains_button) {
// add button to bar
box_.pack_start(workspace->button(), false, false);
workspace->button().show_all();
}
workspace->update();
} else {
if (bar_contains_button) {
// remove button from bar
box_.remove(workspace->button());
}
}
}
}
// WorkspaceGroup
WorkspaceGroup::WorkspaceGroup(WorkspaceManager &manager, ext_workspace_group_handle_v1 *handle,
uint32_t id)
: workspaces_manager_(manager), ext_handle_(handle), id_(id) {
add_workspace_group_listener(ext_handle_, this);
}
WorkspaceGroup::~WorkspaceGroup() {
if (ext_handle_ != nullptr) {
ext_workspace_group_handle_v1_destroy(ext_handle_);
}
spdlog::debug("[ext/workspaces]: Workspace group {} destroyed", id_);
}
bool WorkspaceGroup::has_output(const wl_output *output) {
return std::find(outputs_.begin(), outputs_.end(), output) != outputs_.end();
}
bool WorkspaceGroup::has_workspace(const ext_workspace_handle_v1 *workspace) {
return std::find(workspaces_.begin(), workspaces_.end(), workspace) != workspaces_.end();
}
void WorkspaceGroup::handle_capabilities(uint32_t capabilities) {
spdlog::debug("[ext/workspaces]: Capabilities for workspace group {}:", id_);
if ((capabilities & EXT_WORKSPACE_GROUP_HANDLE_V1_GROUP_CAPABILITIES_CREATE_WORKSPACE) ==
capabilities) {
spdlog::debug("[ext/workspaces]: - create-workspace");
}
}
void WorkspaceGroup::handle_output_enter(wl_output *output) { outputs_.push_back(output); }
void WorkspaceGroup::handle_output_leave(wl_output *output) {
const auto it = std::find(outputs_.begin(), outputs_.end(), output);
if (it != outputs_.end()) {
outputs_.erase(it);
}
}
void WorkspaceGroup::handle_workspace_enter(ext_workspace_handle_v1 *handle) {
workspaces_.push_back(handle);
}
void WorkspaceGroup::handle_workspace_leave(ext_workspace_handle_v1 *handle) {
const auto it = std::find(workspaces_.begin(), workspaces_.end(), handle);
if (it != workspaces_.end()) {
workspaces_.erase(it);
}
}
void WorkspaceGroup::handle_removed() {
spdlog::debug("[ext/workspaces]: Removing workspace group {}", id_);
workspaces_manager_.remove_workspace_group(id_);
}
// Workspace
Workspace::Workspace(const Json::Value &config, WorkspaceManager &manager,
ext_workspace_handle_v1 *handle, uint32_t id, const std::string &name)
: workspace_manager_(manager), ext_handle_(handle), id_(id), workspace_id_(name), name_(name) {
add_workspace_listener(ext_handle_, this);
// parse configuration
const auto &config_active_only = config["active-only"];
if (config_active_only.isBool()) {
active_only_ = config_active_only.asBool();
}
const auto &config_ignore_hidden = config["ignore-hidden"];
if (config_ignore_hidden.isBool()) {
ignore_hidden_ = config_ignore_hidden.asBool();
}
const auto &config_format = config["format"];
format_ = config_format.isString() ? config_format.asString() : "{name}";
with_icon_ = format_.find("{icon}") != std::string::npos;
if (with_icon_ && icon_map_.empty()) {
const auto &format_icons = config["format-icons"];
for (auto &n : format_icons.getMemberNames()) {
icon_map_.emplace(n, format_icons[n].asString());
}
}
const bool config_on_click = config["on-click"].isString();
if (config_on_click) {
on_click_action_ = config["on-click"].asString();
}
const bool config_on_click_middle = config["on-click-middle"].isString();
if (config_on_click_middle) {
on_click_middle_action_ = config["on-click-middle"].asString();
}
const bool config_on_click_right = config["on-click-right"].isString();
if (config_on_click_right) {
on_click_right_action_ = config["on-click-right"].asString();
}
// setup UI
if (config_on_click || config_on_click_middle || config_on_click_right) {
button_.add_events(Gdk::BUTTON_PRESS_MASK);
button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked),
false);
}
button_.set_relief(Gtk::RELIEF_NONE);
content_.set_center_widget(label_);
button_.add(content_);
}
Workspace::~Workspace() {
if (ext_handle_ != nullptr) {
ext_workspace_handle_v1_destroy(ext_handle_);
}
spdlog::debug("[ext/workspaces]: Workspace {} destroyed", id_);
}
void Workspace::update() {
const auto style_context = button_.get_style_context();
// update style and visibility
if (!has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) {
style_context->remove_class("active");
}
if (!has_state(EXT_WORKSPACE_HANDLE_V1_STATE_URGENT)) {
style_context->remove_class("urgent");
}
if (!has_state(EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN)) {
style_context->remove_class("hidden");
}
if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) {
button_.set_visible(true);
style_context->add_class("active");
}
if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_URGENT)) {
button_.set_visible(true);
style_context->add_class("urgent");
}
if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN)) {
button_.set_visible(!active_only_ && !ignore_hidden_);
style_context->add_class("hidden");
}
if (state_ == 0) {
button_.set_visible(!active_only_);
}
// update label
label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_),
fmt::arg("id", workspace_id_),
fmt::arg("icon", with_icon_ ? icon() : "")));
}
void Workspace::handle_id(const std::string &id) {
spdlog::debug("[ext/workspaces]: ID for workspace {}: {}", id_, id);
workspace_id_ = id;
workspace_manager_.set_needs_sorting();
}
void Workspace::handle_name(const std::string &name) {
spdlog::debug("[ext/workspaces]: Name for workspace {}: {}", id_, name);
name_ = name;
workspace_manager_.set_needs_sorting();
}
void Workspace::handle_coordinates(const std::vector<uint32_t> &coordinates) {
coordinates_ = coordinates;
workspace_manager_.set_needs_sorting();
}
void Workspace::handle_state(uint32_t state) { state_ = state; }
void Workspace::handle_capabilities(uint32_t capabilities) {
spdlog::debug("[ext/workspaces]: Capabilities for workspace {}:", id_);
if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE) == capabilities) {
spdlog::debug("[ext/workspaces]: - activate");
}
if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_DEACTIVATE) == capabilities) {
spdlog::debug("[ext/workspaces]: - deactivate");
}
if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_REMOVE) == capabilities) {
spdlog::debug("[ext/workspaces]: - remove");
}
if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ASSIGN) == capabilities) {
spdlog::debug("[ext/workspaces]: - assign");
}
}
void Workspace::handle_removed() {
spdlog::debug("[ext/workspaces]: Removing workspace {}", id_);
workspace_manager_.remove_workspace(id_);
}
bool Workspace::handle_clicked(const GdkEventButton *button) const {
std::string action;
if (button->button == GDK_BUTTON_PRIMARY) {
action = on_click_action_;
} else if (button->button == GDK_BUTTON_MIDDLE) {
action = on_click_middle_action_;
} else if (button->button == GDK_BUTTON_SECONDARY) {
action = on_click_right_action_;
}
if (action.empty()) {
return true;
}
if (action == "activate") {
ext_workspace_handle_v1_activate(ext_handle_);
} else if (action == "deactivate") {
ext_workspace_handle_v1_deactivate(ext_handle_);
} else if (action == "close") {
ext_workspace_handle_v1_remove(ext_handle_);
} else {
spdlog::warn("[ext/workspaces]: Unknown action {}", action);
}
workspace_manager_.commit();
return true;
}
std::string Workspace::icon() {
if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) {
const auto active_icon_it = icon_map_.find("active");
if (active_icon_it != icon_map_.end()) {
return active_icon_it->second;
}
}
const auto named_icon_it = icon_map_.find(name_);
if (named_icon_it != icon_map_.end()) {
return named_icon_it->second;
}
const auto default_icon_it = icon_map_.find("default");
if (default_icon_it != icon_map_.end()) {
return default_icon_it->second;
}
return name_;
}
} // namespace waybar::modules::ext
@@ -0,0 +1,159 @@
#include "modules/ext/workspace_manager_binding.hpp"
#include <spdlog/spdlog.h>
#include <cstdint>
#include "client.hpp"
#include "modules/ext/workspace_manager.hpp"
namespace waybar::modules::ext {
static void handle_global(void *data, wl_registry *registry, uint32_t name, const char *interface,
uint32_t version) {
if (std::strcmp(interface, ext_workspace_manager_v1_interface.name) == 0) {
static_cast<WorkspaceManager *>(data)->register_manager(registry, name, version);
}
}
static void handle_global_remove(void *data, wl_registry *registry, uint32_t name) {
/* Nothing to do here */
}
static const wl_registry_listener registry_listener_impl = {.global = handle_global,
.global_remove = handle_global_remove};
void add_registry_listener(void *data) {
wl_display *display = Client::inst()->wl_display;
wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener_impl, data);
wl_display_roundtrip(display);
}
static void workspace_manager_handle_workspace_group(
void *data, ext_workspace_manager_v1 *_, ext_workspace_group_handle_v1 *workspace_group) {
static_cast<WorkspaceManager *>(data)->handle_workspace_group(workspace_group);
}
static void workspace_manager_handle_workspace(void *data, ext_workspace_manager_v1 *_,
ext_workspace_handle_v1 *workspace) {
static_cast<WorkspaceManager *>(data)->handle_workspace(workspace);
}
static void workspace_manager_handle_done(void *data, ext_workspace_manager_v1 *_) {
static_cast<WorkspaceManager *>(data)->handle_done();
}
static void workspace_manager_handle_finished(void *data, ext_workspace_manager_v1 *_) {
static_cast<WorkspaceManager *>(data)->handle_finished();
}
static const ext_workspace_manager_v1_listener workspace_manager_impl = {
.workspace_group = workspace_manager_handle_workspace_group,
.workspace = workspace_manager_handle_workspace,
.done = workspace_manager_handle_done,
.finished = workspace_manager_handle_finished,
};
ext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name,
uint32_t version, void *data) {
auto *workspace_manager = static_cast<ext_workspace_manager_v1 *>(
wl_registry_bind(registry, name, &ext_workspace_manager_v1_interface, version));
if (workspace_manager)
ext_workspace_manager_v1_add_listener(workspace_manager, &workspace_manager_impl, data);
else
spdlog::error("Failed to register manager");
return workspace_manager;
}
static void workspace_group_handle_capabilities(void *data, ext_workspace_group_handle_v1 *_,
uint32_t capabilities) {
static_cast<WorkspaceGroup *>(data)->handle_capabilities(capabilities);
}
static void workspace_group_handle_output_enter(void *data, ext_workspace_group_handle_v1 *_,
wl_output *output) {
static_cast<WorkspaceGroup *>(data)->handle_output_enter(output);
}
static void workspace_group_handle_output_leave(void *data, ext_workspace_group_handle_v1 *_,
wl_output *output) {
static_cast<WorkspaceGroup *>(data)->handle_output_leave(output);
}
static void workspace_group_handle_workspace_enter(void *data, ext_workspace_group_handle_v1 *_,
ext_workspace_handle_v1 *workspace) {
static_cast<WorkspaceGroup *>(data)->handle_workspace_enter(workspace);
}
static void workspace_group_handle_workspace_leave(void *data, ext_workspace_group_handle_v1 *_,
ext_workspace_handle_v1 *workspace) {
static_cast<WorkspaceGroup *>(data)->handle_workspace_leave(workspace);
}
static void workspace_group_handle_removed(void *data, ext_workspace_group_handle_v1 *_) {
static_cast<WorkspaceGroup *>(data)->handle_removed();
}
static const ext_workspace_group_handle_v1_listener workspace_group_impl = {
.capabilities = workspace_group_handle_capabilities,
.output_enter = workspace_group_handle_output_enter,
.output_leave = workspace_group_handle_output_leave,
.workspace_enter = workspace_group_handle_workspace_enter,
.workspace_leave = workspace_group_handle_workspace_leave,
.removed = workspace_group_handle_removed};
void add_workspace_group_listener(ext_workspace_group_handle_v1 *workspace_group_handle,
void *data) {
ext_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data);
}
void workspace_handle_name(void *data, struct ext_workspace_handle_v1 *_, const char *name) {
static_cast<Workspace *>(data)->handle_name(name);
}
void workspace_handle_id(void *data, struct ext_workspace_handle_v1 *_, const char *id) {
static_cast<Workspace *>(data)->handle_id(id);
}
void workspace_handle_coordinates(void *data, struct ext_workspace_handle_v1 *_,
struct wl_array *coordinates) {
std::vector<uint32_t> coords_vec;
auto coords = static_cast<uint32_t *>(coordinates->data);
for (size_t i = 0; i < coordinates->size / sizeof(uint32_t); ++i) {
coords_vec.push_back(coords[i]);
}
static_cast<Workspace *>(data)->handle_coordinates(coords_vec);
}
void workspace_handle_state(void *data, struct ext_workspace_handle_v1 *workspace_handle,
uint32_t state) {
static_cast<Workspace *>(data)->handle_state(state);
}
static void workspace_handle_capabilities(void *data,
struct ext_workspace_handle_v1 *workspace_handle,
uint32_t capabilities) {
static_cast<Workspace *>(data)->handle_capabilities(capabilities);
}
void workspace_handle_removed(void *data, struct ext_workspace_handle_v1 *workspace_handle) {
static_cast<Workspace *>(data)->handle_removed();
}
static const ext_workspace_handle_v1_listener workspace_impl = {
.id = workspace_handle_id,
.name = workspace_handle_name,
.coordinates = workspace_handle_coordinates,
.state = workspace_handle_state,
.capabilities = workspace_handle_capabilities,
.removed = workspace_handle_removed};
void add_workspace_listener(ext_workspace_handle_v1 *workspace_handle, void *data) {
ext_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data);
}
} // namespace waybar::modules::ext
+5 -2
View File
@@ -53,7 +53,6 @@ Gamemode::Gamemode(const std::string& id, const Json::Value& config)
if (config_["icon-spacing"].isUInt()) {
iconSpacing = config_["icon-spacing"].asUInt();
}
box_.set_spacing(iconSpacing);
// Whether to use icon or not
if (config_["use-icon"].isBool()) {
@@ -64,7 +63,6 @@ Gamemode::Gamemode(const std::string& id, const Json::Value& config)
if (config_["icon-size"].isUInt()) {
iconSize = config_["icon-size"].asUInt();
}
icon_.set_pixel_size(iconSize);
// Format
if (config_["format"].isString()) {
@@ -228,6 +226,11 @@ auto Gamemode::update() -> void {
iconName = DEFAULT_ICON_NAME;
}
icon_.set_from_icon_name(iconName, Gtk::ICON_SIZE_INVALID);
box_.set_spacing(iconSpacing);
icon_.set_pixel_size(iconSize);
} else {
box_.set_spacing(0);
icon_.set_pixel_size(0);
}
// Call parent update
+216
View File
@@ -0,0 +1,216 @@
#include "modules/gps.hpp"
#include <gps.h>
#include <spdlog/spdlog.h>
#include <cmath>
#include <cstdio>
// In the 80000 version of fmt library authors decided to optimize imports
// and moved declarations required for fmt::dynamic_format_arg_store in new
// header fmt/args.h
#if (FMT_VERSION >= 80000)
#include <fmt/args.h>
#else
#include <fmt/core.h>
#endif
namespace {
using namespace waybar::util;
constexpr const char* DEFAULT_FORMAT = "{mode}";
} // namespace
waybar::modules::Gps::Gps(const std::string& id, const Json::Value& config)
: ALabel(config, "gps", id, "{}", 5)
#ifdef WANT_RFKILL
,
rfkill_{RFKILL_TYPE_GPS}
#endif
{
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
if (0 != gps_open("localhost", "2947", &gps_data_)) {
throw std::runtime_error("Can't open gpsd socket");
}
if (config_["hide-disconnected"].isBool()) {
hideDisconnected = config_["hide-disconnected"].asBool();
}
if (config_["hide-no-fix"].isBool()) {
hideNoFix = config_["hide-no-fix"].asBool();
}
gps_thread_ = [this] {
dp.emit();
gps_stream(&gps_data_, WATCH_ENABLE, NULL);
int last_gps_mode = 0;
while (gps_waiting(&gps_data_, 5000000)) {
if (gps_read(&gps_data_, NULL, 0) == -1) {
throw std::runtime_error("Can't read data from gpsd.");
}
if (MODE_SET != (MODE_SET & gps_data_.set)) {
// did not even get mode, nothing to see here
continue;
}
if (gps_data_.fix.mode != last_gps_mode) {
// significant update
dp.emit();
}
last_gps_mode = gps_data_.fix.mode;
}
};
#ifdef WANT_RFKILL
rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Gps::update)));
#endif
}
const std::string waybar::modules::Gps::getFixModeName() const {
switch (gps_data_.fix.mode) {
case MODE_NO_FIX:
return "fix-none";
case MODE_2D:
return "fix-2d";
case MODE_3D:
return "fix-3d";
default:
#ifdef WANT_RFKILL
if (rfkill_.getState()) return "disabled";
#endif
return "disconnected";
}
}
const std::string waybar::modules::Gps::getFixModeString() const {
switch (gps_data_.fix.mode) {
case MODE_NO_FIX:
return "No fix";
case MODE_2D:
return "2D Fix";
case MODE_3D:
return "3D Fix";
default:
return "Disconnected";
}
}
const std::string waybar::modules::Gps::getFixStatusString() const {
switch (gps_data_.fix.status) {
case STATUS_GPS:
return "GPS";
case STATUS_DGPS:
return "DGPS";
case STATUS_RTK_FIX:
return "RTK Fixed";
case STATUS_RTK_FLT:
return "RTK Float";
case STATUS_DR:
return "Dead Reckoning";
case STATUS_GNSSDR:
return "GNSS + Dead Reckoning";
case STATUS_TIME:
return "Time Only";
case STATUS_PPS_FIX:
return "PPS Fix";
default:
#ifdef WANT_RFKILL
if (rfkill_.getState()) return "Disabled";
#endif
return "Unknown";
}
}
auto waybar::modules::Gps::update() -> void {
sleep(0); // Wait for gps status change
if ((gps_data_.fix.mode == MODE_NOT_SEEN && hideDisconnected) ||
(gps_data_.fix.mode == MODE_NO_FIX && hideNoFix)) {
event_box_.set_visible(false);
return;
}
// Show the module
if (!event_box_.get_visible()) event_box_.set_visible(true);
std::string tooltip_format;
if (!alt_) {
auto state = getFixModeName();
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();
} else if (config_["format"].isString()) {
default_format_ = config_["format"].asString();
} else {
default_format_ = DEFAULT_FORMAT;
}
if (config_["tooltip-format-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + state].asString();
}
if (!label_.get_style_context()->has_class(state)) {
label_.get_style_context()->add_class(state);
}
format_ = default_format_;
state_ = state;
}
auto format = format_;
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(fmt::arg("mode", getFixModeString()));
store.push_back(fmt::arg("status", getFixStatusString()));
store.push_back(fmt::arg("latitude", gps_data_.fix.latitude));
store.push_back(fmt::arg("latitude_error", gps_data_.fix.epy));
store.push_back(fmt::arg("longitude", gps_data_.fix.longitude));
store.push_back(fmt::arg("longitude_error", gps_data_.fix.epx));
store.push_back(fmt::arg("altitude_hae", gps_data_.fix.altHAE));
store.push_back(fmt::arg("altitude_msl", gps_data_.fix.altMSL));
store.push_back(fmt::arg("altitude_error", gps_data_.fix.epv));
store.push_back(fmt::arg("speed", gps_data_.fix.speed));
store.push_back(fmt::arg("speed_error", gps_data_.fix.eps));
store.push_back(fmt::arg("climb", gps_data_.fix.climb));
store.push_back(fmt::arg("climb_error", gps_data_.fix.epc));
store.push_back(fmt::arg("satellites_used", gps_data_.satellites_used));
store.push_back(fmt::arg("satellites_visible", gps_data_.satellites_visible));
auto text = fmt::vformat(format, store);
if (tooltipEnabled()) {
if (tooltip_format.empty() && config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
auto tooltip_text = fmt::vformat(tooltip_format, store);
if (label_.get_tooltip_text() != tooltip_text) {
label_.set_tooltip_markup(tooltip_text);
}
} else if (label_.get_tooltip_text() != text) {
label_.set_tooltip_markup(text);
}
}
label_.set_markup(text);
// Call parent update
ALabel::update();
}
waybar::modules::Gps::~Gps() {
gps_stream(&gps_data_, WATCH_DISABLE, NULL);
gps_close(&gps_data_);
}
+5 -2
View File
@@ -46,9 +46,14 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
IPC::IPC() {
// will start IPC and relay events to parseIPC
ipcThread_ = std::thread([this]() { socketListener(); });
socketOwnerPid_ = getpid();
}
IPC::~IPC() {
// Do no stop Hyprland IPC if a child process (with successful fork() but
// failed exec()) exits.
if (getpid() != socketOwnerPid_) return;
running_ = false;
spdlog::info("Hyprland IPC stopping...");
if (socketfd_ != -1) {
@@ -78,8 +83,6 @@ void IPC::socketListener() {
return;
}
if (!modulesReady) return;
spdlog::info("Hyprland IPC starting");
struct sockaddr_un addr;
+12 -3
View File
@@ -11,8 +11,6 @@ namespace waybar::modules::hyprland {
Language::Language(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "language", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
modulesReady = true;
// get the active layout when open
initLanguage();
@@ -66,7 +64,18 @@ auto Language::update() -> void {
void Language::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lg(mutex_);
std::string kbName(begin(ev) + ev.find_last_of('>') + 1, begin(ev) + ev.find_first_of(','));
auto layoutName = ev.substr(ev.find_last_of(',') + 1);
// Last comma before variants parenthesis, eg:
// activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,
// with dead keys)
std::string beforeParenthesis;
auto parenthesisPos = ev.find_last_of('(');
if (parenthesisPos == std::string::npos) {
beforeParenthesis = ev;
} else {
beforeParenthesis = std::string(begin(ev), begin(ev) + parenthesisPos);
}
auto layoutName = ev.substr(beforeParenthesis.find_last_of(',') + 1);
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
return; // ignore
+2 -7
View File
@@ -2,21 +2,17 @@
#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), m_ipc(IPC::inst()) {
modulesReady = true;
parseConfig(config);
label_.hide();
ALabel::update();
// Displays widget immediately if always_on_ assuming default submap
// Needs an actual way to retrive current submap on startup
// Needs an actual way to retrieve current submap on startup
if (always_on_) {
submap_ = default_submap_;
label_.get_style_context()->add_class(submap_);
@@ -68,8 +64,7 @@ void Submap::onEvent(const std::string& ev) {
return;
}
auto submapName = ev.substr(ev.find_last_of('>') + 1);
submapName = waybar::util::sanitize_string(submapName);
auto submapName = ev.substr(ev.find_first_of('>') + 2);
if (!submap_.empty()) {
label_.get_style_context()->remove_class(submap_);
-1
View File
@@ -21,7 +21,6 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
modulesReady = true;
separateOutputs_ = config["separate-outputs"].asBool();
// register for hyprland ipc
+143
View File
@@ -0,0 +1,143 @@
#include "modules/hyprland/windowcount.hpp"
#include <glibmm/fileutils.h>
#include <glibmm/keyfile.h>
#include <glibmm/miscutils.h>
#include <spdlog/spdlog.h>
#include <algorithm>
#include <vector>
#include "modules/hyprland/backend.hpp"
namespace waybar::modules::hyprland {
WindowCount::WindowCount(const std::string& id, const Bar& bar, const Json::Value& config)
: AAppIconLabel(config, "windowcount", id, "{count}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
separateOutputs_ =
config.isMember("separate-outputs") ? config["separate-outputs"].asBool() : true;
queryActiveWorkspace();
update();
dp.emit();
// register for hyprland ipc
m_ipc.registerForIPC("fullscreen", this);
m_ipc.registerForIPC("workspace", this);
m_ipc.registerForIPC("focusedmon", this);
m_ipc.registerForIPC("openwindow", this);
m_ipc.registerForIPC("closewindow", this);
m_ipc.registerForIPC("movewindow", this);
}
WindowCount::~WindowCount() {
m_ipc.unregisterForIPC(this);
// wait for possible event handler to finish
std::lock_guard<std::mutex> lg(mutex_);
}
auto WindowCount::update() -> void {
std::lock_guard<std::mutex> lg(mutex_);
std::string format = config_["format"].asString();
std::string formatEmpty = config_["format-empty"].asString();
std::string formatWindowed = config_["format-windowed"].asString();
std::string formatFullscreen = config_["format-fullscreen"].asString();
setClass("empty", workspace_.windows == 0);
setClass("fullscreen", workspace_.hasfullscreen);
if (workspace_.windows == 0 && !formatEmpty.empty()) {
label_.set_markup(fmt::format(fmt::runtime(formatEmpty), workspace_.windows));
} else if (!workspace_.hasfullscreen && !formatWindowed.empty()) {
label_.set_markup(fmt::format(fmt::runtime(formatWindowed), workspace_.windows));
} else if (workspace_.hasfullscreen && !formatFullscreen.empty()) {
label_.set_markup(fmt::format(fmt::runtime(formatFullscreen), workspace_.windows));
} else if (!format.empty()) {
label_.set_markup(fmt::format(fmt::runtime(format), workspace_.windows));
} else {
label_.set_text(fmt::format("{}", workspace_.windows));
}
label_.show();
AAppIconLabel::update();
}
auto WindowCount::getActiveWorkspace() -> Workspace {
const auto workspace = m_ipc.getSocket1JsonReply("activeworkspace");
if (workspace.isObject()) {
return Workspace::parse(workspace);
}
return {};
}
auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspace {
const auto monitors = m_ipc.getSocket1JsonReply("monitors");
if (monitors.isArray()) {
auto monitor = std::ranges::find_if(
monitors, [&](Json::Value monitor) { return monitor["name"] == monitorName; });
if (monitor == std::end(monitors)) {
spdlog::warn("Monitor not found: {}", monitorName);
return Workspace{
.id = -1,
.windows = 0,
.hasfullscreen = false,
};
}
const int id = (*monitor)["activeWorkspace"]["id"].asInt();
const auto workspaces = m_ipc.getSocket1JsonReply("workspaces");
if (workspaces.isArray()) {
auto workspace = std::ranges::find_if(
workspaces, [&](Json::Value workspace) { return workspace["id"] == id; });
if (workspace == std::end(workspaces)) {
spdlog::warn("No workspace with id {}", id);
return Workspace{
.id = -1,
.windows = 0,
.hasfullscreen = false,
};
}
return Workspace::parse(*workspace);
};
};
return {};
}
auto WindowCount::Workspace::parse(const Json::Value& value) -> WindowCount::Workspace {
return Workspace{
.id = value["id"].asInt(),
.windows = value["windows"].asInt(),
.hasfullscreen = value["hasfullscreen"].asBool(),
};
}
void WindowCount::queryActiveWorkspace() {
std::lock_guard<std::mutex> lg(mutex_);
if (separateOutputs_) {
workspace_ = getActiveWorkspace(this->bar_.output->name);
} else {
workspace_ = getActiveWorkspace();
}
}
void WindowCount::onEvent(const std::string& ev) {
queryActiveWorkspace();
dp.emit();
}
void WindowCount::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
@@ -20,7 +20,7 @@ WindowCreationPayload::WindowCreationPayload(Json::Value const &client_data)
}
WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
WindowAddress window_address, std::string window_repr)
WindowAddress window_address, WindowRepr window_repr)
: m_window(std::move(window_repr)),
m_windowAddress(std::move(window_address)),
m_workspaceName(std::move(workspace_name)) {
@@ -30,10 +30,11 @@ WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
WindowAddress window_address, std::string window_class,
std::string window_title)
std::string window_title, bool is_active)
: m_window(std::make_pair(std::move(window_class), std::move(window_title))),
m_windowAddress(std::move(window_address)),
m_workspaceName(std::move(workspace_name)) {
m_workspaceName(std::move(workspace_name)),
m_isActive(is_active) {
clearAddr();
clearWorkspaceName();
}
@@ -88,17 +89,18 @@ bool WindowCreationPayload::isEmpty(Workspaces &workspace_manager) {
int WindowCreationPayload::incrementTimeSpentUncreated() { return m_timeSpentUncreated++; }
void WindowCreationPayload::moveToWorksace(std::string &new_workspace_name) {
void WindowCreationPayload::moveToWorkspace(std::string &new_workspace_name) {
m_workspaceName = new_workspace_name;
}
std::string WindowCreationPayload::repr(Workspaces &workspace_manager) {
WindowRepr WindowCreationPayload::repr(Workspaces &workspace_manager) {
if (std::holds_alternative<Repr>(m_window)) {
return std::get<Repr>(m_window);
}
if (std::holds_alternative<ClassAndTitle>(m_window)) {
auto [window_class, window_title] = std::get<ClassAndTitle>(m_window);
return workspace_manager.getRewrite(window_class, window_title);
auto const &[window_class, window_title] = std::get<ClassAndTitle>(m_window);
return {m_windowAddress, window_class, window_title,
workspace_manager.getRewrite(window_class, window_title), m_isActive};
}
// Unreachable
spdlog::error("WorkspaceWindow::repr: Unreachable");
+193 -29
View File
@@ -6,6 +6,8 @@
#include <utility>
#include "modules/hyprland/workspaces.hpp"
#include "util/command.hpp"
#include "util/icon_loader.hpp"
namespace waybar::modules::hyprland {
@@ -32,7 +34,12 @@ Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_ma
false);
m_button.set_relief(Gtk::RELIEF_NONE);
m_content.set_center_widget(m_label);
if (m_workspaceManager.enableTaskbar()) {
m_content.set_orientation(m_workspaceManager.taskbarOrientation());
m_content.pack_start(m_labelBefore, false, false);
} else {
m_content.set_center_widget(m_labelBefore);
}
m_button.add(m_content);
initializeWindowMap(clients_data);
@@ -47,9 +54,14 @@ void addOrRemoveClass(const Glib::RefPtr<Gtk::StyleContext> &context, bool condi
}
}
std::optional<std::string> Workspace::closeWindow(WindowAddress const &addr) {
if (m_windowMap.contains(addr)) {
return removeWindow(addr);
std::optional<WindowRepr> Workspace::closeWindow(WindowAddress const &addr) {
auto it = std::ranges::find_if(m_windowMap,
[&addr](const auto &window) { return window.address == addr; });
// If the vector contains the address, remove it and return the window representation
if (it != m_windowMap.end()) {
WindowRepr windowRepr = *it;
m_windowMap.erase(it);
return windowRepr;
}
return std::nullopt;
}
@@ -91,30 +103,55 @@ void Workspace::initializeWindowMap(const Json::Value &clients_data) {
}
}
void Workspace::insertWindow(WindowCreationPayload create_window_paylod) {
if (!create_window_paylod.isEmpty(m_workspaceManager)) {
auto repr = create_window_paylod.repr(m_workspaceManager);
void Workspace::setActiveWindow(WindowAddress const &addr) {
std::optional<long> activeIdx;
for (size_t i = 0; i < m_windowMap.size(); ++i) {
auto &window = m_windowMap[i];
bool isActive = (window.address == addr);
window.setActive(isActive);
if (isActive) {
activeIdx = i;
}
}
if (!repr.empty()) {
m_windowMap[create_window_paylod.getAddress()] = repr;
auto activeWindowPos = m_workspaceManager.activeWindowPosition();
if (activeIdx.has_value() && activeWindowPos != Workspaces::ActiveWindowPosition::NONE) {
auto window = std::move(m_windowMap[*activeIdx]);
m_windowMap.erase(m_windowMap.begin() + *activeIdx);
if (activeWindowPos == Workspaces::ActiveWindowPosition::FIRST) {
m_windowMap.insert(m_windowMap.begin(), std::move(window));
} else if (activeWindowPos == Workspaces::ActiveWindowPosition::LAST) {
m_windowMap.emplace_back(std::move(window));
}
}
}
void Workspace::insertWindow(WindowCreationPayload create_window_payload) {
if (!create_window_payload.isEmpty(m_workspaceManager)) {
auto repr = create_window_payload.repr(m_workspaceManager);
if (!repr.empty() || m_workspaceManager.enableTaskbar()) {
auto addr = create_window_payload.getAddress();
auto it = std::ranges::find_if(
m_windowMap, [&addr](const auto &window) { return window.address == addr; });
// If the vector contains the address, update the window representation, otherwise insert it
if (it != m_windowMap.end()) {
*it = repr;
} else {
m_windowMap.emplace_back(repr);
}
}
}
};
bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_paylod) {
if (create_window_paylod.getWorkspaceName() == name()) {
insertWindow(create_window_paylod);
bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_payload) {
if (create_window_payload.getWorkspaceName() == name()) {
insertWindow(create_window_payload);
return true;
}
return false;
}
std::string Workspace::removeWindow(WindowAddress const &addr) {
std::string windowRepr = m_windowMap[addr];
m_windowMap.erase(addr);
return windowRepr;
}
std::string &Workspace::selectIcon(std::map<std::string, std::string> &icons_map) {
spdlog::trace("Selecting icon for workspace {}", name());
if (isUrgent()) {
@@ -172,7 +209,11 @@ std::string &Workspace::selectIcon(std::map<std::string, std::string> &icons_map
return m_name;
}
void Workspace::update(const std::string &format, const std::string &icon) {
void Workspace::update(const std::string &workspace_icon) {
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
m_button.hide();
return;
}
// clang-format off
if (this->m_workspaceManager.activeOnly() && \
!this->isActive() && \
@@ -200,21 +241,144 @@ void Workspace::update(const std::string &format, const std::string &icon) {
addOrRemoveClass(styleContext, m_workspaceManager.getBarOutput() == output(), "hosting-monitor");
std::string windows;
auto windowSeparator = m_workspaceManager.getWindowSeparator();
// Optimization: The {windows} substitution string is only possible if the taskbar is disabled, no
// need to compute this if enableTaskbar() is true
if (!m_workspaceManager.enableTaskbar()) {
auto windowSeparator = m_workspaceManager.getWindowSeparator();
bool isNotFirst = false;
bool isNotFirst = false;
for (auto &[_pid, window_repr] : m_windowMap) {
if (isNotFirst) {
windows.append(windowSeparator);
for (const auto &window_repr : m_windowMap) {
if (isNotFirst) {
windows.append(windowSeparator);
}
isNotFirst = true;
windows.append(window_repr.repr_rewrite);
}
isNotFirst = true;
windows.append(window_repr);
}
m_label.set_markup(fmt::format(fmt::runtime(format), fmt::arg("id", id()),
fmt::arg("name", name()), fmt::arg("icon", icon),
fmt::arg("windows", windows)));
auto formatBefore = m_workspaceManager.formatBefore();
m_labelBefore.set_markup(fmt::format(fmt::runtime(formatBefore), fmt::arg("id", id()),
fmt::arg("name", name()), fmt::arg("icon", workspace_icon),
fmt::arg("windows", windows)));
m_labelBefore.get_style_context()->add_class("workspace-label");
if (m_workspaceManager.enableTaskbar()) {
updateTaskbar(workspace_icon);
}
}
bool Workspace::isEmpty() const {
auto ignore_list = m_workspaceManager.getIgnoredWindows();
if (ignore_list.empty()) {
return m_windows == 0;
}
// If there are windows but they are all ignored, consider the workspace empty
return std::all_of(
m_windowMap.begin(), m_windowMap.end(),
[this, &ignore_list](const auto &window_repr) { return shouldSkipWindow(window_repr); });
}
void Workspace::updateTaskbar(const std::string &workspace_icon) {
for (auto child : m_content.get_children()) {
if (child != &m_labelBefore) {
m_content.remove(*child);
}
}
bool isFirst = true;
auto processWindow = [&](const WindowRepr &window_repr) {
if (shouldSkipWindow(window_repr)) {
return; // skip
}
if (isFirst) {
isFirst = false;
} else if (m_workspaceManager.getWindowSeparator() != "") {
auto windowSeparator = Gtk::make_managed<Gtk::Label>(m_workspaceManager.getWindowSeparator());
m_content.pack_start(*windowSeparator, false, false);
windowSeparator->show();
}
auto window_box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL);
window_box->set_tooltip_text(window_repr.window_title);
window_box->get_style_context()->add_class("taskbar-window");
if (window_repr.isActive) {
window_box->get_style_context()->add_class("active");
}
auto event_box = Gtk::manage(new Gtk::EventBox());
event_box->add(*window_box);
if (m_workspaceManager.onClickWindow() != "") {
event_box->signal_button_press_event().connect(
sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address));
}
auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()),
fmt::arg("title", window_repr.window_title));
if (!text_before.empty()) {
auto window_label_before = Gtk::make_managed<Gtk::Label>(text_before);
window_box->pack_start(*window_label_before, true, true);
}
if (m_workspaceManager.taskbarWithIcon()) {
auto app_info_ = IconLoader::get_app_info_from_app_id_list(window_repr.window_class);
int icon_size = m_workspaceManager.taskbarIconSize();
auto window_icon = Gtk::make_managed<Gtk::Image>();
m_workspaceManager.iconLoader().image_load_icon(*window_icon, app_info_, icon_size);
window_box->pack_start(*window_icon, false, false);
}
auto text_after = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatAfter()),
fmt::arg("title", window_repr.window_title));
if (!text_after.empty()) {
auto window_label_after = Gtk::make_managed<Gtk::Label>(text_after);
window_box->pack_start(*window_label_after, true, true);
}
m_content.pack_start(*event_box, true, false);
event_box->show_all();
};
if (m_workspaceManager.taskbarReverseDirection()) {
for (auto it = m_windowMap.rbegin(); it != m_windowMap.rend(); ++it) {
processWindow(*it);
}
} else {
for (const auto &window_repr : m_windowMap) {
processWindow(window_repr);
}
}
auto formatAfter = m_workspaceManager.formatAfter();
if (!formatAfter.empty()) {
m_labelAfter.set_markup(fmt::format(fmt::runtime(formatAfter), fmt::arg("id", id()),
fmt::arg("name", name()),
fmt::arg("icon", workspace_icon)));
m_content.pack_end(m_labelAfter, false, false);
m_labelAfter.show();
}
}
bool Workspace::handleClick(const GdkEventButton *event_button, WindowAddress const &addr) const {
if (event_button->type == GDK_BUTTON_PRESS) {
std::string command = std::regex_replace(m_workspaceManager.onClickWindow(),
std::regex("\\{address\\}"), "0x" + addr);
command = std::regex_replace(command, std::regex("\\{button\\}"),
std::to_string(event_button->button));
auto res = util::command::execNoRead(command);
if (res.exit_code != 0) {
spdlog::error("Failed to execute {}: {}", command, res.out);
}
}
return true;
}
bool Workspace::shouldSkipWindow(const WindowRepr &window_repr) const {
auto ignore_list = m_workspaceManager.getIgnoredWindows();
auto it = std::ranges::find_if(ignore_list, [&window_repr](const auto &ignoreItem) {
return std::regex_match(window_repr.window_class, ignoreItem) ||
std::regex_match(window_repr.window_title, ignoreItem);
});
return it != ignore_list.end();
}
} // namespace waybar::modules::hyprland
+186 -26
View File
@@ -10,6 +10,7 @@
#include <utility>
#include "util/regex_collection.hpp"
#include "util/string.hpp"
namespace waybar::modules::hyprland {
@@ -18,7 +19,6 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
m_bar(bar),
m_box(bar.orientation, 0),
m_ipc(IPC::inst()) {
modulesReady = true;
parseConfig(config);
m_box.set_name("workspaces");
@@ -65,14 +65,17 @@ Json::Value Workspaces::createMonitorWorkspaceData(std::string const &name,
void Workspaces::createWorkspace(Json::Value const &workspace_data,
Json::Value const &clients_data) {
auto workspaceName = workspace_data["name"].asString();
auto workspaceId = workspace_data["id"].asInt();
spdlog::debug("Creating workspace {}", workspaceName);
// avoid recreating existing workspaces
auto workspace =
std::ranges::find_if(m_workspaces, [workspaceName](std::unique_ptr<Workspace> const &w) {
return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) ||
workspaceName == w->name();
});
auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr<Workspace> const &w) {
if (workspaceId > 0) {
return w->id() == workspaceId;
}
return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) ||
workspaceName == w->name();
});
if (workspace != m_workspaces.end()) {
// don't recreate workspace, but update persistency if necessary
@@ -253,10 +256,8 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs
// value is an array => create defined workspaces for this monitor
if (canCreate) {
for (const Json::Value &workspace : value) {
if (workspace.isInt()) {
spdlog::debug("Creating workspace {} on monitor {}", workspace, currentMonitor);
persistentWorkspacesToCreate.emplace_back(std::to_string(workspace.asInt()));
}
spdlog::debug("Creating workspace {} on monitor {}", workspace, currentMonitor);
persistentWorkspacesToCreate.emplace_back(workspace.asString());
}
} else {
// key is the workspace and value is array of monitors to create on
@@ -292,8 +293,13 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c
if (!rule["persistent"].asBool()) {
continue;
}
auto const &workspace = rule.isMember("defaultName") ? rule["defaultName"].asString()
: rule["workspaceString"].asString();
auto workspace = rule.isMember("defaultName") ? rule["defaultName"].asString()
: rule["workspaceString"].asString();
// The prefix "name:" cause mismatches with workspace names taken anywhere else.
if (workspace.starts_with("name:")) {
workspace = workspace.substr(5);
}
auto const &monitor = rule["monitor"].asString();
// create this workspace persistently if:
// 1. the allOutputs config option is enabled
@@ -340,6 +346,8 @@ void Workspaces::onEvent(const std::string &ev) {
onWorkspaceRenamed(payload);
} else if (eventName == "windowtitlev2") {
onWindowTitleEvent(payload);
} else if (eventName == "activewindowv2") {
onActiveWindowChanged(payload);
} else if (eventName == "configreloaded") {
onConfigReloaded();
}
@@ -489,12 +497,14 @@ void Workspaces::onWindowOpened(std::string const &payload) {
std::string windowTitle = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx);
m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle);
bool isActive = m_currentActiveWindowAddress == windowAddress;
m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle, isActive);
}
void Workspaces::onWindowClosed(std::string const &addr) {
spdlog::trace("Window closed: {}", addr);
updateWindowCount();
m_orphanWindowMap.erase(addr);
for (auto &workspace : m_workspaces) {
if (workspace->closeWindow(addr)) {
break;
@@ -507,13 +517,13 @@ void Workspaces::onWindowMoved(std::string const &payload) {
updateWindowCount();
auto [windowAddress, _, workspaceName] = splitTriplePayload(payload);
std::string windowRepr;
WindowRepr windowRepr;
// If the window was still queued to be created, just change its destination
// and exit
for (auto &window : m_windowsToCreate) {
if (window.getAddress() == windowAddress) {
window.moveToWorksace(workspaceName);
window.moveToWorkspace(workspaceName);
return;
}
}
@@ -533,6 +543,7 @@ void Workspaces::onWindowMoved(std::string const &payload) {
// ...and then add it to the new workspace
if (!windowRepr.empty()) {
m_orphanWindowMap.erase(windowAddress);
m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowRepr);
}
}
@@ -558,9 +569,10 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
(*windowWorkspace)->insertWindow(std::move(wcp));
};
} else {
auto queuedWindow = std::ranges::find_if(m_windowsToCreate, [payload](auto &windowPayload) {
return windowPayload.getAddress() == payload;
});
auto queuedWindow =
std::ranges::find_if(m_windowsToCreate, [&windowAddress](auto &windowPayload) {
return windowPayload.getAddress() == windowAddress;
});
// If the window was queued, rename it in the queue
if (queuedWindow != m_windowsToCreate.end()) {
@@ -571,7 +583,7 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
if (inserter.has_value()) {
Json::Value clientsData = m_ipc.getSocket1JsonReply("clients");
std::string jsonWindowAddress = fmt::format("0x{}", payload);
std::string jsonWindowAddress = fmt::format("0x{}", windowAddress);
auto client = std::ranges::find_if(clientsData, [jsonWindowAddress](auto &client) {
return client["address"].asString() == jsonWindowAddress;
@@ -583,6 +595,21 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
}
}
void Workspaces::onActiveWindowChanged(WindowAddress const &activeWindowAddress) {
spdlog::trace("Active window changed: {}", activeWindowAddress);
m_currentActiveWindowAddress = activeWindowAddress;
for (auto &[address, window] : m_orphanWindowMap) {
window.setActive(address == activeWindowAddress);
}
for (auto const &workspace : m_workspaces) {
workspace->setActiveWindow(activeWindowAddress);
}
for (auto &window : m_windowsToCreate) {
window.setActive(window.getAddress() == activeWindowAddress);
}
}
void Workspaces::onConfigReloaded() {
spdlog::info("Hyprland config reloaded, reinitializing hyprland/workspaces module...");
init();
@@ -590,8 +617,9 @@ void Workspaces::onConfigReloaded() {
auto Workspaces::parseConfig(const Json::Value &config) -> void {
const auto &configFormat = config["format"];
m_format = configFormat.isString() ? configFormat.asString() : "{name}";
m_withIcon = m_format.find("{icon}") != std::string::npos;
m_formatBefore = configFormat.isString() ? configFormat.asString() : "{name}";
m_withIcon = m_formatBefore.find("{icon}") != std::string::npos;
auto withWindows = m_formatBefore.find("{windows}") != std::string::npos;
if (m_withIcon && m_iconsMap.empty()) {
populateIconsMap(config["format-icons"]);
@@ -600,6 +628,7 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void {
populateBoolConfig(config, "all-outputs", m_allOutputs);
populateBoolConfig(config, "show-special", m_showSpecial);
populateBoolConfig(config, "special-visible-only", m_specialVisibleOnly);
populateBoolConfig(config, "persistent-only", m_persistentOnly);
populateBoolConfig(config, "active-only", m_activeOnly);
populateBoolConfig(config, "move-to-monitor", m_moveToMonitor);
@@ -608,6 +637,15 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void {
populateIgnoreWorkspacesConfig(config);
populateFormatWindowSeparatorConfig(config);
populateWindowRewriteConfig(config);
if (withWindows) {
populateWorkspaceTaskbarConfig(config);
}
if (m_enableTaskbar) {
auto parts = split(m_formatBefore, "{windows}", 1);
m_formatBefore = parts[0];
m_formatAfter = parts.size() > 1 ? parts[1] : "";
}
}
auto Workspaces::populateIconsMap(const Json::Value &formatIcons) -> void {
@@ -680,6 +718,77 @@ auto Workspaces::populateWindowRewriteConfig(const Json::Value &config) -> void
[this](std::string &window_rule) { return windowRewritePriorityFunction(window_rule); });
}
auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> void {
const auto &workspaceTaskbar = config["workspace-taskbar"];
if (!workspaceTaskbar.isObject()) {
spdlog::debug("workspace-taskbar is not defined or is not an object, using default rules.");
return;
}
populateBoolConfig(workspaceTaskbar, "enable", m_enableTaskbar);
populateBoolConfig(workspaceTaskbar, "update-active-window", m_updateActiveWindow);
populateBoolConfig(workspaceTaskbar, "reverse-direction", m_taskbarReverseDirection);
if (workspaceTaskbar["format"].isString()) {
/* The user defined a format string, use it */
std::string format = workspaceTaskbar["format"].asString();
m_taskbarWithTitle = format.find("{title") != std::string::npos; /* {title} or {title.length} */
auto parts = split(format, "{icon}", 1);
m_taskbarFormatBefore = parts[0];
if (parts.size() > 1) {
m_taskbarWithIcon = true;
m_taskbarFormatAfter = parts[1];
}
} else {
/* The default is to only show the icon */
m_taskbarWithIcon = true;
}
auto iconTheme = workspaceTaskbar["icon-theme"];
if (iconTheme.isArray()) {
for (auto &c : iconTheme) {
m_iconLoader.add_custom_icon_theme(c.asString());
}
} else if (iconTheme.isString()) {
m_iconLoader.add_custom_icon_theme(iconTheme.asString());
}
if (workspaceTaskbar["icon-size"].isInt()) {
m_taskbarIconSize = workspaceTaskbar["icon-size"].asInt();
}
if (workspaceTaskbar["orientation"].isString() &&
toLower(workspaceTaskbar["orientation"].asString()) == "vertical") {
m_taskbarOrientation = Gtk::ORIENTATION_VERTICAL;
}
if (workspaceTaskbar["on-click-window"].isString()) {
m_onClickWindow = workspaceTaskbar["on-click-window"].asString();
}
if (workspaceTaskbar["ignore-list"].isArray()) {
for (auto &windowRegex : workspaceTaskbar["ignore-list"]) {
std::string ruleString = windowRegex.asString();
try {
m_ignoreWindows.emplace_back(ruleString, std::regex_constants::icase);
} catch (const std::regex_error &e) {
spdlog::error("Invalid rule {}: {}", ruleString, e.what());
}
}
}
if (workspaceTaskbar["active-window-position"].isString()) {
auto posStr = workspaceTaskbar["active-window-position"].asString();
try {
m_activeWindowPosition =
m_activeWindowEnumParser.parseStringToEnum(posStr, m_activeWindowPositionMap);
} catch (const std::invalid_argument &e) {
spdlog::warn(
"Invalid string representation for active-window-position. Falling back to 'none'.");
m_activeWindowPosition = ActiveWindowPosition::NONE;
}
}
}
void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payload) {
if (!create_window_payload.isEmpty(*this)) {
m_orphanWindowMap[create_window_payload.getAddress()] = create_window_payload.repr(*this);
@@ -700,12 +809,18 @@ auto Workspaces::registerIpc() -> void {
m_ipc.registerForIPC("urgent", this);
m_ipc.registerForIPC("configreloaded", this);
if (windowRewriteConfigUsesTitle()) {
if (windowRewriteConfigUsesTitle() || m_taskbarWithTitle) {
spdlog::info(
"Registering for Hyprland's 'windowtitlev2' events because a user-defined window "
"rewrite rule uses the 'title' field.");
m_ipc.registerForIPC("windowtitlev2", this);
}
if (m_updateActiveWindow) {
spdlog::info(
"Registering for Hyprland's 'activewindowv2' events because 'update-active-window' is set "
"to true.");
m_ipc.registerForIPC("activewindowv2", this);
}
}
void Workspaces::removeWorkspacesToRemove() {
@@ -770,6 +885,40 @@ void Workspaces::setCurrentMonitorId() {
}
}
void Workspaces::sortSpecialCentered() {
std::vector<std::unique_ptr<Workspace>> specialWorkspaces;
std::vector<std::unique_ptr<Workspace>> hiddenWorkspaces;
std::vector<std::unique_ptr<Workspace>> normalWorkspaces;
for (auto &workspace : m_workspaces) {
if (workspace->isSpecial()) {
specialWorkspaces.push_back(std::move(workspace));
} else {
if (workspace->button().is_visible()) {
normalWorkspaces.push_back(std::move(workspace));
} else {
hiddenWorkspaces.push_back(std::move(workspace));
}
}
}
m_workspaces.clear();
size_t center = normalWorkspaces.size() / 2;
m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(normalWorkspaces.begin()),
std::make_move_iterator(normalWorkspaces.begin() + center));
m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(specialWorkspaces.begin()),
std::make_move_iterator(specialWorkspaces.end()));
m_workspaces.insert(m_workspaces.end(),
std::make_move_iterator(normalWorkspaces.begin() + center),
std::make_move_iterator(normalWorkspaces.end()));
m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(hiddenWorkspaces.begin()),
std::make_move_iterator(hiddenWorkspaces.end()));
}
void Workspaces::sortWorkspaces() {
std::ranges::sort( //
m_workspaces, [&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
@@ -828,6 +977,9 @@ void Workspaces::sortWorkspaces() {
// Return a default value if none of the cases match.
return isNameLess; // You can adjust this to your specific needs.
});
if (m_sortBy == SortMethod::SPECIAL_CENTERED) {
this->sortSpecialCentered();
}
for (size_t i = 0; i < m_workspaces.size(); ++i) {
m_box.reorder_child(m_workspaces[i]->button(), i);
@@ -860,7 +1012,7 @@ auto Workspaces::update() -> void {
void Workspaces::updateWindowCount() {
const Json::Value workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
for (auto &workspace : m_workspaces) {
for (auto const &workspace : m_workspaces) {
auto workspaceJson = std::ranges::find_if(workspacesJson, [&](Json::Value const &x) {
return x["name"].asString() == workspace->name() ||
(workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name());
@@ -906,9 +1058,17 @@ bool Workspaces::updateWindowsToCreate() {
void Workspaces::updateWorkspaceStates() {
const std::vector<int> visibleWorkspaces = getVisibleWorkspaces();
auto updatedWorkspaces = m_ipc.getSocket1JsonReply("workspaces");
auto currentWorkspace = m_ipc.getSocket1JsonReply("activeworkspace");
std::string currentWorkspaceName =
currentWorkspace.isMember("name") ? currentWorkspace["name"].asString() : "";
for (auto &workspace : m_workspaces) {
bool isActiveByName =
!currentWorkspaceName.empty() && workspace->name() == currentWorkspaceName;
workspace->setActive(
workspace->id() == m_activeWorkspaceId ||
workspace->id() == m_activeWorkspaceId || isActiveByName ||
(workspace->isSpecial() && workspace->name() == m_activeSpecialWorkspaceName));
if (workspace->isActive() && workspace->isUrgent()) {
workspace->setUrgent(false);
@@ -927,7 +1087,7 @@ void Workspaces::updateWorkspaceStates() {
if (updatedWorkspace != updatedWorkspaces.end()) {
workspace->setOutput((*updatedWorkspace)["monitor"].asString());
}
workspace->update(m_format, workspaceIcon);
workspace->update(workspaceIcon);
}
}
@@ -981,7 +1141,7 @@ std::optional<int> Workspaces::parseWorkspaceId(std::string const &workspaceIdSt
try {
return workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr);
} catch (std::exception const &e) {
spdlog::error("Failed to parse workspace ID: {}", e.what());
spdlog::debug("Workspace \"{}\" is not bound to an id: {}", workspaceIdStr, e.what());
return std::nullopt;
}
}
+10 -5
View File
@@ -14,14 +14,20 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
size_ = config["size"].asInt();
interval_ = config_["interval"].asInt();
interval_ = config_["interval"] == "once"
? std::chrono::milliseconds::max()
: std::chrono::milliseconds(std::max(
1L, // Minimum 1ms due to millisecond precision
static_cast<long>(
(config_["interval"].isNumeric() ? config_["interval"].asDouble() : 0) *
1000)));
if (size_ == 0) {
size_ = 16;
}
if (interval_ == 0) {
interval_ = INT_MAX;
if (interval_.count() == 0) {
interval_ = std::chrono::milliseconds::max();
}
delayWorker();
@@ -30,8 +36,7 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
void waybar::modules::Image::delayWorker() {
thread_ = [this] {
dp.emit();
auto interval = std::chrono::seconds(interval_);
thread_.sleep_for(interval);
thread_.sleep_for(interval_);
};
}
+2
View File
@@ -60,6 +60,7 @@ auto waybar::modules::Memory::update() -> void {
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("swapState", swaptotal == 0 ? "Off" : "On"),
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)));
@@ -72,6 +73,7 @@ auto waybar::modules::Memory::update() -> void {
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("swapState", swaptotal == 0 ? "Off" : "On"),
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)));
+12 -11
View File
@@ -118,7 +118,7 @@ void waybar::modules::MPD::setLabel() {
auto format = format_;
Glib::ustring artist, album_artist, album, title;
std::string date, filename;
std::string date, filename, uri;
int song_pos = 0, queue_length = 0, volume = 0;
std::chrono::seconds elapsedTime, totalTime;
@@ -151,6 +151,7 @@ void waybar::modules::MPD::setLabel() {
title = sanitize_string(getTag(MPD_TAG_TITLE));
date = sanitize_string(getTag(MPD_TAG_DATE));
filename = sanitize_string(getFilename());
uri = mpd_song_get_uri(song_.get());
song_pos = mpd_status_get_song_pos(status_.get()) + 1;
volume = mpd_status_get_volume(status_.get());
if (volume < 0) {
@@ -184,7 +185,7 @@ void waybar::modules::MPD::setLabel() {
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));
fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename), fmt::arg("uri", uri));
if (text.empty()) {
label_.hide();
} else {
@@ -200,15 +201,15 @@ void waybar::modules::MPD::setLabel() {
tooltip_format = config_["tooltip-format"].isString() ? config_["tooltip-format"].asString()
: "MPD (connected)";
try {
auto tooltip_text =
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),
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));
auto tooltip_text = 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), 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), fmt::arg("uri", uri));
label_.set_tooltip_text(tooltip_text);
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error (tooltip): {}", e.what());
+38 -27
View File
@@ -14,7 +14,6 @@ extern "C" {
#include <glib.h>
#include <spdlog/spdlog.h>
namespace waybar::modules::mpris {
const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}";
@@ -435,9 +434,11 @@ auto Mpris::onPlayerNameVanished(PlayerctlPlayerManager* manager, PlayerctlPlaye
auto* mpris = static_cast<Mpris*>(data);
if (!mpris) return;
spdlog::debug("mpris: player-vanished callback: {}", player_name->name);
spdlog::debug("mpris: name-vanished callback: {}", player_name->name);
if (std::string(player_name->name) == mpris->player_) {
if (mpris->player_ == "playerctld") {
mpris->dp.emit();
} else if (mpris->player_ == player_name->name) {
mpris->player = nullptr;
mpris->event_box_.set_visible(false);
mpris->dp.emit();
@@ -508,7 +509,10 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
// > 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 (players)
player_name = static_cast<PlayerctlPlayerName*>(players->data)->name;
else
return std::nullopt; // no players found, hide the widget
}
if (std::any_of(ignored_players_.begin(), ignored_players_.end(),
@@ -601,38 +605,45 @@ errorexit:
}
bool Mpris::handleToggle(GdkEventButton* const& e) {
if (!e || e->type != GdkEventType::GDK_BUTTON_PRESS) {
return false;
}
auto info = getPlayerInfo();
if (!info) return false;
struct ButtonAction {
guint button;
const char* config_key;
std::function<void()> builtin_action;
};
GError* error = nullptr;
waybar::util::ScopeGuard error_deleter([error]() {
waybar::util::ScopeGuard error_deleter([&error]() {
if (error) {
g_error_free(error);
}
});
auto info = getPlayerInfo();
if (!info) return false;
// Command pattern: encapsulate each button's action
const ButtonAction actions[] = {
{1, "on-click", [&]() { playerctl_player_play_pause(player, &error); }},
{2, "on-click-middle", [&]() { playerctl_player_previous(player, &error); }},
{3, "on-click-right", [&]() { playerctl_player_next(player, &error); }},
{8, "on-click-backward", [&]() { playerctl_player_previous(player, &error); }},
{9, "on-click-forward", [&]() { playerctl_player_next(player, &error); }},
};
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-click-middle"].isString()) {
return ALabel::handleToggle(e);
}
playerctl_player_previous(player, &error);
break;
case 3: // right-click
if (config_["on-click-right"].isString()) {
return ALabel::handleToggle(e);
}
playerctl_player_next(player, &error);
break;
for (const auto& action : actions) {
if (e->button == action.button) {
if (config_[action.config_key].isString()) {
return ALabel::handleToggle(e);
}
action.builtin_action();
break;
}
}
if (error) {
spdlog::error("mpris[{}]: error running builtin on-click action: {}", (*info).name,
error->message);
+137 -104
View File
@@ -1,13 +1,18 @@
#include "modules/network.hpp"
#include <linux/if.h>
#include <linux/if_link.h>
#include <netlink/netlink.h>
#include <spdlog/spdlog.h>
#include <sys/eventfd.h>
#include <cassert>
#include <cstring>
#include <fstream>
#include <optional>
#include <sstream>
#include <string>
#include <vector>
#include "util/format.hpp"
#ifdef WANT_RFKILL
@@ -78,25 +83,7 @@ waybar::modules::Network::readBandwidthUsage() {
}
waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
: ALabel(config, "network", id, DEFAULT_FORMAT, 60),
ifid_(-1),
addr_pref_(IPV4),
efd_(-1),
ev_fd_(-1),
want_route_dump_(false),
want_link_dump_(false),
want_addr_dump_(false),
dump_in_progress_(false),
is_p2p_(false),
cidr_(0),
cidr6_(0),
signal_strength_dbm_(0),
signal_strength_(0),
#ifdef WANT_RFKILL
rfkill_{RFKILL_TYPE_WLAN},
#endif
frequency_(0.0) {
: ALabel(config, "network", id, DEFAULT_FORMAT, 60) {
// 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
@@ -223,8 +210,8 @@ void waybar::modules::Network::worker() {
std::lock_guard<std::mutex> lock(mutex_);
if (ifid_ > 0) {
getInfo();
dp.emit();
}
dp.emit();
}
thread_timer_.sleep_for(interval_);
};
@@ -271,13 +258,16 @@ void waybar::modules::Network::worker() {
}
const std::string waybar::modules::Network::getNetworkState() const {
if (ifid_ == -1) {
if (ifid_ == -1 || !carrier_) {
#ifdef WANT_RFKILL
if (rfkill_.getState()) return "disabled";
bool display_rfkill = true;
if (config_["rfkill"].isBool()) {
display_rfkill = config_["rfkill"].asBool();
}
if (rfkill_.getState() && display_rfkill) return "disabled";
#endif
return "disconnected";
}
if (!carrier_) return "disconnected";
if (ipaddr_.empty() && ipaddr6_.empty()) return "linked";
if (essid_.empty()) return "ethernet";
return "wifi";
@@ -343,18 +333,23 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_),
fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthTotalBits",
pow_format((bandwidth_up + bandwidth_down) * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")),
fmt::arg("bandwidthDownBits",
pow_format(bandwidth_down * 8ull / (interval_.count() / 1000.0), "b/s")),
fmt::arg("bandwidthUpBits",
pow_format(bandwidth_up * 8ull / (interval_.count() / 1000.0), "b/s")),
fmt::arg(
"bandwidthTotalBits",
pow_format((bandwidth_up + bandwidth_down) * 8ull / (interval_.count() / 1000.0), "b/s")),
fmt::arg("bandwidthDownOctets",
pow_format(bandwidth_down / (interval_.count() / 1000.0), "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / (interval_.count() / 1000.0), "o/s")),
fmt::arg("bandwidthTotalOctets",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "o/s")),
fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / interval_.count(), "B/s")),
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")),
pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "o/s")),
fmt::arg("bandwidthDownBytes",
pow_format(bandwidth_down / (interval_.count() / 1000.0), "B/s")),
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / (interval_.count() / 1000.0), "B/s")),
fmt::arg("bandwidthTotalBytes",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s")));
pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "B/s")));
if (text.compare(label_.get_label()) != 0) {
label_.set_markup(text);
if (text.empty()) {
@@ -401,11 +396,65 @@ auto waybar::modules::Network::update() -> void {
ALabel::update();
}
bool waybar::modules::Network::checkInterface(std::string name) {
if (config_["interface"].isString()) {
return config_["interface"].asString() == name ||
wildcardMatch(config_["interface"].asString(), name);
// https://gist.github.com/rressi/92af77630faf055934c723ce93ae2495
static bool wildcardMatch(const std::string &pattern, const std::string &text) {
auto P = int(pattern.size());
auto T = int(text.size());
auto p = 0, fallback_p = -1;
auto t = 0, fallback_t = -1;
while (t < T) {
// Wildcard match:
if (p < P && pattern[p] == '*') {
fallback_p = p++; // starting point after failures
fallback_t = t; // starting point after failures
}
// Simple match:
else if (p < P && (pattern[p] == '?' || pattern[p] == text[t])) {
p++;
t++;
}
// Failure, fall back just after last matched '*':
else if (fallback_p >= 0) {
p = fallback_p + 1; // position just after last matched '*"
t = ++fallback_t; // re-try to match text from here
}
// There were no '*' before, so we fail here:
else {
return false;
}
}
// Consume all '*' at the end of pattern:
while (p < P && pattern[p] == '*') p++;
return p == P;
}
bool waybar::modules::Network::matchInterface(const std::string &ifname,
const std::vector<std::string> &altnames,
std::string &matched) const {
if (!config_["interface"].isString()) {
return false;
}
auto config_ifname = config_["interface"].asString();
if (config_ifname == ifname || wildcardMatch(config_ifname, ifname)) {
matched = ifname;
return true;
}
for (const auto &altname : altnames) {
if (config_ifname == altname || wildcardMatch(config_ifname, altname)) {
matched = altname;
return true;
}
}
return false;
}
@@ -420,6 +469,7 @@ void waybar::modules::Network::clearIface() {
netmask_.clear();
netmask6_.clear();
carrier_ = false;
is_p2p_ = false;
cidr_ = 0;
cidr6_ = 0;
signal_strength_dbm_ = 0;
@@ -439,12 +489,16 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
is_del_event = true;
case RTM_NEWLINK: {
struct ifinfomsg *ifi = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
ssize_t attrlen = IFLA_PAYLOAD(nh);
struct rtattr *ifla = IFLA_RTA(ifi);
const char *ifname = NULL;
size_t ifname_len = 0;
struct nlattr *attrs[IFLA_MAX + 1];
std::string ifname;
std::vector<std::string> altnames;
std::optional<bool> carrier;
if (nlmsg_parse(nh, sizeof(*ifi), attrs, IFLA_MAX, nullptr) < 0) {
spdlog::error("network: failed to parse netlink attributes");
return NL_SKIP;
}
if (net->ifid_ != -1 && ifi->ifi_index != net->ifid_) {
return NL_OK;
}
@@ -462,26 +516,33 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
return NL_OK;
}
for (; RTA_OK(ifla, attrlen); ifla = RTA_NEXT(ifla, attrlen)) {
switch (ifla->rta_type) {
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;
break;
if (attrs[IFLA_IFNAME] != nullptr) {
const char *ifname_ptr = nla_get_string(attrs[IFLA_IFNAME]);
size_t ifname_len = nla_len(attrs[IFLA_IFNAME]) - 1; // minus \0
ifname = std::string(ifname_ptr, ifname_len);
}
if (attrs[IFLA_CARRIER] != nullptr) {
carrier = nla_get_u8(attrs[IFLA_CARRIER]) == 1;
}
if (attrs[IFLA_PROP_LIST] != nullptr) {
struct nlattr *prop;
int rem;
nla_for_each_nested(prop, attrs[IFLA_PROP_LIST], rem) {
if (nla_type(prop) == IFLA_ALT_IFNAME) {
const char *altname_ptr = nla_get_string(prop);
size_t altname_len = nla_len(prop) - 1; // minus \0
altnames.emplace_back(altname_ptr, altname_len);
}
}
}
if (!is_del_event && ifi->ifi_index == net->ifid_) {
// Update interface information
if (net->ifname_.empty() && ifname != NULL) {
std::string new_ifname(ifname, ifname_len);
net->ifname_ = new_ifname;
if (net->ifname_.empty() && !ifname.empty()) {
net->ifname_ = ifname;
}
if (carrier.has_value()) {
if (net->carrier_ != *carrier) {
@@ -502,13 +563,25 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
}
} else if (!is_del_event && net->ifid_ == -1) {
// Checking if it's an interface we care about.
std::string new_ifname(ifname, ifname_len);
if (net->checkInterface(new_ifname)) {
spdlog::debug("network: selecting new interface {}/{}", new_ifname, ifi->ifi_index);
std::string matched;
if (net->matchInterface(ifname, altnames, matched)) {
if (ifname == matched) {
spdlog::debug("network: selecting new interface {}/{}", ifname, ifi->ifi_index);
} else {
spdlog::debug("network: selecting new interface {}/{} (matched altname {})", ifname,
ifi->ifi_index, matched);
}
net->ifname_ = new_ifname;
net->ifname_ = ifname;
net->ifid_ = ifi->ifi_index;
if (ifi->ifi_flags & IFF_POINTOPOINT) net->is_p2p_ = true;
if ((ifi->ifi_flags & IFF_POINTOPOINT) != 0) {
net->is_p2p_ = true;
}
if ((ifi->ifi_flags & IFF_UP) == 0) {
// With some network drivers (e.g. mt7921e), the interface may
// report having a carrier even though interface is down.
carrier = false;
}
if (carrier.has_value()) {
net->carrier_ = carrier.value();
}
@@ -602,7 +675,6 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
break;
}
char temp_gw_addr[INET6_ADDRSTRLEN];
case RTM_DELROUTE:
is_del_event = true;
case RTM_NEWROUTE: {
@@ -613,6 +685,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
int family = rtm->rtm_family;
ssize_t attrlen = RTM_PAYLOAD(nh);
struct rtattr *attr = RTM_RTA(rtm);
char gateway_addr[INET6_ADDRSTRLEN];
bool has_gateway = false;
bool has_destination = false;
int temp_idx = -1;
@@ -638,7 +711,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
* If someone ever needs to figure out the gateway address as well,
* it's here as the attribute payload.
*/
inet_ntop(family, RTA_DATA(attr), temp_gw_addr, sizeof(temp_gw_addr));
inet_ntop(family, RTA_DATA(attr), gateway_addr, sizeof(gateway_addr));
has_gateway = true;
break;
case RTA_DST: {
@@ -683,8 +756,8 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
net->clearIface();
net->ifid_ = temp_idx;
net->route_priority = priority;
net->gwaddr_ = temp_gw_addr;
spdlog::debug("network: new default route via {} on if{} metric {}", temp_gw_addr,
net->gwaddr_ = gateway_addr;
spdlog::debug("network: new default route via {} on if{} metric {}", gateway_addr,
temp_idx, priority);
/* Ask ifname associated with temp_idx as well as carrier status */
@@ -897,43 +970,3 @@ auto waybar::modules::Network::getInfo() -> void {
}
nl_send_sync(sock_, nl_msg);
}
// https://gist.github.com/rressi/92af77630faf055934c723ce93ae2495
bool waybar::modules::Network::wildcardMatch(const std::string &pattern,
const std::string &text) const {
auto P = int(pattern.size());
auto T = int(text.size());
auto p = 0, fallback_p = -1;
auto t = 0, fallback_t = -1;
while (t < T) {
// Wildcard match:
if (p < P && pattern[p] == '*') {
fallback_p = p++; // starting point after failures
fallback_t = t; // starting point after failures
}
// Simple match:
else if (p < P && (pattern[p] == '?' || pattern[p] == text[t])) {
p++;
t++;
}
// Failure, fall back just after last matched '*':
else if (fallback_p >= 0) {
p = fallback_p + 1; // position just after last matched '*"
t = ++fallback_t; // re-try to match text from here
}
// There were no '*' before, so we fail here:
else {
return false;
}
}
// Consume all '*' at the end of pattern:
while (p < P && pattern[p] == '*') p++;
return p == P;
}
+11
View File
@@ -147,6 +147,17 @@ void IPC::parseIPC(const std::string &line) {
} else {
spdlog::error("Active window changed on unknown workspace");
}
} else if (const auto &payload = ev["WorkspaceUrgencyChanged"]) {
const auto id = payload["id"].asUInt64();
const auto urgent = payload["urgent"].asBool();
auto it = std::find_if(workspaces_.begin(), workspaces_.end(),
[id](const auto &ws) { return ws["id"].asUInt64() == id; });
if (it != workspaces_.end()) {
auto &ws = *it;
ws["is_urgent"] = urgent;
} else {
spdlog::error("Urgency changed for unknown workspace");
}
} else if (const auto &payload = ev["KeyboardLayoutsChanged"]) {
const auto &layouts = payload["keyboard_layouts"];
const auto &names = layouts["names"];
+10
View File
@@ -58,6 +58,16 @@ void Language::doUpdate() {
spdlog::debug("niri language update with short description {}", layout.short_description);
spdlog::debug("niri language update with variant {}", layout.variant);
if (!last_short_name_.empty()) {
label_.get_style_context()->remove_class(last_short_name_);
}
if (!layout.short_name.empty()) {
label_.get_style_context()->add_class(layout.short_name);
last_short_name_ = layout.short_name;
} else {
last_short_name_.clear();
}
std::string layoutName = std::string{};
if (config_.isMember("format-" + layout.short_description + "-" + layout.variant)) {
const auto propName = "format-" + layout.short_description + "-" + layout.variant;
+10
View File
@@ -20,6 +20,7 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
gIPC->registerForIPC("WorkspacesChanged", this);
gIPC->registerForIPC("WorkspaceActivated", this);
gIPC->registerForIPC("WorkspaceActiveWindowChanged", this);
gIPC->registerForIPC("WorkspaceUrgencyChanged", this);
dp.emit();
}
@@ -67,6 +68,11 @@ void Workspaces::doUpdate() {
else
style_context->remove_class("active");
if (ws["is_urgent"].asBool())
style_context->add_class("urgent");
else
style_context->remove_class("urgent");
if (ws["output"]) {
if (ws["output"].asString() == bar_.output->name)
style_context->add_class("current_output");
@@ -166,6 +172,10 @@ std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws)
const auto &icons = config_["format-icons"];
if (!icons) return value;
if (ws["is_urgent"].asBool() && icons["urgent"]) return icons["urgent"].asString();
if (ws["active_window_id"].isNull() && icons["empty"]) return icons["empty"].asString();
if (ws["is_focused"].asBool() && icons["focused"]) return icons["focused"].asString();
if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString();
+2 -2
View File
@@ -29,14 +29,14 @@ PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Valu
// method on the proxy to see whether or not something's responding
// on the other side.
// NOTE: the DBus adresses are under migration. They should be
// NOTE: the DBus addresses are under migration. They should be
// changed to org.freedesktop.UPower.PowerProfiles at some point.
//
// See
// https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/releases/0.20
//
// The old name is still announced for now. Let's rather use the old
// adresses for compatibility sake.
// addresses for compatibility sake.
//
// Revisit this in 2026, systems should be updated by then.
+23
View File
@@ -74,6 +74,24 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientat
}
}
for (const auto& ignore_item : config_["ignore"]) {
if (!ignore_item.isObject() || !ignore_item["type"].isString() ||
!ignore_item["name"].isString())
continue;
const std::string type = ignore_item["type"].asString();
const std::string name = ignore_item["name"].asString();
auto iter = typeMap.find(type);
if (iter != typeMap.end()) {
auto& [_, nodeType] = iter->second;
ignore.emplace(nodeType, std::move(name));
}
}
if (config_["ignore-monitor"].isBool()) {
ignore_monitor = config_["ignore-monitor"].asBool();
}
backend = util::PipewireBackend::PipewireBackend::getInstance();
backend->privacy_nodes_changed_signal_event.connect(
sigc::mem_fun(*this, &Privacy::onPrivacyNodesChanged));
@@ -88,6 +106,11 @@ void Privacy::onPrivacyNodesChanged() {
nodes_screenshare.clear();
for (auto& node : backend->privacy_nodes) {
if (ignore_monitor && node.second->is_monitor) continue;
auto iter = ignore.find(std::pair(node.second->type, node.second->node_name));
if (iter != ignore.end()) continue;
switch (node.second->state) {
case PW_NODE_STATE_RUNNING:
switch (node.second->type) {
+8 -4
View File
@@ -1,5 +1,7 @@
#include "modules/privacy/privacy_item.hpp"
#include <spdlog/spdlog.h>
#include <string>
#include "glibmm/main.h"
@@ -96,20 +98,22 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac
void PrivacyItem::update_tooltip() {
// Removes all old nodes
for (auto *child : tooltip_window.get_children()) {
tooltip_window.remove(*child);
// despite the remove, still needs a delete to prevent memory leak. Speculating that this might
// work differently in GTK4.
delete child;
}
for (auto *node : *nodes) {
Gtk::Box *box = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4);
auto *box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL, 4);
// Set device icon
Gtk::Image *node_icon = new Gtk::Image();
auto *node_icon = Gtk::make_managed<Gtk::Image>();
node_icon->set_pixel_size(tooltipIconSize);
node_icon->set_from_icon_name(node->getIconName(), Gtk::ICON_SIZE_INVALID);
box->add(*node_icon);
// Set model
auto *nodeName = new Gtk::Label(node->getName());
auto *nodeName = Gtk::make_managed<Gtk::Label>(node->getName());
box->add(*nodeName);
tooltip_window.add(*box);
+1 -1
View File
@@ -132,7 +132,7 @@ auto waybar::modules::Pulseaudio::update() -> void {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(
label_.set_tooltip_markup(fmt::format(
fmt::runtime(tooltip_format), fmt::arg("desc", sink_desc),
fmt::arg("volume", sink_volume), fmt::arg("format_source", format_source),
fmt::arg("source_volume", source_volume), fmt::arg("source_desc", source_desc),
+7 -1
View File
@@ -145,9 +145,15 @@ 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();
if (!name_.empty()) {
label_.get_style_context()->remove_class(name_);
}
label_.get_style_context()->add_class(name);
label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(name).raw()));
label_.show();
}
name_ = name;
ALabel::update();
}
+14 -5
View File
@@ -150,11 +150,7 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
button.show();
}
struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
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_);
box_.signal_show().connect(sigc::mem_fun(*this, &Tags::handle_show));
}
Tags::~Tags() {
@@ -165,6 +161,19 @@ Tags::~Tags() {
if (control_) {
zriver_control_v1_destroy(control_);
}
if (status_manager_) {
zriver_status_manager_v1_destroy(status_manager_);
}
}
void Tags::handle_show() {
struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
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_);
status_manager_ = nullptr;
}
void Tags::handle_primary_clicked(uint32_t tag) {
+40 -7
View File
@@ -74,6 +74,13 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf
cancellable_, interface);
}
Item::~Item() {
if (this->gtk_menu != nullptr) {
this->gtk_menu->popdown();
this->gtk_menu->detach();
}
}
bool Item::handleMouseEnter(GdkEventCrossing* const& e) {
event_box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
return false;
@@ -140,7 +147,25 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
category = get_variant<std::string>(value);
} else if (name == "Id") {
id = get_variant<std::string>(value);
setCustomIcon(id);
/*
* HACK: Electron apps seem to have the same ID, but tooltip seems correct, so use that as ID
* to pass as the custom icon option. I'm avoiding being disruptive and setting that to the ID
* itself as I've no idea what this would affect.
* The tooltip text is converted to lowercase since that's what (most?) themes expect?
* I still haven't found a way for it to pick from theme automatically, although
* it might be my theme.
*/
if (id == "chrome_status_icon_1") {
Glib::VariantBase value;
this->proxy_->get_cached_property(value, "ToolTip");
tooltip = get_variant<ToolTip>(value);
if (!tooltip.text.empty()) {
setCustomIcon(tooltip.text.lowercase());
}
} else {
setCustomIcon(id);
}
} else if (name == "Title") {
title = get_variant<std::string>(value);
if (tooltip.text.empty()) {
@@ -203,6 +228,8 @@ void Item::setStatus(const Glib::ustring& value) {
}
void Item::setCustomIcon(const std::string& id) {
spdlog::debug("SNI tray id: {}", id);
std::string custom_icon = IconManager::instance().getIconForApp(id);
if (!custom_icon.empty()) {
if (std::filesystem::exists(custom_icon)) {
@@ -388,14 +415,17 @@ Glib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() {
Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) {
icon_theme->rescan_if_needed();
if (!icon_theme_path.empty() &&
icon_theme->lookup_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE)) {
return icon_theme->load_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
if (!icon_theme_path.empty()) {
auto icon_info = icon_theme->lookup_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
if (icon_info) {
bool is_sym = false;
return icon_info.load_symbolic(event_box.get_style_context(), is_sym);
}
}
return DefaultGtkIconThemeWrapper::load_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE,
event_box.get_style_context());
}
double Item::getScaledIconSize() {
@@ -420,6 +450,9 @@ void Item::makeMenu() {
gtk_menu->attach_to_widget(event_box);
}
}
// Manually reset prelight to make sure the tray item doesn't stay in a hover state even though
// the menu is focused
event_box.unset_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
}
bool Item::handleClick(GdkEventButton* const& ev) {
+14 -1
View File
@@ -2,6 +2,8 @@
#include <spdlog/spdlog.h>
#include <algorithm>
#include "modules/sni/icon_manager.hpp"
namespace waybar::modules::SNI {
@@ -21,6 +23,9 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
if (config_["spacing"].isUInt()) {
box_.set_spacing(config_["spacing"].asUInt());
}
if (config["show-passive-items"].isBool()) {
show_passive_ = config["show-passive-items"].asBool();
}
nb_hosts_ += 1;
if (config_["icons"].isObject()) {
IconManager::instance().setIconsConfig(config_["icons"]);
@@ -44,7 +49,15 @@ void Tray::onRemove(std::unique_ptr<Item>& item) {
auto Tray::update() -> void {
// Show tray only when items are available
event_box_.set_visible(!box_.get_children().empty());
std::vector<Gtk::Widget*> children = box_.get_children();
if (show_passive_) {
event_box_.set_visible(!children.empty());
} else {
event_box_.set_visible(!std::all_of(children.begin(), children.end(), [](Gtk::Widget* child) {
return child->get_style_context()->has_class("passive");
}));
}
// Call parent update
AModule::update();
}
+2 -2
View File
@@ -22,10 +22,10 @@ Language::Language(const std::string& id, const Json::Value& config)
hide_single_ = config["hide-single-layout"].isBool() && config["hide-single-layout"].asBool();
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);
displayed_short_flag |= static_cast<std::byte>(DisplayedShortFlag::ShortName);
}
if (format_.find("{shortDescription}") != std::string::npos) {
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortDescription);
displayed_short_flag |= static_cast<std::byte>(DisplayedShortFlag::ShortDescription);
}
if (config.isMember("tooltip-format")) {
tooltip_format_ = config["tooltip-format"].asString();
+39 -18
View File
@@ -41,8 +41,8 @@ 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_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_) =
getFocusedNode(payload["nodes"], output);
std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_,
marks_) = getFocusedNode(payload["nodes"], output);
updateAppIconName(app_id_, app_class_);
dp.emit();
} catch (const std::exception& e) {
@@ -96,7 +96,7 @@ auto Window::update() -> void {
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_)),
fmt::arg("shell", shell_), fmt::arg("marks", marks_)),
config_["rewrite"]));
if (tooltipEnabled()) {
label_.set_tooltip_text(window_);
@@ -108,7 +108,7 @@ auto Window::update() -> void {
AAppIconLabel::update();
}
void Window::setClass(std::string classname, bool enable) {
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);
@@ -169,17 +169,31 @@ std::optional<std::reference_wrapper<const Json::Value>> getSingleChildNode(
return {getSingleChildNode(child)};
}
std::tuple<std::string, std::string, std::string> getWindowInfo(const Json::Value& node) {
std::tuple<std::string, std::string, std::string, std::string> getWindowInfo(
const Json::Value& node, bool showHidden) {
const 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() : "";
return {app_id, app_class, shell};
std::string marks = "";
if (node["marks"].isArray()) {
for (const auto& m : node["marks"]) {
if (!m.isString() || (!showHidden && m.asString().at(0) == '_')) {
continue;
}
if (!marks.empty()) {
marks += ',';
}
marks += m.asString();
}
}
return {app_id, app_class, shell, marks};
}
std::tuple<std::size_t, int, int, std::string, 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,
std::string>
gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Value& config_,
const Bar& bar_, Json::Value& parentWorkspace,
const Json::Value& immediateParent) {
@@ -207,7 +221,8 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
"",
"",
"",
node["layout"].asString()};
node["layout"].asString(),
""};
}
parentWorkspace = node;
} else if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") &&
@@ -215,7 +230,8 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
// found node
spdlog::trace("actual output {}, output found {}, node (focused) found {}", bar_.output->name,
output, node["name"].asString());
const auto [app_id, app_class, shell] = getWindowInfo(node);
const auto [app_id, app_class, shell, marks] =
getWindowInfo(node, config_["show-hidden-marks"].asBool());
int nb = node.size();
int floating_count = 0;
std::string workspace_layout = "";
@@ -232,20 +248,21 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
app_id,
app_class,
shell,
workspace_layout};
workspace_layout,
marks};
}
// iterate
auto [nb, f, id, name, app_id, app_class, shell, workspace_layout] =
auto [nb, f, id, name, app_id, app_class, shell, workspace_layout, marks] =
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace, node);
auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2] =
auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2, marks2] =
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};
return {nb, f, id, name, app_id, app_class, shell, workspace_layout, marks};
} else if (id2 > 0 && !name2.empty()) {
return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2};
return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2, marks2};
}
}
@@ -258,10 +275,12 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
std::string app_id = "";
std::string app_class = "";
std::string workspace_layout = "";
std::string marks = "";
if (all_leaf_nodes.first == 1) {
const auto single_child = getSingleChildNode(immediateParent);
if (single_child.has_value()) {
std::tie(app_id, app_class, workspace_layout) = getWindowInfo(single_child.value());
std::tie(app_id, app_class, workspace_layout, marks) =
getWindowInfo(single_child.value(), config_["show-hidden-marks"].asBool());
}
}
return {all_leaf_nodes.first,
@@ -273,13 +292,15 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
app_id,
app_class,
workspace_layout,
immediateParent["layout"].asString()};
immediateParent["layout"].asString(),
marks};
}
return {0, 0, -1, "", "", "", "", ""};
return {0, 0, -1, "", "", "", "", "", ""};
}
std::tuple<std::size_t, int, int, std::string, 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,
std::string>
Window::getFocusedNode(const Json::Value& nodes, std::string& output) {
Json::Value placeholder = Json::Value::null;
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder);
+10 -9
View File
@@ -57,9 +57,9 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
box_.get_style_context()->add_class(MODULE_CLASS);
event_box_.add(box_);
if (config_["format-window-separator"].isString()) {
m_formatWindowSeperator = config_["format-window-separator"].asString();
m_formatWindowSeparator = config_["format-window-separator"].asString();
} else {
m_formatWindowSeperator = " ";
m_formatWindowSeparator = " ";
}
const Json::Value &windowRewrite = config["window-rewrite"];
if (windowRewrite.isObject()) {
@@ -271,7 +271,7 @@ void Workspaces::updateWindows(const Json::Value &node, std::string &windows) {
window = fmt::format(fmt::runtime(window), fmt::arg("name", title),
fmt::arg("class", windowClass));
windows.append(window);
windows.append(m_formatWindowSeperator);
windows.append(m_formatWindowSeparator);
}
}
for (const Json::Value &child : node["nodes"]) {
@@ -340,7 +340,7 @@ auto Workspaces::update() -> void {
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("windows",
windows.substr(0, windows.length() - m_formatWindowSeperator.length())),
windows.substr(0, windows.length() - m_formatWindowSeparator.length())),
fmt::arg("output", (*it)["output"].asString()));
}
if (!config_["disable-markup"].asBool()) {
@@ -443,10 +443,11 @@ bool Workspaces::handleScroll(GdkEventScroll *e) {
if (it == workspaces_.end()) {
return true;
}
bool reverse_scroll = config_["reverse-scroll"].isBool() && config_["reverse-scroll"].asBool();
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) {
name = getCycleWorkspace(it, false);
name = getCycleWorkspace(it, reverse_scroll ? true : false);
} else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) {
name = getCycleWorkspace(it, true);
name = getCycleWorkspace(it, reverse_scroll ? false : true);
} else {
return true;
}
@@ -494,7 +495,7 @@ std::string Workspaces::trimWorkspaceName(std::string name) {
return name;
}
bool is_focused_recursive(const Json::Value& node) {
bool is_focused_recursive(const Json::Value &node) {
// If a workspace has a focused container then get_tree will say
// that the workspace itself isn't focused. Therefore we need to
// check if any of its nodes are focused as well.
@@ -504,13 +505,13 @@ bool is_focused_recursive(const Json::Value& node) {
return true;
}
for (const auto& child : node["nodes"]) {
for (const auto &child : node["nodes"]) {
if (is_focused_recursive(child)) {
return true;
}
}
for (const auto& child : node["floating_nodes"]) {
for (const auto &child : node["floating_nodes"]) {
if (is_focused_recursive(child)) {
return true;
}
+53 -21
View File
@@ -16,6 +16,7 @@ SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value&
update_pending(false),
nr_failed_system(0),
nr_failed_user(0),
nr_failed(0),
last_status() {
if (config["hide-on-ok"].isBool()) {
hide_on_ok = config["hide-on-ok"].asBool();
@@ -67,11 +68,38 @@ auto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name,
}
}
void SystemdFailedUnits::updateData() {
update_pending = false;
void SystemdFailedUnits::RequestSystemState() {
auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> std::string {
try {
if (!proxy) return "unknown";
auto parameters = Glib::VariantContainerBase(
g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "SystemState"));
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
if (data && data.is_of_type(Glib::VariantType("(v)"))) {
Glib::VariantBase variant;
g_variant_get(data.gobj_copy(), "(v)", &variant);
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_STRING)) {
return g_variant_get_string(variant.gobj_copy(), NULL);
}
}
} catch (Glib::Error& e) {
spdlog::error("Failed to get {} state: {}", kind, e.what().c_str());
}
return "unknown";
};
system_state = load("systemwide", system_proxy);
user_state = load("user", user_proxy);
if (system_state == "running" && user_state == "running")
overall_state = "ok";
else
overall_state = "degraded";
}
void SystemdFailedUnits::RequestFailedUnits() {
auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> uint32_t {
try {
if (!proxy) return 0;
auto parameters = Glib::VariantContainerBase(
g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "NFailedUnits"));
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
@@ -79,9 +107,7 @@ void SystemdFailedUnits::updateData() {
Glib::VariantBase variant;
g_variant_get(data.gobj_copy(), "(v)", &variant);
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_UINT32)) {
uint32_t value = 0;
g_variant_get(variant.gobj_copy(), "u", &value);
return value;
return g_variant_get_uint32(variant.gobj_copy());
}
}
} catch (Glib::Error& e) {
@@ -90,40 +116,46 @@ void SystemdFailedUnits::updateData() {
return 0;
};
if (system_proxy) {
nr_failed_system = load("systemwide", system_proxy);
}
if (user_proxy) {
nr_failed_user = load("user", user_proxy);
}
nr_failed_system = load("systemwide", system_proxy);
nr_failed_user = load("user", user_proxy);
nr_failed = nr_failed_system + nr_failed_user;
}
void SystemdFailedUnits::updateData() {
update_pending = false;
RequestSystemState();
if (overall_state == "degraded") RequestFailedUnits();
dp.emit();
}
auto SystemdFailedUnits::update() -> void {
uint32_t nr_failed = nr_failed_system + nr_failed_user;
if (last_status == overall_state) return;
// Hide if needed.
if (nr_failed == 0 && hide_on_ok) {
if (overall_state == "ok" && hide_on_ok) {
event_box_.set_visible(false);
return;
}
if (!event_box_.get_visible()) {
event_box_.set_visible(true);
}
event_box_.set_visible(true);
// Set state class.
const std::string status = nr_failed == 0 ? "ok" : "degraded";
if (!last_status.empty() && label_.get_style_context()->has_class(last_status)) {
label_.get_style_context()->remove_class(last_status);
}
if (!label_.get_style_context()->has_class(status)) {
label_.get_style_context()->add_class(status);
if (!label_.get_style_context()->has_class(overall_state)) {
label_.get_style_context()->add_class(overall_state);
}
last_status = status;
last_status = overall_state;
label_.set_markup(fmt::format(
fmt::runtime(nr_failed == 0 ? format_ok : format_), fmt::arg("nr_failed", nr_failed),
fmt::arg("nr_failed_system", nr_failed_system), fmt::arg("nr_failed_user", nr_failed_user)));
fmt::arg("nr_failed_system", nr_failed_system), fmt::arg("nr_failed_user", nr_failed_user),
fmt::arg("system_state", system_state), fmt::arg("user_state", user_state),
fmt::arg("overall_state", overall_state)));
ALabel::update();
}
+7 -5
View File
@@ -45,7 +45,7 @@ waybar::modules::Temperature::Temperature(const std::string& id, const Json::Val
file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone);
}
// check if file_path_ can be used to retrive the temperature
// check if file_path_ can be used to retrieve the temperature
std::ifstream temp(file_path_);
if (!temp.is_open()) {
throw std::runtime_error("Can't open " + file_path_);
@@ -74,12 +74,14 @@ auto waybar::modules::Temperature::update() -> void {
if (critical) {
format = config_["format-critical"].isString() ? config_["format-critical"].asString() : format;
label_.get_style_context()->add_class("critical");
} else if (warning) {
format = config_["format-warning"].isString() ? config_["format-warning"].asString() : format;
label_.get_style_context()->add_class("warning");
} else {
label_.get_style_context()->remove_class("critical");
label_.get_style_context()->remove_class("warning");
if (warning) {
format = config_["format-warning"].isString() ? config_["format-warning"].asString() : format;
label_.get_style_context()->add_class("warning");
} else {
label_.get_style_context()->remove_class("warning");
}
}
if (format.empty()) {
+4 -3
View File
@@ -10,7 +10,6 @@ UPower::UPower(const std::string &id, const Json::Value &config)
: AIconLabel(config, "upower", id, "{percentage}", 0, true, true, true), sleeping_{false} {
box_.set_name(name_);
box_.set_spacing(0);
box_.set_has_tooltip(AModule::tooltipEnabled());
// Tooltip box
contentBox_.set_orientation((box_.get_orientation() == Gtk::ORIENTATION_HORIZONTAL)
? Gtk::ORIENTATION_VERTICAL
@@ -70,8 +69,10 @@ UPower::UPower(const std::string &id, const Json::Value &config)
g_signal_connect(upClient_, "device-removed", G_CALLBACK(deviceRemoved_cb), this);
// Subscribe tooltip query events
box_.set_has_tooltip();
box_.signal_query_tooltip().connect(sigc::mem_fun(*this, &UPower::queryTooltipCb), false);
box_.set_has_tooltip(AModule::tooltipEnabled());
if (AModule::tooltipEnabled()) {
box_.signal_query_tooltip().connect(sigc::mem_fun(*this, &UPower::queryTooltipCb), false);
}
resetDevices();
setDisplayDevice();
+7 -1
View File
@@ -62,7 +62,13 @@ long User::uptime_as_seconds() {
#if HAVE_CPU_BSD
struct timespec s_info;
if (0 == clock_gettime(CLOCK_UPTIME_PRECISE, &s_info)) {
int flags = 0;
#ifndef __OpenBSD__
flags = CLOCK_UPTIME_PRECISE;
#else
flags = CLOCK_UPTIME;
#endif
if (0 == clock_gettime(flags, &s_info)) {
uptime = s_info.tv_sec;
}
#endif
+445
View File
@@ -0,0 +1,445 @@
#include "modules/wayfire/backend.hpp"
#include <json/json.h>
#include <spdlog/spdlog.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <algorithm>
#include <bit>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <ranges>
#include <thread>
namespace waybar::modules::wayfire {
std::weak_ptr<IPC> IPC::instance;
// C++23: std::byteswap
inline auto byteswap(uint32_t x) -> uint32_t {
return (x & 0xff000000) >> 24 | (x & 0x00ff0000) >> 8 | (x & 0x0000ff00) << 8 |
(x & 0x000000ff) << 24;
}
auto pack_and_write(Sock& sock, std::string&& buf) -> void {
uint32_t len = buf.size();
if constexpr (std::endian::native != std::endian::little) len = byteswap(len);
(void)write(sock.fd, &len, 4);
(void)write(sock.fd, buf.data(), buf.size());
}
auto read_exact(Sock& sock, size_t n) -> std::string {
auto buf = std::string(n, 0);
for (size_t i = 0; i < n;) i += read(sock.fd, &buf[i], n - i);
return buf;
}
// https://github.com/WayfireWM/pywayfire/blob/69b7c21/wayfire/ipc.py#L438
inline auto is_mapped_toplevel_view(const Json::Value& view) -> bool {
return view["mapped"].asBool() && view["role"] != "desktop-environment" &&
view["pid"].asInt() != -1;
}
auto State::Wset::count_ws(const Json::Value& pos) -> Workspace& {
auto x = pos["x"].asInt();
auto y = pos["y"].asInt();
return wss.at(ws_w * y + x);
}
auto State::Wset::locate_ws(const Json::Value& geo) -> Workspace& {
return const_cast<Workspace&>(std::as_const(*this).locate_ws(geo));
}
auto State::Wset::locate_ws(const Json::Value& geo) const -> const Workspace& {
const auto& out = output.value().get();
auto [qx, rx] = std::div(geo["x"].asInt(), out.w);
auto [qy, ry] = std::div(geo["y"].asInt(), out.h);
auto x = std::max(0, (int)ws_x + qx - int{rx < 0});
auto y = std::max(0, (int)ws_y + qy - int{ry < 0});
return wss.at(ws_w * y + x);
}
auto State::update_view(const Json::Value& view) -> void {
auto id = view["id"].asUInt();
// erase old view information
if (views.contains(id)) {
auto& old_view = views.at(id);
auto& ws = wsets.at(old_view["wset-index"].asUInt()).locate_ws(old_view["geometry"]);
ws.num_views--;
if (old_view["sticky"].asBool()) ws.num_sticky_views--;
views.erase(id);
}
// insert or assign new view information
if (is_mapped_toplevel_view(view)) {
try {
// view["wset-index"] could be messed up
auto& ws = wsets.at(view["wset-index"].asUInt()).locate_ws(view["geometry"]);
ws.num_views++;
if (view["sticky"].asBool()) ws.num_sticky_views++;
views.emplace(id, view);
} catch (const std::exception&) {
}
}
}
auto IPC::get_instance() -> std::shared_ptr<IPC> {
auto p = instance.lock();
if (!p) instance = p = std::shared_ptr<IPC>(new IPC);
return p;
}
auto IPC::connect() -> Sock {
auto* path = std::getenv("WAYFIRE_SOCKET");
if (path == nullptr) {
throw std::runtime_error{"Wayfire IPC: ipc not available"};
}
auto sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1) {
throw std::runtime_error{"Wayfire IPC: socket() failed"};
}
auto addr = sockaddr_un{.sun_family = AF_UNIX};
std::strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
if (::connect(sock, (const sockaddr*)&addr, sizeof(addr)) == -1) {
close(sock);
throw std::runtime_error{"Wayfire IPC: connect() failed"};
}
return {sock};
}
auto IPC::receive(Sock& sock) -> Json::Value {
auto len = *reinterpret_cast<uint32_t*>(read_exact(sock, 4).data());
if constexpr (std::endian::native != std::endian::little) len = byteswap(len);
auto buf = read_exact(sock, len);
Json::Value json;
std::string err;
auto* reader = reader_builder.newCharReader();
if (!reader->parse(&*buf.begin(), &*buf.end(), &json, &err)) {
throw std::runtime_error{"Wayfire IPC: parse json failed: " + err};
}
return json;
}
auto IPC::send(const std::string& method, Json::Value&& data) -> Json::Value {
spdlog::debug("Wayfire IPC: send method \"{}\"", method);
auto sock = connect();
Json::Value json;
json["method"] = method;
json["data"] = std::move(data);
pack_and_write(sock, Json::writeString(writer_builder, json));
auto res = receive(sock);
root_event_handler(method, res);
return res;
}
auto IPC::start() -> void {
spdlog::info("Wayfire IPC: starting");
// init state
send("window-rules/list-outputs", {});
send("window-rules/list-wsets", {});
send("window-rules/list-views", {});
send("window-rules/get-focused-view", {});
send("window-rules/get-focused-output", {});
std::thread([&] {
auto sock = connect();
{
Json::Value json;
json["method"] = "window-rules/events/watch";
pack_and_write(sock, Json::writeString(writer_builder, json));
if (receive(sock)["result"] != "ok") {
spdlog::error(
"Wayfire IPC: method \"window-rules/events/watch\""
" have failed");
return;
}
}
while (auto json = receive(sock)) {
auto ev = json["event"].asString();
spdlog::debug("Wayfire IPC: received event \"{}\"", ev);
root_event_handler(ev, json);
}
}).detach();
}
auto IPC::register_handler(const std::string& event, const EventHandler& handler) -> void {
auto _ = std::lock_guard{handlers_mutex};
handlers.emplace_back(event, handler);
}
auto IPC::unregister_handler(EventHandler& handler) -> void {
auto _ = std::lock_guard{handlers_mutex};
handlers.remove_if([&](auto& e) { return &e.second.get() == &handler; });
}
auto IPC::root_event_handler(const std::string& event, const Json::Value& data) -> void {
bool new_output_detected;
{
auto _ = lock_state();
update_state_handler(event, data);
new_output_detected = state.new_output_detected;
state.new_output_detected = false;
}
if (new_output_detected) {
send("window-rules/list-outputs", {});
send("window-rules/list-wsets", {});
}
{
auto _ = std::lock_guard{handlers_mutex};
for (const auto& [_event, handler] : handlers)
if (_event == event) handler(event);
}
}
auto IPC::update_state_handler(const std::string& event, const Json::Value& data) -> void {
// IPC events
// https://github.com/WayfireWM/wayfire/blob/053b222/plugins/ipc-rules/ipc-events.hpp#L108-L125
/*
[x] view-mapped
[x] view-unmapped
[-] view-set-output // for detect new output
[ ] view-geometry-changed // -> view-workspace-changed
[x] view-wset-changed
[x] view-focused
[x] view-title-changed
[x] view-app-id-changed
[x] plugin-activation-state-changed
[x] output-gain-focus
[ ] view-tiled
[ ] view-minimized
[ ] view-fullscreened
[x] view-sticky
[x] view-workspace-changed
[x] output-wset-changed
[x] wset-workspace-changed
*/
if (event == "view-mapped") {
// data: { event, view }
state.update_view(data["view"]);
return;
}
if (event == "view-unmapped") {
// data: { event, view }
try {
// data["view"]["wset-index"] could be messed up
state.update_view(data["view"]);
state.maybe_empty_focus_wset_idx = data["view"]["wset-index"].asUInt();
} catch (const std::exception&) {
}
return;
}
if (event == "view-set-output") {
// data: { event, output?, view }
// new output event
if (!state.outputs.contains(data["view"]["output-name"].asString())) {
state.new_output_detected = true;
}
return;
}
if (event == "view-wset-changed") {
// data: { event, old-wset: wset, new-wset: wset, view }
state.maybe_empty_focus_wset_idx = data["old-wset"]["index"].asUInt();
state.update_view(data["view"]);
return;
}
if (event == "view-focused") {
// data: { event, view? }
if (const auto& view = data["view"]) {
try {
// view["wset-index"] could be messed up
auto& wset = state.wsets.at(view["wset-index"].asUInt());
wset.focused_view_id = view["id"].asUInt();
} catch (const std::exception&) {
}
} else {
// focused to null
if (state.wsets.contains(state.maybe_empty_focus_wset_idx))
state.wsets.at(state.maybe_empty_focus_wset_idx).focused_view_id = {};
}
return;
}
if (event == "view-title-changed" || event == "view-app-id-changed" || event == "view-sticky") {
// data: { event, view }
state.update_view(data["view"]);
return;
}
if (event == "plugin-activation-state-changed") {
// data: { event, plugin: name, state: bool, output: id, output-data: output }
auto plugin = data["plugin"].asString();
auto plugin_state = data["state"].asBool();
if (plugin == "vswitch") {
state.vswitching = plugin_state;
if (plugin_state) {
state.maybe_empty_focus_wset_idx = data["output-data"]["wset-index"].asUInt();
}
}
return;
}
if (event == "output-gain-focus") {
// data: { event, output }
state.focused_output_name = data["output"]["name"].asString();
return;
}
if (event == "view-workspace-changed") {
// data: { event, from: point, to: point, view }
if (state.vswitching) {
if (state.vswitch_sticky_view_id == 0) {
auto& wset = state.wsets.at(data["view"]["wset-index"].asUInt());
auto& old_ws = wset.locate_ws(state.views.at(data["view"]["id"].asUInt())["geometry"]);
auto& new_ws = wset.count_ws(data["to"]);
old_ws.num_views--;
new_ws.num_views++;
if (data["view"]["sticky"].asBool()) {
old_ws.num_sticky_views--;
new_ws.num_sticky_views++;
}
state.update_view(data["view"]);
state.vswitch_sticky_view_id = data["view"]["id"].asUInt();
} else {
state.vswitch_sticky_view_id = {};
}
return;
}
state.update_view(data["view"]);
return;
}
if (event == "output-wset-changed") {
// data: { event, new-wset: wset.name, output: id, new-wset-data: wset, output-data: output }
auto& output = state.outputs.at(data["output-data"]["name"].asString());
auto wset_idx = data["new-wset-data"]["index"].asUInt();
state.wsets.at(wset_idx).output = output;
output.wset_idx = wset_idx;
return;
}
if (event == "wset-workspace-changed") {
// data: { event, previous-workspace: point, new-workspace: point,
// output: id, wset: wset.name, output-data: output, wset-data: wset }
auto wset_idx = data["wset-data"]["index"].asUInt();
auto& wset = state.wsets.at(wset_idx);
wset.ws_x = data["new-workspace"]["x"].asUInt();
wset.ws_y = data["new-workspace"]["y"].asUInt();
// correct existing views geometry
auto& out = wset.output.value().get();
auto dx = (int)out.w * ((int)wset.ws_x - data["previous-workspace"]["x"].asInt());
auto dy = (int)out.h * ((int)wset.ws_y - data["previous-workspace"]["y"].asInt());
for (auto& [_, view] : state.views) {
if (view["wset-index"].asUInt() == wset_idx &&
view["id"].asUInt() != state.vswitch_sticky_view_id) {
view["geometry"]["x"] = view["geometry"]["x"].asInt() - dx;
view["geometry"]["y"] = view["geometry"]["y"].asInt() - dy;
}
}
return;
}
// IPC responses
// https://github.com/WayfireWM/wayfire/blob/053b222/plugins/ipc-rules/ipc-rules.cpp#L27-L37
if (event == "window-rules/list-views") {
// data: [ view ]
state.views.clear();
for (auto& [_, wset] : state.wsets) std::ranges::fill(wset.wss, State::Workspace{});
for (const auto& view : data | std::views::filter(is_mapped_toplevel_view)) {
state.update_view(view);
}
return;
}
if (event == "window-rules/list-outputs") {
// data: [ output ]
state.outputs.clear();
for (const auto& output_data : data) {
state.outputs.emplace(output_data["name"].asString(),
State::Output{
.id = output_data["id"].asUInt(),
.w = output_data["geometry"]["width"].asUInt(),
.h = output_data["geometry"]["height"].asUInt(),
.wset_idx = output_data["wset-index"].asUInt(),
});
}
return;
}
if (event == "window-rules/list-wsets") {
// data: [ wset ]
std::unordered_map<size_t, State::Wset> wsets;
for (const auto& wset_data : data) {
auto wset_idx = wset_data["index"].asUInt();
auto output_name = wset_data["output-name"].asString();
auto output = state.outputs.contains(output_name)
? std::optional{std::ref(state.outputs.at(output_name))}
: std::nullopt;
const auto& ws_data = wset_data["workspace"];
auto ws_w = ws_data["grid_width"].asUInt();
auto ws_h = ws_data["grid_height"].asUInt();
wsets.emplace(wset_idx, State::Wset{
.output = output,
.wss = std::vector<State::Workspace>(ws_w * ws_h),
.ws_w = ws_w,
.ws_h = ws_h,
.ws_x = ws_data["x"].asUInt(),
.ws_y = ws_data["y"].asUInt(),
});
if (state.wsets.contains(wset_idx)) {
auto& old_wset = state.wsets.at(wset_idx);
auto& new_wset = wsets.at(wset_idx);
new_wset.wss = std::move(old_wset.wss);
new_wset.focused_view_id = old_wset.focused_view_id;
}
}
state.wsets = std::move(wsets);
return;
}
if (event == "window-rules/get-focused-view") {
// data: { ok, info: view? }
if (const auto& view = data["info"]) {
auto& wset = state.wsets.at(view["wset-index"].asUInt());
wset.focused_view_id = view["id"].asUInt();
state.update_view(view);
}
return;
}
if (event == "window-rules/get-focused-output") {
// data: { ok, info: output }
state.focused_output_name = data["info"]["name"].asString();
return;
}
}
} // namespace waybar::modules::wayfire
+77
View File
@@ -0,0 +1,77 @@
#include "modules/wayfire/window.hpp"
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include <spdlog/spdlog.h>
#include "util/rewrite_string.hpp"
#include "util/sanitize_str.hpp"
namespace waybar::modules::wayfire {
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: AAppIconLabel(config, "window", id, "{title}", 0, true),
ipc{IPC::get_instance()},
handler{[this](const auto&) { dp.emit(); }},
bar_{bar} {
ipc->register_handler("view-unmapped", handler);
ipc->register_handler("view-focused", handler);
ipc->register_handler("view-title-changed", handler);
ipc->register_handler("view-app-id-changed", handler);
ipc->register_handler("window-rules/get-focused-view", handler);
dp.emit();
}
Window::~Window() { ipc->unregister_handler(handler); }
auto Window::update() -> void {
update_icon_label();
AAppIconLabel::update();
}
auto Window::update_icon_label() -> void {
auto _ = ipc->lock_state();
const auto& output = ipc->get_outputs().at(bar_.output->name);
const auto& wset = ipc->get_wsets().at(output.wset_idx);
const auto& views = ipc->get_views();
auto ctx = bar_.window.get_style_context();
if (views.contains(wset.focused_view_id)) {
const auto& view = views.at(wset.focused_view_id);
auto title = view["title"].asString();
auto app_id = view["app-id"].asString();
// update label
label_.set_markup(waybar::util::rewriteString(
fmt::format(fmt::runtime(format_), fmt::arg("title", waybar::util::sanitize_string(title)),
fmt::arg("app_id", waybar::util::sanitize_string(app_id))),
config_["rewrite"]));
// update window#waybar.solo
if (wset.locate_ws(view["geometry"]).num_views > 1)
ctx->remove_class("solo");
else
ctx->add_class("solo");
// update window#waybar.<app_id>
ctx->remove_class(old_app_id_);
ctx->add_class(old_app_id_ = app_id);
// update window#waybar.empty
ctx->remove_class("empty");
//
updateAppIconName(app_id, "");
label_.show();
} else {
ctx->add_class("empty");
updateAppIconName("", "");
label_.hide();
}
}
} // namespace waybar::modules::wayfire
+183
View File
@@ -0,0 +1,183 @@
#include "modules/wayfire/workspaces.hpp"
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include <spdlog/spdlog.h>
#include <string>
#include <utility>
#include "modules/wayfire/backend.hpp"
namespace waybar::modules::wayfire {
Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config)
: AModule{config, "workspaces", id, false, !config["disable-scroll"].asBool()},
ipc{IPC::get_instance()},
handler{[this](const auto&) { dp.emit(); }},
bar_{bar} {
// init box_
box_.set_name("workspaces");
if (!id.empty()) box_.get_style_context()->add_class(id);
box_.get_style_context()->add_class(MODULE_CLASS);
event_box_.add(box_);
// scroll events
if (!config_["disable-scroll"].asBool()) {
auto& target = config_["enable-bar-scroll"].asBool() ? const_cast<Bar&>(bar_).window
: dynamic_cast<Gtk::Widget&>(box_);
target.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
target.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
}
// listen events
ipc->register_handler("view-mapped", handler);
ipc->register_handler("view-unmapped", handler);
ipc->register_handler("view-wset-changed", handler);
ipc->register_handler("output-gain-focus", handler);
ipc->register_handler("view-sticky", handler);
ipc->register_handler("view-workspace-changed", handler);
ipc->register_handler("output-wset-changed", handler);
ipc->register_handler("wset-workspace-changed", handler);
ipc->register_handler("window-rules/list-views", handler);
ipc->register_handler("window-rules/list-outputs", handler);
ipc->register_handler("window-rules/list-wsets", handler);
ipc->register_handler("window-rules/get-focused-output", handler);
// initial render
dp.emit();
}
Workspaces::~Workspaces() { ipc->unregister_handler(handler); }
auto Workspaces::handleScroll(GdkEventScroll* e) -> bool {
// Ignore emulated scroll events on window
if (gdk_event_get_pointer_emulated((GdkEvent*)e) != 0) return false;
auto dir = AModule::getScrollDir(e);
if (dir == SCROLL_DIR::NONE) return true;
int delta;
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT)
delta = 1;
else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT)
delta = -1;
else
return true;
// cycle workspace
Json::Value data;
{
auto _ = ipc->lock_state();
const auto& output = ipc->get_outputs().at(bar_.output->name);
const auto& wset = ipc->get_wsets().at(output.wset_idx);
auto n = wset.ws_w * wset.ws_h;
auto i = (wset.ws_idx() + delta + n) % n;
data["x"] = Json::Value((uint64_t)i % wset.ws_w);
data["y"] = Json::Value((uint64_t)i / wset.ws_h);
data["output-id"] = Json::Value((uint64_t)output.id);
}
ipc->send("vswitch/set-workspace", std::move(data));
return true;
}
auto Workspaces::update() -> void {
update_box();
AModule::update();
}
auto Workspaces::update_box() -> void {
auto _ = ipc->lock_state();
const auto& output_name = bar_.output->name;
const auto& output = ipc->get_outputs().at(output_name);
const auto& wset = ipc->get_wsets().at(output.wset_idx);
auto output_focused = ipc->get_focused_output_name() == output_name;
auto ws_w = wset.ws_w;
auto ws_h = wset.ws_h;
auto num_wss = ws_w * ws_h;
// add buttons for new workspaces
for (auto i = buttons_.size(); i < num_wss; i++) {
auto& btn = buttons_.emplace_back("");
box_.pack_start(btn, false, false, 0);
btn.set_relief(Gtk::RELIEF_NONE);
if (!config_["disable-click"].asBool()) {
btn.signal_pressed().connect([=, this] {
Json::Value data;
data["x"] = Json::Value((uint64_t)i % ws_w);
data["y"] = Json::Value((uint64_t)i / ws_h);
data["output-id"] = Json::Value((uint64_t)output.id);
ipc->send("vswitch/set-workspace", std::move(data));
});
}
}
// remove buttons for removed workspaces
buttons_.resize(num_wss);
// update buttons
for (size_t i = 0; i < num_wss; i++) {
const auto& ws = wset.wss[i];
auto& btn = buttons_[i];
auto ctx = btn.get_style_context();
auto ws_focused = i == wset.ws_idx();
auto ws_empty = ws.num_views == 0;
// update #workspaces button.focused
if (ws_focused)
ctx->add_class("focused");
else
ctx->remove_class("focused");
// update #workspaces button.empty
if (ws_empty)
ctx->add_class("empty");
else
ctx->remove_class("empty");
// update #workspaces button.current_output
if (output_focused)
ctx->add_class("current_output");
else
ctx->remove_class("current_output");
// update label
auto label = std::to_string(i + 1);
if (config_["format"].isString()) {
auto format = config_["format"].asString();
auto ws_idx = std::to_string(i + 1);
const auto& icons = config_["format-icons"];
std::string icon;
if (!icons)
icon = ws_idx;
else if (ws_focused && icons["focused"])
icon = icons["focused"].asString();
else if (icons[ws_idx])
icon = icons[ws_idx].asString();
else if (icons["default"])
icon = icons["default"].asString();
else
icon = ws_idx;
label = fmt::format(fmt::runtime(format), fmt::arg("icon", icon), fmt::arg("index", ws_idx),
fmt::arg("output", output_name));
}
if (!config_["disable-markup"].asBool())
static_cast<Gtk::Label*>(btn.get_children()[0])->set_markup(label);
else
btn.set_label(label);
//
if (config_["current-only"].asBool() && i != wset.ws_idx())
btn.hide();
else
btn.show();
}
}
} // namespace waybar::modules::wayfire
+184 -58
View File
@@ -14,11 +14,15 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
mixer_api_(nullptr),
def_nodes_api_(nullptr),
default_node_name_(nullptr),
default_source_name_(nullptr),
pending_plugins_(0),
muted_(false),
source_muted_(false),
volume_(0.0),
source_volume_(0.0),
min_step_(0.0),
node_id_(0),
source_node_id_(0),
type_(nullptr) {
waybar::modules::Wireplumber::modules.push_back(this);
@@ -55,6 +59,7 @@ waybar::modules::Wireplumber::~Wireplumber() {
g_clear_object(&mixer_api_);
g_clear_object(&def_nodes_api_);
g_free(default_node_name_);
g_free(default_source_name_);
g_free(type_);
}
@@ -94,6 +99,43 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber*
spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, self->node_name_);
}
void waybar::modules::Wireplumber::updateSourceName(waybar::modules::Wireplumber* self,
uint32_t id) {
spdlog::debug("[{}]: updating source name with node.id {}", self->name_, id);
if (!isValidNodeId(id)) {
spdlog::warn("[{}]: '{}' is not a valid source node ID. Ignoring source 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, nullptr));
if (proxy == nullptr) {
auto err = fmt::format("Source object '{}' not found\n", id);
spdlog::error("[{}]: {}", self->name_, err);
return;
}
g_autoptr(WpProperties) properties =
WP_IS_PIPEWIRE_OBJECT(proxy) != 0
? wp_pipewire_object_get_properties(WP_PIPEWIRE_OBJECT(proxy))
: wp_properties_new_empty();
g_autoptr(WpProperties) globalP = wp_global_proxy_get_global_properties(WP_GLOBAL_PROXY(proxy));
properties = wp_properties_ensure_unique_owner(properties);
wp_properties_add(properties, globalP);
wp_properties_set(properties, "object.id", nullptr);
const auto* nick = wp_properties_get(properties, "node.nick");
const auto* description = wp_properties_get(properties, "node.description");
self->source_name_ = nick != nullptr ? nick
: description != nullptr ? description
: "Unknown source name";
spdlog::debug("[{}]: Updating source name to: {}", self->name_, self->source_name_);
}
void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self, uint32_t id) {
spdlog::debug("[{}]: updating volume", self->name_);
GVariant* variant = nullptr;
@@ -120,6 +162,31 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se
self->dp.emit();
}
void waybar::modules::Wireplumber::updateSourceVolume(waybar::modules::Wireplumber* self,
uint32_t id) {
spdlog::debug("[{}]: updating source volume", self->name_);
GVariant* variant = nullptr;
if (!isValidNodeId(id)) {
spdlog::error("[{}]: '{}' is not a valid source node ID. Ignoring source volume update.",
self->name_, id);
return;
}
g_signal_emit_by_name(self->mixer_api_, "get-volume", id, &variant);
if (variant == nullptr) {
spdlog::debug("[{}]: Source node {} does not support volume", self->name_, id);
return;
}
g_variant_lookup(variant, "volume", "d", &self->source_volume_);
g_variant_lookup(variant, "mute", "b", &self->source_muted_);
g_clear_pointer(&variant, g_variant_unref);
self->dp.emit();
}
void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* self, uint32_t 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, nullptr));
@@ -127,9 +194,9 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber*
if (node == nullptr) {
// log a warning only if no other widget is targeting the id.
// this reduces log spam when multiple instances of the module are used on different node types.
if (id != self->node_id_) {
if (id != self->node_id_ && id != self->source_node_id_) {
for (auto const& module : waybar::modules::Wireplumber::modules) {
if (module->node_id_ == id) {
if (module->node_id_ == id || module->source_node_id_ == id) {
return;
}
}
@@ -142,68 +209,73 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber*
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_, self->type_, id, name, self->default_node_name_, self->node_id_);
return;
if (self->node_id_ == id) {
spdlog::debug("[{}]: (onMixerChanged: {}) - updating sink volume for node: {}", self->name_,
self->type_, name);
updateVolume(self, id);
} else if (self->source_node_id_ == id) {
spdlog::debug("[{}]: (onMixerChanged: {}) - updating source volume for node: {}", self->name_,
self->type_, name);
updateSourceVolume(self, id);
}
spdlog::debug(
"[{}]: (onMixerChanged: {}) - Need to update volume for node with id {} and name {}",
self->name_, self->type_, id, name);
updateVolume(self, id);
}
void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wireplumber* self) {
spdlog::debug("[{}]: (onDefaultNodesApiChanged: {})", self->name_, self->type_);
// Handle sink
uint32_t defaultNodeId;
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &defaultNodeId);
if (!isValidNodeId(defaultNodeId)) {
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring '{}' node change.", self->name_,
defaultNodeId, self->type_);
return;
if (isValidNodeId(defaultNodeId)) {
g_autoptr(WpNode) node = static_cast<WpNode*>(
wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id",
"=u", defaultNodeId, nullptr));
if (node != nullptr) {
const gchar* defaultNodeName =
wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name");
if (g_strcmp0(self->default_node_name_, defaultNodeName) != 0 ||
self->node_id_ != defaultNodeId) {
spdlog::debug("[{}]: Default sink changed to -> Node(name: {}, id: {})", self->name_,
defaultNodeName, defaultNodeId);
g_free(self->default_node_name_);
self->default_node_name_ = g_strdup(defaultNodeName);
self->node_id_ = defaultNodeId;
updateVolume(self, defaultNodeId);
updateNodeName(self, defaultNodeId);
}
}
}
g_autoptr(WpNode) node = static_cast<WpNode*>(
wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id",
"=u", defaultNodeId, nullptr));
// Handle source
uint32_t defaultSourceId;
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Source", &defaultSourceId);
if (node == nullptr) {
spdlog::warn("[{}]: (onDefaultNodesApiChanged: {}) - Object with id {} not found", self->name_,
self->type_, defaultNodeId);
return;
if (isValidNodeId(defaultSourceId)) {
g_autoptr(WpNode) sourceNode = static_cast<WpNode*>(
wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id",
"=u", defaultSourceId, nullptr));
if (sourceNode != nullptr) {
const gchar* defaultSourceName =
wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(sourceNode), "node.name");
if (g_strcmp0(self->default_source_name_, defaultSourceName) != 0 ||
self->source_node_id_ != defaultSourceId) {
spdlog::debug("[{}]: Default source changed to -> Node(name: {}, id: {})", self->name_,
defaultSourceName, defaultSourceId);
g_free(self->default_source_name_);
self->default_source_name_ = g_strdup(defaultSourceName);
self->source_node_id_ = defaultSourceId;
updateSourceVolume(self, defaultSourceId);
updateSourceName(self, defaultSourceId);
}
}
}
const gchar* defaultNodeName =
wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name");
spdlog::debug(
"[{}]: (onDefaultNodesApiChanged: {}) - got the following default node: Node(name: {}, id: "
"{})",
self->name_, self->type_, defaultNodeName, defaultNodeId);
if (g_strcmp0(self->default_node_name_, defaultNodeName) == 0 &&
self->node_id_ == defaultNodeId) {
spdlog::debug(
"[{}]: (onDefaultNodesApiChanged: {}) - Default node has not changed. Node(name: {}, id: "
"{}). Ignoring.",
self->name_, self->type_, self->default_node_name_, defaultNodeId);
return;
}
spdlog::debug(
"[{}]: (onDefaultNodesApiChanged: {}) - Default node changed to -> Node(name: {}, id: {})",
self->name_, self->type_, defaultNodeName, defaultNodeId);
g_free(self->default_node_name_);
self->default_node_name_ = g_strdup(defaultNodeName);
self->node_id_ = defaultNodeId;
updateVolume(self, defaultNodeId);
updateNodeName(self, defaultNodeId);
}
void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wireplumber* self) {
@@ -223,18 +295,31 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir
throw std::runtime_error("Mixer api is not loaded\n");
}
// Get default sink
g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", self->type_,
&self->default_node_name_);
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &self->node_id_);
// Get default source
g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", "Audio/Source",
&self->default_source_name_);
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Source",
&self->source_node_id_);
if (self->default_node_name_ != nullptr) {
spdlog::debug(
"[{}]: (onObjectManagerInstalled: {}) - default configured node name: {} and id: {}",
self->name_, self->type_, self->default_node_name_, self->node_id_);
}
if (self->default_source_name_ != nullptr) {
spdlog::debug("[{}]: default source: {} (id: {})", self->name_, self->default_source_name_,
self->source_node_id_);
}
updateVolume(self, self->node_id_);
updateNodeName(self, self->node_id_);
updateSourceVolume(self, self->source_node_id_);
updateSourceName(self, self->source_node_id_);
g_signal_connect_swapped(self->mixer_api_, "changed", (GCallback)onMixerChanged, self);
g_signal_connect_swapped(self->def_nodes_api_, "changed", (GCallback)onDefaultNodesApiChanged,
@@ -271,6 +356,8 @@ void waybar::modules::Wireplumber::prepare(waybar::modules::Wireplumber* self) {
spdlog::debug("[{}]: preparing object manager: '{}'", name_, self->type_);
wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
"=s", self->type_, nullptr);
wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
"=s", "Audio/Source", nullptr);
}
void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res,
@@ -332,19 +419,57 @@ auto waybar::modules::Wireplumber::update() -> void {
auto format = format_;
std::string tooltipFormat;
// Handle sink mute state
if (muted_) {
format = config_["format-muted"].isString() ? config_["format-muted"].asString() : format;
label_.get_style_context()->add_class("muted");
label_.get_style_context()->add_class("sink-muted");
} else {
label_.get_style_context()->remove_class("muted");
label_.get_style_context()->remove_class("sink-muted");
}
// Handle source mute state
if (source_muted_) {
label_.get_style_context()->add_class("source-muted");
} else {
label_.get_style_context()->remove_class("source-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);
int source_vol = round(source_volume_ * 100.0);
getState(vol);
// Get the state and apply state-specific format if available
auto state = getState(vol);
if (!state.empty()) {
std::string format_name = muted_ ? "format-muted" : "format";
std::string state_format_name = format_name + "-" + state;
if (config_[state_format_name].isString()) {
format = config_[state_format_name].asString();
}
}
// Prepare source format string (similar to PulseAudio)
std::string format_source = "{volume}%";
if (source_muted_) {
if (config_["format-source-muted"].isString()) {
format_source = config_["format-source-muted"].asString();
}
} else {
if (config_["format-source"].isString()) {
format_source = config_["format-source"].asString();
}
}
// Format the source string with actual volume
std::string formatted_source =
fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_vol));
std::string markup =
fmt::format(fmt::runtime(format), fmt::arg("node_name", node_name_), fmt::arg("volume", vol),
fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source),
fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_));
label_.set_markup(markup);
if (tooltipEnabled()) {
if (tooltipFormat.empty() && config_["tooltip-format"].isString()) {
@@ -352,9 +477,10 @@ auto waybar::modules::Wireplumber::update() -> void {
}
if (!tooltipFormat.empty()) {
label_.set_tooltip_text(fmt::format(fmt::runtime(tooltipFormat),
fmt::arg("node_name", node_name_),
fmt::arg("volume", vol), fmt::arg("icon", getIcon(vol))));
label_.set_tooltip_text(fmt::format(
fmt::runtime(tooltipFormat), fmt::arg("node_name", node_name_), fmt::arg("volume", vol),
fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source),
fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_)));
} else {
label_.set_tooltip_text(node_name_);
}
+14 -236
View File
@@ -26,194 +26,6 @@
namespace waybar::modules::wlr {
/* Icon loading functions */
static std::vector<std::string> search_prefix() {
std::vector<std::string> prefixes = {""};
std::string home_dir = std::getenv("HOME");
prefixes.push_back(home_dir + "/.local/share/");
auto xdg_data_dirs = std::getenv("XDG_DATA_DIRS");
if (!xdg_data_dirs) {
prefixes.emplace_back("/usr/share/");
prefixes.emplace_back("/usr/local/share/");
} else {
std::string xdg_data_dirs_str(xdg_data_dirs);
size_t start = 0, end = 0;
do {
end = xdg_data_dirs_str.find(':', start);
auto p = xdg_data_dirs_str.substr(start, end - start);
prefixes.push_back(trim(p) + "/");
start = end == std::string::npos ? end : end + 1;
} while (end != std::string::npos);
}
for (auto &p : prefixes) spdlog::debug("Using 'desktop' search path prefix: {}", p);
return prefixes;
}
static Glib::RefPtr<Gdk::Pixbuf> load_icon_from_file(std::string icon_path, int size) {
try {
auto pb = Gdk::Pixbuf::create_from_file(icon_path, size, size);
return pb;
} catch (...) {
return {};
}
}
static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_by_name(const std::string &app_id) {
static std::vector<std::string> prefixes = search_prefix();
std::vector<std::string> app_folders = {"", "applications/", "applications/kde/",
"applications/org.kde."};
std::vector<std::string> suffixes = {"", ".desktop"};
for (auto &prefix : prefixes) {
for (auto &folder : app_folders) {
for (auto &suffix : suffixes) {
auto app_info_ =
Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
if (!app_info_) {
continue;
}
return app_info_;
}
}
}
return {};
}
Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id) {
auto app_info = get_app_info_by_name(app_id);
if (app_info) {
return app_info;
}
std::string desktop_file = "";
gchar ***desktop_list = g_desktop_app_info_search(app_id.c_str());
if (desktop_list != nullptr && desktop_list[0] != nullptr) {
for (size_t i = 0; desktop_list[0][i]; i++) {
if (desktop_file == "") {
desktop_file = desktop_list[0][i];
} else {
auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]);
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;
}
}
}
g_strfreev(desktop_list[0]);
}
g_free(desktop_list);
return get_app_info_by_name(desktop_file);
}
void Task::set_app_info_from_app_id_list(const std::string &app_id_list) {
std::string app_id;
std::istringstream stream(app_id_list);
/* Wayfire sends a list of app-id's in space separated format, other compositors
* send a single app-id, but in any case this works fine */
while (stream >> app_id) {
app_info_ = get_desktop_app_info(app_id);
if (app_info_) {
return;
}
auto lower_app_id = app_id;
std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(),
[](char c) { return std::tolower(c); });
app_info_ = get_desktop_app_info(lower_app_id);
if (app_info_) {
return;
}
size_t start = 0, end = app_id.size();
start = app_id.rfind(".", end);
std::string app_name = app_id.substr(start + 1, app_id.size());
app_info_ = get_desktop_app_info(app_name);
if (app_info_) {
return;
}
start = app_id.find("-");
app_name = app_id.substr(0, start);
app_info_ = get_desktop_app_info(app_name);
}
}
static std::string get_icon_name_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
const std::string &app_id) {
if (icon_theme->lookup_icon(app_id, 24)) return app_id;
return "";
}
bool Task::image_load_icon(Gtk::Image &image, const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size) {
std::string ret_icon_name = "unknown";
if (app_info) {
std::string icon_name =
get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class());
if (!icon_name.empty()) {
ret_icon_name = icon_name;
} else {
if (app_info->get_icon()) {
ret_icon_name = app_info->get_icon()->to_string();
}
}
}
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
auto scaled_icon_size = size * image.get_scale_factor();
try {
pixbuf = icon_theme->load_icon(ret_icon_name, scaled_icon_size, Gtk::ICON_LOOKUP_FORCE_SIZE);
spdlog::debug("{} Loaded icon '{}'", repr(), ret_icon_name);
} catch (...) {
if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS)) {
pixbuf = load_icon_from_file(ret_icon_name, scaled_icon_size);
spdlog::debug("{} Loaded icon from file '{}'", repr(), ret_icon_name);
} else {
try {
pixbuf = DefaultGtkIconThemeWrapper::load_icon(
"image-missing", scaled_icon_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
spdlog::debug("{} Loaded icon from resource", repr());
} catch (...) {
pixbuf = {};
spdlog::debug("{} Unable to load icon.", repr());
}
}
}
if (pixbuf) {
if (pixbuf->get_width() != scaled_icon_size) {
int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();
pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
}
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image.get_scale_factor(),
image.get_window());
image.set(surface);
return true;
}
return false;
}
/* Task class implementation */
uint32_t Task::global_id = 0;
@@ -299,16 +111,11 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
with_name_ = true;
}
auto icon_pos = format.find("{icon}");
if (icon_pos == 0) {
auto parts = split(format, "{icon}", 1);
format_before_ = parts[0];
if (parts.size() > 1) {
with_icon_ = true;
format_after_ = format.substr(6);
} else if (icon_pos == std::string::npos) {
format_before_ = format;
} else {
with_icon_ = true;
format_before_ = format.substr(0, icon_pos);
format_after_ = format.substr(icon_pos + 6);
format_after_ = parts[1];
}
} else {
/* The default is to only show the icon */
@@ -395,7 +202,7 @@ void Task::handle_title(const char *title) {
return;
}
set_app_info_from_app_id_list(title_);
app_info_ = IconLoader::get_app_info_from_app_id_list(title_);
name_ = app_info_ ? app_info_->get_display_name() : title;
if (!with_icon_) {
@@ -403,15 +210,7 @@ void Task::handle_title(const char *title) {
}
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
bool found = false;
for (auto &icon_theme : tbar_->icon_themes()) {
if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) {
found = true;
break;
}
}
if (found)
if (tbar_->icon_loader().image_load_icon(icon_, app_info_, icon_size))
icon_.show();
else
spdlog::debug("Couldn't find icon for {}", title_);
@@ -460,7 +259,7 @@ void Task::handle_app_id(const char *app_id) {
return;
}
set_app_info_from_app_id_list(app_id_);
app_info_ = IconLoader::get_app_info_from_app_id_list(app_id_);
name_ = app_info_ ? app_info_->get_display_name() : app_id;
if (!with_icon_) {
@@ -468,15 +267,7 @@ void Task::handle_app_id(const char *app_id) {
}
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
bool found = false;
for (auto &icon_theme : tbar_->icon_themes()) {
if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) {
found = true;
break;
}
}
if (found)
if (tbar_->icon_loader().image_load_icon(icon_, app_info_, icon_size))
icon_.show();
else
spdlog::debug("Couldn't find icon for {}", app_id_);
@@ -712,6 +503,9 @@ void Task::update() {
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)));
txt = waybar::util::rewriteString(txt, config_["rewrite"]);
if (markup)
button.set_tooltip_markup(txt);
else
@@ -799,22 +593,10 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu
/* Get the configured icon theme if specified */
if (config_["icon-theme"].isArray()) {
for (auto &c : config_["icon-theme"]) {
auto it_name = c.asString();
auto it = Gtk::IconTheme::create();
it->set_custom_theme(it_name);
spdlog::debug("Use custom icon theme: {}", it_name);
icon_themes_.push_back(it);
icon_loader_.add_custom_icon_theme(c.asString());
}
} else if (config_["icon-theme"].isString()) {
auto it_name = config_["icon-theme"].asString();
auto it = Gtk::IconTheme::create();
it->set_custom_theme(it_name);
spdlog::debug("Use custom icon theme: {}", it_name);
icon_themes_.push_back(it);
icon_loader_.add_custom_icon_theme(config_["icon-theme"].asString());
}
// Load ignore-list
@@ -833,8 +615,6 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu
}
}
icon_themes_.push_back(Gtk::IconTheme::get_default());
for (auto &t : tasks_) {
t->handle_app_id(t->app_id().c_str());
}
@@ -969,9 +749,7 @@ bool Taskbar::all_outputs() const {
return config_["all-outputs"].isBool() && config_["all-outputs"].asBool();
}
const std::vector<Glib::RefPtr<Gtk::IconTheme>> &Taskbar::icon_themes() const {
return icon_themes_;
}
const IconLoader &Taskbar::icon_loader() const { return icon_loader_; }
const std::unordered_set<std::string> &Taskbar::ignore_list() const { return ignore_list_; }
-585
View File
@@ -1,585 +0,0 @@
#include "modules/wlr/workspace_manager.hpp"
#include <gdk/gdkwayland.h>
#include <gtkmm.h>
#include <spdlog/spdlog.h>
#include <algorithm>
#include <iterator>
#include <stdexcept>
#include <vector>
#include "client.hpp"
#include "gtkmm/widget.h"
#include "modules/wlr/workspace_manager_binding.hpp"
namespace waybar::modules::wlr {
uint32_t WorkspaceGroup::workspace_global_id = 0;
uint32_t WorkspaceManager::group_global_id = 0;
std::map<std::string, std::string> Workspace::icons_map_;
WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar,
const Json::Value &config)
: waybar::AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) {
auto config_sort_by_name = config_["sort-by-name"];
if (config_sort_by_name.isBool()) {
sort_by_name_ = config_sort_by_name.asBool();
}
auto config_sort_by_coordinates = config_["sort-by-coordinates"];
if (config_sort_by_coordinates.isBool()) {
sort_by_coordinates_ = config_sort_by_coordinates.asBool();
}
auto config_sort_by_number = config_["sort-by-number"];
if (config_sort_by_number.isBool()) {
sort_by_number_ = config_sort_by_number.asBool();
}
auto config_all_outputs = config_["all-outputs"];
if (config_all_outputs.isBool()) {
all_outputs_ = config_all_outputs.asBool();
}
auto config_active_only = config_["active-only"];
if (config_active_only.isBool()) {
active_only_ = config_active_only.asBool();
creation_delayed_ = active_only_;
}
box_.set_name("workspaces");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
box_.get_style_context()->add_class(MODULE_CLASS);
event_box_.add(box_);
add_registry_listener(this);
if (!workspace_manager_) {
return;
}
}
auto WorkspaceManager::workspace_comparator() const
-> std::function<bool(std::unique_ptr<Workspace> &, std::unique_ptr<Workspace> &)> {
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();
if (sort_by_number_) {
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_) {
if (sort_by_coordinates_) {
return is_name_eq ? is_coords_less : is_name_less;
} else {
return is_name_less;
}
}
if (sort_by_coordinates_) {
return is_coords_less;
}
return lhs->id() < rhs->id();
};
}
auto WorkspaceManager::sort_workspaces() -> void {
std::vector<std::reference_wrapper<std::unique_ptr<Workspace>>> all_workspaces;
for (auto &group : groups_) {
auto &group_workspaces = group->workspaces();
all_workspaces.reserve(all_workspaces.size() +
std::distance(group_workspaces.begin(), group_workspaces.end()));
if (!active_only()) {
all_workspaces.insert(all_workspaces.end(), group_workspaces.begin(), group_workspaces.end());
continue;
}
for (auto &workspace : group_workspaces) {
if (!workspace->is_active()) {
continue;
}
all_workspaces.push_back(workspace);
}
}
std::sort(all_workspaces.begin(), all_workspaces.end(), workspace_comparator());
for (size_t i = 0; i < all_workspaces.size(); ++i) {
box_.reorder_child(all_workspaces[i].get()->get_button_ref(), i);
}
}
auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version)
-> void {
if (workspace_manager_) {
spdlog::warn("Register workspace manager again although already registered!");
return;
}
if (version != 1) {
spdlog::warn("Using different workspace manager protocol version: {}", version);
}
workspace_manager_ = workspace_manager_bind(registry, name, version, this);
}
auto WorkspaceManager::handle_workspace_group_create(
zext_workspace_group_handle_v1 *workspace_group_handle) -> void {
auto new_id = ++group_global_id;
groups_.push_back(
std::make_unique<WorkspaceGroup>(bar_, box_, config_, *this, workspace_group_handle, new_id));
spdlog::debug("Workspace group {} created", new_id);
}
auto WorkspaceManager::handle_finished() -> void {
zext_workspace_manager_v1_destroy(workspace_manager_);
workspace_manager_ = nullptr;
}
auto WorkspaceManager::handle_done() -> void {
for (auto &group : groups_) {
group->handle_done();
}
dp.emit();
}
auto WorkspaceManager::update() -> void {
for (auto &group : groups_) {
group->update();
}
if (creation_delayed()) {
creation_delayed_ = false;
sort_workspaces();
}
AModule::update();
}
WorkspaceManager::~WorkspaceManager() {
if (!workspace_manager_) {
return;
}
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 {
auto it = std::find_if(groups_.begin(), groups_.end(),
[id](const std::unique_ptr<WorkspaceGroup> &g) { return g->id() == id; });
if (it == groups_.end()) {
spdlog::warn("Can't find group with id {}", id);
return;
}
groups_.erase(it);
}
auto WorkspaceManager::commit() -> void { zext_workspace_manager_v1_commit(workspace_manager_); }
WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value &config,
WorkspaceManager &manager,
zext_workspace_group_handle_v1 *workspace_group_handle, uint32_t id)
: bar_(bar),
box_(box),
config_(config),
workspace_manager_(manager),
workspace_group_handle_(workspace_group_handle),
id_(id) {
add_workspace_group_listener(workspace_group_handle, this);
}
auto WorkspaceGroup::fill_persistent_workspaces() -> void {
if (config_["persistent_workspaces"].isObject()) {
spdlog::warn(
"persistent_workspaces is deprecated. Please change config to use persistent-workspaces.");
}
if ((config_["persistent-workspaces"].isObject() ||
config_["persistent_workspaces"].isObject()) &&
!workspace_manager_.all_outputs()) {
const Json::Value &p_workspaces = config_["persistent-workspaces"].isObject()
? config_["persistent-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();
}
auto WorkspaceGroup::add_button(Gtk::Button &button) -> void {
box_.pack_start(button, false, false);
}
WorkspaceGroup::~WorkspaceGroup() {
if (!workspace_group_handle_) {
return;
}
zext_workspace_group_handle_v1_destroy(workspace_group_handle_);
workspace_group_handle_ = nullptr;
}
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, ""));
spdlog::debug("Workspace {} created", new_id);
if (!persistent_created_) {
fill_persistent_workspaces();
create_persistent_workspaces();
persistent_created_ = true;
}
}
auto WorkspaceGroup::handle_remove() -> void {
zext_workspace_group_handle_v1_destroy(workspace_group_handle_);
workspace_group_handle_ = nullptr;
workspace_manager_.remove_workspace_group(id_);
}
auto WorkspaceGroup::handle_output_enter(wl_output *output) -> void {
spdlog::debug("Output {} assigned to {} group", (void *)output, id_);
output_ = output;
if (!is_visible() || workspace_manager_.creation_delayed()) {
return;
}
for (auto &workspace : workspaces_) {
add_button(workspace->get_button_ref());
}
}
auto WorkspaceGroup::is_visible() const -> bool {
return output_ != nullptr &&
(workspace_manager_.all_outputs() ||
output_ == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()));
}
auto WorkspaceGroup::handle_output_leave() -> void {
spdlog::debug("Output {} remove from {} group", (void *)output_, id_);
output_ = nullptr;
if (output_ != gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())) {
return;
}
for (auto &workspace : workspaces_) {
remove_button(workspace->get_button_ref());
}
}
auto WorkspaceGroup::update() -> void {
for (auto &workspace : workspaces_) {
if (workspace_manager_.creation_delayed()) {
add_button(workspace->get_button_ref());
if (is_visible() && (workspace->is_active() || workspace->is_urgent())) {
workspace->show();
}
}
workspace->update();
}
}
auto WorkspaceGroup::remove_workspace(uint32_t id) -> void {
auto it = std::find_if(workspaces_.begin(), workspaces_.end(),
[id](const std::unique_ptr<Workspace> &w) { return w->id() == id; });
if (it == workspaces_.end()) {
spdlog::warn("Can't find workspace with id {}", id);
return;
}
workspaces_.erase(it);
}
auto WorkspaceGroup::handle_done() -> void {
need_to_sort = false;
if (!is_visible()) {
return;
}
for (auto &workspace : workspaces_) {
workspace->handle_done();
}
if (creation_delayed()) {
return;
}
if (!workspace_manager_.all_outputs()) {
sort_workspaces();
} else {
workspace_manager_.sort_workspaces();
}
}
auto WorkspaceGroup::commit() -> void { workspace_manager_.commit(); }
auto WorkspaceGroup::sort_workspaces() -> void {
std::sort(workspaces_.begin(), workspaces_.end(), workspace_manager_.workspace_comparator());
for (size_t i = 0; i < workspaces_.size(); ++i) {
box_.reorder_child(workspaces_[i]->get_button_ref(), i);
}
}
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, std::string name)
: bar_(bar),
config_(config),
workspace_group_(workspace_group),
workspace_handle_(workspace),
id_(id),
name_(name) {
if (workspace) {
add_workspace_listener(workspace, this);
} else {
state_ = (uint32_t)State::EMPTY;
}
auto config_format = config["format"];
format_ = config_format.isString() ? config_format.asString() : "{name}";
with_icon_ = format_.find("{icon}") != std::string::npos;
if (with_icon_ && icons_map_.empty()) {
auto format_icons = config["format-icons"];
for (auto &name : format_icons.getMemberNames()) {
icons_map_.emplace(name, format_icons[name].asString());
}
}
/* Handle click events if configured */
if (config_["on-click"].isString() || config_["on-click-middle"].isString() ||
config_["on-click-right"].isString()) {
button_.add_events(Gdk::BUTTON_PRESS_MASK);
button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked),
false);
}
button_.set_relief(Gtk::RELIEF_NONE);
content_.set_center_widget(label_);
button_.add(content_);
if (!workspace_group.is_visible()) {
return;
}
workspace_group.add_button(button_);
button_.show_all();
}
Workspace::~Workspace() {
workspace_group_.remove_button(button_);
if (!workspace_handle_) {
return;
}
zext_workspace_handle_v1_destroy(workspace_handle_);
workspace_handle_ = nullptr;
}
auto Workspace::update() -> void {
label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_),
fmt::arg("icon", with_icon_ ? get_icon() : "")));
}
auto Workspace::handle_state(const std::vector<uint32_t> &state) -> void {
state_ = 0;
for (auto state_entry : state) {
switch (state_entry) {
case ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE:
state_ |= (uint32_t)State::ACTIVE;
break;
case ZEXT_WORKSPACE_HANDLE_V1_STATE_URGENT:
state_ |= (uint32_t)State::URGENT;
break;
case ZEXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN:
state_ |= (uint32_t)State::HIDDEN;
break;
}
}
}
auto Workspace::handle_remove() -> void {
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,
const std::string &class_name) {
if (condition) {
context->add_class(class_name);
} else {
context->remove_class(class_name);
}
}
auto Workspace::handle_done() -> void {
spdlog::debug("Workspace {} changed to state {}", id_, state_);
auto style_context = button_.get_style_context();
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;
}
if (workspace_group_.active_only() && (is_active() || is_urgent())) {
button_.show_all();
} else if (workspace_group_.active_only() && !(is_active() || is_urgent())) {
button_.hide();
}
}
auto Workspace::get_icon() -> std::string {
if (is_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(name_);
if (named_icon_it != icons_map_.end()) {
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;
}
return name_;
}
auto Workspace::handle_clicked(GdkEventButton *bt) -> bool {
std::string action;
if (config_["on-click"].isString() && bt->button == 1) {
action = config_["on-click"].asString();
} else if (config_["on-click-middle"].isString() && bt->button == 2) {
action = config_["on-click-middle"].asString();
} else if (config_["on-click-right"].isString() && bt->button == 3) {
action = config_["on-click-right"].asString();
}
if (action.empty())
return true;
else if (action == "activate") {
zext_workspace_handle_v1_activate(workspace_handle_);
} else if (action == "close") {
zext_workspace_handle_v1_remove(workspace_handle_);
} else {
spdlog::warn("Unknown action {}", action);
}
workspace_group_.commit();
return true;
}
auto Workspace::show() -> void { button_.show_all(); }
auto Workspace::hide() -> void { button_.hide(); }
auto Workspace::handle_name(const std::string &name) -> void {
if (name_ != name) {
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 {
if (coordinates_ != coordinates) {
workspace_group_.set_need_to_sort();
}
coordinates_ = coordinates;
}
} // namespace waybar::modules::wlr
@@ -1,136 +0,0 @@
#include "modules/wlr/workspace_manager_binding.hpp"
#include <spdlog/spdlog.h>
#include <cstdint>
#include "client.hpp"
#include "modules/wlr/workspace_manager.hpp"
namespace waybar::modules::wlr {
static void handle_global(void *data, wl_registry *registry, uint32_t name, const char *interface,
uint32_t version) {
if (std::strcmp(interface, zext_workspace_manager_v1_interface.name) == 0) {
static_cast<WorkspaceManager *>(data)->register_manager(registry, name, version);
}
}
static void handle_global_remove(void *data, wl_registry *registry, uint32_t name) {
/* Nothing to do here */
}
static const wl_registry_listener registry_listener_impl = {.global = handle_global,
.global_remove = handle_global_remove};
void add_registry_listener(void *data) {
wl_display *display = Client::inst()->wl_display;
wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener_impl, data);
wl_display_roundtrip(display);
wl_display_roundtrip(display);
}
static void workspace_manager_handle_workspace_group(
void *data, zext_workspace_manager_v1 *_, zext_workspace_group_handle_v1 *workspace_group) {
static_cast<WorkspaceManager *>(data)->handle_workspace_group_create(workspace_group);
}
static void workspace_manager_handle_done(void *data, zext_workspace_manager_v1 *_) {
static_cast<WorkspaceManager *>(data)->handle_done();
}
static void workspace_manager_handle_finished(void *data, zext_workspace_manager_v1 *_) {
static_cast<WorkspaceManager *>(data)->handle_finished();
}
static const zext_workspace_manager_v1_listener workspace_manager_impl = {
.workspace_group = workspace_manager_handle_workspace_group,
.done = workspace_manager_handle_done,
.finished = workspace_manager_handle_finished,
};
zext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name,
uint32_t version, void *data) {
auto *workspace_manager = static_cast<zext_workspace_manager_v1 *>(
wl_registry_bind(registry, name, &zext_workspace_manager_v1_interface, version));
if (workspace_manager)
zext_workspace_manager_v1_add_listener(workspace_manager, &workspace_manager_impl, data);
else
spdlog::error("Failed to register manager");
return workspace_manager;
}
static void workspace_group_handle_output_enter(void *data, zext_workspace_group_handle_v1 *_,
wl_output *output) {
static_cast<WorkspaceGroup *>(data)->handle_output_enter(output);
}
static void workspace_group_handle_output_leave(void *data, zext_workspace_group_handle_v1 *_,
wl_output *output) {
static_cast<WorkspaceGroup *>(data)->handle_output_leave();
}
static void workspace_group_handle_workspace(void *data, zext_workspace_group_handle_v1 *_,
zext_workspace_handle_v1 *workspace) {
static_cast<WorkspaceGroup *>(data)->handle_workspace_create(workspace);
}
static void workspace_group_handle_remove(void *data, zext_workspace_group_handle_v1 *_) {
static_cast<WorkspaceGroup *>(data)->handle_remove();
}
static const zext_workspace_group_handle_v1_listener workspace_group_impl = {
.output_enter = workspace_group_handle_output_enter,
.output_leave = workspace_group_handle_output_leave,
.workspace = workspace_group_handle_workspace,
.remove = workspace_group_handle_remove};
void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle,
void *data) {
zext_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data);
}
void workspace_handle_name(void *data, struct zext_workspace_handle_v1 *_, const char *name) {
static_cast<Workspace *>(data)->handle_name(name);
}
void workspace_handle_coordinates(void *data, struct zext_workspace_handle_v1 *_,
struct wl_array *coordinates) {
std::vector<uint32_t> coords_vec;
auto coords = static_cast<uint32_t *>(coordinates->data);
for (size_t i = 0; i < coordinates->size / sizeof(uint32_t); ++i) {
coords_vec.push_back(coords[i]);
}
static_cast<Workspace *>(data)->handle_coordinates(coords_vec);
}
void workspace_handle_state(void *data, struct zext_workspace_handle_v1 *workspace_handle,
struct wl_array *state) {
std::vector<uint32_t> state_vec;
auto states = static_cast<uint32_t *>(state->data);
for (size_t i = 0; i < state->size / sizeof(uint32_t); ++i) {
state_vec.push_back(states[i]);
}
static_cast<Workspace *>(data)->handle_state(state_vec);
}
void workspace_handle_remove(void *data, struct zext_workspace_handle_v1 *_) {
static_cast<Workspace *>(data)->handle_remove();
}
static const zext_workspace_handle_v1_listener workspace_impl = {
.name = workspace_handle_name,
.coordinates = workspace_handle_coordinates,
.state = workspace_handle_state,
.remove = workspace_handle_remove};
void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data) {
zext_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data);
}
} // namespace waybar::modules::wlr