Merge branch 'master' into hyprland-window_no_title_from_special
This commit is contained in:
+56
-24
@@ -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
@@ -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_);
|
||||
|
||||
@@ -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
@@ -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,5 +1,4 @@
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#include "modules/cpu_frequency.hpp"
|
||||
|
||||
+19
-8
@@ -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()) {
|
||||
|
||||
@@ -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, ®istry_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_);
|
||||
}
|
||||
|
||||
@@ -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, ®istry_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
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
+438
-678
File diff suppressed because it is too large
Load Diff
+29
-2
@@ -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);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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 ¶meters) {
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user