Merge branch 'master' into hyprland-window_no_title_from_special

This commit is contained in:
Alexis Rouillard
2024-07-02 23:19:39 +02:00
committed by GitHub
155 changed files with 4825 additions and 2445 deletions
+56 -24
View File
@@ -181,7 +181,8 @@ static bool status_gt(const std::string& a, const std::string& b) {
return false;
}
const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::getInfos() {
std::tuple<uint8_t, float, std::string, float, uint16_t, float>
waybar::modules::Battery::getInfos() {
std::lock_guard<std::mutex> guard(battery_list_mutex_);
try {
@@ -234,7 +235,7 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
}
// spdlog::info("{} {} {} {}", capacity,time,status,rate);
return {capacity, time / 60.0, status, rate};
return {capacity, time / 60.0, status, rate, 0, 0.0F};
#elif defined(__linux__)
uint32_t total_power = 0; // μW
@@ -252,6 +253,10 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
uint32_t time_to_full_now = 0;
bool time_to_full_now_exists = false;
uint32_t largestDesignCapacity = 0;
uint16_t mainBatCycleCount = 0;
float mainBatHealthPercent = 0.0F;
std::string status = "Unknown";
for (auto const& item : batteries_) {
auto bat = item.first;
@@ -267,13 +272,6 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
// Some battery will report current and charge in μA/μAh.
// Scale these by the voltage to get μW/μWh.
uint32_t capacity = 0;
bool capacity_exists = false;
if (fs::exists(bat / "capacity")) {
capacity_exists = true;
std::ifstream(bat / "capacity") >> capacity;
}
uint32_t current_now = 0;
bool current_now_exists = false;
if (fs::exists(bat / "current_now")) {
@@ -353,6 +351,43 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
std::ifstream(bat / "energy_full_design") >> energy_full_design;
}
uint16_t cycleCount = 0;
if (fs::exists(bat / "cycle_count")) {
std::ifstream(bat / "cycle_count") >> cycleCount;
}
if (charge_full_design >= largestDesignCapacity) {
largestDesignCapacity = charge_full_design;
if (cycleCount > mainBatCycleCount) {
mainBatCycleCount = cycleCount;
}
if (charge_full_exists && charge_full_design_exists) {
float batHealthPercent = ((float)charge_full / charge_full_design) * 100;
if (mainBatHealthPercent == 0.0F || batHealthPercent < mainBatHealthPercent) {
mainBatHealthPercent = batHealthPercent;
}
} else if (energy_full_exists && energy_full_design_exists) {
float batHealthPercent = ((float)energy_full / energy_full_design) * 100;
if (mainBatHealthPercent == 0.0F || batHealthPercent < mainBatHealthPercent) {
mainBatHealthPercent = batHealthPercent;
}
}
}
uint32_t capacity = 0;
bool capacity_exists = false;
if (charge_now_exists && charge_full_exists && charge_full != 0) {
capacity_exists = true;
capacity = 100 * (uint64_t)charge_now / (uint64_t)charge_full;
} else if (energy_now_exists && energy_full_exists && energy_full != 0) {
capacity_exists = true;
capacity = 100 * (uint64_t)energy_now / (uint64_t)energy_full;
} else if (fs::exists(bat / "capacity")) {
capacity_exists = true;
std::ifstream(bat / "capacity") >> capacity;
}
if (!voltage_now_exists) {
if (power_now_exists && current_now_exists && current_now != 0) {
voltage_now_exists = true;
@@ -393,13 +428,7 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
}
if (!capacity_exists) {
if (charge_now_exists && charge_full_exists && charge_full != 0) {
capacity_exists = true;
capacity = 100 * (uint64_t)charge_now / (uint64_t)charge_full;
} else if (energy_now_exists && energy_full_exists && energy_full != 0) {
capacity_exists = true;
capacity = 100 * (uint64_t)energy_now / (uint64_t)energy_full;
} else if (charge_now_exists && energy_full_exists && voltage_now_exists) {
if (charge_now_exists && energy_full_exists && voltage_now_exists) {
if (!charge_full_exists && voltage_now != 0) {
charge_full_exists = true;
charge_full = 1000000 * (uint64_t)energy_full / (uint64_t)voltage_now;
@@ -573,11 +602,12 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
// still charging but not yet done
if (cap == 100 && status == "Charging") status = "Full";
return {cap, time_remaining, status, total_power / 1e6};
return {
cap, time_remaining, status, total_power / 1e6, mainBatCycleCount, mainBatHealthPercent};
#endif
} catch (const std::exception& e) {
spdlog::error("Battery: {}", e.what());
return {0, 0, "Unknown", 0};
return {0, 0, "Unknown", 0, 0, 0.0f};
}
}
@@ -633,7 +663,7 @@ auto waybar::modules::Battery::update() -> void {
return;
}
#endif
auto [capacity, time_remaining, status, power] = getInfos();
auto [capacity, time_remaining, status, power, cycles, health] = getInfos();
if (status == "Unknown") {
status = getAdapterStatus(capacity);
}
@@ -663,10 +693,11 @@ auto waybar::modules::Battery::update() -> void {
} else if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format),
fmt::arg("timeTo", tooltip_text_default),
fmt::arg("power", power), fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted)));
label_.set_tooltip_text(
fmt::format(fmt::runtime(tooltip_format), fmt::arg("timeTo", tooltip_text_default),
fmt::arg("power", power), fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted), fmt::arg("cycles", cycles),
fmt::arg("health", fmt::format("{:.3}", health))));
}
if (!old_status_.empty()) {
label_.get_style_context()->remove_class(old_status_);
@@ -687,7 +718,8 @@ auto waybar::modules::Battery::update() -> void {
auto icons = std::vector<std::string>{status + "-" + state, status, state};
label_.set_markup(fmt::format(
fmt::runtime(format), fmt::arg("capacity", capacity), fmt::arg("power", power),
fmt::arg("icon", getIcon(capacity, icons)), fmt::arg("time", time_remaining_formatted)));
fmt::arg("icon", getIcon(capacity, icons)), fmt::arg("time", time_remaining_formatted),
fmt::arg("cycles", cycles), fmt::arg("health", fmt::format("{:.3}", health))));
}
// Call parent update
ALabel::update();
+66 -19
View File
@@ -98,30 +98,30 @@ waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value&
std::back_inserter(device_preference_), [](auto x) { return x.asString(); });
}
// NOTE: assumption made that the controller that is selected stays unchanged
// for duration of the module
if (cur_controller_ = findCurController(); !cur_controller_) {
if (config_["controller-alias"].isString()) {
spdlog::error("findCurController() failed: no bluetooth controller found with alias '{}'",
config_["controller-alias"].asString());
spdlog::warn("no bluetooth controller found with alias '{}'",
config_["controller-alias"].asString());
} else {
spdlog::error("findCurController() failed: no bluetooth controller found");
spdlog::warn("no bluetooth controller found");
}
update();
} else {
// These calls only make sense if a controller could be found
// This call only make sense if a controller could be found
findConnectedDevices(cur_controller_->path, connected_devices_);
g_signal_connect(manager_.get(), "interface-proxy-properties-changed",
G_CALLBACK(onInterfaceProxyPropertiesChanged), this);
g_signal_connect(manager_.get(), "interface-added", G_CALLBACK(onInterfaceAddedOrRemoved),
this);
g_signal_connect(manager_.get(), "interface-removed", G_CALLBACK(onInterfaceAddedOrRemoved),
this);
}
g_signal_connect(manager_.get(), "object-added", G_CALLBACK(onObjectAdded), this);
g_signal_connect(manager_.get(), "object-removed", G_CALLBACK(onObjectRemoved), this);
g_signal_connect(manager_.get(), "interface-proxy-properties-changed",
G_CALLBACK(onInterfaceProxyPropertiesChanged), this);
g_signal_connect(manager_.get(), "interface-added", G_CALLBACK(onInterfaceAddedOrRemoved), this);
g_signal_connect(manager_.get(), "interface-removed", G_CALLBACK(onInterfaceAddedOrRemoved),
this);
#ifdef WANT_RFKILL
rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update)));
rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update)));
#endif
}
dp.emit();
}
@@ -282,6 +282,46 @@ auto waybar::modules::Bluetooth::update() -> void {
ALabel::update();
}
auto waybar::modules::Bluetooth::onObjectAdded(GDBusObjectManager* manager, GDBusObject* object,
gpointer user_data) -> void {
ControllerInfo info;
Bluetooth* bt = static_cast<Bluetooth*>(user_data);
if (!bt->cur_controller_.has_value() && bt->getControllerProperties(object, info) &&
(!bt->config_["controller-alias"].isString() ||
bt->config_["controller-alias"].asString() == info.alias)) {
bt->cur_controller_ = std::move(info);
bt->dp.emit();
}
}
auto waybar::modules::Bluetooth::onObjectRemoved(GDBusObjectManager* manager, GDBusObject* object,
gpointer user_data) -> void {
Bluetooth* bt = static_cast<Bluetooth*>(user_data);
GDBusProxy* proxy_controller;
if (!bt->cur_controller_.has_value()) {
return;
}
proxy_controller = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Adapter1"));
if (proxy_controller != NULL) {
std::string object_path = g_dbus_object_get_object_path(object);
if (object_path == bt->cur_controller_->path) {
bt->cur_controller_ = bt->findCurController();
if (bt->cur_controller_.has_value()) {
bt->connected_devices_.clear();
bt->findConnectedDevices(bt->cur_controller_->path, bt->connected_devices_);
}
bt->dp.emit();
}
g_object_unref(proxy_controller);
}
}
// NOTE: only for when the org.bluez.Battery1 interface is added/removed after/before a device is
// connected/disconnected
auto waybar::modules::Bluetooth::onInterfaceAddedOrRemoved(GDBusObjectManager* manager,
@@ -292,11 +332,13 @@ auto waybar::modules::Bluetooth::onInterfaceAddedOrRemoved(GDBusObjectManager* m
std::string object_path = g_dbus_proxy_get_object_path(G_DBUS_PROXY(interface));
if (interface_name == "org.bluez.Battery1") {
Bluetooth* bt = static_cast<Bluetooth*>(user_data);
auto device = std::find_if(bt->connected_devices_.begin(), bt->connected_devices_.end(),
[object_path](auto d) { return d.path == object_path; });
if (device != bt->connected_devices_.end()) {
device->battery_percentage = bt->getDeviceBatteryPercentage(object);
bt->dp.emit();
if (bt->cur_controller_.has_value()) {
auto device = std::find_if(bt->connected_devices_.begin(), bt->connected_devices_.end(),
[object_path](auto d) { return d.path == object_path; });
if (device != bt->connected_devices_.end()) {
device->battery_percentage = bt->getDeviceBatteryPercentage(object);
bt->dp.emit();
}
}
}
}
@@ -309,6 +351,11 @@ auto waybar::modules::Bluetooth::onInterfaceProxyPropertiesChanged(
std::string object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object_proxy));
Bluetooth* bt = static_cast<Bluetooth*>(user_data);
if (!bt->cur_controller_.has_value()) {
return;
}
if (interface_name == "org.bluez.Adapter1") {
if (object_path == bt->cur_controller_->path) {
bt->getControllerProperties(G_DBUS_OBJECT(object_proxy), *bt->cur_controller_);
+1 -7
View File
@@ -8,13 +8,7 @@ waybar::modules::Cava::Cava(const std::string& id, const Json::Value& config)
char cfgPath[PATH_MAX];
cfgPath[0] = '\0';
if (config_["cava_config"].isString()) {
std::string strPath{config_["cava_config"].asString()};
const std::string fnd{"XDG_CONFIG_HOME"};
const std::string::size_type npos{strPath.find("$" + fnd)};
if (npos != std::string::npos) strPath.replace(npos, fnd.length() + 1, getenv(fnd.c_str()));
strcpy(cfgPath, strPath.data());
}
if (config_["cava_config"].isString()) strcpy(cfgPath, config_["cava_config"].asString().data());
// Load cava config
error_.length = 0;
+68 -37
View File
@@ -1,5 +1,6 @@
#include "modules/clock.hpp"
#include <gtkmm/tooltip.h>
#include <spdlog/spdlog.h>
#include <chrono>
@@ -11,20 +12,22 @@
#ifdef HAVE_LANGINFO_1STDAY
#include <langinfo.h>
#include <locale.h>
#include <clocale>
#endif
namespace fmt_lib = waybar::util::date::format;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true),
locale_{std::locale(config_["locale"].isString() ? config_["locale"].asString() : "")},
tlpFmt_{(config_["tooltip-format"].isString()) ? config_["tooltip-format"].asString() : ""},
cldInTooltip_{tlpFmt_.find("{" + kCldPlaceholder + "}") != std::string::npos},
tzInTooltip_{tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos},
m_locale_{std::locale(config_["locale"].isString() ? config_["locale"].asString() : "")},
m_tlpFmt_{(config_["tooltip-format"].isString()) ? config_["tooltip-format"].asString() : ""},
m_tooltip_{new Gtk::Label()},
cldInTooltip_{m_tlpFmt_.find("{" + kCldPlaceholder + "}") != std::string::npos},
tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos},
tzCurrIdx_{0},
ordInTooltip_{tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} {
tlpText_ = tlpFmt_;
ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} {
m_tlpText_ = m_tlpFmt_;
if (config_["timezones"].isArray() && !config_["timezones"].empty()) {
for (const auto& zone_name : config_["timezones"]) {
@@ -87,7 +90,7 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
fmtMap_.insert({3, config_[kCldPlaceholder]["format"]["today"].asString()});
cldBaseDay_ =
year_month_day{
floor<days>(zoned_time{current_zone(), system_clock::now()}.get_local_time())}
floor<days>(zoned_time{local_zone(), system_clock::now()}.get_local_time())}
.day();
} else
fmtMap_.insert({3, "{}"});
@@ -115,6 +118,7 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
} else
cldMonCols_ = 1;
if (config_[kCldPlaceholder]["on-scroll"].isInt()) {
cldShift_ = config_[kCldPlaceholder]["on-scroll"].asInt();
event_box_.add_events(Gdk::LEAVE_NOTIFY_MASK);
event_box_.signal_leave_notify_event().connect([this](GdkEventCrossing*) {
cldCurrShift_ = months{0};
@@ -123,17 +127,28 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
}
}
if (tooltipEnabled()) {
label_.set_has_tooltip(true);
label_.signal_query_tooltip().connect(sigc::mem_fun(*this, &Clock::query_tlp_cb));
}
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_ - system_clock::now().time_since_epoch() % interval_);
};
}
bool waybar::modules::Clock::query_tlp_cb(int, int, bool,
const Glib::RefPtr<Gtk::Tooltip>& tooltip) {
tooltip->set_custom(*m_tooltip_.get());
return true;
}
auto waybar::modules::Clock::update() -> void {
const auto* tz = tzList_[tzCurrIdx_] != nullptr ? tzList_[tzCurrIdx_] : current_zone();
const auto* tz = tzList_[tzCurrIdx_] != nullptr ? tzList_[tzCurrIdx_] : local_zone();
const zoned_time now{tz, floor<seconds>(system_clock::now())};
label_.set_markup(fmt_lib::vformat(locale_, format_, fmt_lib::make_format_args(now)));
label_.set_markup(fmt_lib::vformat(m_locale_, format_, fmt_lib::make_format_args(now)));
if (tooltipEnabled()) {
const year_month_day today{floor<days>(now.get_local_time())};
@@ -146,16 +161,19 @@ auto waybar::modules::Clock::update() -> void {
if (ordInTooltip_) ordText_ = get_ordinal_date(shiftedDay);
if (tzInTooltip_ || cldInTooltip_ || ordInTooltip_) {
// std::vformat doesn't support named arguments.
tlpText_ = std::regex_replace(tlpFmt_, std::regex("\\{" + kTZPlaceholder + "\\}"), tzText_);
tlpText_ =
std::regex_replace(tlpText_, std::regex("\\{" + kCldPlaceholder + "\\}"), cldText_);
tlpText_ =
std::regex_replace(tlpText_, std::regex("\\{" + kOrdPlaceholder + "\\}"), ordText_);
m_tlpText_ =
std::regex_replace(m_tlpFmt_, std::regex("\\{" + kTZPlaceholder + "\\}"), tzText_);
m_tlpText_ =
std::regex_replace(m_tlpText_, std::regex("\\{" + kCldPlaceholder + "\\}"), cldText_);
m_tlpText_ =
std::regex_replace(m_tlpText_, std::regex("\\{" + kOrdPlaceholder + "\\}"), ordText_);
} else {
m_tlpText_ = m_tlpFmt_;
}
tlpText_ = fmt_lib::vformat(locale_, tlpText_, fmt_lib::make_format_args(shiftedNow));
label_.set_tooltip_markup(tlpText_);
m_tlpText_ = fmt_lib::vformat(m_locale_, m_tlpText_, fmt_lib::make_format_args(shiftedNow));
m_tooltip_->set_markup(m_tlpText_);
label_.trigger_tooltip_query();
}
ALabel::update();
@@ -167,9 +185,9 @@ auto waybar::modules::Clock::getTZtext(sys_seconds now) -> std::string {
std::stringstream os;
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] : current_zone();
const auto* tz = tzList_[tz_idx] != nullptr ? tzList_[tz_idx] : local_zone();
auto zt{zoned_time{tz, now}};
os << fmt_lib::vformat(locale_, format_, fmt_lib::make_format_args(zt)) << '\n';
os << fmt_lib::vformat(m_locale_, format_, fmt_lib::make_format_args(zt)) << '\n';
}
return os.str();
@@ -187,13 +205,13 @@ auto cldGetWeekForLine(const year_month& ym, const weekday& firstdow, const unsi
}
auto getCalendarLine(const year_month_day& currDate, const year_month ym, const unsigned line,
const weekday& firstdow, const std::locale* const locale_) -> std::string {
const weekday& firstdow, const std::locale* const m_locale_) -> std::string {
std::ostringstream os;
switch (line) {
// Print month and year title
case 0: {
os << date::format(*locale_, "{:L%B %Y}", ym);
os << date::format(*m_locale_, "{:L%B %Y}", ym);
break;
}
// Print weekday names title
@@ -203,7 +221,7 @@ auto getCalendarLine(const year_month_day& currDate, const year_month ym, const
Glib::ustring::size_type wdLen{0};
int clen{0};
do {
wdStr = date::format(*locale_, "{:L%a}", wd);
wdStr = date::format(*m_locale_, "{:L%a}", wd);
clen = ustring_clen(wdStr);
wdLen = wdStr.length();
while (clen > 2) {
@@ -226,7 +244,7 @@ auto getCalendarLine(const year_month_day& currDate, const year_month ym, const
os << std::string((wd - firstdow).count() * 3, ' ');
if (currDate != ym / d)
os << date::format(*locale_, "{:L%e}", d);
os << date::format(*m_locale_, "{:L%e}", d);
else
os << "{today}";
@@ -234,7 +252,7 @@ auto getCalendarLine(const year_month_day& currDate, const year_month ym, const
++d;
if (currDate != ym / d)
os << date::format(*locale_, " {:L%e}", d);
os << date::format(*m_locale_, " {:L%e}", d);
else
os << " {today}";
}
@@ -249,13 +267,13 @@ auto getCalendarLine(const year_month_day& currDate, const year_month ym, const
auto wd{firstdow};
if (currDate != ym / d)
os << date::format(*locale_, "{:L%e}", d);
os << date::format(*m_locale_, "{:L%e}", d);
else
os << "{today}";
while (++wd != firstdow && ++d <= dlast) {
if (currDate != ym / d)
os << date::format(*locale_, " {:L%e}", d);
os << date::format(*m_locale_, " {:L%e}", d);
else
os << " {today}";
}
@@ -325,7 +343,7 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
if (line > 1) {
if (line < ml[(unsigned)ymTmp.month() - 1u]) {
os << fmt_lib::vformat(
locale_, fmtMap_[4],
m_locale_, fmtMap_[4],
fmt_lib::make_format_args(
(line == 2)
? static_cast<const date::zoned_seconds&&>(
@@ -341,7 +359,7 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
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, &locale_));
getCalendarLine(today, ymTmp, line, firstdow, &m_locale_));
// Week numbers on the right
if (cldWPos_ == WS::RIGHT && line > 0) {
@@ -349,7 +367,7 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
if (line < ml[(unsigned)ymTmp.month() - 1u])
os << ' '
<< fmt_lib::vformat(
locale_, fmtMap_[4],
m_locale_, fmtMap_[4],
fmt_lib::make_format_args(
(line == 2) ? static_cast<const date::zoned_seconds&&>(
zoned_seconds{tz, local_days{ymTmp / 1}})
@@ -365,7 +383,7 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
// Apply user's formats
if (line < 2)
tmp << fmt_lib::vformat(
locale_, fmtMap_[line],
m_locale_, fmtMap_[line],
fmt_lib::make_format_args(static_cast<const std::string_view&&>(os.str())));
else
tmp << os.str();
@@ -377,10 +395,10 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
}
os << std::regex_replace(
fmt_lib::vformat(locale_, fmtMap_[2],
fmt_lib::vformat(m_locale_, fmtMap_[2],
fmt_lib::make_format_args(static_cast<const std::string_view&&>(tmp.str()))),
std::regex("\\{today\\}"),
fmt_lib::vformat(locale_, fmtMap_[3],
fmt_lib::vformat(m_locale_, fmtMap_[3],
fmt_lib::make_format_args(
static_cast<const std::string_view&&>(date::format("{:L%e}", d)))));
@@ -392,6 +410,18 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
return os.str();
}
auto waybar::modules::Clock::local_zone() -> const time_zone* {
const char* tz_name = getenv("TZ");
if (tz_name) {
try {
return locate_zone(tz_name);
} catch (const std::runtime_error& e) {
spdlog::warn("Timezone: {0}. {1}", tz_name, e.what());
}
}
return current_zone();
}
// Actions handler
auto waybar::modules::Clock::doAction(const std::string& name) -> void {
if (actionMap_[name]) {
@@ -405,11 +435,12 @@ void waybar::modules::Clock::cldModeSwitch() {
cldMode_ = (cldMode_ == CldMode::YEAR) ? CldMode::MONTH : CldMode::YEAR;
}
void waybar::modules::Clock::cldShift_up() {
cldCurrShift_ += (months)((cldMode_ == CldMode::YEAR) ? 12 : 1);
cldCurrShift_ += (months)((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;
}
void waybar::modules::Clock::cldShift_down() {
cldCurrShift_ -= (months)((cldMode_ == CldMode::YEAR) ? 12 : 1);
cldCurrShift_ -= (months)((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;
}
void waybar::modules::Clock::cldShift_reset() { cldCurrShift_ = (months)0; }
void waybar::modules::Clock::tz_up() {
const auto tzSize{tzList_.size()};
if (tzSize == 1) return;
@@ -434,7 +465,7 @@ using deleting_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>;
auto waybar::modules::Clock::first_day_of_week() -> weekday {
#ifdef HAVE_LANGINFO_1STDAY
deleting_unique_ptr<std::remove_pointer<locale_t>::type, freelocale> posix_locale{
newlocale(LC_ALL, locale_.name().c_str(), nullptr)};
newlocale(LC_ALL, m_locale_.name().c_str(), nullptr)};
if (posix_locale) {
const auto i{(int)((std::intptr_t)nl_langinfo_l(_NL_TIME_WEEK_1STDAY, posix_locale.get()))};
const weekday wd{year_month_day{year(i / 10000) / month(i / 100 % 100) / day(i % 100)}};
@@ -468,4 +499,4 @@ auto waybar::modules::Clock::get_ordinal_date(const year_month_day& today) -> st
res << "th";
}
return res.str();
}
}
-1
View File
@@ -1,5 +1,4 @@
#include <spdlog/spdlog.h>
#include <sys/sysctl.h>
#include "modules/cpu_frequency.hpp"
+19 -8
View File
@@ -10,6 +10,7 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
name_(name),
output_name_(output_name),
id_(id),
tooltip_format_enabled_{config_["tooltip-format"].isString()},
percentage_(0),
fp_(nullptr),
pid_(-1) {
@@ -161,21 +162,21 @@ auto waybar::modules::Custom::update() -> void {
auto str = fmt::format(fmt::runtime(format_), text_, fmt::arg("alt", alt_),
fmt::arg("icon", getIcon(percentage_, alt_)),
fmt::arg("percentage", percentage_));
if (str.empty()) {
if ((config_["hide-empty-text"].asBool() && text_.empty()) || str.empty()) {
event_box_.hide();
} else {
label_.set_markup(str);
if (tooltipEnabled()) {
if (text_ == tooltip_) {
if (label_.get_tooltip_markup() != str) {
label_.set_tooltip_markup(str);
}
} else if (config_["tooltip-format"].isString()) {
if (tooltip_format_enabled_) {
auto tooltip = config_["tooltip-format"].asString();
tooltip = fmt::format(fmt::runtime(tooltip), text_, fmt::arg("alt", alt_),
fmt::arg("icon", getIcon(percentage_, alt_)),
fmt::arg("percentage", percentage_));
label_.set_tooltip_markup(tooltip);
} else if (text_ == tooltip_) {
if (label_.get_tooltip_markup() != str) {
label_.set_tooltip_markup(str);
}
} else {
if (label_.get_tooltip_markup() != tooltip_) {
label_.set_tooltip_markup(tooltip_);
@@ -214,13 +215,19 @@ void waybar::modules::Custom::parseOutputRaw() {
if (i == 0) {
if (config_["escape"].isBool() && config_["escape"].asBool()) {
text_ = Glib::Markup::escape_text(validated_line);
tooltip_ = Glib::Markup::escape_text(validated_line);
} else {
text_ = validated_line;
tooltip_ = validated_line;
}
tooltip_ = validated_line;
class_.clear();
} else if (i == 1) {
tooltip_ = validated_line;
if (config_["escape"].isBool() && config_["escape"].asBool()) {
tooltip_ = Glib::Markup::escape_text(validated_line);
} else {
tooltip_ = validated_line;
}
} else if (i == 2) {
class_.push_back(validated_line);
} else {
@@ -246,7 +253,11 @@ void waybar::modules::Custom::parseOutputJson() {
} else {
alt_ = parsed["alt"].asString();
}
tooltip_ = parsed["tooltip"].asString();
if (config_["escape"].isBool() && config_["escape"].asBool()) {
tooltip_ = Glib::Markup::escape_text(parsed["tooltip"].asString());
} else {
tooltip_ = parsed["tooltip"].asString();
}
if (parsed["class"].isString()) {
class_.push_back(parsed["class"].asString());
} else if (parsed["class"].isArray()) {
+9 -5
View File
@@ -21,11 +21,11 @@ wl_array tags, layouts;
static uint num_tags = 0;
void toggle_visibility(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) {
static void toggle_visibility(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) {
// Intentionally empty
}
void active(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t active) {
static void active(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t active) {
// Intentionally empty
}
@@ -37,15 +37,15 @@ static void set_tag(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t tag
: num_tags & ~(1 << tag);
}
void set_layout_symbol(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *layout) {
static void set_layout_symbol(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *layout) {
// Intentionally empty
}
void title(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *title) {
static void title(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *title) {
// Intentionally empty
}
void dwl_frame(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) {
static void dwl_frame(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) {
// Intentionally empty
}
@@ -97,6 +97,7 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
output_status_{nullptr} {
struct wl_display *display = Client::inst()->wl_display;
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener_impl, this);
wl_display_roundtrip(display);
@@ -155,6 +156,9 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
}
Tags::~Tags() {
if (output_status_) {
zdwl_ipc_output_v2_destroy(output_status_);
}
if (status_manager_) {
zdwl_ipc_manager_v2_destroy(status_manager_);
}
+120
View File
@@ -0,0 +1,120 @@
#include "modules/dwl/window.hpp"
#include <gdkmm/pixbuf.h>
#include <glibmm/fileutils.h>
#include <glibmm/keyfile.h>
#include <glibmm/miscutils.h>
#include <gtkmm/enums.h>
#include <spdlog/spdlog.h>
#include "client.hpp"
#include "dwl-ipc-unstable-v2-client-protocol.h"
#include "util/rewrite_string.hpp"
namespace waybar::modules::dwl {
static void toggle_visibility(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) {
// Intentionally empty
}
static void active(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t active) {
// Intentionally empty
}
static void set_tag(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t tag, uint32_t state,
uint32_t clients, uint32_t focused) {
// Intentionally empty
}
static void set_layout_symbol(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *layout) {
static_cast<Window *>(data)->handle_layout_symbol(layout);
}
static void title(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *title) {
static_cast<Window *>(data)->handle_title(title);
}
static void dwl_frame(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) {
static_cast<Window *>(data)->handle_frame();
}
static void set_layout(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t layout) {
static_cast<Window *>(data)->handle_layout(layout);
}
static void appid(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *appid) {
static_cast<Window *>(data)->handle_appid(appid);
};
static const zdwl_ipc_output_v2_listener output_status_listener_impl{
.toggle_visibility = toggle_visibility,
.active = active,
.tag = set_tag,
.layout = set_layout,
.title = title,
.appid = appid,
.layout_symbol = set_layout_symbol,
.frame = dwl_frame,
};
static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) {
if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {
static_cast<Window *>(data)->status_manager_ = static_cast<struct zdwl_ipc_manager_v2 *>(
(zdwl_ipc_manager_v2 *)wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1));
}
}
static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {
/* Ignore event */
}
static const wl_registry_listener registry_listener_impl = {.global = handle_global,
.global_remove = handle_global_remove};
Window::Window(const std::string &id, const Bar &bar, const Json::Value &config)
: AAppIconLabel(config, "window", id, "{}", 0, true), bar_(bar) {
struct wl_display *display = Client::inst()->wl_display;
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener_impl, this);
wl_display_roundtrip(display);
if (status_manager_ == nullptr) {
spdlog::error("dwl_status_manager_v2 not advertised");
return;
}
struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
output_status_ = zdwl_ipc_manager_v2_get_output(status_manager_, output);
zdwl_ipc_output_v2_add_listener(output_status_, &output_status_listener_impl, this);
zdwl_ipc_manager_v2_destroy(status_manager_);
}
Window::~Window() {
if (output_status_ != nullptr) {
zdwl_ipc_output_v2_destroy(output_status_);
}
}
void Window::handle_title(const char *title) { title_ = title; }
void Window::handle_appid(const char *appid) { appid_ = appid; }
void Window::handle_layout_symbol(const char *layout_symbol) { layout_symbol_ = layout_symbol; }
void Window::handle_layout(const uint32_t layout) { layout_ = layout; }
void Window::handle_frame() {
label_.set_markup(waybar::util::rewriteString(
fmt::format(fmt::runtime(format_), fmt::arg("title", title_),
fmt::arg("layout", layout_symbol_), fmt::arg("app_id", appid_)),
config_["rewrite"]));
updateAppIconName(appid_, "");
updateAppIcon();
if (tooltipEnabled()) {
label_.set_tooltip_text(title_);
}
}
} // namespace waybar::modules::dwl
+65 -29
View File
@@ -9,11 +9,38 @@
#include <sys/un.h>
#include <unistd.h>
#include <filesystem>
#include <string>
#include <thread>
namespace waybar::modules::hyprland {
std::filesystem::path IPC::socketFolder_;
std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
// socket path, specified by EventManager of Hyprland
if (!socketFolder_.empty()) {
return socketFolder_;
}
const char* xdgRuntimeDirEnv = std::getenv("XDG_RUNTIME_DIR");
std::filesystem::path xdgRuntimeDir;
// Only set path if env variable is set
if (xdgRuntimeDirEnv != nullptr) {
xdgRuntimeDir = std::filesystem::path(xdgRuntimeDirEnv);
}
if (!xdgRuntimeDir.empty() && std::filesystem::exists(xdgRuntimeDir / "hypr")) {
socketFolder_ = xdgRuntimeDir / "hypr";
} else {
spdlog::warn("$XDG_RUNTIME_DIR/hypr does not exist, falling back to /tmp/hypr");
socketFolder_ = std::filesystem::path("/tmp") / "hypr";
}
socketFolder_ = socketFolder_ / instanceSig;
return socketFolder_;
}
void IPC::startIPC() {
// will start IPC and relay events to parseIPC
@@ -40,9 +67,7 @@ void IPC::startIPC() {
addr.sun_family = AF_UNIX;
// socket path, specified by EventManager of Hyprland
std::string socketPath = "/tmp/hypr/" + std::string(his) + "/.socket2.sock";
auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock";
strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
@@ -54,22 +79,29 @@ void IPC::startIPC() {
return;
}
auto file = fdopen(socketfd, "r");
auto* file = fdopen(socketfd, "r");
while (true) {
char buffer[1024]; // Hyprland socket2 events are max 1024 bytes
std::array<char, 1024> buffer; // Hyprland socket2 events are max 1024 bytes
auto recievedCharPtr = fgets(buffer, 1024, file);
auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file);
if (!recievedCharPtr) {
if (receivedCharPtr == nullptr) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
}
std::string messageRecieved(buffer);
messageRecieved = messageRecieved.substr(0, messageRecieved.find_first_of('\n'));
spdlog::debug("hyprland IPC received {}", messageRecieved);
parseIPC(messageRecieved);
std::string messageReceived(buffer.data());
messageReceived = messageReceived.substr(0, messageReceived.find_first_of('\n'));
spdlog::debug("hyprland IPC received {}", messageReceived);
try {
parseIPC(messageReceived);
} catch (std::exception& e) {
spdlog::warn("Failed to parse IPC message: {}, reason: {}", messageReceived, e.what());
} catch (...) {
throw;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
@@ -78,9 +110,9 @@ void IPC::startIPC() {
void IPC::parseIPC(const std::string& ev) {
std::string request = ev.substr(0, ev.find_first_of('>'));
std::unique_lock lock(m_callbackMutex);
std::unique_lock lock(callbackMutex_);
for (auto& [eventname, handler] : m_callbacks) {
for (auto& [eventname, handler] : callbacks_) {
if (eventname == request) {
handler->onEvent(ev);
}
@@ -88,25 +120,25 @@ void IPC::parseIPC(const std::string& ev) {
}
void IPC::registerForIPC(const std::string& ev, EventHandler* ev_handler) {
if (!ev_handler) {
if (ev_handler == nullptr) {
return;
}
std::unique_lock lock(m_callbackMutex);
m_callbacks.emplace_back(ev, ev_handler);
std::unique_lock lock(callbackMutex_);
callbacks_.emplace_back(ev, ev_handler);
}
void IPC::unregisterForIPC(EventHandler* ev_handler) {
if (!ev_handler) {
if (ev_handler == nullptr) {
return;
}
std::unique_lock lock(m_callbackMutex);
std::unique_lock lock(callbackMutex_);
for (auto it = m_callbacks.begin(); it != m_callbacks.end();) {
for (auto it = callbacks_.begin(); it != callbacks_.end();) {
auto& [eventname, handler] = *it;
if (handler == ev_handler) {
m_callbacks.erase(it++);
callbacks_.erase(it++);
} else {
++it;
}
@@ -135,19 +167,17 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
}
// get the instance signature
auto instanceSig = getenv("HYPRLAND_INSTANCE_SIGNATURE");
auto* instanceSig = getenv("HYPRLAND_INSTANCE_SIGNATURE");
if (!instanceSig) {
if (instanceSig == nullptr) {
spdlog::error("Hyprland IPC: HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?)");
return "";
}
std::string instanceSigStr = std::string(instanceSig);
sockaddr_un serverAddress = {0};
serverAddress.sun_family = AF_UNIX;
std::string socketPath = "/tmp/hypr/" + instanceSigStr + "/.socket.sock";
std::string socketPath = IPC::getSocketFolder(instanceSig) / ".socket.sock";
// Use snprintf to copy the socketPath string into serverAddress.sun_path
if (snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), "%s", socketPath.c_str()) <
@@ -169,18 +199,18 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
return "";
}
char buffer[8192] = {0};
std::array<char, 8192> buffer = {0};
std::string response;
do {
sizeWritten = read(serverSocket, buffer, 8192);
sizeWritten = read(serverSocket, buffer.data(), 8192);
if (sizeWritten < 0) {
spdlog::error("Hyprland IPC: Couldn't read (5)");
close(serverSocket);
return "";
}
response.append(buffer, sizeWritten);
response.append(buffer.data(), sizeWritten);
} while (sizeWritten > 0);
close(serverSocket);
@@ -188,7 +218,13 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
}
Json::Value IPC::getSocket1JsonReply(const std::string& rq) {
return m_parser.parse(getSocket1Reply("j/" + rq));
std::string reply = getSocket1Reply("j/" + rq);
if (reply.empty()) {
return {};
}
return parser_.parse(reply);
}
} // namespace waybar::modules::hyprland
+20 -14
View File
@@ -13,7 +13,7 @@ Language::Language(const std::string& id, const Bar& bar, const Json::Value& con
: ALabel(config, "language", id, "{}", 0, true), bar_(bar) {
modulesReady = true;
if (!gIPC.get()) {
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
@@ -36,6 +36,11 @@ Language::~Language() {
auto Language::update() -> void {
std::lock_guard<std::mutex> lg(mutex_);
spdlog::debug("hyprland language update with full name {}", layout_.full_name);
spdlog::debug("hyprland language update with short name {}", layout_.short_name);
spdlog::debug("hyprland language update with short description {}", layout_.short_description);
spdlog::debug("hyprland language update with variant {}", layout_.variant);
std::string layoutName = std::string{};
if (config_.isMember("format-" + layout_.short_description + "-" + layout_.variant)) {
const auto propName = "format-" + layout_.short_description + "-" + layout_.variant;
@@ -50,6 +55,8 @@ auto Language::update() -> void {
fmt::arg("variant", layout_.variant)));
}
spdlog::debug("hyprland language formatted layout name {}", layoutName);
if (!format_.empty()) {
label_.show();
label_.set_markup(layoutName);
@@ -63,7 +70,7 @@ 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_first_of(',') + 1);
auto layoutName = ev.substr(ev.find_last_of(',') + 1);
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
return; // ignore
@@ -102,11 +109,11 @@ void Language::initLanguage() {
}
auto Language::getLayout(const std::string& fullName) -> Layout {
const auto CONTEXT = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES);
rxkb_context_parse_default_ruleset(CONTEXT);
auto* const context = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES);
rxkb_context_parse_default_ruleset(context);
rxkb_layout* layout = rxkb_layout_first(CONTEXT);
while (layout) {
rxkb_layout* layout = rxkb_layout_first(context);
while (layout != nullptr) {
std::string nameOfLayout = rxkb_layout_get_description(layout);
if (nameOfLayout != fullName) {
@@ -115,21 +122,20 @@ auto Language::getLayout(const std::string& fullName) -> Layout {
}
auto name = std::string(rxkb_layout_get_name(layout));
auto variant_ = rxkb_layout_get_variant(layout);
std::string variant = variant_ == nullptr ? "" : std::string(variant_);
const auto* variantPtr = rxkb_layout_get_variant(layout);
std::string variant = variantPtr == nullptr ? "" : std::string(variantPtr);
auto short_description_ = rxkb_layout_get_brief(layout);
std::string short_description =
short_description_ == nullptr ? "" : std::string(short_description_);
const auto* descriptionPtr = rxkb_layout_get_brief(layout);
std::string description = descriptionPtr == nullptr ? "" : std::string(descriptionPtr);
Layout info = Layout{nameOfLayout, name, variant, short_description};
Layout info = Layout{nameOfLayout, name, variant, description};
rxkb_context_unref(CONTEXT);
rxkb_context_unref(context);
return info;
}
rxkb_context_unref(CONTEXT);
rxkb_context_unref(context);
spdlog::debug("hyprland language didn't find matching layout");
+26 -1
View File
@@ -10,13 +10,22 @@ Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "submap", id, "{}", 0, true), bar_(bar) {
modulesReady = true;
if (!gIPC.get()) {
parseConfig(config);
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
label_.hide();
ALabel::update();
// Displays widget immediately if always_on_ assuming default submap
// Needs an actual way to retrive current submap on startup
if (always_on_) {
submap_ = default_submap_;
label_.get_style_context()->add_class(submap_);
}
// register for hyprland ipc
gIPC->registerForIPC("submap", this);
dp.emit();
@@ -28,6 +37,18 @@ Submap::~Submap() {
std::lock_guard<std::mutex> lg(mutex_);
}
auto Submap::parseConfig(const Json::Value& config) -> void {
auto const& alwaysOn = config["always-on"];
if (alwaysOn.isBool()) {
always_on_ = alwaysOn.asBool();
}
auto const& defaultSubmap = config["default-submap"];
if (defaultSubmap.isString()) {
default_submap_ = defaultSubmap.asString();
}
}
auto Submap::update() -> void {
std::lock_guard<std::mutex> lg(mutex_);
@@ -60,6 +81,10 @@ void Submap::onEvent(const std::string& ev) {
submap_ = submapName;
if (submap_.empty() && always_on_) {
submap_ = default_submap_;
}
label_.get_style_context()->add_class(submap_);
spdlog::debug("hyprland submap onevent with {}", submap_);
+55 -41
View File
@@ -17,9 +17,9 @@ namespace waybar::modules::hyprland {
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) {
modulesReady = true;
separate_outputs = config["separate-outputs"].asBool();
separateOutputs_ = config["separate-outputs"].asBool();
if (!gIPC.get()) {
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
@@ -45,41 +45,47 @@ auto Window::update() -> void {
// fix ampersands
std::lock_guard<std::mutex> lg(mutex_);
std::string window_name = waybar::util::sanitize_string(workspace_.last_window_title);
std::string window_address = workspace_.last_window;
std::string windowName = waybar::util::sanitize_string(workspace_.last_window_title);
std::string windowAddress = workspace_.last_window;
window_data_.title = window_name;
windowData_.title = windowName;
if (!format_.empty()) {
label_.show();
label_.set_markup(waybar::util::rewriteString(
fmt::format(fmt::runtime(format_), fmt::arg("title", window_name),
fmt::arg("initialTitle", window_data_.initial_title),
fmt::arg("class", window_data_.class_name),
fmt::arg("initialClass", window_data_.initial_class_name)),
fmt::format(fmt::runtime(format_), fmt::arg("title", windowName),
fmt::arg("initialTitle", windowData_.initial_title),
fmt::arg("class", windowData_.class_name),
fmt::arg("initialClass", windowData_.initial_class_name)),
config_["rewrite"]));
} else {
label_.hide();
}
if (focused_) {
image_.show();
} else {
image_.hide();
}
setClass("empty", workspace_.windows == 0);
setClass("solo", solo_);
setClass("floating", all_floating_);
setClass("floating", allFloating_);
setClass("swallowing", swallowing_);
setClass("fullscreen", fullscreen_);
if (!last_solo_class_.empty() && solo_class_ != last_solo_class_) {
if (bar_.window.get_style_context()->has_class(last_solo_class_)) {
bar_.window.get_style_context()->remove_class(last_solo_class_);
spdlog::trace("Removing solo class: {}", last_solo_class_);
if (!lastSoloClass_.empty() && soloClass_ != lastSoloClass_) {
if (bar_.window.get_style_context()->has_class(lastSoloClass_)) {
bar_.window.get_style_context()->remove_class(lastSoloClass_);
spdlog::trace("Removing solo class: {}", lastSoloClass_);
}
}
if (!solo_class_.empty() && solo_class_ != last_solo_class_) {
bar_.window.get_style_context()->add_class(solo_class_);
spdlog::trace("Adding solo class: {}", solo_class_);
if (!soloClass_.empty() && soloClass_ != lastSoloClass_) {
bar_.window.get_style_context()->add_class(soloClass_);
spdlog::trace("Adding solo class: {}", soloClass_);
}
last_solo_class_ = solo_class_;
lastSoloClass_ = soloClass_;
AAppIconLabel::update();
}
@@ -109,8 +115,12 @@ auto Window::getActiveWorkspace(const std::string& monitorName = "") -> Workspac
}
auto Window::Workspace::parse(const Json::Value& value) -> Window::Workspace {
return Workspace{value["id"].asInt(), value["windows"].asInt(), value["lastwindow"].asString(),
value["lastwindowtitle"].asString()};
return Workspace{
value["id"].asInt(),
value["windows"].asInt(),
value["lastwindow"].asString(),
value["lastwindowtitle"].asString(),
};
}
auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData {
@@ -123,42 +133,45 @@ auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData {
void Window::queryActiveWorkspace() {
std::lock_guard<std::mutex> lg(mutex_);
if (separate_outputs) {
if (separateOutputs_) {
workspace_ = getActiveWorkspace(this->bar_.output->name);
} else {
workspace_ = getActiveWorkspace();
}
focused_ = true;
if (workspace_.windows > 0) {
const auto clients = gIPC->getSocket1JsonReply("clients");
assert(clients.isArray());
auto active_window = std::find_if(clients.begin(), clients.end(), [&](Json::Value window) {
auto activeWindow = std::find_if(clients.begin(), clients.end(), [&](Json::Value window) {
return window["address"] == workspace_.last_window;
});
if (active_window == std::end(clients)) {
if (activeWindow == std::end(clients)) {
focused_ = false;
return;
}
window_data_ = WindowData::parse(*active_window);
updateAppIconName(window_data_.class_name, window_data_.initial_class_name);
std::vector<Json::Value> workspace_windows;
std::copy_if(clients.begin(), clients.end(), std::back_inserter(workspace_windows),
windowData_ = WindowData::parse(*activeWindow);
updateAppIconName(windowData_.class_name, windowData_.initial_class_name);
std::vector<Json::Value> workspaceWindows;
std::copy_if(clients.begin(), clients.end(), std::back_inserter(workspaceWindows),
[&](Json::Value window) {
return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool();
});
swallowing_ =
std::any_of(workspace_windows.begin(), workspace_windows.end(), [&](Json::Value window) {
std::any_of(workspaceWindows.begin(), workspaceWindows.end(), [&](Json::Value window) {
return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0";
});
std::vector<Json::Value> visible_windows;
std::copy_if(workspace_windows.begin(), workspace_windows.end(),
std::back_inserter(visible_windows),
std::vector<Json::Value> visibleWindows;
std::copy_if(workspaceWindows.begin(), workspaceWindows.end(),
std::back_inserter(visibleWindows),
[&](Json::Value window) { return !window["hidden"].asBool(); });
solo_ = 1 == std::count_if(visible_windows.begin(), visible_windows.end(),
solo_ = 1 == std::count_if(visibleWindows.begin(), visibleWindows.end(),
[&](Json::Value window) { return !window["floating"].asBool(); });
all_floating_ = std::all_of(visible_windows.begin(), visible_windows.end(),
[&](Json::Value window) { return window["floating"].asBool(); });
fullscreen_ = window_data_.fullscreen;
allFloating_ = std::all_of(visibleWindows.begin(), visibleWindows.end(),
[&](Json::Value window) { return window["floating"].asBool(); });
fullscreen_ = windowData_.fullscreen;
// Fullscreen windows look like they are solo
if (fullscreen_) {
@@ -166,23 +179,24 @@ void Window::queryActiveWorkspace() {
}
// Grouped windows have a tab bar and therefore don't look fullscreen or solo
if (window_data_.grouped) {
if (windowData_.grouped) {
fullscreen_ = false;
solo_ = false;
}
if (solo_) {
solo_class_ = window_data_.class_name;
soloClass_ = windowData_.class_name;
} else {
solo_class_ = "";
soloClass_ = "";
}
} else {
window_data_ = WindowData{};
all_floating_ = false;
focused_ = false;
windowData_ = WindowData{};
allFloating_ = false;
swallowing_ = false;
fullscreen_ = false;
solo_ = false;
solo_class_ = "";
soloClass_ = "";
}
}
@@ -0,0 +1,108 @@
#include "modules/hyprland/windowcreationpayload.hpp"
#include <json/value.h>
#include <spdlog/spdlog.h>
#include <string>
#include <utility>
#include <variant>
#include "modules/hyprland/workspaces.hpp"
namespace waybar::modules::hyprland {
WindowCreationPayload::WindowCreationPayload(Json::Value const &client_data)
: m_window(std::make_pair(client_data["class"].asString(), client_data["title"].asString())),
m_windowAddress(client_data["address"].asString()),
m_workspaceName(client_data["workspace"]["name"].asString()) {
clearAddr();
clearWorkspaceName();
}
WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
WindowAddress window_address, std::string window_repr)
: m_window(std::move(window_repr)),
m_windowAddress(std::move(window_address)),
m_workspaceName(std::move(workspace_name)) {
clearAddr();
clearWorkspaceName();
}
WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
WindowAddress window_address, std::string window_class,
std::string window_title)
: 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)) {
clearAddr();
clearWorkspaceName();
}
void WindowCreationPayload::clearAddr() {
// substr(2, ...) is necessary because Hyprland's JSON follows this format:
// 0x{ADDR}
// While Hyprland's IPC follows this format:
// {ADDR}
static const std::string ADDR_PREFIX = "0x";
static const int ADDR_PREFIX_LEN = ADDR_PREFIX.length();
if (m_windowAddress.starts_with(ADDR_PREFIX)) {
m_windowAddress =
m_windowAddress.substr(ADDR_PREFIX_LEN, m_windowAddress.length() - ADDR_PREFIX_LEN);
}
}
void WindowCreationPayload::clearWorkspaceName() {
// The workspace name may optionally feature "special:" at the beginning.
// If so, we need to remove it because the workspace is saved WITHOUT the
// special qualifier. The reasoning is that not all of Hyprland's IPC events
// use this qualifier, so it's better to be consistent about our uses.
static const std::string SPECIAL_QUALIFIER_PREFIX = "special:";
static const int SPECIAL_QUALIFIER_PREFIX_LEN = SPECIAL_QUALIFIER_PREFIX.length();
if (m_workspaceName.starts_with(SPECIAL_QUALIFIER_PREFIX)) {
m_workspaceName = m_workspaceName.substr(
SPECIAL_QUALIFIER_PREFIX_LEN, m_workspaceName.length() - SPECIAL_QUALIFIER_PREFIX_LEN);
}
std::size_t spaceFound = m_workspaceName.find(' ');
if (spaceFound != std::string::npos) {
m_workspaceName.erase(m_workspaceName.begin() + spaceFound, m_workspaceName.end());
}
}
bool WindowCreationPayload::isEmpty(Workspaces &workspace_manager) {
if (std::holds_alternative<Repr>(m_window)) {
return std::get<Repr>(m_window).empty();
}
if (std::holds_alternative<ClassAndTitle>(m_window)) {
auto [window_class, window_title] = std::get<ClassAndTitle>(m_window);
return (window_class.empty() &&
(!workspace_manager.windowRewriteConfigUsesTitle() || window_title.empty()));
}
// Unreachable
spdlog::error("WorkspaceWindow::isEmpty: Unreachable");
throw std::runtime_error("WorkspaceWindow::isEmpty: Unreachable");
}
int WindowCreationPayload::incrementTimeSpentUncreated() { return m_timeSpentUncreated++; }
void WindowCreationPayload::moveToWorksace(std::string &new_workspace_name) {
m_workspaceName = new_workspace_name;
}
std::string 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);
}
// Unreachable
spdlog::error("WorkspaceWindow::repr: Unreachable");
throw std::runtime_error("WorkspaceWindow::repr: Unreachable");
}
} // namespace waybar::modules::hyprland
+215
View File
@@ -0,0 +1,215 @@
#include <json/value.h>
#include <spdlog/spdlog.h>
#include <memory>
#include <string>
#include <utility>
#include "modules/hyprland/workspaces.hpp"
namespace waybar::modules::hyprland {
Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_manager,
const Json::Value &clients_data)
: m_workspaceManager(workspace_manager),
m_id(workspace_data["id"].asInt()),
m_name(workspace_data["name"].asString()),
m_output(workspace_data["monitor"].asString()), // TODO:allow using monitor desc
m_windows(workspace_data["windows"].asInt()),
m_isActive(true),
m_isPersistentRule(workspace_data["persistent-rule"].asBool()),
m_isPersistentConfig(workspace_data["persistent-config"].asBool()) {
if (m_name.starts_with("name:")) {
m_name = m_name.substr(5);
} else if (m_name.starts_with("special")) {
m_name = m_id == -99 ? m_name : m_name.substr(8);
m_isSpecial = true;
}
m_button.add_events(Gdk::BUTTON_PRESS_MASK);
m_button.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handleClicked),
false);
m_button.set_relief(Gtk::RELIEF_NONE);
m_content.set_center_widget(m_label);
m_button.add(m_content);
initializeWindowMap(clients_data);
}
void addOrRemoveClass(const 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);
}
}
std::optional<std::string> Workspace::closeWindow(WindowAddress const &addr) {
if (m_windowMap.contains(addr)) {
return removeWindow(addr);
}
return std::nullopt;
}
bool Workspace::handleClicked(GdkEventButton *bt) const {
if (bt->type == GDK_BUTTON_PRESS) {
try {
if (id() > 0) { // normal
if (m_workspaceManager.moveToMonitor()) {
gIPC->getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id()));
} else {
gIPC->getSocket1Reply("dispatch workspace " + std::to_string(id()));
}
} else if (!isSpecial()) { // named (this includes persistent)
if (m_workspaceManager.moveToMonitor()) {
gIPC->getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name());
} else {
gIPC->getSocket1Reply("dispatch workspace name:" + name());
}
} else if (id() != -99) { // named special
gIPC->getSocket1Reply("dispatch togglespecialworkspace " + name());
} else { // special
gIPC->getSocket1Reply("dispatch togglespecialworkspace");
}
return true;
} catch (const std::exception &e) {
spdlog::error("Failed to dispatch workspace: {}", e.what());
}
}
return false;
}
void Workspace::initializeWindowMap(const Json::Value &clients_data) {
m_windowMap.clear();
for (auto client : clients_data) {
if (client["workspace"]["id"].asInt() == id()) {
insertWindow({client});
}
}
}
void Workspace::insertWindow(WindowCreationPayload create_window_paylod) {
if (!create_window_paylod.isEmpty(m_workspaceManager)) {
m_windowMap[create_window_paylod.getAddress()] = create_window_paylod.repr(m_workspaceManager);
}
};
bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_paylod) {
if (create_window_paylod.getWorkspaceName() == name()) {
insertWindow(create_window_paylod);
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()) {
auto urgentIconIt = icons_map.find("urgent");
if (urgentIconIt != icons_map.end()) {
return urgentIconIt->second;
}
}
if (isActive()) {
auto activeIconIt = icons_map.find("active");
if (activeIconIt != icons_map.end()) {
return activeIconIt->second;
}
}
if (isSpecial()) {
auto specialIconIt = icons_map.find("special");
if (specialIconIt != icons_map.end()) {
return specialIconIt->second;
}
}
auto namedIconIt = icons_map.find(name());
if (namedIconIt != icons_map.end()) {
return namedIconIt->second;
}
if (isVisible()) {
auto visibleIconIt = icons_map.find("visible");
if (visibleIconIt != icons_map.end()) {
return visibleIconIt->second;
}
}
if (isEmpty()) {
auto emptyIconIt = icons_map.find("empty");
if (emptyIconIt != icons_map.end()) {
return emptyIconIt->second;
}
}
if (isPersistent()) {
auto persistentIconIt = icons_map.find("persistent");
if (persistentIconIt != icons_map.end()) {
return persistentIconIt->second;
}
}
auto defaultIconIt = icons_map.find("default");
if (defaultIconIt != icons_map.end()) {
return defaultIconIt->second;
}
return m_name;
}
void Workspace::update(const std::string &format, const std::string &icon) {
// clang-format off
if (this->m_workspaceManager.activeOnly() && \
!this->isActive() && \
!this->isPersistent() && \
!this->isVisible() && \
!this->isSpecial()) {
// clang-format on
// if activeOnly is true, hide if not active, persistent, visible or special
m_button.hide();
return;
}
if (this->m_workspaceManager.specialVisibleOnly() && this->isSpecial() && !this->isVisible()) {
m_button.hide();
return;
}
m_button.show();
auto styleContext = m_button.get_style_context();
addOrRemoveClass(styleContext, isActive(), "active");
addOrRemoveClass(styleContext, isSpecial(), "special");
addOrRemoveClass(styleContext, isEmpty(), "empty");
addOrRemoveClass(styleContext, isPersistent(), "persistent");
addOrRemoveClass(styleContext, isUrgent(), "urgent");
addOrRemoveClass(styleContext, isVisible(), "visible");
addOrRemoveClass(styleContext, m_workspaceManager.getBarOutput() == output(), "hosting-monitor");
std::string windows;
auto windowSeparator = m_workspaceManager.getWindowSeparator();
bool isNotFirst = false;
for (auto &[_pid, window_repr] : m_windowMap) {
if (isNotFirst) {
windows.append(windowSeparator);
}
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)));
}
} // namespace waybar::modules::hyprland
File diff suppressed because it is too large Load Diff
+29 -2
View File
@@ -4,6 +4,7 @@
#include <glibmm/ustring.h>
#include <spdlog/spdlog.h>
#include <system_error>
#include <util/sanitize_str.hpp>
using namespace waybar::util;
@@ -52,10 +53,10 @@ auto waybar::modules::MPD::update() -> void {
void waybar::modules::MPD::queryMPD() {
if (connection_ != nullptr) {
spdlog::debug("{}: fetching state information", module_name_);
spdlog::trace("{}: fetching state information", module_name_);
try {
fetchState();
spdlog::debug("{}: fetch complete", module_name_);
spdlog::trace("{}: fetch complete", module_name_);
} catch (std::exception const& e) {
spdlog::error("{}: {}", module_name_, e.what());
state_ = MPD_STATE_UNKNOWN;
@@ -254,6 +255,21 @@ std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool act
}
}
static bool isServerUnavailable(const std::error_code& ec) {
if (ec.category() == std::system_category()) {
switch (ec.value()) {
case ECONNREFUSED:
case ECONNRESET:
case ENETDOWN:
case ENETUNREACH:
case EHOSTDOWN:
case ENOENT:
return true;
}
}
return false;
}
void waybar::modules::MPD::tryConnect() {
if (connection_ != nullptr) {
return;
@@ -281,6 +297,11 @@ void waybar::modules::MPD::tryConnect() {
}
checkErrors(connection_.get());
}
} catch (std::system_error& e) {
/* Tone down logs if it's likely that the mpd server is not running */
auto level = isServerUnavailable(e.code()) ? spdlog::level::debug : spdlog::level::err;
spdlog::log(level, "{}: Failed to connect to MPD: {}", module_name_, e.what());
connection_.reset();
} catch (std::runtime_error& e) {
spdlog::error("{}: Failed to connect to MPD: {}", module_name_, e.what());
connection_.reset();
@@ -298,6 +319,12 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) {
connection_.reset();
state_ = MPD_STATE_UNKNOWN;
throw std::runtime_error("Connection to MPD closed");
case MPD_ERROR_SYSTEM:
if (auto ec = mpd_connection_get_system_error(conn); ec != 0) {
mpd_connection_clear_error(conn);
throw std::system_error(ec, std::system_category());
}
G_GNUC_FALLTHROUGH;
default:
if (conn) {
auto error_message = mpd_connection_get_error_message(conn);
+12 -8
View File
@@ -119,7 +119,7 @@ bool Idle::on_io(Glib::IOCondition const&) {
void Playing::entry() noexcept {
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Playing::on_timer);
timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 1'000);
timer_connection_ = Glib::signal_timeout().connect_seconds(timer_slot, 1);
spdlog::debug("mpd: Playing: enabled 1 second periodic timer.");
}
@@ -327,14 +327,20 @@ void Stopped::pause() {
void Stopped::update() noexcept { ctx_->do_update(); }
void Disconnected::arm_timer(int interval) noexcept {
bool Disconnected::arm_timer(int interval) noexcept {
// check if it's necessary to modify the timer
if (timer_connection_ && last_interval_ == interval) {
return true;
}
// unregister timer, if present
disarm_timer();
// register timer
last_interval_ = interval;
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Disconnected::on_timer);
timer_connection_ = Glib::signal_timeout().connect(timer_slot, interval);
spdlog::debug("mpd: Disconnected: enabled interval timer.");
timer_connection_ = Glib::signal_timeout().connect_seconds(timer_slot, interval);
spdlog::debug("mpd: Disconnected: enabled {}s interval timer.", interval);
return false;
}
void Disconnected::disarm_timer() noexcept {
@@ -347,7 +353,7 @@ void Disconnected::disarm_timer() noexcept {
void Disconnected::entry() noexcept {
ctx_->emit();
arm_timer(1'000);
arm_timer(1 /* second */);
}
void Disconnected::exit() noexcept { disarm_timer(); }
@@ -376,9 +382,7 @@ bool Disconnected::on_timer() {
spdlog::warn("mpd: Disconnected: error: {}", e.what());
}
arm_timer(ctx_->interval() * 1'000);
return false;
return arm_timer(ctx_->interval());
}
void Disconnected::update() noexcept { ctx_->do_update(); }
+35 -38
View File
@@ -96,9 +96,9 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
}
if (config_["dynamic-order"].isArray()) {
dynamic_order_.clear();
for (auto it = config_["dynamic-order"].begin(); it != config_["dynamic-order"].end(); ++it) {
if (it->isString()) {
dynamic_order_.push_back(it->asString());
for (const auto& item : config_["dynamic-order"]) {
if (item.isString()) {
dynamic_order_.push_back(item.asString());
}
}
}
@@ -110,10 +110,9 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
player_ = config_["player"].asString();
}
if (config_["ignored-players"].isArray()) {
for (auto it = config_["ignored-players"].begin(); it != config_["ignored-players"].end();
++it) {
if (it->isString()) {
ignored_players_.push_back(it->asString());
for (const auto& item : config_["ignored-players"]) {
if (item.isString()) {
ignored_players_.push_back(item.asString());
}
}
}
@@ -146,8 +145,8 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
throw std::runtime_error(fmt::format("unable to list players: {}", error->message));
}
for (auto p = players; p != NULL; p = p->next) {
auto pn = static_cast<PlayerctlPlayerName*>(p->data);
for (auto* p = players; p != nullptr; p = p->next) {
auto* pn = static_cast<PlayerctlPlayerName*>(p->data);
if (strcmp(pn->name, player_.c_str()) == 0) {
player = playerctl_player_new_from_name(pn, &error);
break;
@@ -180,17 +179,14 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
}
Mpris::~Mpris() {
if (manager != NULL) g_object_unref(manager);
if (player != NULL) g_object_unref(player);
if (manager != nullptr) g_object_unref(manager);
if (player != nullptr) g_object_unref(player);
}
auto Mpris::getIconFromJson(const Json::Value& icons, const std::string& key) -> std::string {
if (icons.isObject()) {
if (icons[key].isString()) {
return icons[key].asString();
} else if (icons["default"].isString()) {
return icons["default"].asString();
}
if (icons[key].isString()) return icons[key].asString();
if (icons["default"].isString()) return icons["default"].asString();
}
return "";
}
@@ -205,7 +201,7 @@ size_t utf8_truncate(std::string& str, size_t width = std::string::npos) {
size_t total_width = 0;
for (gchar *data = str.data(), *end = data + str.size(); data;) {
for (gchar *data = str.data(), *end = data + str.size(); data != nullptr;) {
gunichar c = g_utf8_get_char_validated(data, end - data);
if (c == -1U || c == -2U) {
// invalid unicode, treat string as ascii
@@ -269,7 +265,7 @@ auto Mpris::getLengthStr(const PlayerInfo& info, bool truncated) -> std::string
auto length = info.length.value();
return (truncated && length.substr(0, 3) == "00:") ? length.substr(3) : length;
}
return std::string();
return {};
}
auto Mpris::getPositionStr(const PlayerInfo& info, bool truncated) -> std::string {
@@ -277,7 +273,7 @@ auto Mpris::getPositionStr(const PlayerInfo& info, bool truncated) -> std::strin
auto position = info.position.value();
return (truncated && position.substr(0, 3) == "00:") ? position.substr(3) : position;
}
return std::string();
return {};
}
auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) -> std::string {
@@ -319,33 +315,33 @@ auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) ->
size_t totalLen = 0;
for (auto it = dynamic_prio_.begin(); it != dynamic_prio_.end(); ++it) {
if (*it == "artist") {
for (const auto& item : dynamic_prio_) {
if (item == "artist") {
if (totalLen + artistLen > dynamicLen) {
showArtist = false;
} else if (showArtist) {
totalLen += artistLen;
}
} else if (*it == "album") {
} else if (item == "album") {
if (totalLen + albumLen > dynamicLen) {
showAlbum = false;
} else if (showAlbum) {
totalLen += albumLen;
}
} else if (*it == "title") {
} else if (item == "title") {
if (totalLen + titleLen > dynamicLen) {
showTitle = false;
} else if (showTitle) {
totalLen += titleLen;
}
} else if (*it == "length") {
} else if (item == "length") {
if (totalLen + lengthLen > dynamicLen) {
showLength = false;
} else if (showLength) {
totalLen += lengthLen;
posLen = std::max((size_t)2, posLen) - 2;
}
} else if (*it == "position") {
} else if (item == "position") {
if (totalLen + posLen > dynamicLen) {
showPos = false;
} else if (showPos) {
@@ -406,7 +402,7 @@ auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) ->
auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name,
gpointer data) -> void {
Mpris* mpris = static_cast<Mpris*>(data);
auto* mpris = static_cast<Mpris*>(data);
if (!mpris) return;
spdlog::debug("mpris: name-appeared callback: {}", player_name->name);
@@ -415,7 +411,7 @@ auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlaye
return;
}
mpris->player = playerctl_player_new_from_name(player_name, NULL);
mpris->player = playerctl_player_new_from_name(player_name, nullptr);
g_object_connect(mpris->player, "signal::play", G_CALLBACK(onPlayerPlay), mpris, "signal::pause",
G_CALLBACK(onPlayerPause), mpris, "signal::stop", G_CALLBACK(onPlayerStop),
mpris, "signal::stop", G_CALLBACK(onPlayerStop), mpris, "signal::metadata",
@@ -426,19 +422,20 @@ auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlaye
auto Mpris::onPlayerNameVanished(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name,
gpointer data) -> void {
Mpris* mpris = static_cast<Mpris*>(data);
auto* mpris = static_cast<Mpris*>(data);
if (!mpris) return;
spdlog::debug("mpris: player-vanished callback: {}", player_name->name);
if (std::string(player_name->name) == mpris->player_) {
mpris->player = nullptr;
mpris->event_box_.set_visible(false);
mpris->dp.emit();
}
}
auto Mpris::onPlayerPlay(PlayerctlPlayer* player, gpointer data) -> void {
Mpris* mpris = static_cast<Mpris*>(data);
auto* mpris = static_cast<Mpris*>(data);
if (!mpris) return;
spdlog::debug("mpris: player-play callback");
@@ -447,7 +444,7 @@ auto Mpris::onPlayerPlay(PlayerctlPlayer* player, gpointer data) -> void {
}
auto Mpris::onPlayerPause(PlayerctlPlayer* player, gpointer data) -> void {
Mpris* mpris = static_cast<Mpris*>(data);
auto* mpris = static_cast<Mpris*>(data);
if (!mpris) return;
spdlog::debug("mpris: player-pause callback");
@@ -456,7 +453,7 @@ auto Mpris::onPlayerPause(PlayerctlPlayer* player, gpointer data) -> void {
}
auto Mpris::onPlayerStop(PlayerctlPlayer* player, gpointer data) -> void {
Mpris* mpris = static_cast<Mpris*>(data);
auto* mpris = static_cast<Mpris*>(data);
if (!mpris) return;
spdlog::debug("mpris: player-stop callback");
@@ -468,7 +465,7 @@ auto Mpris::onPlayerStop(PlayerctlPlayer* player, gpointer data) -> void {
}
auto Mpris::onPlayerMetadata(PlayerctlPlayer* player, GVariant* metadata, gpointer data) -> void {
Mpris* mpris = static_cast<Mpris*>(data);
auto* mpris = static_cast<Mpris*>(data);
if (!mpris) return;
spdlog::debug("mpris: player-metadata callback");
@@ -523,30 +520,30 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
.length = std::nullopt,
};
if (auto artist_ = playerctl_player_get_artist(player, &error)) {
if (auto* artist_ = playerctl_player_get_artist(player, &error)) {
spdlog::debug("mpris[{}]: artist = {}", info.name, artist_);
info.artist = artist_;
g_free(artist_);
}
if (error) goto errorexit;
if (auto album_ = playerctl_player_get_album(player, &error)) {
if (auto* album_ = playerctl_player_get_album(player, &error)) {
spdlog::debug("mpris[{}]: album = {}", info.name, album_);
info.album = album_;
g_free(album_);
}
if (error) goto errorexit;
if (auto title_ = playerctl_player_get_title(player, &error)) {
if (auto* title_ = playerctl_player_get_title(player, &error)) {
spdlog::debug("mpris[{}]: title = {}", info.name, title_);
info.title = title_;
g_free(title_);
}
if (error) goto errorexit;
if (auto length_ = playerctl_player_print_metadata_prop(player, "mpris:length", &error)) {
if (auto* length_ = playerctl_player_print_metadata_prop(player, "mpris:length", &error)) {
spdlog::debug("mpris[{}]: mpris:length = {}", info.name, length_);
std::chrono::microseconds len = std::chrono::microseconds(std::strtol(length_, nullptr, 10));
auto len = std::chrono::microseconds(std::strtol(length_, nullptr, 10));
auto len_h = std::chrono::duration_cast<std::chrono::hours>(len);
auto len_m = std::chrono::duration_cast<std::chrono::minutes>(len - len_h);
auto len_s = std::chrono::duration_cast<std::chrono::seconds>(len - len_h - len_m);
@@ -563,7 +560,7 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
error = nullptr;
} else {
spdlog::debug("mpris[{}]: position = {}", info.name, position_);
std::chrono::microseconds len = std::chrono::microseconds(position_);
auto len = std::chrono::microseconds(position_);
auto len_h = std::chrono::duration_cast<std::chrono::hours>(len);
auto len_m = std::chrono::duration_cast<std::chrono::minutes>(len - len_h);
auto len_s = std::chrono::duration_cast<std::chrono::seconds>(len - len_h - len_m);
+213
View File
@@ -0,0 +1,213 @@
#include "modules/power_profiles_daemon.hpp"
#include <fmt/args.h>
#include <glibmm.h>
#include <glibmm/variant.h>
#include <spdlog/spdlog.h>
namespace waybar::modules {
PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Value& config)
: ALabel(config, "power-profiles-daemon", id, "{icon}", 0, false, true), connected_(false) {
if (config_["tooltip-format"].isString()) {
tooltipFormat_ = config_["tooltip-format"].asString();
} else {
tooltipFormat_ = "Power profile: {profile}\nDriver: {driver}";
}
// Fasten your seatbelt, we're up for quite a ride. The rest of the
// init is performed asynchronously. There's 2 callbacks involved.
// Here's the overall idea:
// 1. Async connect to the system bus.
// 2. In the system bus connect callback, try to call
// org.freedesktop.DBus.Properties.GetAll to see if
// power-profiles-daemon is able to respond.
// 3. In the GetAll callback, connect the activeProfile monitoring
// callback, consider the init to be successful. Meaning start
// drawing the module.
//
// There's sadly no other way around that, we have to try to call a
// 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
// 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.
//
// Revisit this in 2026, systems should be updated by then.
Gio::DBus::Proxy::create_for_bus(Gio::DBus::BusType::BUS_TYPE_SYSTEM, "net.hadess.PowerProfiles",
"/net/hadess/PowerProfiles", "net.hadess.PowerProfiles",
sigc::mem_fun(*this, &PowerProfilesDaemon::busConnectedCb));
// Schedule update to set the initial visibility
dp.emit();
}
void PowerProfilesDaemon::busConnectedCb(Glib::RefPtr<Gio::AsyncResult>& r) {
try {
powerProfilesProxy_ = Gio::DBus::Proxy::create_for_bus_finish(r);
using GetAllProfilesVar = Glib::Variant<std::tuple<Glib::ustring>>;
auto callArgs = GetAllProfilesVar::create(std::make_tuple("net.hadess.PowerProfiles"));
powerProfilesProxy_->call("org.freedesktop.DBus.Properties.GetAll",
sigc::mem_fun(*this, &PowerProfilesDaemon::getAllPropsCb), callArgs);
// Connect active profile callback
} catch (const std::exception& e) {
spdlog::error("Failed to create the power profiles daemon DBus proxy: {}", e.what());
} catch (const Glib::Error& e) {
spdlog::error("Failed to create the power profiles daemon DBus proxy: {}",
std::string(e.what()));
}
}
// Callback for the GetAll call.
//
// We're abusing this call to make sure power-profiles-daemon is
// available on the host. We're not really using
void PowerProfilesDaemon::getAllPropsCb(Glib::RefPtr<Gio::AsyncResult>& r) {
try {
auto _ = powerProfilesProxy_->call_finish(r);
// Power-profiles-daemon responded something, we can assume it's
// available, we can safely attach the activeProfile monitoring
// now.
connected_ = true;
powerProfilesProxy_->signal_properties_changed().connect(
sigc::mem_fun(*this, &PowerProfilesDaemon::profileChangedCb));
populateInitState();
} catch (const std::exception& err) {
spdlog::error("Failed to query power-profiles-daemon via dbus: {}", err.what());
} catch (const Glib::Error& err) {
spdlog::error("Failed to query power-profiles-daemon via dbus: {}", std::string(err.what()));
}
}
void PowerProfilesDaemon::populateInitState() {
// Retrieve current active profile
Glib::Variant<std::string> profileStr;
powerProfilesProxy_->get_cached_property(profileStr, "ActiveProfile");
// Retrieve profiles list, it's aa{sv}.
using ProfilesType = std::vector<std::map<Glib::ustring, Glib::Variant<std::string>>>;
Glib::Variant<ProfilesType> profilesVariant;
powerProfilesProxy_->get_cached_property(profilesVariant, "Profiles");
for (auto& variantDict : profilesVariant.get()) {
Glib::ustring name;
Glib::ustring driver;
if (auto p = variantDict.find("Profile"); p != variantDict.end()) {
name = p->second.get();
}
if (auto d = variantDict.find("Driver"); d != variantDict.end()) {
driver = d->second.get();
}
if (!name.empty()) {
availableProfiles_.emplace_back(std::move(name), std::move(driver));
} else {
spdlog::error(
"Power profiles daemon: power-profiles-daemon sent us an empty power profile name. "
"Something is wrong.");
}
}
// Find the index of the current activated mode (to toggle)
std::string str = profileStr.get();
switchToProfile(str);
}
void PowerProfilesDaemon::profileChangedCb(
const Gio::DBus::Proxy::MapChangedProperties& changedProperties,
const std::vector<Glib::ustring>& invalidatedProperties) {
// We're likely connected if this callback gets triggered.
// But better be safe than sorry.
if (connected_) {
if (auto activeProfileVariant = changedProperties.find("ActiveProfile");
activeProfileVariant != changedProperties.end()) {
std::string activeProfile =
Glib::VariantBase::cast_dynamic<Glib::Variant<std::string>>(activeProfileVariant->second)
.get();
switchToProfile(activeProfile);
}
}
}
// Look for the profile str in our internal profiles list. Using a
// vector to store the profiles ain't the smartest move
// complexity-wise, but it makes toggling between the mode easy. This
// vector is 3 elements max, we'll be fine :P
void PowerProfilesDaemon::switchToProfile(std::string const& str) {
auto pred = [str](Profile const& p) { return p.name == str; };
this->activeProfile_ = std::find_if(availableProfiles_.begin(), availableProfiles_.end(), pred);
if (activeProfile_ == availableProfiles_.end()) {
spdlog::error(
"Power profile daemon: can't find the active profile {} in the available profiles list",
str);
}
dp.emit();
}
auto PowerProfilesDaemon::update() -> void {
if (connected_ && activeProfile_ != availableProfiles_.end()) {
auto profile = (*activeProfile_);
// Set label
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(fmt::arg("profile", profile.name));
store.push_back(fmt::arg("driver", profile.driver));
store.push_back(fmt::arg("icon", getIcon(0, profile.name)));
label_.set_markup(fmt::vformat(format_, store));
if (tooltipEnabled()) {
label_.set_tooltip_text(fmt::vformat(tooltipFormat_, store));
}
// Set CSS class
if (!currentStyle_.empty()) {
label_.get_style_context()->remove_class(currentStyle_);
}
label_.get_style_context()->add_class(profile.name);
currentStyle_ = profile.name;
event_box_.set_visible(true);
} else {
event_box_.set_visible(false);
}
ALabel::update();
}
bool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) {
if (e->type == GdkEventType::GDK_BUTTON_PRESS && connected_) {
if (e->button == 1) /* left click */ {
activeProfile_++;
if (activeProfile_ == availableProfiles_.end()) {
activeProfile_ = availableProfiles_.begin();
}
} else {
if (activeProfile_ == availableProfiles_.begin()) {
activeProfile_ = availableProfiles_.end();
}
activeProfile_--;
}
using VarStr = Glib::Variant<Glib::ustring>;
using SetPowerProfileVar = Glib::Variant<std::tuple<Glib::ustring, Glib::ustring, VarStr>>;
VarStr activeProfileVariant = VarStr::create(activeProfile_->name);
auto callArgs = SetPowerProfileVar::create(
std::make_tuple("net.hadess.PowerProfiles", "ActiveProfile", activeProfileVariant));
powerProfilesProxy_->call("org.freedesktop.DBus.Properties.Set",
sigc::mem_fun(*this, &PowerProfilesDaemon::setPropCb), callArgs);
}
return true;
}
void PowerProfilesDaemon::setPropCb(Glib::RefPtr<Gio::AsyncResult>& r) {
try {
auto _ = powerProfilesProxy_->call_finish(r);
dp.emit();
} catch (const std::exception& e) {
spdlog::error("Failed to set the the active power profile: {}", e.what());
} catch (const Glib::Error& e) {
spdlog::error("Failed to set the active power profile: {}", std::string(e.what()));
}
}
} // namespace waybar::modules
+50 -43
View File
@@ -1,16 +1,11 @@
#include "modules/privacy/privacy.hpp"
#include <fmt/core.h>
#include <json/value.h>
#include <pipewire/pipewire.h>
#include <spdlog/spdlog.h>
#include <cstdio>
#include <cstring>
#include <string>
#include "AModule.hpp"
#include "gtkmm/image.h"
#include "modules/privacy/privacy_item.hpp"
namespace waybar::modules::privacy {
@@ -50,32 +45,30 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, const std::st
// Initialize each privacy module
Json::Value modules = config_["modules"];
// Add Screenshare and Mic usage as default modules if none are specified
if (!modules.isArray() || modules.size() == 0) {
if (!modules.isArray() || modules.empty()) {
modules = Json::Value(Json::arrayValue);
for (auto& type : {"screenshare", "audio-in"}) {
for (const auto& type : {"screenshare", "audio-in"}) {
Json::Value obj = Json::Value(Json::objectValue);
obj["type"] = type;
modules.append(obj);
}
}
for (uint i = 0; i < modules.size(); i++) {
const Json::Value& module_config = modules[i];
if (!module_config.isObject() || !module_config["type"].isString()) continue;
const std::string type = module_config["type"].asString();
if (type == "screenshare") {
auto item =
Gtk::make_managed<PrivacyItem>(module_config, PRIVACY_NODE_TYPE_VIDEO_INPUT,
&nodes_screenshare, pos, iconSize, transition_duration);
box_.add(*item);
} else if (type == "audio-in") {
auto item =
Gtk::make_managed<PrivacyItem>(module_config, PRIVACY_NODE_TYPE_AUDIO_INPUT,
&nodes_audio_in, pos, iconSize, transition_duration);
box_.add(*item);
} else if (type == "audio-out") {
auto item =
Gtk::make_managed<PrivacyItem>(module_config, PRIVACY_NODE_TYPE_AUDIO_OUTPUT,
&nodes_audio_out, pos, iconSize, transition_duration);
std::map<std::string, std::tuple<decltype(&nodes_audio_in), PrivacyNodeType> > typeMap = {
{"screenshare", {&nodes_screenshare, PRIVACY_NODE_TYPE_VIDEO_INPUT}},
{"audio-in", {&nodes_audio_in, PRIVACY_NODE_TYPE_AUDIO_INPUT}},
{"audio-out", {&nodes_audio_out, PRIVACY_NODE_TYPE_AUDIO_OUTPUT}},
};
for (const auto& module : modules) {
if (!module.isObject() || !module["type"].isString()) continue;
const std::string type = module["type"].asString();
auto iter = typeMap.find(type);
if (iter != typeMap.end()) {
auto& [nodePtr, nodeType] = iter->second;
auto* item = Gtk::make_managed<PrivacyItem>(module, nodeType, nodePtr, pos, iconSize,
transition_duration);
box_.add(*item);
}
}
@@ -120,24 +113,35 @@ void Privacy::onPrivacyNodesChanged() {
}
auto Privacy::update() -> void {
mutex_.lock();
bool screenshare, audio_in, audio_out;
// set in modules or not
bool setScreenshare = false;
bool setAudioIn = false;
bool setAudioOut = false;
// used or not
bool useScreenshare = false;
bool useAudioIn = false;
bool useAudioOut = false;
mutex_.lock();
for (Gtk::Widget* widget : box_.get_children()) {
PrivacyItem* module = dynamic_cast<PrivacyItem*>(widget);
if (!module) continue;
auto* module = dynamic_cast<PrivacyItem*>(widget);
if (module == nullptr) continue;
switch (module->privacy_type) {
case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT:
screenshare = !nodes_screenshare.empty();
module->set_in_use(screenshare);
setScreenshare = true;
useScreenshare = !nodes_screenshare.empty();
module->set_in_use(useScreenshare);
break;
case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_INPUT:
audio_in = !nodes_audio_in.empty();
module->set_in_use(audio_in);
setAudioIn = true;
useAudioIn = !nodes_audio_in.empty();
module->set_in_use(useAudioIn);
break;
case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT:
audio_out = !nodes_audio_out.empty();
module->set_in_use(audio_out);
setAudioOut = true;
useAudioOut = !nodes_audio_out.empty();
module->set_in_use(useAudioOut);
break;
case util::PipewireBackend::PRIVACY_NODE_TYPE_NONE:
break;
@@ -146,25 +150,28 @@ auto Privacy::update() -> void {
mutex_.unlock();
// Hide the whole widget if none are in use
bool is_visible = screenshare || audio_in || audio_out;
if (is_visible != event_box_.get_visible()) {
bool isVisible = (setScreenshare && useScreenshare) || (setAudioIn && useAudioIn) ||
(setAudioOut && useAudioOut);
if (isVisible != event_box_.get_visible()) {
// Disconnect any previous connection so that it doesn't get activated in
// the future, hiding the module when it should be visible
visibility_conn.disconnect();
if (is_visible) {
if (isVisible) {
event_box_.set_visible(true);
} else {
// Hides the widget when all of the privacy_item revealers animations
// have finished animating
visibility_conn = Glib::signal_timeout().connect(
sigc::track_obj(
[this] {
[this, setScreenshare, setAudioOut, setAudioIn]() {
mutex_.lock();
bool screenshare = !nodes_screenshare.empty();
bool audio_in = !nodes_audio_in.empty();
bool audio_out = !nodes_audio_out.empty();
bool visible = false;
visible |= setScreenshare && !nodes_screenshare.empty();
visible |= setAudioIn && !nodes_audio_in.empty();
visible |= setAudioOut && !nodes_audio_out.empty();
mutex_.unlock();
event_box_.set_visible(screenshare || audio_in || audio_out);
event_box_.set_visible(visible);
return false;
},
*this),
+4 -16
View File
@@ -1,23 +1,11 @@
#include "modules/privacy/privacy_item.hpp"
#include <fmt/core.h>
#include <pipewire/pipewire.h>
#include <spdlog/spdlog.h>
#include <cstdio>
#include <cstring>
#include <string>
#include <thread>
#include "AModule.hpp"
#include "glibmm/main.h"
#include "glibmm/priorities.h"
#include "gtkmm/enums.h"
#include "gtkmm/label.h"
#include "gtkmm/revealer.h"
#include "gtkmm/tooltip.h"
#include "sigc++/adaptors/bind.h"
#include "util/gtk_icon.hpp"
#include "util/pipewire/privacy_node_info.hpp"
namespace waybar::modules::privacy {
@@ -98,7 +86,7 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac
void PrivacyItem::update_tooltip() {
// Removes all old nodes
for (auto child : tooltip_window.get_children()) {
for (auto *child : tooltip_window.get_children()) {
delete child;
}
@@ -108,12 +96,12 @@ void PrivacyItem::update_tooltip() {
// Set device icon
Gtk::Image *node_icon = new Gtk::Image();
node_icon->set_pixel_size(tooltipIconSize);
node_icon->set_from_icon_name(node->get_icon_name(), Gtk::ICON_SIZE_INVALID);
node_icon->set_from_icon_name(node->getIconName(), Gtk::ICON_SIZE_INVALID);
box->add(*node_icon);
// Set model
Gtk::Label *node_name = new Gtk::Label(node->get_name());
box->add(*node_name);
auto *nodeName = new Gtk::Label(node->getName());
box->add(*nodeName);
tooltip_window.add(*box);
}
+14 -2
View File
@@ -42,15 +42,27 @@ static const std::array<std::string, 9> ports = {
};
const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {
std::vector<std::string> res = {backend->getCurrentSinkName(), backend->getDefaultSourceName()};
std::vector<std::string> res;
auto sink_muted = backend->getSinkMuted();
if (sink_muted) {
res.emplace_back(backend->getCurrentSinkName() + "-muted");
}
res.push_back(backend->getCurrentSinkName());
res.push_back(backend->getDefaultSourceName());
std::string nameLC = backend->getSinkPortName() + backend->getFormFactor();
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
for (auto const &port : ports) {
if (nameLC.find(port) != std::string::npos) {
if (sink_muted) {
res.emplace_back(port + "-muted");
}
res.push_back(port);
return res;
break;
}
}
if (sink_muted) {
res.emplace_back("default-muted");
}
return res;
}
+13
View File
@@ -8,6 +8,7 @@
#include <fstream>
#include <map>
#include "gdk/gdk.h"
#include "util/format.hpp"
#include "util/gtk_icon.hpp"
@@ -57,6 +58,8 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf
event_box.add_events(Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick));
event_box.signal_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll));
event_box.signal_enter_notify_event().connect(sigc::mem_fun(*this, &Item::handleMouseEnter));
event_box.signal_leave_notify_event().connect(sigc::mem_fun(*this, &Item::handleMouseLeave));
// initial visibility
event_box.show_all();
event_box.set_visible(show_passive_);
@@ -69,6 +72,16 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf
cancellable_, interface);
}
bool Item::handleMouseEnter(GdkEventCrossing* const& e) {
event_box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
return false;
}
bool Item::handleMouseLeave(GdkEventCrossing* const& e) {
event_box.unset_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
return false;
}
void Item::onConfigure(GdkEventConfigure* ev) { this->updateImage(); }
void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
+5
View File
@@ -19,6 +19,7 @@ const std::string Language::XKB_ACTIVE_LAYOUT_NAME_KEY = "xkb_active_layout_name
Language::Language(const std::string& id, const Json::Value& config)
: ALabel(config, "language", id, "{}", 0, true) {
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);
@@ -95,6 +96,10 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
auto Language::update() -> void {
std::lock_guard<std::mutex> lock(mutex_);
if (hide_single_ && layouts_map_.size() <= 1) {
event_box_.hide();
return;
}
auto display_layout = trim(fmt::format(
fmt::runtime(format_), fmt::arg("short", layout_.short_name),
fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name),
+44 -16
View File
@@ -107,11 +107,16 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
auto payload = parser_.parse(res.payload);
workspaces_.clear();
std::vector<Json::Value> outputs;
bool alloutputs = config_["all-outputs"].asBool();
std::copy_if(payload["nodes"].begin(), payload["nodes"].end(), std::back_inserter(outputs),
[&](const auto &workspace) {
return !config_["all-outputs"].asBool()
? workspace["name"].asString() == bar_.output->name
: true;
[&](const auto &output) {
if (alloutputs && output["name"].asString() != "__i3") {
return true;
}
if (output["name"].asString() == bar_.output->name) {
return true;
}
return false;
});
for (auto &output : outputs) {
@@ -136,12 +141,12 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
for (const std::string &p_w_name : p_workspaces_names) {
const Json::Value &p_w = p_workspaces[p_w_name];
auto it =
std::find_if(payload.begin(), payload.end(), [&p_w_name](const Json::Value &node) {
return node["name"].asString() == p_w_name;
});
auto it = std::find_if(workspaces_.begin(), workspaces_.end(),
[&p_w_name](const Json::Value &node) {
return node["name"].asString() == p_w_name;
});
if (it != payload.end()) {
if (it != workspaces_.end()) {
continue; // already displayed by some bar
}
@@ -253,11 +258,14 @@ bool Workspaces::hasFlag(const Json::Value &node, const std::string &flag) {
[&](auto const &e) { return hasFlag(e, flag); })) {
return true;
}
if (std::any_of(node["floating_nodes"].begin(), node["floating_nodes"].end(),
[&](auto const &e) { return hasFlag(e, flag); })) {
return true;
}
return false;
}
void Workspaces::updateWindows(const Json::Value &node, std::string &windows) {
auto format = config_["window-format"].asString();
if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") &&
node["name"].isString()) {
std::string title = g_markup_escape_text(node["name"].asString().c_str(), -1);
@@ -290,12 +298,13 @@ auto Workspaces::update() -> void {
if (needReorder) {
box_.reorder_child(button, it - workspaces_.begin());
}
bool noNodes = (*it)["nodes"].empty() && (*it)["floating_nodes"].empty();
if (hasFlag((*it), "focused")) {
button.get_style_context()->add_class("focused");
} else {
button.get_style_context()->remove_class("focused");
}
if (hasFlag((*it), "visible")) {
if (hasFlag((*it), "visible") || ((*it)["output"].isString() && noNodes)) {
button.get_style_context()->add_class("visible");
} else {
button.get_style_context()->remove_class("visible");
@@ -305,11 +314,16 @@ auto Workspaces::update() -> void {
} else {
button.get_style_context()->remove_class("urgent");
}
if (hasFlag((*it), "target_output")) {
if ((*it)["target_output"].isString()) {
button.get_style_context()->add_class("persistent");
} else {
button.get_style_context()->remove_class("persistent");
}
if (noNodes) {
button.get_style_context()->add_class("empty");
} else {
button.get_style_context()->remove_class("empty");
}
if ((*it)["output"].isString()) {
if (((*it)["output"].asString()) == bar_.output->name) {
button.get_style_context()->add_class("current_output");
@@ -392,7 +406,7 @@ std::string Workspaces::getIcon(const std::string &name, const Json::Value &node
}
}
if (key == "focused" || key == "urgent") {
if (config_["format-icons"][key].isString() && node[key].asBool()) {
if (config_["format-icons"][key].isString() && hasFlag(node, key)) {
return config_["format-icons"][key].asString();
}
} else if (config_["format-icons"]["persistent"].isString() &&
@@ -420,9 +434,16 @@ bool Workspaces::handleScroll(GdkEventScroll *e) {
}
std::string name;
{
bool alloutputs = config_["all-outputs"].asBool();
std::lock_guard<std::mutex> lock(mutex_);
auto it = std::find_if(workspaces_.begin(), workspaces_.end(),
[](const auto &workspace) { return workspace["focused"].asBool(); });
auto it =
std::find_if(workspaces_.begin(), workspaces_.end(), [alloutputs](const auto &workspace) {
if (alloutputs) {
return hasFlag(workspace, "focused");
}
bool noNodes = workspace["nodes"].empty() && workspace["floating_nodes"].empty();
return hasFlag(workspace, "visible") || (workspace["output"].isString() && noNodes);
});
if (it == workspaces_.end()) {
return true;
}
@@ -480,7 +501,14 @@ std::string Workspaces::trimWorkspaceName(std::string name) {
void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) {
if (config_["current-only"].asBool()) {
if (node["focused"].asBool()) {
// 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.
bool focused = node["focused"].asBool() ||
std::any_of(node["nodes"].begin(), node["nodes"].end(),
[](const auto &child) { return child["focused"].asBool(); });
if (focused) {
button.show();
} else {
button.hide();
+43 -25
View File
@@ -1,6 +1,7 @@
#include "modules/temperature.hpp"
#include <filesystem>
#include <string>
#if defined(__FreeBSD__)
#include <sys/sysctl.h>
@@ -9,39 +10,53 @@
waybar::modules::Temperature::Temperature(const std::string& id, const Json::Value& config)
: ALabel(config, "temperature", id, "{temperatureC}°C", 10) {
#if defined(__FreeBSD__)
// try to read sysctl?
// FreeBSD uses sysctlbyname instead of read from a file
#else
auto& hwmon_path = config_["hwmon-path"];
if (hwmon_path.isString()) {
file_path_ = hwmon_path.asString();
} else if (hwmon_path.isArray()) {
// if hwmon_path is an array, loop to find first valid item
for (auto& item : hwmon_path) {
auto path = item.asString();
if (std::filesystem::exists(path)) {
file_path_ = path;
break;
}
}
} else if (config_["hwmon-path-abs"].isString() && config_["input-filename"].isString()) {
for (const auto& hwmon :
std::filesystem::directory_iterator(config_["hwmon-path-abs"].asString())) {
if (hwmon.path().filename().string().starts_with("hwmon")) {
file_path_ = hwmon.path().string() + "/" + config_["input-filename"].asString();
break;
}
}
auto traverseAsArray = [](const Json::Value& value, auto&& check_set_path) {
if (value.isString())
check_set_path(value.asString());
else if (value.isArray())
for (const auto& item : value)
if (check_set_path(item.asString())) break;
};
// if hwmon_path is an array, loop to find first valid item
traverseAsArray(config_["hwmon-path"], [this](const std::string& path) {
if (!std::filesystem::exists(path)) return false;
file_path_ = path;
return true;
});
if (file_path_.empty() && config_["input-filename"].isString()) {
// fallback to hwmon_paths-abs
traverseAsArray(config_["hwmon-path-abs"], [this](const std::string& path) {
if (!std::filesystem::is_directory(path)) return false;
return std::ranges::any_of(
std::filesystem::directory_iterator(path), [this](const auto& hwmon) {
if (!hwmon.path().filename().string().starts_with("hwmon")) return false;
file_path_ = hwmon.path().string() + "/" + config_["input-filename"].asString();
return true;
});
});
}
if (file_path_.empty()) {
auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0;
file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone);
}
// check if file_path_ can be used to retrive the temperature
std::ifstream temp(file_path_);
if (!temp.is_open()) {
throw std::runtime_error("Can't open " + file_path_);
}
if (!temp.good()) {
temp.close();
throw std::runtime_error("Can't read from " + file_path_);
}
temp.close();
#endif
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
@@ -93,11 +108,11 @@ float waybar::modules::Temperature::getTemperature() {
size_t size = sizeof temp;
auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0;
auto sysctl_thermal = fmt::format("hw.acpi.thermal.tz{}.temperature", zone);
if (sysctlbyname("hw.acpi.thermal.tz0.temperature", &temp, &size, NULL, 0) != 0) {
throw std::runtime_error(
"sysctl hw.acpi.thermal.tz0.temperature or dev.cpu.0.temperature failed");
if (sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size,
NULL, 0) != 0) {
throw std::runtime_error(fmt::format(
"sysctl hw.acpi.thermal.tz{}.temperature or dev.cpu.{}.temperature failed", zone, zone));
}
auto temperature_c = ((float)temp - 2732) / 10;
return temperature_c;
@@ -110,6 +125,9 @@ float waybar::modules::Temperature::getTemperature() {
std::string line;
if (temp.good()) {
getline(temp, line);
} else {
temp.close();
throw std::runtime_error("Can't read from " + file_path_);
}
temp.close();
auto temperature_c = std::strtol(line.c_str(), nullptr, 10) / 1000.0;
+496
View File
@@ -0,0 +1,496 @@
#include "modules/upower.hpp"
#include <giomm/dbuswatchname.h>
#include <gtkmm/tooltip.h>
#include <spdlog/spdlog.h>
namespace waybar::modules {
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
: Gtk::ORIENTATION_HORIZONTAL);
// Get current theme
gtkTheme_ = Gtk::IconTheme::get_default();
// Icon Size
if (config_["icon-size"].isInt()) {
iconSize_ = config_["icon-size"].asInt();
}
image_.set_pixel_size(iconSize_);
// Show icon only when "show-icon" isn't set to false
if (config_["show-icon"].isBool()) showIcon_ = config_["show-icon"].asBool();
if (!showIcon_) box_.remove(image_);
// Device user wants
if (config_["native-path"].isString()) nativePath_ = config_["native-path"].asString();
// Device model user wants
if (config_["model"].isString()) model_ = config_["model"].asString();
// Hide If Empty
if (config_["hide-if-empty"].isBool()) hideIfEmpty_ = config_["hide-if-empty"].asBool();
// Tooltip Spacing
if (config_["tooltip-spacing"].isInt()) tooltip_spacing_ = config_["tooltip-spacing"].asInt();
// Tooltip Padding
if (config_["tooltip-padding"].isInt()) {
tooltip_padding_ = config_["tooltip-padding"].asInt();
contentBox_.set_margin_top(tooltip_padding_);
contentBox_.set_margin_bottom(tooltip_padding_);
contentBox_.set_margin_left(tooltip_padding_);
contentBox_.set_margin_right(tooltip_padding_);
}
// Tooltip Format
if (config_["tooltip-format"].isString()) tooltipFormat_ = config_["tooltip-format"].asString();
// Start watching DBUS
watcherID_ = Gio::DBus::watch_name(
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.UPower",
sigc::mem_fun(*this, &UPower::onAppear), sigc::mem_fun(*this, &UPower::onVanished),
Gio::DBus::BusNameWatcherFlags::BUS_NAME_WATCHER_FLAGS_AUTO_START);
// Get DBus async connect
Gio::DBus::Connection::get(Gio::DBus::BusType::BUS_TYPE_SYSTEM,
sigc::mem_fun(*this, &UPower::getConn_cb));
// Make UPower client
GError **gErr = NULL;
upClient_ = up_client_new_full(NULL, gErr);
if (upClient_ == NULL)
spdlog::error("Upower. UPower client connection error. {}", (*gErr)->message);
// Subscribe UPower events
g_signal_connect(upClient_, "device-added", G_CALLBACK(deviceAdded_cb), this);
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);
resetDevices();
setDisplayDevice();
// Update the widget
dp.emit();
}
UPower::~UPower() {
if (upDevice_.upDevice != NULL) g_object_unref(upDevice_.upDevice);
if (upClient_ != NULL) g_object_unref(upClient_);
if (subscrID_ > 0u) {
conn_->signal_unsubscribe(subscrID_);
subscrID_ = 0u;
}
Gio::DBus::unwatch_name(watcherID_);
watcherID_ = 0u;
removeDevices();
}
static const std::string getDeviceStatus(UpDeviceState &state) {
switch (state) {
case UP_DEVICE_STATE_CHARGING:
case UP_DEVICE_STATE_PENDING_CHARGE:
return "charging";
case UP_DEVICE_STATE_DISCHARGING:
case UP_DEVICE_STATE_PENDING_DISCHARGE:
return "discharging";
case UP_DEVICE_STATE_FULLY_CHARGED:
return "full";
case UP_DEVICE_STATE_EMPTY:
return "empty";
default:
return "unknown-status";
}
}
static const std::string getDeviceIcon(UpDeviceKind &kind) {
switch (kind) {
case UP_DEVICE_KIND_LINE_POWER:
return "ac-adapter-symbolic";
case UP_DEVICE_KIND_BATTERY:
return "battery-symbolic";
case UP_DEVICE_KIND_UPS:
return "uninterruptible-power-supply-symbolic";
case UP_DEVICE_KIND_MONITOR:
return "video-display-symbolic";
case UP_DEVICE_KIND_MOUSE:
return "input-mouse-symbolic";
case UP_DEVICE_KIND_KEYBOARD:
return "input-keyboard-symbolic";
case UP_DEVICE_KIND_PDA:
return "pda-symbolic";
case UP_DEVICE_KIND_PHONE:
return "phone-symbolic";
case UP_DEVICE_KIND_MEDIA_PLAYER:
return "multimedia-player-symbolic";
case UP_DEVICE_KIND_TABLET:
return "computer-apple-ipad-symbolic";
case UP_DEVICE_KIND_COMPUTER:
return "computer-symbolic";
case UP_DEVICE_KIND_GAMING_INPUT:
return "input-gaming-symbolic";
case UP_DEVICE_KIND_PEN:
return "input-tablet-symbolic";
case UP_DEVICE_KIND_TOUCHPAD:
return "input-touchpad-symbolic";
case UP_DEVICE_KIND_MODEM:
return "modem-symbolic";
case UP_DEVICE_KIND_NETWORK:
return "network-wired-symbolic";
case UP_DEVICE_KIND_HEADSET:
return "audio-headset-symbolic";
case UP_DEVICE_KIND_HEADPHONES:
return "audio-headphones-symbolic";
case UP_DEVICE_KIND_OTHER_AUDIO:
case UP_DEVICE_KIND_SPEAKERS:
return "audio-speakers-symbolic";
case UP_DEVICE_KIND_VIDEO:
return "camera-web-symbolic";
case UP_DEVICE_KIND_PRINTER:
return "printer-symbolic";
case UP_DEVICE_KIND_SCANNER:
return "scanner-symbolic";
case UP_DEVICE_KIND_CAMERA:
return "camera-photo-symbolic";
case UP_DEVICE_KIND_BLUETOOTH_GENERIC:
return "bluetooth-active-symbolic";
case UP_DEVICE_KIND_TOY:
case UP_DEVICE_KIND_REMOTE_CONTROL:
case UP_DEVICE_KIND_WEARABLE:
case UP_DEVICE_KIND_LAST:
default:
return "battery-symbolic";
}
}
static std::string secondsToString(const std::chrono::seconds sec) {
const auto ds{std::chrono::duration_cast<std::chrono::days>(sec)};
const auto hrs{std::chrono::duration_cast<std::chrono::hours>(sec - ds)};
const auto min{std::chrono::duration_cast<std::chrono::minutes>(sec - ds - hrs)};
std::string_view strRet{(ds.count() > 0) ? "{D}d {H}h {M}min"
: (hrs.count() > 0) ? "{H}h {M}min"
: (min.count() > 0) ? "{M}min"
: ""};
spdlog::debug(
"UPower::secondsToString(). seconds: \"{0}\", minutes: \"{1}\", hours: \"{2}\", \
days: \"{3}\", strRet: \"{4}\"",
sec.count(), min.count(), hrs.count(), ds.count(), strRet);
return fmt::format(fmt::runtime(strRet), fmt::arg("D", ds.count()), fmt::arg("H", hrs.count()),
fmt::arg("M", min.count()));
}
auto UPower::update() -> void {
std::lock_guard<std::mutex> guard{mutex_};
// Don't update widget if the UPower service isn't running
if (!upRunning_ || sleeping_) {
if (hideIfEmpty_) box_.hide();
return;
}
getUpDeviceInfo(upDevice_);
if (upDevice_.upDevice == NULL && hideIfEmpty_) {
box_.hide();
return;
}
/* Every Device which is handled by Upower and which is not
* UP_DEVICE_KIND_UNKNOWN (0) or UP_DEVICE_KIND_LINE_POWER (1) is a Battery
*/
const bool upDeviceValid{upDevice_.kind != UpDeviceKind::UP_DEVICE_KIND_UNKNOWN &&
upDevice_.kind != UpDeviceKind::UP_DEVICE_KIND_LINE_POWER};
// Get CSS status
const auto status{getDeviceStatus(upDevice_.state)};
// Remove last status if it exists
if (!lastStatus_.empty() && box_.get_style_context()->has_class(lastStatus_))
box_.get_style_context()->remove_class(lastStatus_);
if (!box_.get_style_context()->has_class(status)) box_.get_style_context()->add_class(status);
lastStatus_ = status;
if (devices_.size() == 0 && !upDeviceValid && hideIfEmpty_) {
box_.hide();
// Call parent update
AModule::update();
return;
}
label_.set_markup(getText(upDevice_, format_));
// Set icon
if (upDevice_.icon_name == NULL || !gtkTheme_->has_icon(upDevice_.icon_name))
upDevice_.icon_name = (char *)NO_BATTERY.c_str();
image_.set_from_icon_name(upDevice_.icon_name, Gtk::ICON_SIZE_INVALID);
box_.show();
// Call parent update
ALabel::update();
}
void UPower::getConn_cb(Glib::RefPtr<Gio::AsyncResult> &result) {
try {
conn_ = Gio::DBus::Connection::get_finish(result);
// Subscribe DBUs events
subscrID_ = conn_->signal_subscribe(sigc::mem_fun(*this, &UPower::prepareForSleep_cb),
"org.freedesktop.login1", "org.freedesktop.login1.Manager",
"PrepareForSleep", "/org/freedesktop/login1");
} catch (const Glib::Error &e) {
spdlog::error("Upower. DBus connection error. {}", e.what().c_str());
}
}
void UPower::onAppear(const Glib::RefPtr<Gio::DBus::Connection> &conn, const Glib::ustring &name,
const Glib::ustring &name_owner) {
upRunning_ = true;
}
void UPower::onVanished(const Glib::RefPtr<Gio::DBus::Connection> &conn,
const Glib::ustring &name) {
upRunning_ = false;
}
void UPower::prepareForSleep_cb(const Glib::RefPtr<Gio::DBus::Connection> &connection,
const Glib::ustring &sender_name, const Glib::ustring &object_path,
const Glib::ustring &interface_name,
const Glib::ustring &signal_name,
const Glib::VariantContainerBase &parameters) {
if (parameters.is_of_type(Glib::VariantType("(b)"))) {
Glib::Variant<bool> sleeping;
parameters.get_child(sleeping, 0);
if (!sleeping.get()) {
resetDevices();
setDisplayDevice();
sleeping_ = false;
// Update the widget
dp.emit();
} else
sleeping_ = true;
}
}
void UPower::deviceAdded_cb(UpClient *client, UpDevice *device, gpointer data) {
UPower *up{static_cast<UPower *>(data)};
up->addDevice(device);
up->setDisplayDevice();
// Update the widget
up->dp.emit();
}
void UPower::deviceRemoved_cb(UpClient *client, const gchar *objectPath, gpointer data) {
UPower *up{static_cast<UPower *>(data)};
up->removeDevice(objectPath);
up->setDisplayDevice();
// Update the widget
up->dp.emit();
}
void UPower::deviceNotify_cb(UpDevice *device, GParamSpec *pspec, gpointer data) {
UPower *up{static_cast<UPower *>(data)};
// Update the widget
up->dp.emit();
}
void UPower::addDevice(UpDevice *device) {
std::lock_guard<std::mutex> guard{mutex_};
if (G_IS_OBJECT(device)) {
const gchar *objectPath{up_device_get_object_path(device)};
// Due to the device getting cleared after this event is fired, we
// create a new object pointing to its objectPath
device = up_device_new();
upDevice_output upDevice{.upDevice = device};
gboolean ret{up_device_set_object_path_sync(device, objectPath, NULL, NULL)};
if (!ret) {
g_object_unref(G_OBJECT(device));
return;
}
if (devices_.find(objectPath) != devices_.cend()) {
auto upDevice{devices_[objectPath]};
if (G_IS_OBJECT(upDevice.upDevice)) g_object_unref(upDevice.upDevice);
devices_.erase(objectPath);
}
g_signal_connect(device, "notify", G_CALLBACK(deviceNotify_cb), this);
devices_.emplace(Devices::value_type(objectPath, upDevice));
}
}
void UPower::removeDevice(const gchar *objectPath) {
std::lock_guard<std::mutex> guard{mutex_};
if (devices_.find(objectPath) != devices_.cend()) {
auto upDevice{devices_[objectPath]};
if (G_IS_OBJECT(upDevice.upDevice)) g_object_unref(upDevice.upDevice);
devices_.erase(objectPath);
}
}
void UPower::removeDevices() {
std::lock_guard<std::mutex> guard{mutex_};
if (!devices_.empty()) {
auto it{devices_.cbegin()};
while (it != devices_.cend()) {
if (G_IS_OBJECT(it->second.upDevice)) g_object_unref(it->second.upDevice);
devices_.erase(it++);
}
}
}
// Removes all devices and adds the current devices
void UPower::resetDevices() {
// Remove all devices
removeDevices();
// Adds all devices
GPtrArray *newDevices = up_client_get_devices2(upClient_);
if (newDevices != NULL)
for (guint i{0}; i < newDevices->len; ++i) {
UpDevice *device{(UpDevice *)g_ptr_array_index(newDevices, i)};
if (device && G_IS_OBJECT(device)) addDevice(device);
}
}
void UPower::setDisplayDevice() {
std::lock_guard<std::mutex> guard{mutex_};
if (nativePath_.empty() && model_.empty()) {
// Unref current upDevice
if (upDevice_.upDevice != NULL) g_object_unref(upDevice_.upDevice);
upDevice_.upDevice = up_client_get_display_device(upClient_);
getUpDeviceInfo(upDevice_);
} else {
g_ptr_array_foreach(
up_client_get_devices2(upClient_),
[](gpointer data, gpointer user_data) {
upDevice_output upDevice;
auto thisPtr{static_cast<UPower *>(user_data)};
upDevice.upDevice = static_cast<UpDevice *>(data);
thisPtr->getUpDeviceInfo(upDevice);
upDevice_output displayDevice{NULL};
if (!thisPtr->nativePath_.empty()) {
if (upDevice.nativePath == nullptr) return;
if (0 == std::strcmp(upDevice.nativePath, thisPtr->nativePath_.c_str())) {
displayDevice = upDevice;
}
} else {
if (upDevice.model == nullptr) return;
if (0 == std::strcmp(upDevice.model, thisPtr->model_.c_str())) {
displayDevice = upDevice;
}
}
// Unref current upDevice
if (displayDevice.upDevice != NULL) g_object_unref(thisPtr->upDevice_.upDevice);
// Reassign new upDevice
thisPtr->upDevice_ = displayDevice;
},
this);
}
if (upDevice_.upDevice != NULL)
g_signal_connect(upDevice_.upDevice, "notify", G_CALLBACK(deviceNotify_cb), this);
}
void UPower::getUpDeviceInfo(upDevice_output &upDevice_) {
if (upDevice_.upDevice != NULL && G_IS_OBJECT(upDevice_.upDevice)) {
g_object_get(upDevice_.upDevice, "kind", &upDevice_.kind, "state", &upDevice_.state,
"percentage", &upDevice_.percentage, "icon-name", &upDevice_.icon_name,
"time-to-empty", &upDevice_.time_empty, "time-to-full", &upDevice_.time_full,
"temperature", &upDevice_.temperature, "native-path", &upDevice_.nativePath,
"model", &upDevice_.model, NULL);
spdlog::debug(
"UPower. getUpDeviceInfo. kind: \"{0}\". state: \"{1}\". percentage: \"{2}\". \
icon_name: \"{3}\". time-to-empty: \"{4}\". time-to-full: \"{5}\". temperature: \"{6}\". \
native_path: \"{7}\". model: \"{8}\"",
fmt::format_int(upDevice_.kind).str(), fmt::format_int(upDevice_.state).str(),
upDevice_.percentage, upDevice_.icon_name, upDevice_.time_empty, upDevice_.time_full,
upDevice_.temperature, upDevice_.nativePath, upDevice_.model);
}
}
const Glib::ustring UPower::getText(const upDevice_output &upDevice_, const std::string &format) {
Glib::ustring ret{""};
if (upDevice_.upDevice != NULL) {
std::string timeStr{""};
switch (upDevice_.state) {
case UP_DEVICE_STATE_CHARGING:
case UP_DEVICE_STATE_PENDING_CHARGE:
timeStr = secondsToString(std::chrono::seconds(upDevice_.time_full));
break;
case UP_DEVICE_STATE_DISCHARGING:
case UP_DEVICE_STATE_PENDING_DISCHARGE:
timeStr = secondsToString(std::chrono::seconds(upDevice_.time_empty));
break;
default:
break;
}
ret = fmt::format(
fmt::runtime(format),
fmt::arg("percentage", std::to_string((int)std::round(upDevice_.percentage)) + '%'),
fmt::arg("time", timeStr),
fmt::arg("temperature", fmt::format("{:-.2g}C", upDevice_.temperature)),
fmt::arg("model", upDevice_.model), fmt::arg("native-path", upDevice_.nativePath));
}
return ret;
}
bool UPower::queryTooltipCb(int x, int y, bool keyboard_tooltip,
const Glib::RefPtr<Gtk::Tooltip> &tooltip) {
std::lock_guard<std::mutex> guard{mutex_};
// Clear content box
contentBox_.forall([this](Gtk::Widget &wg) { contentBox_.remove(wg); });
// Fill content box with the content
for (auto pairDev : devices_) {
// Get device info
getUpDeviceInfo(pairDev.second);
if (pairDev.second.kind != UpDeviceKind::UP_DEVICE_KIND_UNKNOWN &&
pairDev.second.kind != UpDeviceKind::UP_DEVICE_KIND_LINE_POWER) {
// Make box record
Gtk::Box *boxRec{new Gtk::Box{box_.get_orientation(), tooltip_spacing_}};
contentBox_.add(*boxRec);
Gtk::Box *boxDev{new Gtk::Box{box_.get_orientation()}};
Gtk::Box *boxUsr{new Gtk::Box{box_.get_orientation()}};
boxRec->add(*boxDev);
boxRec->add(*boxUsr);
// Construct device box
// Set icon from kind
std::string iconNameDev{getDeviceIcon(pairDev.second.kind)};
if (!gtkTheme_->has_icon(iconNameDev)) iconNameDev = (char *)NO_BATTERY.c_str();
Gtk::Image *iconDev{new Gtk::Image{}};
iconDev->set_from_icon_name(iconNameDev, Gtk::ICON_SIZE_INVALID);
iconDev->set_pixel_size(iconSize_);
boxDev->add(*iconDev);
// Set label from model
Gtk::Label *labelDev{new Gtk::Label{pairDev.second.model}};
boxDev->add(*labelDev);
// Construct user box
// Set icon from icon state
if (pairDev.second.icon_name == NULL || !gtkTheme_->has_icon(pairDev.second.icon_name))
pairDev.second.icon_name = (char *)NO_BATTERY.c_str();
Gtk::Image *iconTooltip{new Gtk::Image{}};
iconTooltip->set_from_icon_name(pairDev.second.icon_name, Gtk::ICON_SIZE_INVALID);
iconTooltip->set_pixel_size(iconSize_);
boxUsr->add(*iconTooltip);
// Set markup text
Gtk::Label *labelTooltip{new Gtk::Label{}};
labelTooltip->set_markup(getText(pairDev.second, tooltipFormat_));
boxUsr->add(*labelTooltip);
}
}
tooltip->set_custom(contentBox_);
contentBox_.show_all();
return true;
}
} // namespace waybar::modules
-397
View File
@@ -1,397 +0,0 @@
#include "modules/upower/upower.hpp"
#include <fmt/core.h>
#include <cstring>
#include <string>
#include "gtkmm/tooltip.h"
#include "util/gtk_icon.hpp"
namespace waybar::modules::upower {
UPower::UPower(const std::string& id, const Json::Value& config)
: AModule(config, "upower", id),
box_(Gtk::ORIENTATION_HORIZONTAL, 0),
icon_(),
label_(),
devices(),
m_Mutex(),
client(),
showAltText(false) {
// Show icon only when "show-icon" isn't set to false
if (config_["show-icon"].isBool()) {
showIcon = config_["show-icon"].asBool();
}
if (showIcon) {
box_.pack_start(icon_);
}
box_.pack_start(label_);
box_.set_name(name_);
event_box_.add(box_);
// Device user wants
if (config_["native-path"].isString()) nativePath_ = config_["native-path"].asString();
// Icon Size
if (config_["icon-size"].isUInt()) {
iconSize = config_["icon-size"].asUInt();
}
icon_.set_pixel_size(iconSize);
// Hide If Empty
if (config_["hide-if-empty"].isBool()) {
hideIfEmpty = config_["hide-if-empty"].asBool();
}
// Format
if (config_["format"].isString()) {
format = config_["format"].asString();
}
// Format Alt
if (config_["format-alt"].isString()) {
format_alt = config_["format-alt"].asString();
}
// Tooltip Spacing
if (config_["tooltip-spacing"].isUInt()) {
tooltip_spacing = config_["tooltip-spacing"].asUInt();
}
// Tooltip Padding
if (config_["tooltip-padding"].isUInt()) {
tooltip_padding = config_["tooltip-padding"].asUInt();
}
// Tooltip
if (config_["tooltip"].isBool()) {
tooltip_enabled = config_["tooltip"].asBool();
}
box_.set_has_tooltip(tooltip_enabled);
if (tooltip_enabled) {
// Sets the window to use when showing the tooltip
upower_tooltip = std::make_unique<UPowerTooltip>(iconSize, tooltip_spacing, tooltip_padding);
box_.set_tooltip_window(*upower_tooltip);
box_.signal_query_tooltip().connect(sigc::mem_fun(*this, &UPower::show_tooltip_callback));
}
upowerWatcher_id = g_bus_watch_name(G_BUS_TYPE_SYSTEM, "org.freedesktop.UPower",
G_BUS_NAME_WATCHER_FLAGS_AUTO_START, upowerAppear,
upowerDisappear, this, NULL);
client = up_client_new_full(NULL, NULL);
if (client == NULL) {
throw std::runtime_error("Unable to create UPower client!");
}
// Connect to Login1 PrepareForSleep signal
login1_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL);
if (!login1_connection) {
throw std::runtime_error("Unable to connect to the SYSTEM Bus!...");
} else {
login1_id = g_dbus_connection_signal_subscribe(
login1_connection, "org.freedesktop.login1", "org.freedesktop.login1.Manager",
"PrepareForSleep", "/org/freedesktop/login1", NULL, G_DBUS_SIGNAL_FLAGS_NONE,
prepareForSleep_cb, this, NULL);
}
event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &UPower::handleToggle));
g_signal_connect(client, "device-added", G_CALLBACK(deviceAdded_cb), this);
g_signal_connect(client, "device-removed", G_CALLBACK(deviceRemoved_cb), this);
resetDevices();
setDisplayDevice();
}
UPower::~UPower() {
if (displayDevice != NULL) g_object_unref(displayDevice);
if (client != NULL) g_object_unref(client);
if (login1_id > 0) {
g_dbus_connection_signal_unsubscribe(login1_connection, login1_id);
login1_id = 0;
}
g_bus_unwatch_name(upowerWatcher_id);
removeDevices();
}
void UPower::deviceAdded_cb(UpClient* client, UpDevice* device, gpointer data) {
UPower* up = static_cast<UPower*>(data);
up->addDevice(device);
up->setDisplayDevice();
// Update the widget
up->dp.emit();
}
void UPower::deviceRemoved_cb(UpClient* client, const gchar* objectPath, gpointer data) {
UPower* up = static_cast<UPower*>(data);
up->removeDevice(objectPath);
up->setDisplayDevice();
// Update the widget
up->dp.emit();
}
void UPower::deviceNotify_cb(UpDevice* device, GParamSpec* pspec, gpointer data) {
UPower* up = static_cast<UPower*>(data);
// Update the widget
up->dp.emit();
}
void UPower::prepareForSleep_cb(GDBusConnection* system_bus, const gchar* sender_name,
const gchar* object_path, const gchar* interface_name,
const gchar* signal_name, GVariant* parameters, gpointer data) {
if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(b)"))) {
gboolean sleeping;
g_variant_get(parameters, "(b)", &sleeping);
if (!sleeping) {
UPower* up = static_cast<UPower*>(data);
up->resetDevices();
up->setDisplayDevice();
}
}
}
void UPower::upowerAppear(GDBusConnection* conn, const gchar* name, const gchar* name_owner,
gpointer data) {
UPower* up = static_cast<UPower*>(data);
up->upowerRunning = true;
up->event_box_.set_visible(true);
}
void UPower::upowerDisappear(GDBusConnection* conn, const gchar* name, gpointer data) {
UPower* up = static_cast<UPower*>(data);
up->upowerRunning = false;
up->event_box_.set_visible(false);
}
void UPower::removeDevice(const gchar* objectPath) {
std::lock_guard<std::mutex> guard(m_Mutex);
if (devices.find(objectPath) != devices.end()) {
UpDevice* device = devices[objectPath];
if (G_IS_OBJECT(device)) {
g_object_unref(device);
}
devices.erase(objectPath);
}
}
void UPower::addDevice(UpDevice* device) {
if (G_IS_OBJECT(device)) {
const gchar* objectPath = up_device_get_object_path(device);
// Due to the device getting cleared after this event is fired, we
// create a new object pointing to its objectPath
gboolean ret;
device = up_device_new();
ret = up_device_set_object_path_sync(device, objectPath, NULL, NULL);
if (!ret) {
g_object_unref(G_OBJECT(device));
return;
}
std::lock_guard<std::mutex> guard(m_Mutex);
if (devices.find(objectPath) != devices.end()) {
UpDevice* device = devices[objectPath];
if (G_IS_OBJECT(device)) {
g_object_unref(device);
}
devices.erase(objectPath);
}
g_signal_connect(device, "notify", G_CALLBACK(deviceNotify_cb), this);
devices.emplace(Devices::value_type(objectPath, device));
}
}
void UPower::setDisplayDevice() {
std::lock_guard<std::mutex> guard(m_Mutex);
if (nativePath_.empty())
displayDevice = up_client_get_display_device(client);
else {
g_ptr_array_foreach(
up_client_get_devices2(client),
[](gpointer data, gpointer user_data) {
UpDevice* device{static_cast<UpDevice*>(data)};
UPower* thisPtr{static_cast<UPower*>(user_data)};
gchar* nativePath;
if (!thisPtr->displayDevice) {
g_object_get(device, "native-path", &nativePath, NULL);
if (!std::strcmp(nativePath, thisPtr->nativePath_.c_str()))
thisPtr->displayDevice = device;
}
},
this);
}
if (displayDevice) g_signal_connect(displayDevice, "notify", G_CALLBACK(deviceNotify_cb), this);
}
void UPower::removeDevices() {
std::lock_guard<std::mutex> guard(m_Mutex);
if (!devices.empty()) {
auto it = devices.cbegin();
while (it != devices.cend()) {
if (G_IS_OBJECT(it->second)) {
g_object_unref(it->second);
}
devices.erase(it++);
}
}
}
/** Removes all devices and adds the current devices */
void UPower::resetDevices() {
// Removes all devices
removeDevices();
// Adds all devices
GPtrArray* newDevices = up_client_get_devices2(client);
for (guint i = 0; i < newDevices->len; i++) {
UpDevice* device = (UpDevice*)g_ptr_array_index(newDevices, i);
if (device && G_IS_OBJECT(device)) addDevice(device);
}
// Update the widget
dp.emit();
}
bool UPower::show_tooltip_callback(int, int, bool, const Glib::RefPtr<Gtk::Tooltip>& tooltip) {
return true;
}
const std::string UPower::getDeviceStatus(UpDeviceState& state) {
switch (state) {
case UP_DEVICE_STATE_CHARGING:
case UP_DEVICE_STATE_PENDING_CHARGE:
return "charging";
case UP_DEVICE_STATE_DISCHARGING:
case UP_DEVICE_STATE_PENDING_DISCHARGE:
return "discharging";
case UP_DEVICE_STATE_FULLY_CHARGED:
return "full";
case UP_DEVICE_STATE_EMPTY:
return "empty";
default:
return "unknown-status";
}
}
bool UPower::handleToggle(GdkEventButton* const& event) {
std::lock_guard<std::mutex> guard(m_Mutex);
showAltText = !showAltText;
return AModule::handleToggle(event);
}
std::string UPower::timeToString(gint64 time) {
if (time == 0) return "";
float hours = (float)time / 3600;
float hours_fixed = static_cast<float>(static_cast<int>(hours * 10)) / 10;
float minutes = static_cast<float>(static_cast<int>(hours * 60 * 10)) / 10;
if (hours_fixed >= 1) {
return fmt::format("{H} h", fmt::arg("H", hours_fixed));
} else {
return fmt::format("{M} min", fmt::arg("M", minutes));
}
}
auto UPower::update() -> void {
std::lock_guard<std::mutex> guard(m_Mutex);
// Don't update widget if the UPower service isn't running
if (!upowerRunning) {
if (hideIfEmpty) {
event_box_.set_visible(false);
}
return;
}
UpDeviceKind kind;
UpDeviceState state;
double percentage;
gint64 time_empty;
gint64 time_full;
gchar* icon_name{(char*)'\0'};
std::string percentString{""};
std::string time_format{""};
bool displayDeviceValid{false};
if (displayDevice) {
g_object_get(displayDevice, "kind", &kind, "state", &state, "percentage", &percentage,
"icon-name", &icon_name, "time-to-empty", &time_empty, "time-to-full", &time_full,
NULL);
/* Every Device which is handled by Upower and which is not
* UP_DEVICE_KIND_UNKNOWN (0) or UP_DEVICE_KIND_LINE_POWER (1) is a Battery
*/
displayDeviceValid = (kind != UpDeviceKind::UP_DEVICE_KIND_UNKNOWN &&
kind != UpDeviceKind::UP_DEVICE_KIND_LINE_POWER);
}
// CSS status class
const std::string status = getDeviceStatus(state);
// Remove last status if it exists
if (!lastStatus.empty() && box_.get_style_context()->has_class(lastStatus)) {
box_.get_style_context()->remove_class(lastStatus);
}
// Add the new status class to the Box
if (!box_.get_style_context()->has_class(status)) {
box_.get_style_context()->add_class(status);
}
lastStatus = status;
if (devices.size() == 0 && !displayDeviceValid && hideIfEmpty) {
event_box_.set_visible(false);
// Call parent update
AModule::update();
return;
}
event_box_.set_visible(true);
if (displayDeviceValid) {
// Tooltip
if (tooltip_enabled) {
uint tooltipCount = upower_tooltip->updateTooltip(devices);
// Disable the tooltip if there aren't any devices in the tooltip
box_.set_has_tooltip(!devices.empty() && tooltipCount > 0);
}
// Set percentage
percentString = std::to_string(int(percentage + 0.5)) + "%";
// Label format
switch (state) {
case UP_DEVICE_STATE_CHARGING:
case UP_DEVICE_STATE_PENDING_CHARGE:
time_format = timeToString(time_full);
break;
case UP_DEVICE_STATE_DISCHARGING:
case UP_DEVICE_STATE_PENDING_DISCHARGE:
time_format = timeToString(time_empty);
break;
default:
break;
}
}
std::string label_format =
fmt::format(fmt::runtime(showAltText ? format_alt : format),
fmt::arg("percentage", percentString), fmt::arg("time", time_format));
// Only set the label text if it doesn't only contain spaces
bool onlySpaces = true;
for (auto& character : label_format) {
if (character == ' ') continue;
onlySpaces = false;
break;
}
label_.set_markup(onlySpaces ? "" : label_format);
// Set icon
if (icon_name == NULL || !DefaultGtkIconThemeWrapper::has_icon(icon_name)) {
icon_name = (char*)"battery-missing-symbolic";
}
icon_.set_from_icon_name(icon_name, Gtk::ICON_SIZE_INVALID);
// Call parent update
AModule::update();
}
} // namespace waybar::modules::upower
-160
View File
@@ -1,160 +0,0 @@
#include "modules/upower/upower_tooltip.hpp"
#include "gtkmm/box.h"
#include "gtkmm/enums.h"
#include "gtkmm/image.h"
#include "gtkmm/label.h"
#include "util/gtk_icon.hpp"
namespace waybar::modules::upower {
UPowerTooltip::UPowerTooltip(uint iconSize_, uint tooltipSpacing_, uint tooltipPadding_)
: Gtk::Window(),
contentBox(std::make_unique<Gtk::Box>(Gtk::ORIENTATION_VERTICAL)),
iconSize(iconSize_),
tooltipSpacing(tooltipSpacing_),
tooltipPadding(tooltipPadding_) {
// Sets the Tooltip Padding
contentBox->set_margin_top(tooltipPadding);
contentBox->set_margin_bottom(tooltipPadding);
contentBox->set_margin_left(tooltipPadding);
contentBox->set_margin_right(tooltipPadding);
add(*contentBox);
contentBox->show();
}
UPowerTooltip::~UPowerTooltip() {}
uint UPowerTooltip::updateTooltip(Devices& devices) {
// Removes all old devices
for (auto child : contentBox->get_children()) {
delete child;
}
uint deviceCount = 0;
// Adds all valid devices
for (auto pair : devices) {
UpDevice* device = pair.second;
std::string objectPath = pair.first;
if (!G_IS_OBJECT(device)) continue;
Gtk::Box* box = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, tooltipSpacing);
UpDeviceKind kind;
double percentage;
gchar* native_path;
gchar* model;
gchar* icon_name;
g_object_get(device, "kind", &kind, "percentage", &percentage, "native-path", &native_path,
"model", &model, "icon-name", &icon_name, NULL);
// Skip Line_Power and BAT0 devices
if (kind == UP_DEVICE_KIND_LINE_POWER || native_path == NULL || strlen(native_path) == 0 ||
strcmp(native_path, "BAT0") == 0)
continue;
Gtk::Box* modelBox = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL);
box->add(*modelBox);
// Set device icon
std::string deviceIconName = getDeviceIcon(kind);
Gtk::Image* deviceIcon = new Gtk::Image();
deviceIcon->set_pixel_size(iconSize);
if (!DefaultGtkIconThemeWrapper::has_icon(deviceIconName)) {
deviceIconName = "battery-missing-symbolic";
}
deviceIcon->set_from_icon_name(deviceIconName, Gtk::ICON_SIZE_INVALID);
modelBox->add(*deviceIcon);
// Set model
if (model == NULL) model = (gchar*)"";
Gtk::Label* modelLabel = new Gtk::Label(model);
modelBox->add(*modelLabel);
Gtk::Box* chargeBox = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL);
box->add(*chargeBox);
// Set icon
Gtk::Image* icon = new Gtk::Image();
icon->set_pixel_size(iconSize);
if (icon_name == NULL || !DefaultGtkIconThemeWrapper::has_icon(icon_name)) {
icon_name = (char*)"battery-missing-symbolic";
}
icon->set_from_icon_name(icon_name, Gtk::ICON_SIZE_INVALID);
chargeBox->add(*icon);
// Set percentage
std::string percentString = std::to_string(int(percentage + 0.5)) + "%";
Gtk::Label* percentLabel = new Gtk::Label(percentString);
chargeBox->add(*percentLabel);
contentBox->add(*box);
deviceCount++;
}
contentBox->show_all();
return deviceCount;
}
const std::string UPowerTooltip::getDeviceIcon(UpDeviceKind& kind) {
switch (kind) {
case UP_DEVICE_KIND_LINE_POWER:
return "ac-adapter-symbolic";
case UP_DEVICE_KIND_BATTERY:
return "battery";
case UP_DEVICE_KIND_UPS:
return "uninterruptible-power-supply-symbolic";
case UP_DEVICE_KIND_MONITOR:
return "video-display-symbolic";
case UP_DEVICE_KIND_MOUSE:
return "input-mouse-symbolic";
case UP_DEVICE_KIND_KEYBOARD:
return "input-keyboard-symbolic";
case UP_DEVICE_KIND_PDA:
return "pda-symbolic";
case UP_DEVICE_KIND_PHONE:
return "phone-symbolic";
case UP_DEVICE_KIND_MEDIA_PLAYER:
return "multimedia-player-symbolic";
case UP_DEVICE_KIND_TABLET:
return "computer-apple-ipad-symbolic";
case UP_DEVICE_KIND_COMPUTER:
return "computer-symbolic";
case UP_DEVICE_KIND_GAMING_INPUT:
return "input-gaming-symbolic";
case UP_DEVICE_KIND_PEN:
return "input-tablet-symbolic";
case UP_DEVICE_KIND_TOUCHPAD:
return "input-touchpad-symbolic";
case UP_DEVICE_KIND_MODEM:
return "modem-symbolic";
case UP_DEVICE_KIND_NETWORK:
return "network-wired-symbolic";
case UP_DEVICE_KIND_HEADSET:
return "audio-headset-symbolic";
case UP_DEVICE_KIND_HEADPHONES:
return "audio-headphones-symbolic";
case UP_DEVICE_KIND_OTHER_AUDIO:
case UP_DEVICE_KIND_SPEAKERS:
return "audio-speakers-symbolic";
case UP_DEVICE_KIND_VIDEO:
return "camera-web-symbolic";
case UP_DEVICE_KIND_PRINTER:
return "printer-symbolic";
case UP_DEVICE_KIND_SCANNER:
return "scanner-symbolic";
case UP_DEVICE_KIND_CAMERA:
return "camera-photo-symbolic";
case UP_DEVICE_KIND_BLUETOOTH_GENERIC:
return "bluetooth-active-symbolic";
case UP_DEVICE_KIND_TOY:
case UP_DEVICE_KIND_REMOTE_CONTROL:
case UP_DEVICE_KIND_WEARABLE:
case UP_DEVICE_KIND_LAST:
default:
return "battery-symbolic";
}
}
} // namespace waybar::modules::upower
+112 -82
View File
@@ -18,31 +18,24 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
min_step_(0.0),
node_id_(0) {
wp_init(WP_INIT_PIPEWIRE);
wp_core_ = wp_core_new(NULL, NULL);
wp_core_ = wp_core_new(nullptr, nullptr, nullptr);
apis_ = g_ptr_array_new_with_free_func(g_object_unref);
om_ = wp_object_manager_new();
prepare();
loadRequiredApiModules();
spdlog::debug("[{}]: connecting to pipewire...", name_);
spdlog::debug("[{}]: connecting to pipewire...", this->name_);
if (!wp_core_connect(wp_core_)) {
spdlog::error("[{}]: Could not connect to PipeWire", this->name_);
if (wp_core_connect(wp_core_) == 0) {
spdlog::error("[{}]: Could not connect to PipeWire", name_);
throw std::runtime_error("Could not connect to PipeWire\n");
}
spdlog::debug("[{}]: connected!", this->name_);
spdlog::debug("[{}]: connected!", name_);
g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this);
activatePlugins();
dp.emit();
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Wireplumber::handleScroll));
asyncLoadRequiredApiModules();
}
waybar::modules::Wireplumber::~Wireplumber() {
@@ -63,32 +56,36 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber*
return;
}
auto proxy = static_cast<WpProxy*>(wp_object_manager_lookup(
self->om_, WP_TYPE_GLOBAL_PROXY, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, NULL));
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) {
if (proxy == nullptr) {
auto err = fmt::format("Object '{}' not found\n", id);
spdlog::error("[{}]: {}", self->name_, err);
throw std::runtime_error(err);
}
g_autoptr(WpProperties) properties =
WP_IS_PIPEWIRE_OBJECT(proxy) ? wp_pipewire_object_get_properties(WP_PIPEWIRE_OBJECT(proxy))
: wp_properties_new_empty();
g_autoptr(WpProperties) global_p = wp_global_proxy_get_global_properties(WP_GLOBAL_PROXY(proxy));
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, global_p);
wp_properties_set(properties, "object.id", NULL);
auto nick = wp_properties_get(properties, "node.nick");
auto description = wp_properties_get(properties, "node.description");
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->node_name_ = nick ? nick : description ? description : "Unknown node name";
self->node_name_ = nick != nullptr ? nick
: description != nullptr ? description
: "Unknown node name";
spdlog::debug("[{}]: Updating node name to: {}", self->name_, self->node_name_);
}
void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self, uint32_t id) {
spdlog::debug("[{}]: updating volume", self->name_);
GVariant* variant = NULL;
GVariant* variant = nullptr;
if (!isValidNodeId(id)) {
spdlog::error("[{}]: '{}' is not a valid node ID. Ignoring volume update.", self->name_, id);
@@ -97,7 +94,7 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se
g_signal_emit_by_name(self->mixer_api_, "get-volume", id, &variant);
if (!variant) {
if (variant == nullptr) {
auto err = fmt::format("Node {} does not support volume\n", id);
spdlog::error("[{}]: {}", self->name_, err);
throw std::runtime_error(err);
@@ -115,9 +112,9 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber*
spdlog::debug("[{}]: (onMixerChanged) - id: {}", self->name_, id);
g_autoptr(WpNode) node = static_cast<WpNode*>(wp_object_manager_lookup(
self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, NULL));
self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, nullptr));
if (!node) {
if (node == nullptr) {
spdlog::warn("[{}]: (onMixerChanged) - Object with id {} not found", self->name_, id);
return;
}
@@ -140,49 +137,49 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber*
void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wireplumber* self) {
spdlog::debug("[{}]: (onDefaultNodesApiChanged)", self->name_);
uint32_t default_node_id;
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &default_node_id);
uint32_t defaultNodeId;
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &defaultNodeId);
if (!isValidNodeId(default_node_id)) {
if (!isValidNodeId(defaultNodeId)) {
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node change.", self->name_,
default_node_id);
defaultNodeId);
return;
}
g_autoptr(WpNode) node = static_cast<WpNode*>(
wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id",
"=u", default_node_id, NULL));
"=u", defaultNodeId, nullptr));
if (!node) {
if (node == nullptr) {
spdlog::warn("[{}]: (onDefaultNodesApiChanged) - Object with id {} not found", self->name_,
default_node_id);
defaultNodeId);
return;
}
const gchar* default_node_name =
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_, default_node_name, default_node_id);
self->name_, defaultNodeName, defaultNodeId);
if (g_strcmp0(self->default_node_name_, default_node_name) == 0) {
if (g_strcmp0(self->default_node_name_, defaultNodeName) == 0) {
spdlog::debug(
"[{}]: (onDefaultNodesApiChanged) - Default node has not changed. Node(name: {}, id: {}). "
"Ignoring.",
self->name_, self->default_node_name_, default_node_id);
self->name_, self->default_node_name_, defaultNodeId);
return;
}
spdlog::debug(
"[{}]: (onDefaultNodesApiChanged) - Default node changed to -> Node(name: {}, id: {})",
self->name_, default_node_name, default_node_id);
self->name_, defaultNodeName, defaultNodeId);
g_free(self->default_node_name_);
self->default_node_name_ = g_strdup(default_node_name);
self->node_id_ = default_node_id;
updateVolume(self, default_node_id);
updateNodeName(self, default_node_id);
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) {
@@ -190,14 +187,14 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir
self->def_nodes_api_ = wp_plugin_find(self->wp_core_, "default-nodes-api");
if (!self->def_nodes_api_) {
if (self->def_nodes_api_ == nullptr) {
spdlog::error("[{}]: default nodes api is not loaded.", self->name_);
throw std::runtime_error("Default nodes API is not loaded\n");
}
self->mixer_api_ = wp_plugin_find(self->wp_core_, "mixer-api");
if (!self->mixer_api_) {
if (self->mixer_api_ == nullptr) {
spdlog::error("[{}]: mixer api is not loaded.", self->name_);
throw std::runtime_error("Mixer api is not loaded\n");
}
@@ -206,7 +203,7 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir
&self->default_node_name_);
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &self->node_id_);
if (self->default_node_name_) {
if (self->default_node_name_ != nullptr) {
spdlog::debug("[{}]: (onObjectManagerInstalled) - default configured node name: {} and id: {}",
self->name_, self->default_node_name_, self->node_id_);
}
@@ -221,11 +218,11 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir
void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult* res,
waybar::modules::Wireplumber* self) {
auto plugin_name = wp_plugin_get_name(WP_PLUGIN(p));
spdlog::debug("[{}]: onPluginActivated: {}", self->name_, plugin_name);
g_autoptr(GError) error = NULL;
const auto* pluginName = wp_plugin_get_name(WP_PLUGIN(p));
spdlog::debug("[{}]: onPluginActivated: {}", self->name_, pluginName);
g_autoptr(GError) error = nullptr;
if (!wp_object_activate_finish(p, res, &error)) {
if (wp_object_activate_finish(p, res, &error) == 0) {
spdlog::error("[{}]: error activating plugin: {}", self->name_, error->message);
throw std::runtime_error(error->message);
}
@@ -240,7 +237,7 @@ void waybar::modules::Wireplumber::activatePlugins() {
for (uint16_t i = 0; i < apis_->len; i++) {
WpPlugin* plugin = static_cast<WpPlugin*>(g_ptr_array_index(apis_, i));
pending_plugins_++;
wp_object_activate(WP_OBJECT(plugin), WP_PLUGIN_FEATURE_ENABLED, NULL,
wp_object_activate(WP_OBJECT(plugin), WP_PLUGIN_FEATURE_ENABLED, nullptr,
(GAsyncReadyCallback)onPluginActivated, this);
}
}
@@ -248,34 +245,67 @@ void waybar::modules::Wireplumber::activatePlugins() {
void waybar::modules::Wireplumber::prepare() {
spdlog::debug("[{}]: preparing object manager", name_);
wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
"=s", "Audio/Sink", NULL);
"=s", "Audio/Sink", nullptr);
}
void waybar::modules::Wireplumber::loadRequiredApiModules() {
spdlog::debug("[{}]: loading required modules", name_);
g_autoptr(GError) error = NULL;
void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res,
waybar::modules::Wireplumber* self) {
gboolean success = FALSE;
g_autoptr(GError) error = nullptr;
if (!wp_core_load_component(wp_core_, "libwireplumber-module-default-nodes-api", "module", NULL,
&error)) {
spdlog::debug("[{}]: callback loading default node api module", self->name_);
success = wp_core_load_component_finish(self->wp_core_, res, &error);
if (success == FALSE) {
spdlog::error("[{}]: default nodes API load failed", self->name_);
throw std::runtime_error(error->message);
}
spdlog::debug("[{}]: loaded default nodes api", self->name_);
g_ptr_array_add(self->apis_, wp_plugin_find(self->wp_core_, "default-nodes-api"));
spdlog::debug("[{}]: loading mixer api module", self->name_);
wp_core_load_component(self->wp_core_, "libwireplumber-module-mixer-api", "module", nullptr,
"mixer-api", nullptr, (GAsyncReadyCallback)onMixerApiLoaded, self);
}
void waybar::modules::Wireplumber::onMixerApiLoaded(WpObject* p, GAsyncResult* res,
waybar::modules::Wireplumber* self) {
gboolean success = FALSE;
g_autoptr(GError) error = nullptr;
success = wp_core_load_component_finish(self->wp_core_, res, nullptr);
if (success == FALSE) {
spdlog::error("[{}]: mixer API load failed", self->name_);
throw std::runtime_error(error->message);
}
if (!wp_core_load_component(wp_core_, "libwireplumber-module-mixer-api", "module", NULL,
&error)) {
throw std::runtime_error(error->message);
}
g_ptr_array_add(apis_, wp_plugin_find(wp_core_, "default-nodes-api"));
g_ptr_array_add(apis_, ({
WpPlugin* p = wp_plugin_find(wp_core_, "mixer-api");
g_object_set(G_OBJECT(p), "scale", 1 /* cubic */, NULL);
spdlog::debug("[{}]: loaded mixer API", self->name_);
g_ptr_array_add(self->apis_, ({
WpPlugin* p = wp_plugin_find(self->wp_core_, "mixer-api");
g_object_set(G_OBJECT(p), "scale", 1 /* cubic */, nullptr);
p;
}));
self->activatePlugins();
self->dp.emit();
self->event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
self->event_box_.signal_scroll_event().connect(sigc::mem_fun(*self, &Wireplumber::handleScroll));
}
void waybar::modules::Wireplumber::asyncLoadRequiredApiModules() {
spdlog::debug("[{}]: loading default nodes api module", name_);
wp_core_load_component(wp_core_, "libwireplumber-module-default-nodes-api", "module", nullptr,
"default-nodes-api", nullptr, (GAsyncReadyCallback)onDefaultNodesApiLoaded,
this);
}
auto waybar::modules::Wireplumber::update() -> void {
auto format = format_;
std::string tooltip_format;
std::string tooltipFormat;
if (muted_) {
format = config_["format-muted"].isString() ? config_["format-muted"].asString() : format;
@@ -292,12 +322,12 @@ auto waybar::modules::Wireplumber::update() -> void {
getState(vol);
if (tooltipEnabled()) {
if (tooltip_format.empty() && config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
if (tooltipFormat.empty() && config_["tooltip-format"].isString()) {
tooltipFormat = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format),
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))));
} else {
@@ -317,31 +347,31 @@ bool waybar::modules::Wireplumber::handleScroll(GdkEventScroll* e) {
if (dir == SCROLL_DIR::NONE) {
return true;
}
double max_volume = 1;
double maxVolume = 1;
double step = 1.0 / 100.0;
if (config_["scroll-step"].isDouble()) {
step = config_["scroll-step"].asDouble() / 100.0;
}
if (config_["max-volume"].isDouble()) {
max_volume = config_["max-volume"].asDouble() / 100.0;
maxVolume = config_["max-volume"].asDouble() / 100.0;
}
if (step < min_step_) step = min_step_;
double new_vol = volume_;
double newVol = volume_;
if (dir == SCROLL_DIR::UP) {
if (volume_ < max_volume) {
new_vol = volume_ + step;
if (new_vol > max_volume) new_vol = max_volume;
if (volume_ < maxVolume) {
newVol = volume_ + step;
if (newVol > maxVolume) newVol = maxVolume;
}
} else if (dir == SCROLL_DIR::DOWN) {
if (volume_ > 0) {
new_vol = volume_ - step;
if (new_vol < 0) new_vol = 0;
newVol = volume_ - step;
if (newVol < 0) newVol = 0;
}
}
if (new_vol != volume_) {
GVariant* variant = g_variant_new_double(new_vol);
if (newVol != volume_) {
GVariant* variant = g_variant_new_double(newVol);
gboolean ret;
g_signal_emit_by_name(mixer_api_, "set-volume", node_id_, variant, &ret);
}
+10 -12
View File
@@ -30,6 +30,9 @@ namespace waybar::modules::wlr {
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/");
@@ -47,9 +50,6 @@ static std::vector<std::string> search_prefix() {
} while (end != std::string::npos);
}
std::string home_dir = std::getenv("HOME");
prefixes.push_back(home_dir + "/.local/share/");
for (auto &p : prefixes) spdlog::debug("Using 'desktop' search path prefix: {}", p);
return prefixes;
@@ -334,9 +334,7 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
}
button.add_events(Gdk::BUTTON_PRESS_MASK);
button.signal_button_press_event().connect(sigc::mem_fun(*this, &Task::handle_clicked), false);
button.signal_button_release_event().connect(sigc::mem_fun(*this, &Task::handle_button_release),
false);
button.signal_button_release_event().connect(sigc::mem_fun(*this, &Task::handle_clicked), false);
button.signal_motion_notify_event().connect(sigc::mem_fun(*this, &Task::handle_motion_notify),
false);
@@ -573,12 +571,8 @@ bool Task::handle_clicked(GdkEventButton *bt) {
else
spdlog::warn("Unknown action {}", action);
return true;
}
bool Task::handle_button_release(GdkEventButton *bt) {
drag_start_button = -1;
return false;
return true;
}
bool Task::handle_motion_notify(GdkEventMotion *mn) {
@@ -794,6 +788,10 @@ 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());
}
}
Taskbar::~Taskbar() {
@@ -900,7 +898,7 @@ void Taskbar::move_button(Gtk::Button &bt, int pos) { box_.reorder_child(bt, pos
void Taskbar::remove_button(Gtk::Button &bt) {
box_.remove(bt);
if (tasks_.empty()) {
if (box_.get_children().empty()) {
box_.get_style_context()->add_class("empty");
}
}