Merge branch 'master' into issue-1681
This commit is contained in:
@ -2,6 +2,7 @@
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <libudev.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@ -9,179 +10,26 @@
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
class FileDescriptor {
|
||||
public:
|
||||
explicit FileDescriptor(int fd) : fd_(fd) {}
|
||||
FileDescriptor(const FileDescriptor &other) = delete;
|
||||
FileDescriptor(FileDescriptor &&other) noexcept = delete;
|
||||
FileDescriptor &operator=(const FileDescriptor &other) = delete;
|
||||
FileDescriptor &operator=(FileDescriptor &&other) noexcept = delete;
|
||||
~FileDescriptor() {
|
||||
if (fd_ != -1) {
|
||||
if (close(fd_) != 0) {
|
||||
fmt::print(stderr, "Failed to close fd: {}\n", errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
int get() const { return fd_; }
|
||||
|
||||
private:
|
||||
int fd_;
|
||||
};
|
||||
|
||||
struct UdevDeleter {
|
||||
void operator()(udev *ptr) { udev_unref(ptr); }
|
||||
};
|
||||
|
||||
struct UdevDeviceDeleter {
|
||||
void operator()(udev_device *ptr) { udev_device_unref(ptr); }
|
||||
};
|
||||
|
||||
struct UdevEnumerateDeleter {
|
||||
void operator()(udev_enumerate *ptr) { udev_enumerate_unref(ptr); }
|
||||
};
|
||||
|
||||
struct UdevMonitorDeleter {
|
||||
void operator()(udev_monitor *ptr) { udev_monitor_unref(ptr); }
|
||||
};
|
||||
|
||||
void check_eq(int rc, int expected, const char *message = "eq, rc was: ") {
|
||||
if (rc != expected) {
|
||||
throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
|
||||
}
|
||||
}
|
||||
|
||||
void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") {
|
||||
if (rc == bad_rc) {
|
||||
throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
|
||||
}
|
||||
}
|
||||
|
||||
void check0(int rc, const char *message = "rc wasn't 0") { check_eq(rc, 0, message); }
|
||||
|
||||
void check_gte(int rc, int gte, const char *message = "rc was: ") {
|
||||
if (rc < gte) {
|
||||
throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
|
||||
}
|
||||
}
|
||||
|
||||
void check_nn(const void *ptr, const char *message = "ptr was null") {
|
||||
if (ptr == nullptr) {
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name, int actual, int max,
|
||||
bool powered)
|
||||
: name_(std::move(name)), actual_(actual), max_(max), powered_(powered) {}
|
||||
|
||||
std::string_view waybar::modules::Backlight::BacklightDev::name() const { return name_; }
|
||||
|
||||
int waybar::modules::Backlight::BacklightDev::get_actual() const { return actual_; }
|
||||
|
||||
void waybar::modules::Backlight::BacklightDev::set_actual(int actual) { actual_ = actual; }
|
||||
|
||||
int waybar::modules::Backlight::BacklightDev::get_max() const { return max_; }
|
||||
|
||||
void waybar::modules::Backlight::BacklightDev::set_max(int max) { max_ = max; }
|
||||
|
||||
bool waybar::modules::Backlight::BacklightDev::get_powered() const { return powered_; }
|
||||
|
||||
void waybar::modules::Backlight::BacklightDev::set_powered(bool powered) { powered_ = powered; }
|
||||
#include "util/backend_common.hpp"
|
||||
#include "util/backlight_backend.hpp"
|
||||
|
||||
waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &config)
|
||||
: ALabel(config, "backlight", id, "{percent}%", 2),
|
||||
preferred_device_(config["device"].isString() ? config["device"].asString() : "") {
|
||||
// Get initial state
|
||||
{
|
||||
std::unique_ptr<udev, UdevDeleter> udev_check{udev_new()};
|
||||
check_nn(udev_check.get(), "Udev check new failed");
|
||||
enumerate_devices(devices_.begin(), devices_.end(), std::back_inserter(devices_),
|
||||
udev_check.get());
|
||||
if (devices_.empty()) {
|
||||
throw std::runtime_error("No backlight found");
|
||||
}
|
||||
dp.emit();
|
||||
}
|
||||
preferred_device_(config["device"].isString() ? config["device"].asString() : ""),
|
||||
backend(interval_, [this] { dp.emit(); }) {
|
||||
dp.emit();
|
||||
|
||||
// Set up scroll handler
|
||||
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
||||
event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Backlight::handleScroll));
|
||||
|
||||
// Connect to the login interface
|
||||
login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(
|
||||
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1",
|
||||
"/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session");
|
||||
|
||||
udev_thread_ = [this] {
|
||||
std::unique_ptr<udev, UdevDeleter> udev{udev_new()};
|
||||
check_nn(udev.get(), "Udev new failed");
|
||||
|
||||
std::unique_ptr<udev_monitor, UdevMonitorDeleter> mon{
|
||||
udev_monitor_new_from_netlink(udev.get(), "udev")};
|
||||
check_nn(mon.get(), "udev monitor new failed");
|
||||
check_gte(udev_monitor_filter_add_match_subsystem_devtype(mon.get(), "backlight", nullptr), 0,
|
||||
"udev failed to add monitor filter: ");
|
||||
udev_monitor_enable_receiving(mon.get());
|
||||
|
||||
auto udev_fd = udev_monitor_get_fd(mon.get());
|
||||
|
||||
auto epoll_fd = FileDescriptor{epoll_create1(EPOLL_CLOEXEC)};
|
||||
check_neq(epoll_fd.get(), -1, "epoll init failed: ");
|
||||
epoll_event ctl_event{};
|
||||
ctl_event.events = EPOLLIN;
|
||||
ctl_event.data.fd = udev_fd;
|
||||
|
||||
check0(epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, ctl_event.data.fd, &ctl_event),
|
||||
"epoll_ctl failed: {}");
|
||||
epoll_event events[EPOLL_MAX_EVENTS];
|
||||
|
||||
while (udev_thread_.isRunning()) {
|
||||
const int event_count = epoll_wait(epoll_fd.get(), events, EPOLL_MAX_EVENTS,
|
||||
std::chrono::milliseconds{interval_}.count());
|
||||
if (!udev_thread_.isRunning()) {
|
||||
break;
|
||||
}
|
||||
decltype(devices_) devices;
|
||||
{
|
||||
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
|
||||
devices = devices_;
|
||||
}
|
||||
for (int i = 0; i < event_count; ++i) {
|
||||
const auto &event = events[i];
|
||||
check_eq(event.data.fd, udev_fd, "unexpected udev fd");
|
||||
std::unique_ptr<udev_device, UdevDeviceDeleter> dev{udev_monitor_receive_device(mon.get())};
|
||||
check_nn(dev.get(), "epoll dev was null");
|
||||
upsert_device(devices.begin(), devices.end(), std::back_inserter(devices), dev.get());
|
||||
}
|
||||
|
||||
// Refresh state if timed out
|
||||
if (event_count == 0) {
|
||||
enumerate_devices(devices.begin(), devices.end(), std::back_inserter(devices), udev.get());
|
||||
}
|
||||
{
|
||||
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
|
||||
devices_ = devices;
|
||||
}
|
||||
dp.emit();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
waybar::modules::Backlight::~Backlight() = default;
|
||||
|
||||
auto waybar::modules::Backlight::update() -> void {
|
||||
decltype(devices_) devices;
|
||||
{
|
||||
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
|
||||
devices = devices_;
|
||||
}
|
||||
GET_BEST_DEVICE(best, backend, preferred_device_);
|
||||
|
||||
const auto best = best_device(devices.cbegin(), devices.cend(), preferred_device_);
|
||||
const auto previous_best_device = backend.get_previous_best_device();
|
||||
if (best != nullptr) {
|
||||
if (previous_best_.has_value() && previous_best_.value() == *best &&
|
||||
if (previous_best_device != nullptr && *previous_best_device == *best &&
|
||||
!previous_format_.empty() && previous_format_ == format_) {
|
||||
return;
|
||||
}
|
||||
@ -190,9 +38,8 @@ auto waybar::modules::Backlight::update() -> void {
|
||||
event_box_.show();
|
||||
const uint8_t percent =
|
||||
best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());
|
||||
std::string desc =
|
||||
fmt::format(fmt::runtime(format_), fmt::arg("percent", std::to_string(percent)),
|
||||
fmt::arg("icon", getIcon(percent)));
|
||||
std::string desc = fmt::format(fmt::runtime(format_), fmt::arg("percent", percent),
|
||||
fmt::arg("icon", getIcon(percent)));
|
||||
label_.set_markup(desc);
|
||||
getState(percent);
|
||||
if (tooltipEnabled()) {
|
||||
@ -202,7 +49,7 @@ auto waybar::modules::Backlight::update() -> void {
|
||||
}
|
||||
if (!tooltip_format.empty()) {
|
||||
label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format),
|
||||
fmt::arg("percent", std::to_string(percent)),
|
||||
fmt::arg("percent", percent),
|
||||
fmt::arg("icon", getIcon(percent))));
|
||||
} else {
|
||||
label_.set_tooltip_text(desc);
|
||||
@ -212,82 +59,16 @@ auto waybar::modules::Backlight::update() -> void {
|
||||
event_box_.hide();
|
||||
}
|
||||
} else {
|
||||
if (!previous_best_.has_value()) {
|
||||
if (previous_best_device == nullptr) {
|
||||
return;
|
||||
}
|
||||
label_.set_markup("");
|
||||
}
|
||||
previous_best_ = best == nullptr ? std::nullopt : std::optional{*best};
|
||||
backend.set_previous_best_device(best);
|
||||
previous_format_ = format_;
|
||||
// Call parent update
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
template <class ForwardIt>
|
||||
const waybar::modules::Backlight::BacklightDev *waybar::modules::Backlight::best_device(
|
||||
ForwardIt first, ForwardIt last, std::string_view preferred_device) {
|
||||
const auto found = std::find_if(
|
||||
first, last, [preferred_device](const auto &dev) { return dev.name() == preferred_device; });
|
||||
if (found != last) {
|
||||
return &(*found);
|
||||
}
|
||||
|
||||
const auto max = std::max_element(
|
||||
first, last, [](const auto &l, const auto &r) { return l.get_max() < r.get_max(); });
|
||||
|
||||
return max == last ? nullptr : &(*max);
|
||||
}
|
||||
|
||||
template <class ForwardIt, class Inserter>
|
||||
void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last, Inserter inserter,
|
||||
udev_device *dev) {
|
||||
const char *name = udev_device_get_sysname(dev);
|
||||
check_nn(name);
|
||||
|
||||
const char *actual_brightness_attr =
|
||||
strncmp(name, "amdgpu_bl", 9) == 0 ? "brightness" : "actual_brightness";
|
||||
|
||||
const char *actual = udev_device_get_sysattr_value(dev, actual_brightness_attr);
|
||||
const char *max = udev_device_get_sysattr_value(dev, "max_brightness");
|
||||
const char *power = udev_device_get_sysattr_value(dev, "bl_power");
|
||||
|
||||
auto found =
|
||||
std::find_if(first, last, [name](const auto &device) { return device.name() == name; });
|
||||
if (found != last) {
|
||||
if (actual != nullptr) {
|
||||
found->set_actual(std::stoi(actual));
|
||||
}
|
||||
if (max != nullptr) {
|
||||
found->set_max(std::stoi(max));
|
||||
}
|
||||
if (power != nullptr) {
|
||||
found->set_powered(std::stoi(power) == 0);
|
||||
}
|
||||
} else {
|
||||
const int actual_int = actual == nullptr ? 0 : std::stoi(actual);
|
||||
const int max_int = max == nullptr ? 0 : std::stoi(max);
|
||||
const bool power_bool = power == nullptr ? true : std::stoi(power) == 0;
|
||||
*inserter = BacklightDev{name, actual_int, max_int, power_bool};
|
||||
++inserter;
|
||||
}
|
||||
}
|
||||
|
||||
template <class ForwardIt, class Inserter>
|
||||
void waybar::modules::Backlight::enumerate_devices(ForwardIt first, ForwardIt last,
|
||||
Inserter inserter, udev *udev) {
|
||||
std::unique_ptr<udev_enumerate, UdevEnumerateDeleter> enumerate{udev_enumerate_new(udev)};
|
||||
udev_enumerate_add_match_subsystem(enumerate.get(), "backlight");
|
||||
udev_enumerate_scan_devices(enumerate.get());
|
||||
udev_list_entry *enum_devices = udev_enumerate_get_list_entry(enumerate.get());
|
||||
udev_list_entry *dev_list_entry;
|
||||
udev_list_entry_foreach(dev_list_entry, enum_devices) {
|
||||
const char *path = udev_list_entry_get_name(dev_list_entry);
|
||||
std::unique_ptr<udev_device, UdevDeviceDeleter> dev{udev_device_new_from_syspath(udev, path)};
|
||||
check_nn(dev.get(), "dev new failed");
|
||||
upsert_device(first, last, inserter, dev.get());
|
||||
}
|
||||
}
|
||||
|
||||
bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) {
|
||||
// Check if the user has set a custom command for scrolling
|
||||
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
|
||||
@ -295,14 +76,33 @@ bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) {
|
||||
}
|
||||
|
||||
// Fail fast if the proxy could not be initialized
|
||||
if (!login_proxy_) {
|
||||
if (!backend.is_login_proxy_initialized()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check scroll direction
|
||||
auto dir = AModule::getScrollDir(e);
|
||||
if (dir == SCROLL_DIR::NONE) {
|
||||
return true;
|
||||
|
||||
// No worries, it will always be set because of the switch below. This is purely to suppress a
|
||||
// warning
|
||||
util::ChangeType ct = util::ChangeType::Increase;
|
||||
|
||||
switch (dir) {
|
||||
case SCROLL_DIR::UP:
|
||||
[[fallthrough]];
|
||||
case SCROLL_DIR::RIGHT:
|
||||
ct = util::ChangeType::Increase;
|
||||
break;
|
||||
|
||||
case SCROLL_DIR::DOWN:
|
||||
[[fallthrough]];
|
||||
case SCROLL_DIR::LEFT:
|
||||
ct = util::ChangeType::Decrease;
|
||||
break;
|
||||
|
||||
case SCROLL_DIR::NONE:
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Get scroll step
|
||||
@ -312,38 +112,15 @@ bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) {
|
||||
step = config_["scroll-step"].asDouble();
|
||||
}
|
||||
|
||||
// Get the best device
|
||||
decltype(devices_) devices;
|
||||
{
|
||||
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
|
||||
devices = devices_;
|
||||
double min_brightness = 0;
|
||||
if (config_["min-brightness"].isDouble()) {
|
||||
min_brightness = config_["min-brightness"].asDouble();
|
||||
}
|
||||
const auto best = best_device(devices.cbegin(), devices.cend(), preferred_device_);
|
||||
|
||||
if (best == nullptr) {
|
||||
if (backend.get_scaled_brightness(preferred_device_) <= min_brightness &&
|
||||
ct == util::ChangeType::Decrease) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Compute the absolute step
|
||||
const auto abs_step = static_cast<int>(round(step * best->get_max() / 100.0f));
|
||||
|
||||
// Compute the new value
|
||||
int new_value = best->get_actual();
|
||||
|
||||
if (dir == SCROLL_DIR::UP) {
|
||||
new_value += abs_step;
|
||||
} else if (dir == SCROLL_DIR::DOWN) {
|
||||
new_value -= abs_step;
|
||||
}
|
||||
|
||||
// Clamp the value
|
||||
new_value = std::clamp(new_value, 0, best->get_max());
|
||||
|
||||
// Set the new value
|
||||
auto call_args = Glib::VariantContainerBase(
|
||||
g_variant_new("(ssu)", "backlight", std::string(best->name()).c_str(), new_value));
|
||||
|
||||
login_proxy_->call_sync("SetBrightness", call_args);
|
||||
backend.set_brightness(preferred_device_, ct, step);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
23
src/modules/backlight_slider.cpp
Normal file
23
src/modules/backlight_slider.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
#include "modules/backlight_slider.hpp"
|
||||
|
||||
#include "ASlider.hpp"
|
||||
|
||||
namespace waybar::modules {
|
||||
|
||||
BacklightSlider::BacklightSlider(const std::string& id, const Json::Value& config)
|
||||
: ASlider(config, "backlight-slider", id),
|
||||
interval_(config_["interval"].isUInt() ? config_["interval"].asUInt() : 1000),
|
||||
preferred_device_(config["device"].isString() ? config["device"].asString() : ""),
|
||||
backend(interval_, [this] { this->dp.emit(); }) {}
|
||||
|
||||
void BacklightSlider::update() {
|
||||
uint16_t brightness = backend.get_scaled_brightness(preferred_device_);
|
||||
scale_.set_value(brightness);
|
||||
}
|
||||
|
||||
void BacklightSlider::onValueChanged() {
|
||||
auto brightness = scale_.get_value();
|
||||
backend.set_scaled_brightness(preferred_device_, brightness);
|
||||
}
|
||||
|
||||
} // namespace waybar::modules
|
||||
@ -1,12 +1,14 @@
|
||||
#include "modules/battery.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#if defined(__FreeBSD__)
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <iostream>
|
||||
waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "battery", id, "{capacity}%", 60) {
|
||||
waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: ALabel(config, "battery", id, "{capacity}%", 60), bar_(bar) {
|
||||
#if defined(__linux__)
|
||||
battery_watch_fd_ = inotify_init1(IN_CLOEXEC);
|
||||
if (battery_watch_fd_ == -1) {
|
||||
@ -100,9 +102,11 @@ void waybar::modules::Battery::refreshBatteries() {
|
||||
}
|
||||
auto dir_name = node.path().filename();
|
||||
auto bat_defined = config_["bat"].isString();
|
||||
bool bat_compatibility = config_["bat-compatibility"].asBool();
|
||||
if (((bat_defined && dir_name == config_["bat"].asString()) || !bat_defined) &&
|
||||
(fs::exists(node.path() / "capacity") || fs::exists(node.path() / "charge_now")) &&
|
||||
fs::exists(node.path() / "uevent") && fs::exists(node.path() / "status") &&
|
||||
fs::exists(node.path() / "uevent") &&
|
||||
(fs::exists(node.path() / "status") || bat_compatibility) &&
|
||||
fs::exists(node.path() / "type")) {
|
||||
std::string type;
|
||||
std::ifstream(node.path() / "type") >> type;
|
||||
@ -177,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 {
|
||||
@ -230,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
|
||||
@ -248,31 +253,38 @@ 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;
|
||||
std::string _status;
|
||||
std::getline(std::ifstream(bat / "status"), _status);
|
||||
|
||||
/* Check for adapter status if battery is not available */
|
||||
if (!std::ifstream(bat / "status")) {
|
||||
std::getline(std::ifstream(adapter_ / "status"), _status);
|
||||
} else {
|
||||
std::getline(std::ifstream(bat / "status"), _status);
|
||||
}
|
||||
|
||||
// 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;
|
||||
int32_t _current_now_int = 0;
|
||||
bool current_now_exists = false;
|
||||
if (fs::exists(bat / "current_now")) {
|
||||
current_now_exists = true;
|
||||
std::ifstream(bat / "current_now") >> current_now;
|
||||
std::ifstream(bat / "current_now") >> _current_now_int;
|
||||
} else if (fs::exists(bat / "current_avg")) {
|
||||
current_now_exists = true;
|
||||
std::ifstream(bat / "current_avg") >> current_now;
|
||||
std::ifstream(bat / "current_avg") >> _current_now_int;
|
||||
}
|
||||
// Documentation ABI allows a negative value when discharging, positive
|
||||
// value when charging.
|
||||
current_now = std::abs(_current_now_int);
|
||||
|
||||
if (fs::exists(bat / "time_to_empty_now")) {
|
||||
time_to_empty_now_exists = true;
|
||||
@ -316,11 +328,15 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
|
||||
}
|
||||
|
||||
uint32_t power_now = 0;
|
||||
int32_t _power_now_int = 0;
|
||||
bool power_now_exists = false;
|
||||
if (fs::exists(bat / "power_now")) {
|
||||
power_now_exists = true;
|
||||
std::ifstream(bat / "power_now") >> power_now;
|
||||
std::ifstream(bat / "power_now") >> _power_now_int;
|
||||
}
|
||||
// Some drivers (example: Qualcomm) exposes use a negative value when
|
||||
// discharging, positive value when charging.
|
||||
power_now = std::abs(_power_now_int);
|
||||
|
||||
uint32_t energy_now = 0;
|
||||
bool energy_now_exists = false;
|
||||
@ -343,6 +359,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;
|
||||
@ -383,13 +436,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;
|
||||
@ -534,6 +581,13 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
|
||||
}
|
||||
}
|
||||
|
||||
// Handle weighted-average
|
||||
if ((config_["weighted-average"].isBool() ? config_["weighted-average"].asBool() : false) &&
|
||||
total_energy_exists && total_energy_full_exists) {
|
||||
if (total_energy_full > 0.0f)
|
||||
calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full);
|
||||
}
|
||||
|
||||
// Handle design-capacity
|
||||
if ((config_["design-capacity"].isBool() ? config_["design-capacity"].asBool() : false) &&
|
||||
total_energy_exists && total_energy_full_design_exists) {
|
||||
@ -556,11 +610,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};
|
||||
}
|
||||
}
|
||||
|
||||
@ -616,7 +671,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);
|
||||
}
|
||||
@ -626,6 +681,7 @@ auto waybar::modules::Battery::update() -> void {
|
||||
[](char ch) { return ch == ' ' ? '-' : std::tolower(ch); });
|
||||
auto format = format_;
|
||||
auto state = getState(capacity, true);
|
||||
setBarClass(state);
|
||||
auto time_remaining_formatted = formatTimeRemaining(time_remaining);
|
||||
if (tooltipEnabled()) {
|
||||
std::string tooltip_text_default;
|
||||
@ -645,10 +701,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_);
|
||||
@ -669,8 +726,44 @@ 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();
|
||||
}
|
||||
|
||||
void waybar::modules::Battery::setBarClass(std::string& state) {
|
||||
auto classes = bar_.window.get_style_context()->list_classes();
|
||||
const std::string prefix = "battery-";
|
||||
|
||||
auto old_class_it = std::find_if(classes.begin(), classes.end(), [&prefix](auto classname) {
|
||||
return classname.rfind(prefix, 0) == 0;
|
||||
});
|
||||
|
||||
auto new_class = prefix + state;
|
||||
|
||||
// If the bar doesn't have any `battery-` class
|
||||
if (old_class_it == classes.end()) {
|
||||
if (!state.empty()) {
|
||||
bar_.window.get_style_context()->add_class(new_class);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto old_class = *old_class_it;
|
||||
|
||||
// If the bar has a `battery-` class,
|
||||
// but `state` is empty
|
||||
if (state.empty()) {
|
||||
bar_.window.get_style_context()->remove_class(old_class);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the bar has a `battery-` class,
|
||||
// and `state` is NOT empty
|
||||
if (old_class != new_class) {
|
||||
bar_.window.get_style_context()->remove_class(old_class);
|
||||
bar_.window.get_style_context()->add_class(new_class);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,12 +6,19 @@
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
#include "util/scope_guard.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
using GDBusManager = std::unique_ptr<GDBusObjectManager, void (*)(GDBusObjectManager*)>;
|
||||
|
||||
auto generateManager() -> GDBusManager {
|
||||
GError* error = nullptr;
|
||||
waybar::util::ScopeGuard error_deleter([error]() {
|
||||
if (error) {
|
||||
g_error_free(error);
|
||||
}
|
||||
});
|
||||
GDBusObjectManager* manager = g_dbus_object_manager_client_new_for_bus_sync(
|
||||
G_BUS_TYPE_SYSTEM,
|
||||
GDBusObjectManagerClientFlags::G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
|
||||
@ -19,7 +26,6 @@ auto generateManager() -> GDBusManager {
|
||||
|
||||
if (error) {
|
||||
spdlog::error("g_dbus_object_manager_client_new_for_bus_sync() failed: {}", error->message);
|
||||
g_error_free(error);
|
||||
}
|
||||
|
||||
auto destructor = [](GDBusObjectManager* manager) {
|
||||
@ -92,25 +98,27 @@ 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 selcected stays unchanged
|
||||
// for duration of the module
|
||||
if (!findCurController(cur_controller_)) {
|
||||
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");
|
||||
}
|
||||
event_box_.hide();
|
||||
return;
|
||||
update();
|
||||
} else {
|
||||
// This call only make sense if a controller could be found
|
||||
findConnectedDevices(cur_controller_->path, connected_devices_);
|
||||
}
|
||||
findConnectedDevices(cur_controller_.path, connected_devices_);
|
||||
|
||||
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)));
|
||||
#endif
|
||||
@ -144,12 +152,16 @@ auto waybar::modules::Bluetooth::update() -> void {
|
||||
|
||||
std::string state;
|
||||
std::string tooltip_format;
|
||||
if (!cur_controller_.powered)
|
||||
state = "off";
|
||||
else if (!connected_devices_.empty())
|
||||
state = "connected";
|
||||
else
|
||||
state = "on";
|
||||
if (cur_controller_) {
|
||||
if (!cur_controller_->powered)
|
||||
state = "off";
|
||||
else if (!connected_devices_.empty())
|
||||
state = "connected";
|
||||
else
|
||||
state = "on";
|
||||
} else {
|
||||
state = "no-controller";
|
||||
}
|
||||
#ifdef WANT_RFKILL
|
||||
if (rfkill_.getState()) state = "disabled";
|
||||
#endif
|
||||
@ -187,8 +199,6 @@ auto waybar::modules::Bluetooth::update() -> void {
|
||||
tooltip_format = config_["tooltip-format"].asString();
|
||||
}
|
||||
|
||||
format_.empty() ? event_box_.hide() : event_box_.show();
|
||||
|
||||
auto update_style_context = [this](const std::string& style_class, bool in_next_state) {
|
||||
if (in_next_state && !label_.get_style_context()->has_class(style_class)) {
|
||||
label_.get_style_context()->add_class(style_class);
|
||||
@ -196,25 +206,32 @@ auto waybar::modules::Bluetooth::update() -> void {
|
||||
label_.get_style_context()->remove_class(style_class);
|
||||
}
|
||||
};
|
||||
update_style_context("discoverable", cur_controller_.discoverable);
|
||||
update_style_context("discovering", cur_controller_.discovering);
|
||||
update_style_context("pairable", cur_controller_.pairable);
|
||||
update_style_context("discoverable", cur_controller_ ? cur_controller_->discoverable : false);
|
||||
update_style_context("discovering", cur_controller_ ? cur_controller_->discovering : false);
|
||||
update_style_context("pairable", cur_controller_ ? cur_controller_->pairable : false);
|
||||
if (!state_.empty()) {
|
||||
update_style_context(state_, false);
|
||||
}
|
||||
update_style_context(state, true);
|
||||
state_ = state;
|
||||
|
||||
label_.set_markup(fmt::format(
|
||||
fmt::runtime(format_), fmt::arg("status", state_),
|
||||
fmt::arg("num_connections", connected_devices_.size()),
|
||||
fmt::arg("controller_address", cur_controller_.address),
|
||||
fmt::arg("controller_address_type", cur_controller_.address_type),
|
||||
fmt::arg("controller_alias", cur_controller_.alias),
|
||||
fmt::arg("device_address", cur_focussed_device_.address),
|
||||
fmt::arg("device_address_type", cur_focussed_device_.address_type),
|
||||
fmt::arg("device_alias", cur_focussed_device_.alias), fmt::arg("icon", icon_label),
|
||||
fmt::arg("device_battery_percentage", cur_focussed_device_.battery_percentage.value_or(0))));
|
||||
if (format_.empty()) {
|
||||
event_box_.hide();
|
||||
} else {
|
||||
event_box_.show();
|
||||
label_.set_markup(fmt::format(
|
||||
fmt::runtime(format_), fmt::arg("status", state_),
|
||||
fmt::arg("num_connections", connected_devices_.size()),
|
||||
fmt::arg("controller_address", cur_controller_ ? cur_controller_->address : "null"),
|
||||
fmt::arg("controller_address_type",
|
||||
cur_controller_ ? cur_controller_->address_type : "null"),
|
||||
fmt::arg("controller_alias", cur_controller_ ? cur_controller_->alias : "null"),
|
||||
fmt::arg("device_address", cur_focussed_device_.address),
|
||||
fmt::arg("device_address_type", cur_focussed_device_.address_type),
|
||||
fmt::arg("device_alias", cur_focussed_device_.alias), fmt::arg("icon", icon_label),
|
||||
fmt::arg("device_battery_percentage",
|
||||
cur_focussed_device_.battery_percentage.value_or(0))));
|
||||
}
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
bool tooltip_enumerate_connections_ = config_["tooltip-format-enumerate-connected"].isString();
|
||||
@ -250,9 +267,10 @@ auto waybar::modules::Bluetooth::update() -> void {
|
||||
label_.set_tooltip_text(fmt::format(
|
||||
fmt::runtime(tooltip_format), fmt::arg("status", state_),
|
||||
fmt::arg("num_connections", connected_devices_.size()),
|
||||
fmt::arg("controller_address", cur_controller_.address),
|
||||
fmt::arg("controller_address_type", cur_controller_.address_type),
|
||||
fmt::arg("controller_alias", cur_controller_.alias),
|
||||
fmt::arg("controller_address", cur_controller_ ? cur_controller_->address : "null"),
|
||||
fmt::arg("controller_address_type",
|
||||
cur_controller_ ? cur_controller_->address_type : "null"),
|
||||
fmt::arg("controller_alias", cur_controller_ ? cur_controller_->alias : "null"),
|
||||
fmt::arg("device_address", cur_focussed_device_.address),
|
||||
fmt::arg("device_address_type", cur_focussed_device_.address_type),
|
||||
fmt::arg("device_alias", cur_focussed_device_.alias), fmt::arg("icon", icon_tooltip),
|
||||
@ -264,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,
|
||||
@ -274,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -291,9 +351,14 @@ 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_);
|
||||
if (object_path == bt->cur_controller_->path) {
|
||||
bt->getControllerProperties(G_DBUS_OBJECT(object_proxy), *bt->cur_controller_);
|
||||
bt->dp.emit();
|
||||
}
|
||||
} else if (interface_name == "org.bluez.Device1" || interface_name == "org.bluez.Battery1") {
|
||||
@ -378,22 +443,23 @@ auto waybar::modules::Bluetooth::getControllerProperties(GDBusObject* object,
|
||||
return false;
|
||||
}
|
||||
|
||||
auto waybar::modules::Bluetooth::findCurController(ControllerInfo& controller_info) -> bool {
|
||||
bool found_controller = false;
|
||||
auto waybar::modules::Bluetooth::findCurController() -> std::optional<ControllerInfo> {
|
||||
std::optional<ControllerInfo> controller_info;
|
||||
|
||||
GList* objects = g_dbus_object_manager_get_objects(manager_.get());
|
||||
for (GList* l = objects; l != NULL; l = l->next) {
|
||||
GDBusObject* object = G_DBUS_OBJECT(l->data);
|
||||
if (getControllerProperties(object, controller_info) &&
|
||||
ControllerInfo info;
|
||||
if (getControllerProperties(object, info) &&
|
||||
(!config_["controller-alias"].isString() ||
|
||||
config_["controller-alias"].asString() == controller_info.alias)) {
|
||||
found_controller = true;
|
||||
config_["controller-alias"].asString() == info.alias)) {
|
||||
controller_info = std::move(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_list_free_full(objects, g_object_unref);
|
||||
|
||||
return found_controller;
|
||||
return controller_info;
|
||||
}
|
||||
|
||||
auto waybar::modules::Bluetooth::findConnectedDevices(const std::string& cur_controller_path,
|
||||
@ -404,7 +470,7 @@ auto waybar::modules::Bluetooth::findConnectedDevices(const std::string& cur_con
|
||||
GDBusObject* object = G_DBUS_OBJECT(l->data);
|
||||
DeviceInfo device;
|
||||
if (getDeviceProperties(object, device) && device.connected &&
|
||||
device.paired_controller == cur_controller_.path) {
|
||||
device.paired_controller == cur_controller_->path) {
|
||||
connected_devices.push_back(device);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -25,7 +19,7 @@ waybar::modules::Cava::Cava(const std::string& id, const Json::Value& config)
|
||||
|
||||
// Override cava parameters by the user config
|
||||
prm_.inAtty = 0;
|
||||
prm_.output = output_method::OUTPUT_RAW;
|
||||
prm_.output = cava::output_method::OUTPUT_RAW;
|
||||
strcpy(prm_.data_format, "ascii");
|
||||
strcpy(prm_.raw_target, "/dev/stdout");
|
||||
prm_.ascii_range = config_["format-icons"].size() - 1;
|
||||
@ -34,9 +28,9 @@ waybar::modules::Cava::Cava(const std::string& id, const Json::Value& config)
|
||||
prm_.bar_spacing = 0;
|
||||
prm_.bar_height = 32;
|
||||
prm_.bar_width = 1;
|
||||
prm_.orientation = ORIENT_TOP;
|
||||
prm_.xaxis = xaxis_scale::NONE;
|
||||
prm_.mono_opt = AVERAGE;
|
||||
prm_.orientation = cava::ORIENT_TOP;
|
||||
prm_.xaxis = cava::xaxis_scale::NONE;
|
||||
prm_.mono_opt = cava::AVERAGE;
|
||||
prm_.autobars = 0;
|
||||
prm_.gravity = 0;
|
||||
prm_.integral = 1;
|
||||
@ -51,10 +45,10 @@ waybar::modules::Cava::Cava(const std::string& id, const Json::Value& config)
|
||||
prm_.upper_cut_off = config_["higher_cutoff_freq"].asLargestInt();
|
||||
if (config_["sleep_timer"].isInt()) prm_.sleep_timer = config_["sleep_timer"].asInt();
|
||||
if (config_["method"].isString())
|
||||
prm_.input = input_method_by_name(config_["method"].asString().c_str());
|
||||
prm_.input = cava::input_method_by_name(config_["method"].asString().c_str());
|
||||
if (config_["source"].isString()) prm_.audio_source = config_["source"].asString().data();
|
||||
if (config_["sample_rate"].isNumeric()) prm_.fifoSample = config_["sample_rate"].asLargestInt();
|
||||
if (config_["sample_bits"].isInt()) prm_.fifoSampleBits = config_["sample_bits"].asInt();
|
||||
if (config_["sample_rate"].isNumeric()) prm_.samplerate = config_["sample_rate"].asLargestInt();
|
||||
if (config_["sample_bits"].isInt()) prm_.samplebits = config_["sample_bits"].asInt();
|
||||
if (config_["stereo"].isBool()) prm_.stereo = config_["stereo"].asBool();
|
||||
if (config_["reverse"].isBool()) prm_.reverse = config_["reverse"].asBool();
|
||||
if (config_["bar_delimiter"].isInt()) prm_.bar_delim = config_["bar_delimiter"].asInt();
|
||||
@ -64,8 +58,10 @@ waybar::modules::Cava::Cava(const std::string& id, const Json::Value& config)
|
||||
prm_.noise_reduction = config_["noise_reduction"].asDouble();
|
||||
if (config_["input_delay"].isInt())
|
||||
fetch_input_delay_ = std::chrono::seconds(config_["input_delay"].asInt());
|
||||
if (config_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool();
|
||||
if (config_["format_silent"].isString()) format_silent_ = config_["format_silent"].asString();
|
||||
// Make cava parameters configuration
|
||||
plan_ = new cava_plan{};
|
||||
plan_ = new cava::cava_plan{};
|
||||
|
||||
audio_raw_.height = prm_.ascii_range;
|
||||
audio_data_.format = -1;
|
||||
@ -143,7 +139,7 @@ auto waybar::modules::Cava::update() -> void {
|
||||
}
|
||||
}
|
||||
|
||||
if (silence_ && prm_.sleep_timer) {
|
||||
if (silence_ && prm_.sleep_timer != 0) {
|
||||
if (sleep_counter_ <=
|
||||
(int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) {
|
||||
++sleep_counter_;
|
||||
@ -151,16 +147,17 @@ auto waybar::modules::Cava::update() -> void {
|
||||
}
|
||||
}
|
||||
|
||||
if (!silence_) {
|
||||
if (!silence_ || prm_.sleep_timer == 0) {
|
||||
downThreadDelay(frame_time_milsec_, suspend_silence_delay_);
|
||||
// Process: execute cava
|
||||
pthread_mutex_lock(&audio_data_.lock);
|
||||
cava_execute(audio_data_.cava_in, audio_data_.samples_counter, audio_raw_.cava_out, plan_);
|
||||
cava::cava_execute(audio_data_.cava_in, audio_data_.samples_counter, audio_raw_.cava_out,
|
||||
plan_);
|
||||
if (audio_data_.samples_counter > 0) audio_data_.samples_counter = 0;
|
||||
pthread_mutex_unlock(&audio_data_.lock);
|
||||
|
||||
// Do transformation under raw data
|
||||
audio_raw_fetch(&audio_raw_, &prm_, &rePaint_);
|
||||
audio_raw_fetch(&audio_raw_, &prm_, &rePaint_, plan_);
|
||||
|
||||
if (rePaint_ == 1) {
|
||||
text_.clear();
|
||||
@ -174,10 +171,22 @@ auto waybar::modules::Cava::update() -> void {
|
||||
}
|
||||
|
||||
label_.set_markup(text_);
|
||||
label_.show();
|
||||
ALabel::update();
|
||||
label_.get_style_context()->add_class("updated");
|
||||
}
|
||||
} else
|
||||
|
||||
label_.get_style_context()->remove_class("silent");
|
||||
} else {
|
||||
upThreadDelay(frame_time_milsec_, suspend_silence_delay_);
|
||||
if (hide_on_silence_)
|
||||
label_.hide();
|
||||
else if (config_["format_silent"].isString())
|
||||
label_.set_markup(format_silent_);
|
||||
|
||||
label_.get_style_context()->add_class("silent");
|
||||
label_.get_style_context()->remove_class("updated");
|
||||
}
|
||||
}
|
||||
|
||||
auto waybar::modules::Cava::doAction(const std::string& name) -> void {
|
||||
|
||||
123
src/modules/cffi.cpp
Normal file
123
src/modules/cffi.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
#include "modules/cffi.hpp"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <json/value.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <type_traits>
|
||||
|
||||
namespace waybar::modules {
|
||||
|
||||
CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& config)
|
||||
: AModule(config, name, id, true, true) {
|
||||
const auto dynlib_path = config_["module_path"].asString();
|
||||
if (dynlib_path.empty()) {
|
||||
throw std::runtime_error{"Missing or empty 'module_path' in module config"};
|
||||
}
|
||||
|
||||
void* handle = dlopen(dynlib_path.c_str(), RTLD_LAZY);
|
||||
if (handle == nullptr) {
|
||||
throw std::runtime_error{std::string{"Failed to load CFFI module: "} + dlerror()};
|
||||
}
|
||||
|
||||
// Fetch ABI version
|
||||
auto wbcffi_version = reinterpret_cast<size_t*>(dlsym(handle, "wbcffi_version"));
|
||||
if (wbcffi_version == nullptr) {
|
||||
throw std::runtime_error{std::string{"Missing wbcffi_version function: "} + dlerror()};
|
||||
}
|
||||
|
||||
// Fetch functions
|
||||
if (*wbcffi_version == 1 || *wbcffi_version == 2) {
|
||||
// Mandatory functions
|
||||
hooks_.init = reinterpret_cast<InitFn*>(dlsym(handle, "wbcffi_init"));
|
||||
if (!hooks_.init) {
|
||||
throw std::runtime_error{std::string{"Missing wbcffi_init function: "} + dlerror()};
|
||||
}
|
||||
hooks_.deinit = reinterpret_cast<DenitFn*>(dlsym(handle, "wbcffi_deinit"));
|
||||
if (!hooks_.init) {
|
||||
throw std::runtime_error{std::string{"Missing wbcffi_deinit function: "} + dlerror()};
|
||||
}
|
||||
// Optional functions
|
||||
if (auto fn = reinterpret_cast<UpdateFn*>(dlsym(handle, "wbcffi_update"))) {
|
||||
hooks_.update = fn;
|
||||
}
|
||||
if (auto fn = reinterpret_cast<RefreshFn*>(dlsym(handle, "wbcffi_refresh"))) {
|
||||
hooks_.refresh = fn;
|
||||
}
|
||||
if (auto fn = reinterpret_cast<DoActionFn*>(dlsym(handle, "wbcffi_doaction"))) {
|
||||
hooks_.doAction = fn;
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error{"Unknown wbcffi_version " + std::to_string(*wbcffi_version)};
|
||||
}
|
||||
|
||||
// Prepare init() arguments
|
||||
// Convert JSON values to string
|
||||
std::vector<std::string> config_entries_stringstor;
|
||||
const auto& keys = config.getMemberNames();
|
||||
for (size_t i = 0; i < keys.size(); i++) {
|
||||
const auto& value = config[keys[i]];
|
||||
if (*wbcffi_version == 1) {
|
||||
if (value.isConvertibleTo(Json::ValueType::stringValue)) {
|
||||
config_entries_stringstor.push_back(value.asString());
|
||||
} else {
|
||||
config_entries_stringstor.push_back(value.toStyledString());
|
||||
}
|
||||
} else {
|
||||
config_entries_stringstor.push_back(value.toStyledString());
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare config_entries array
|
||||
std::vector<ffi::wbcffi_config_entry> config_entries;
|
||||
for (size_t i = 0; i < keys.size(); i++) {
|
||||
config_entries.push_back({keys[i].c_str(), config_entries_stringstor[i].c_str()});
|
||||
}
|
||||
|
||||
ffi::wbcffi_init_info init_info = {
|
||||
.obj = (ffi::wbcffi_module*)this,
|
||||
.waybar_version = VERSION,
|
||||
.get_root_widget =
|
||||
[](ffi::wbcffi_module* obj) {
|
||||
return dynamic_cast<Gtk::Container*>(&((CFFI*)obj)->event_box_)->gobj();
|
||||
},
|
||||
.queue_update = [](ffi::wbcffi_module* obj) { ((CFFI*)obj)->dp.emit(); },
|
||||
};
|
||||
|
||||
// Call init
|
||||
cffi_instance_ = hooks_.init(&init_info, config_entries.data(), config_entries.size());
|
||||
|
||||
// Handle init failures
|
||||
if (cffi_instance_ == nullptr) {
|
||||
throw std::runtime_error{"Failed to initialize C ABI module"};
|
||||
}
|
||||
}
|
||||
|
||||
CFFI::~CFFI() {
|
||||
if (cffi_instance_ != nullptr) {
|
||||
hooks_.deinit(cffi_instance_);
|
||||
}
|
||||
}
|
||||
|
||||
auto CFFI::update() -> void {
|
||||
assert(cffi_instance_ != nullptr);
|
||||
hooks_.update(cffi_instance_);
|
||||
|
||||
// Execute the on-update command set in config
|
||||
AModule::update();
|
||||
}
|
||||
|
||||
auto CFFI::refresh(int signal) -> void {
|
||||
assert(cffi_instance_ != nullptr);
|
||||
hooks_.refresh(cffi_instance_, signal);
|
||||
}
|
||||
|
||||
auto CFFI::doAction(const std::string& name) -> void {
|
||||
assert(cffi_instance_ != nullptr);
|
||||
if (!name.empty()) {
|
||||
hooks_.doAction(cffi_instance_, name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace waybar::modules
|
||||
@ -1,345 +1,316 @@
|
||||
#include "modules/clock.hpp"
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
#include <glib.h>
|
||||
#include <gtkmm/tooltip.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <ctime>
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
|
||||
#include "util/ustring_clen.hpp"
|
||||
|
||||
#ifdef HAVE_LANGINFO_1STDAY
|
||||
#include <langinfo.h>
|
||||
#include <locale.h>
|
||||
|
||||
#include <clocale>
|
||||
#endif
|
||||
|
||||
using namespace date;
|
||||
namespace fmt_lib = waybar::util::date::format;
|
||||
|
||||
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true),
|
||||
current_time_zone_idx_(0),
|
||||
is_calendar_in_tooltip_(false),
|
||||
is_timezoned_list_in_tooltip_(false) {
|
||||
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},
|
||||
cldYearShift_{January / 1 / 1900},
|
||||
cldMonShift_{year(1900) / January},
|
||||
tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos},
|
||||
tzCurrIdx_{0},
|
||||
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"]) {
|
||||
if (!zone_name.isString()) continue;
|
||||
if (zone_name.asString().empty())
|
||||
time_zones_.push_back(date::current_zone());
|
||||
// local time should be shown
|
||||
tzList_.push_back(nullptr);
|
||||
else
|
||||
try {
|
||||
time_zones_.push_back(date::locate_zone(zone_name.asString()));
|
||||
tzList_.push_back(locate_zone(zone_name.asString()));
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::warn("Timezone: {0}. {1}", zone_name.asString(), e.what());
|
||||
}
|
||||
}
|
||||
} else if (config_["timezone"].isString()) {
|
||||
if (config_["timezone"].asString().empty())
|
||||
time_zones_.push_back(date::current_zone());
|
||||
// local time should be shown
|
||||
tzList_.push_back(nullptr);
|
||||
else
|
||||
try {
|
||||
time_zones_.push_back(date::locate_zone(config_["timezone"].asString()));
|
||||
tzList_.push_back(locate_zone(config_["timezone"].asString()));
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::warn("Timezone: {0}. {1}", config_["timezone"].asString(), e.what());
|
||||
}
|
||||
}
|
||||
if (!tzList_.size()) tzList_.push_back(nullptr);
|
||||
|
||||
// If all timezones are parsed and no one is good, add current time zone. nullptr in timezones
|
||||
// vector means that local time should be shown
|
||||
if (!time_zones_.size()) {
|
||||
time_zones_.push_back(date::current_zone());
|
||||
}
|
||||
|
||||
// Check if a particular placeholder is present in the tooltip format, to know what to calculate
|
||||
// on update.
|
||||
if (config_["tooltip-format"].isString()) {
|
||||
std::string trimmed_format = config_["tooltip-format"].asString();
|
||||
trimmed_format.erase(std::remove_if(trimmed_format.begin(), trimmed_format.end(),
|
||||
[](unsigned char x) { return std::isspace(x); }),
|
||||
trimmed_format.end());
|
||||
if (trimmed_format.find("{" + kCalendarPlaceholder + "}") != std::string::npos) {
|
||||
is_calendar_in_tooltip_ = true;
|
||||
}
|
||||
if (trimmed_format.find("{" + KTimezonedTimeListPlaceholder + "}") != std::string::npos) {
|
||||
is_timezoned_list_in_tooltip_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Calendar configuration
|
||||
if (is_calendar_in_tooltip_) {
|
||||
if (config_[kCalendarPlaceholder]["weeks-pos"].isString()) {
|
||||
if (config_[kCalendarPlaceholder]["weeks-pos"].asString() == "left") {
|
||||
cldWPos_ = WeeksSide::LEFT;
|
||||
} else if (config_[kCalendarPlaceholder]["weeks-pos"].asString() == "right") {
|
||||
cldWPos_ = WeeksSide::RIGHT;
|
||||
}
|
||||
}
|
||||
if (config_[kCalendarPlaceholder]["format"]["months"].isString())
|
||||
fmtMap_.insert({0, config_[kCalendarPlaceholder]["format"]["months"].asString()});
|
||||
else
|
||||
fmtMap_.insert({0, "{}"});
|
||||
if (config_[kCalendarPlaceholder]["format"]["days"].isString())
|
||||
fmtMap_.insert({2, config_[kCalendarPlaceholder]["format"]["days"].asString()});
|
||||
else
|
||||
fmtMap_.insert({2, "{}"});
|
||||
if (config_[kCalendarPlaceholder]["format"]["weeks"].isString() &&
|
||||
cldWPos_ != WeeksSide::HIDDEN) {
|
||||
fmtMap_.insert(
|
||||
{4, std::regex_replace(config_[kCalendarPlaceholder]["format"]["weeks"].asString(),
|
||||
std::regex("\\{\\}"),
|
||||
(first_day_of_week() == date::Monday) ? "{:%W}" : "{:%U}")});
|
||||
Glib::ustring tmp{std::regex_replace(fmtMap_[4], std::regex("</?[^>]+>|\\{.*\\}"), "")};
|
||||
cldWnLen_ += tmp.size();
|
||||
} else {
|
||||
if (cldWPos_ != WeeksSide::HIDDEN)
|
||||
fmtMap_.insert({4, (first_day_of_week() == date::Monday) ? "{:%W}" : "{:%U}"});
|
||||
else
|
||||
cldWnLen_ = 0;
|
||||
}
|
||||
if (config_[kCalendarPlaceholder]["format"]["weekdays"].isString())
|
||||
fmtMap_.insert({1, config_[kCalendarPlaceholder]["format"]["weekdays"].asString()});
|
||||
else
|
||||
fmtMap_.insert({1, "{}"});
|
||||
if (config_[kCalendarPlaceholder]["format"]["today"].isString()) {
|
||||
fmtMap_.insert({3, config_[kCalendarPlaceholder]["format"]["today"].asString()});
|
||||
cldBaseDay_ =
|
||||
date::year_month_day{date::floor<date::days>(std::chrono::system_clock::now())}.day();
|
||||
} else
|
||||
fmtMap_.insert({3, "{}"});
|
||||
if (config_[kCalendarPlaceholder]["mode"].isString()) {
|
||||
const std::string cfgMode{(config_[kCalendarPlaceholder]["mode"].isString())
|
||||
? config_[kCalendarPlaceholder]["mode"].asString()
|
||||
: "month"};
|
||||
const std::map<std::string, const CldMode&> monthModes{{"month", CldMode::MONTH},
|
||||
{"year", CldMode::YEAR}};
|
||||
// Calendar properties
|
||||
if (cldInTooltip_) {
|
||||
if (config_[kCldPlaceholder]["mode"].isString()) {
|
||||
const std::string cfgMode{config_[kCldPlaceholder]["mode"].asString()};
|
||||
const std::map<std::string_view, const CldMode&> monthModes{{"month", CldMode::MONTH},
|
||||
{"year", CldMode::YEAR}};
|
||||
if (monthModes.find(cfgMode) != monthModes.end())
|
||||
cldMode_ = monthModes.at(cfgMode);
|
||||
else
|
||||
spdlog::warn(
|
||||
"Clock calendar configuration \"mode\"\"\" \"{0}\" is not recognized. Mode = \"month\" "
|
||||
"is using instead",
|
||||
"Clock calendar configuration mode \"{0}\" is not recognized. Mode = \"month\" is "
|
||||
"using instead",
|
||||
cfgMode);
|
||||
}
|
||||
if (config_[kCalendarPlaceholder]["mode-mon-col"].isInt()) {
|
||||
cldMonCols_ = config_[kCalendarPlaceholder]["mode-mon-col"].asInt();
|
||||
if (cldMonCols_ == 0u || 12 % cldMonCols_ != 0u) {
|
||||
cldMonCols_ = 3u;
|
||||
if (config_[kCldPlaceholder]["weeks-pos"].isString()) {
|
||||
if (config_[kCldPlaceholder]["weeks-pos"].asString() == "left") cldWPos_ = WS::LEFT;
|
||||
if (config_[kCldPlaceholder]["weeks-pos"].asString() == "right") cldWPos_ = WS::RIGHT;
|
||||
}
|
||||
if (config_[kCldPlaceholder]["format"]["months"].isString())
|
||||
fmtMap_.insert({0, config_[kCldPlaceholder]["format"]["months"].asString()});
|
||||
else
|
||||
fmtMap_.insert({0, "{}"});
|
||||
if (config_[kCldPlaceholder]["format"]["weekdays"].isString())
|
||||
fmtMap_.insert({1, config_[kCldPlaceholder]["format"]["weekdays"].asString()});
|
||||
else
|
||||
fmtMap_.insert({1, "{}"});
|
||||
|
||||
if (config_[kCldPlaceholder]["format"]["days"].isString())
|
||||
fmtMap_.insert({2, config_[kCldPlaceholder]["format"]["days"].asString()});
|
||||
else
|
||||
fmtMap_.insert({2, "{}"});
|
||||
if (config_[kCldPlaceholder]["format"]["today"].isString()) {
|
||||
fmtMap_.insert({3, config_[kCldPlaceholder]["format"]["today"].asString()});
|
||||
cldBaseDay_ =
|
||||
year_month_day{
|
||||
floor<days>(zoned_time{local_zone(), system_clock::now()}.get_local_time())}
|
||||
.day();
|
||||
} else
|
||||
fmtMap_.insert({3, "{}"});
|
||||
if (config_[kCldPlaceholder]["format"]["weeks"].isString() && cldWPos_ != WS::HIDDEN) {
|
||||
fmtMap_.insert({4, std::regex_replace(config_[kCldPlaceholder]["format"]["weeks"].asString(),
|
||||
std::regex("\\{\\}"),
|
||||
(first_day_of_week() == Monday) ? "{:%W}" : "{:%U}")});
|
||||
Glib::ustring tmp{std::regex_replace(fmtMap_[4], std::regex("</?[^>]+>|\\{.*\\}"), "")};
|
||||
cldWnLen_ += tmp.size();
|
||||
} else {
|
||||
if (cldWPos_ != WS::HIDDEN)
|
||||
fmtMap_.insert({4, (first_day_of_week() == Monday) ? "{:%W}" : "{:%U}"});
|
||||
else
|
||||
cldWnLen_ = 0;
|
||||
}
|
||||
if (config_[kCldPlaceholder]["mode-mon-col"].isInt()) {
|
||||
cldMonCols_ = config_[kCldPlaceholder]["mode-mon-col"].asInt();
|
||||
if (cldMonCols_ == 0u || (12 % cldMonCols_) != 0u) {
|
||||
spdlog::warn(
|
||||
"Clock calendar configuration \"mode-mon-col\" = {0} must be one of [1, 2, 3, 4, 6, "
|
||||
"12]. Value 3 is using instead",
|
||||
"Clock calendar configuration mode-mon-col = {0} must be one of [1, 2, 3, 4, 6, 12]. "
|
||||
"Value 3 is using instead",
|
||||
cldMonCols_);
|
||||
cldMonCols_ = 3u;
|
||||
}
|
||||
} else
|
||||
cldMonCols_ = 1;
|
||||
if (config_[kCalendarPlaceholder]["on-scroll"].isInt()) {
|
||||
cldShift_ = date::months{config_[kCalendarPlaceholder]["on-scroll"].asInt()};
|
||||
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_ = date::months{0};
|
||||
cldCurrShift_ = months{0};
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (config_["locale"].isString())
|
||||
locale_ = std::locale(config_["locale"].asString());
|
||||
else
|
||||
locale_ = std::locale("");
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_has_tooltip(true);
|
||||
label_.signal_query_tooltip().connect(sigc::mem_fun(*this, &Clock::query_tlp_cb));
|
||||
}
|
||||
|
||||
thread_ = [this] {
|
||||
dp.emit();
|
||||
auto now = std::chrono::system_clock::now();
|
||||
/* difference with projected wakeup time */
|
||||
auto diff = now.time_since_epoch() % interval_;
|
||||
/* sleep until the next projected time */
|
||||
thread_.sleep_for(interval_ - diff);
|
||||
thread_.sleep_for(interval_ - system_clock::now().time_since_epoch() % interval_);
|
||||
};
|
||||
}
|
||||
|
||||
const date::time_zone* waybar::modules::Clock::current_timezone() {
|
||||
return time_zones_[current_time_zone_idx_] ? time_zones_[current_time_zone_idx_]
|
||||
: date::current_zone();
|
||||
}
|
||||
|
||||
bool waybar::modules::Clock::is_timezone_fixed() {
|
||||
return time_zones_[current_time_zone_idx_] != nullptr;
|
||||
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* time_zone = current_timezone();
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto ztime = date::zoned_time{time_zone, date::floor<std::chrono::seconds>(now)};
|
||||
const auto* tz = tzList_[tzCurrIdx_] != nullptr ? tzList_[tzCurrIdx_] : local_zone();
|
||||
const zoned_time now{tz, floor<seconds>(system_clock::now())};
|
||||
|
||||
auto shifted_date = date::year_month_day{date::floor<date::days>(now)} + cldCurrShift_;
|
||||
if (cldCurrShift_.count()) {
|
||||
shifted_date = date::year_month_day(shifted_date.year(), shifted_date.month(), date::day(1));
|
||||
}
|
||||
auto now_shifted = date::sys_days{shifted_date} + (now - date::floor<date::days>(now));
|
||||
auto shifted_ztime = date::zoned_time{time_zone, date::floor<std::chrono::seconds>(now_shifted)};
|
||||
|
||||
std::string text{""};
|
||||
if (!is_timezone_fixed()) {
|
||||
// As date dep is not fully compatible, prefer fmt
|
||||
tzset();
|
||||
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
|
||||
text = fmt::format(locale_, fmt::runtime(format_), localtime);
|
||||
} else {
|
||||
text = fmt::format(locale_, fmt::runtime(format_), ztime);
|
||||
}
|
||||
label_.set_markup(text);
|
||||
label_.set_markup(fmt_lib::vformat(m_locale_, format_, fmt_lib::make_format_args(now)));
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
if (config_["tooltip-format"].isString()) {
|
||||
std::string calendar_lines{""};
|
||||
std::string timezoned_time_lines{""};
|
||||
if (is_calendar_in_tooltip_) {
|
||||
calendar_lines = get_calendar(ztime, shifted_ztime);
|
||||
}
|
||||
if (is_timezoned_list_in_tooltip_) {
|
||||
timezoned_time_lines = timezones_text(&now);
|
||||
}
|
||||
auto tooltip_format = config_["tooltip-format"].asString();
|
||||
text = fmt::format(locale_, fmt::runtime(tooltip_format), shifted_ztime,
|
||||
fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines),
|
||||
fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines));
|
||||
label_.set_tooltip_markup(text);
|
||||
const year_month_day today{floor<days>(now.get_local_time())};
|
||||
const auto shiftedDay{today + cldCurrShift_};
|
||||
const zoned_time shiftedNow{
|
||||
tz, local_days(shiftedDay) + (now.get_local_time() - floor<days>(now.get_local_time()))};
|
||||
|
||||
if (tzInTooltip_) tzText_ = getTZtext(now.get_sys_time());
|
||||
if (cldInTooltip_) cldText_ = get_calendar(today, shiftedDay, tz);
|
||||
if (ordInTooltip_) ordText_ = get_ordinal_date(shiftedDay);
|
||||
if (tzInTooltip_ || cldInTooltip_ || ordInTooltip_) {
|
||||
// std::vformat doesn't support named arguments.
|
||||
m_tlpText_ =
|
||||
std::regex_replace(m_tlpFmt_, std::regex("\\{" + kTZPlaceholder + "\\}"), tzText_);
|
||||
m_tlpText_ = std::regex_replace(
|
||||
m_tlpText_, std::regex("\\{" + kCldPlaceholder + "\\}"),
|
||||
fmt_lib::vformat(m_locale_, cldText_, fmt_lib::make_format_args(shiftedNow)));
|
||||
m_tlpText_ =
|
||||
std::regex_replace(m_tlpText_, std::regex("\\{" + kOrdPlaceholder + "\\}"), ordText_);
|
||||
} else {
|
||||
m_tlpText_ = m_tlpFmt_;
|
||||
}
|
||||
|
||||
m_tlpText_ = fmt_lib::vformat(m_locale_, m_tlpText_, fmt_lib::make_format_args(now));
|
||||
m_tooltip_->set_markup(m_tlpText_);
|
||||
label_.trigger_tooltip_query();
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
auto waybar::modules::Clock::doAction(const std::string& name) -> void {
|
||||
if ((actionMap_[name])) {
|
||||
(this->*actionMap_[name])();
|
||||
update();
|
||||
} else
|
||||
spdlog::error("Clock. Unsupported action \"{0}\"", name);
|
||||
auto waybar::modules::Clock::getTZtext(sys_seconds now) -> std::string {
|
||||
if (tzList_.size() == 1) return "";
|
||||
|
||||
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] : local_zone();
|
||||
auto zt{zoned_time{tz, now}};
|
||||
os << fmt_lib::vformat(m_locale_, format_, fmt_lib::make_format_args(zt)) << '\n';
|
||||
}
|
||||
|
||||
return os.str();
|
||||
}
|
||||
|
||||
// The number of weeks in calendar month layout plus 1 more for calendar titles
|
||||
unsigned cldRowsInMonth(date::year_month const ym, date::weekday const firstdow) {
|
||||
using namespace date;
|
||||
return static_cast<unsigned>(
|
||||
ceil<weeks>((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count()) +
|
||||
2;
|
||||
const unsigned cldRowsInMonth(const year_month& ym, const weekday& firstdow) {
|
||||
return 2u + ceil<weeks>((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count();
|
||||
}
|
||||
|
||||
auto cldGetWeekForLine(date::year_month const ym, date::weekday const firstdow, unsigned const line)
|
||||
-> const date::year_month_weekday {
|
||||
unsigned index = line - 2;
|
||||
auto sd = date::sys_days{ym / 1};
|
||||
if (date::weekday{sd} == firstdow) ++index;
|
||||
auto ymdw = ym / firstdow[index];
|
||||
return ymdw;
|
||||
auto cldGetWeekForLine(const year_month& ym, const weekday& firstdow, const unsigned line)
|
||||
-> const year_month_weekday {
|
||||
unsigned index{line - 2};
|
||||
if (weekday{ym / 1} == firstdow) ++index;
|
||||
return ym / firstdow[index];
|
||||
}
|
||||
|
||||
auto getCalendarLine(date::year_month_day const currDate, date::year_month const ym,
|
||||
unsigned const line, date::weekday const firstdow,
|
||||
const std::locale* const locale_) -> std::string {
|
||||
using namespace date::literals;
|
||||
std::ostringstream res;
|
||||
auto getCalendarLine(const year_month_day& currDate, const year_month ym, const unsigned line,
|
||||
const weekday& firstdow, const std::locale* const m_locale_) -> std::string {
|
||||
std::ostringstream os;
|
||||
|
||||
switch (line) {
|
||||
// Print month and year title
|
||||
case 0: {
|
||||
// Output month and year title
|
||||
res << date::format(*locale_, "%B %Y", ym);
|
||||
os << date::format(*m_locale_, "{:L%B %Y}", ym);
|
||||
break;
|
||||
}
|
||||
// Print weekday names title
|
||||
case 1: {
|
||||
// Output weekday names title
|
||||
auto wd{firstdow};
|
||||
Glib::ustring wdStr;
|
||||
Glib::ustring::size_type wdLen{0};
|
||||
int clen{0};
|
||||
do {
|
||||
Glib::ustring wd_ustring{date::format(*locale_, "%a", wd)};
|
||||
auto clen{ustring_clen(wd_ustring)};
|
||||
auto wd_len{wd_ustring.length()};
|
||||
wdStr = date::format(*m_locale_, "{:L%a}", wd);
|
||||
clen = ustring_clen(wdStr);
|
||||
wdLen = wdStr.length();
|
||||
while (clen > 2) {
|
||||
wd_ustring = wd_ustring.substr(0, wd_len - 1);
|
||||
--wd_len;
|
||||
clen = ustring_clen(wd_ustring);
|
||||
wdStr = wdStr.substr(0, wdLen - 1);
|
||||
--wdLen;
|
||||
clen = ustring_clen(wdStr);
|
||||
}
|
||||
const std::string pad(2 - clen, ' ');
|
||||
|
||||
if (wd != firstdow) res << ' ';
|
||||
if (wd != firstdow) os << ' ';
|
||||
|
||||
res << pad << wd_ustring;
|
||||
os << pad << wdStr;
|
||||
} while (++wd != firstdow);
|
||||
break;
|
||||
}
|
||||
// Print first week prefixed with spaces if necessary
|
||||
case 2: {
|
||||
// Output first week prefixed with spaces if necessary
|
||||
auto wd = date::weekday{ym / 1};
|
||||
res << std::string(static_cast<unsigned>((wd - firstdow).count()) * 3, ' ');
|
||||
auto d{day{1}};
|
||||
auto wd{weekday{ym / 1}};
|
||||
os << std::string((wd - firstdow).count() * 3, ' ');
|
||||
|
||||
if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / 1_d)
|
||||
res << date::format("%e", 1_d);
|
||||
if (currDate != ym / d)
|
||||
os << date::format(*m_locale_, "{:L%e}", d);
|
||||
else
|
||||
res << "{today}";
|
||||
|
||||
auto d = 2_d;
|
||||
os << "{today}";
|
||||
|
||||
while (++wd != firstdow) {
|
||||
if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / d)
|
||||
res << date::format(" %e", d);
|
||||
else
|
||||
res << " {today}";
|
||||
|
||||
++d;
|
||||
|
||||
if (currDate != ym / d)
|
||||
os << date::format(*m_locale_, " {:L%e}", d);
|
||||
else
|
||||
os << " {today}";
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Print non-first week
|
||||
default: {
|
||||
// Output a non-first week:
|
||||
auto ymdw{cldGetWeekForLine(ym, firstdow, line)};
|
||||
if (ymdw.ok()) {
|
||||
auto d = date::year_month_day{ymdw}.day();
|
||||
auto const e = (ym / last).day();
|
||||
auto wd = firstdow;
|
||||
auto ymdTmp{cldGetWeekForLine(ym, firstdow, line)};
|
||||
if (ymdTmp.ok()) {
|
||||
auto d{year_month_day{ymdTmp}.day()};
|
||||
const auto dlast{(ym / last).day()};
|
||||
auto wd{firstdow};
|
||||
|
||||
if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / d)
|
||||
res << date::format("%e", d);
|
||||
if (currDate != ym / d)
|
||||
os << date::format(*m_locale_, "{:L%e}", d);
|
||||
else
|
||||
res << "{today}";
|
||||
os << "{today}";
|
||||
|
||||
while (++wd != firstdow && ++d <= e) {
|
||||
if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / d)
|
||||
res << date::format(" %e", d);
|
||||
while (++wd != firstdow && ++d <= dlast) {
|
||||
if (currDate != ym / d)
|
||||
os << date::format(*m_locale_, " {:L%e}", d);
|
||||
else
|
||||
res << " {today}";
|
||||
os << " {today}";
|
||||
}
|
||||
// Append row with spaces if the week did not complete
|
||||
res << std::string(static_cast<unsigned>((firstdow - wd).count()) * 3, ' ');
|
||||
// Append row with spaces if the week was not completed
|
||||
os << std::string((firstdow - wd).count() * 3, ' ');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res.str();
|
||||
return os.str();
|
||||
}
|
||||
|
||||
auto waybar::modules::Clock::get_calendar(const date::zoned_seconds& now,
|
||||
const date::zoned_seconds& wtime) -> std::string {
|
||||
auto daypoint = date::floor<date::days>(wtime.get_local_time());
|
||||
const auto ymd{date::year_month_day{daypoint}};
|
||||
auto waybar::modules::Clock::get_calendar(const year_month_day& today, const year_month_day& ymd,
|
||||
const time_zone* tz) -> const std::string {
|
||||
const auto firstdow{first_day_of_week()};
|
||||
const auto maxRows{12 / cldMonCols_};
|
||||
const auto ym{ymd.year() / ymd.month()};
|
||||
const auto y{ymd.year()};
|
||||
const auto d{ymd.day()};
|
||||
const auto firstdow = first_day_of_week();
|
||||
const auto maxRows{12 / cldMonCols_};
|
||||
|
||||
std::ostringstream os;
|
||||
std::ostringstream tmp;
|
||||
// get currdate
|
||||
daypoint = date::floor<date::days>(now.get_local_time());
|
||||
const auto currDate{date::year_month_day{daypoint}};
|
||||
|
||||
if (cldMode_ == CldMode::YEAR) {
|
||||
if (y / date::month{1} / 1 == cldYearShift_)
|
||||
if (y / month{1} / 1 == cldYearShift_)
|
||||
if (d == cldBaseDay_ || (uint)cldBaseDay_ == 0u)
|
||||
return cldYearCached_;
|
||||
else
|
||||
cldBaseDay_ = d;
|
||||
else
|
||||
cldYearShift_ = y / date::month{1} / 1;
|
||||
cldYearShift_ = y / month{1} / 1;
|
||||
}
|
||||
if (cldMode_ == CldMode::MONTH) {
|
||||
if (ym == cldMonShift_)
|
||||
@ -350,64 +321,88 @@ auto waybar::modules::Clock::get_calendar(const date::zoned_seconds& now,
|
||||
else
|
||||
cldMonShift_ = ym;
|
||||
}
|
||||
|
||||
// Pad object
|
||||
const std::string pads(cldWnLen_, ' ');
|
||||
// Compute number of lines needed for each calendar month
|
||||
unsigned ml[12]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
|
||||
|
||||
for (auto& m : ml) {
|
||||
if (cldMode_ == CldMode::YEAR || m == static_cast<unsigned>(ymd.month()))
|
||||
m = cldRowsInMonth(y / date::month{m}, firstdow);
|
||||
m = cldRowsInMonth(y / month{m}, firstdow);
|
||||
else
|
||||
m = 0u;
|
||||
}
|
||||
|
||||
for (auto row{0u}; row < maxRows; ++row) {
|
||||
const auto lines = *std::max_element(std::begin(ml) + (row * cldMonCols_),
|
||||
std::begin(ml) + ((row + 1) * cldMonCols_));
|
||||
const auto lines{*std::max_element(std::begin(ml) + (row * cldMonCols_),
|
||||
std::begin(ml) + ((row + 1) * cldMonCols_))};
|
||||
for (auto line{0u}; line < lines; ++line) {
|
||||
for (auto col{0u}; col < cldMonCols_; ++col) {
|
||||
const auto mon{date::month{row * cldMonCols_ + col + 1}};
|
||||
const auto mon{month{row * cldMonCols_ + col + 1}};
|
||||
if (cldMode_ == CldMode::YEAR || y / mon == ym) {
|
||||
date::year_month ymTmp{y / mon};
|
||||
if (col != 0 && cldMode_ == CldMode::YEAR) os << " ";
|
||||
const year_month ymTmp{y / mon};
|
||||
if (col != 0 && cldMode_ == CldMode::YEAR) os << std::string(3, ' ');
|
||||
|
||||
// Week numbers on the left
|
||||
if (cldWPos_ == WeeksSide::LEFT && line > 0) {
|
||||
if (cldWPos_ == WS::LEFT && line > 0) {
|
||||
if (line > 1) {
|
||||
if (line < ml[static_cast<unsigned>(ymTmp.month()) - 1u])
|
||||
os << fmt::format(fmt::runtime(fmtMap_[4]),
|
||||
(line == 2)
|
||||
? date::sys_days{ymTmp / 1}
|
||||
: date::sys_days{cldGetWeekForLine(ymTmp, firstdow, line)})
|
||||
if (line < ml[(unsigned)ymTmp.month() - 1u]) {
|
||||
os << fmt_lib::vformat(
|
||||
m_locale_, fmtMap_[4],
|
||||
fmt_lib::make_format_args(
|
||||
(line == 2)
|
||||
? static_cast<const zoned_seconds&&>(
|
||||
zoned_seconds{tz, local_days{ymTmp / 1}})
|
||||
: static_cast<const zoned_seconds&&>(zoned_seconds{
|
||||
tz, local_days{cldGetWeekForLine(ymTmp, firstdow, line)}})))
|
||||
<< ' ';
|
||||
else
|
||||
os << std::string(cldWnLen_, ' ');
|
||||
} else
|
||||
os << pads;
|
||||
}
|
||||
}
|
||||
|
||||
os << fmt::format(
|
||||
fmt::runtime((cldWPos_ != WeeksSide::LEFT || line == 0) ? "{:<{}}" : "{:>{}}"),
|
||||
getCalendarLine(currDate, ymTmp, line, firstdow, &locale_),
|
||||
(cldMonColLen_ + ((line < 2) ? cldWnLen_ : 0)));
|
||||
// Count wide characters to avoid extra padding
|
||||
size_t wideCharCount = 0;
|
||||
std::string calendarLine = getCalendarLine(today, ymTmp, line, firstdow, &m_locale_);
|
||||
if (line < 2) {
|
||||
for (gchar *data = calendarLine.data(), *end = data + calendarLine.size();
|
||||
data != nullptr;) {
|
||||
gunichar c = g_utf8_get_char_validated(data, end - data);
|
||||
if (g_unichar_iswide(c)) {
|
||||
wideCharCount++;
|
||||
}
|
||||
data = g_utf8_find_next_char(data, end);
|
||||
}
|
||||
}
|
||||
os << Glib::ustring::format(
|
||||
(cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right, std::setfill(L' '),
|
||||
std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ - wideCharCount : 0)),
|
||||
calendarLine);
|
||||
|
||||
// Week numbers on the right
|
||||
if (cldWPos_ == WeeksSide ::RIGHT && line > 0) {
|
||||
if (cldWPos_ == WS::RIGHT && line > 0) {
|
||||
if (line > 1) {
|
||||
if (line < ml[static_cast<unsigned>(ymTmp.month()) - 1u])
|
||||
if (line < ml[(unsigned)ymTmp.month() - 1u])
|
||||
os << ' '
|
||||
<< fmt::format(fmt::runtime(fmtMap_[4]),
|
||||
(line == 2)
|
||||
? date::sys_days{ymTmp / 1}
|
||||
: date::sys_days{cldGetWeekForLine(ymTmp, firstdow, line)});
|
||||
<< fmt_lib::vformat(
|
||||
m_locale_, fmtMap_[4],
|
||||
fmt_lib::make_format_args(
|
||||
(line == 2) ? static_cast<const zoned_seconds&&>(
|
||||
zoned_seconds{tz, local_days{ymTmp / 1}})
|
||||
: static_cast<const zoned_seconds&&>(
|
||||
zoned_seconds{tz, local_days{cldGetWeekForLine(
|
||||
ymTmp, firstdow, line)}})));
|
||||
else
|
||||
os << std::string(cldWnLen_, ' ');
|
||||
os << pads;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply user formats to calendar
|
||||
// Apply user's formats
|
||||
if (line < 2)
|
||||
tmp << fmt::format(fmt::runtime(fmtMap_[line]), os.str());
|
||||
tmp << fmt_lib::vformat(
|
||||
m_locale_, fmtMap_[line],
|
||||
fmt_lib::make_format_args(static_cast<const std::string_view&&>(os.str())));
|
||||
else
|
||||
tmp << os.str();
|
||||
// Clear ostringstream
|
||||
@ -417,10 +412,13 @@ auto waybar::modules::Clock::get_calendar(const date::zoned_seconds& now,
|
||||
if (row + 1u != maxRows && cldMode_ == CldMode::YEAR) tmp << '\n';
|
||||
}
|
||||
|
||||
os << fmt::format( // Apply days format
|
||||
fmt::runtime(fmt::format(fmt::runtime(fmtMap_[2]), tmp.str())),
|
||||
// Apply today format
|
||||
fmt::arg("today", fmt::format(fmt::runtime(fmtMap_[3]), date::format("%e", ymd.day()))));
|
||||
os << std::regex_replace(
|
||||
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(m_locale_, fmtMap_[3],
|
||||
fmt_lib::make_format_args(
|
||||
static_cast<const std::string_view&&>(date::format("{:L%e}", d)))));
|
||||
|
||||
if (cldMode_ == CldMode::YEAR)
|
||||
cldYearCached_ = os.str();
|
||||
@ -430,50 +428,47 @@ auto waybar::modules::Clock::get_calendar(const date::zoned_seconds& now,
|
||||
return os.str();
|
||||
}
|
||||
|
||||
/*Clock actions*/
|
||||
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]) {
|
||||
(this->*actionMap_[name])();
|
||||
} else
|
||||
spdlog::error("Clock. Unsupported action \"{0}\"", name);
|
||||
}
|
||||
|
||||
// Module actions
|
||||
void waybar::modules::Clock::cldModeSwitch() {
|
||||
cldMode_ = (cldMode_ == CldMode::YEAR) ? CldMode::MONTH : CldMode::YEAR;
|
||||
}
|
||||
void waybar::modules::Clock::cldShift_up() {
|
||||
cldCurrShift_ += ((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;
|
||||
cldCurrShift_ += (months)((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;
|
||||
}
|
||||
void waybar::modules::Clock::cldShift_down() {
|
||||
cldCurrShift_ -= ((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;
|
||||
cldCurrShift_ -= (months)((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;
|
||||
}
|
||||
void waybar::modules::Clock::cldShift_reset() { cldCurrShift_ = (months)0; }
|
||||
void waybar::modules::Clock::tz_up() {
|
||||
auto nr_zones = time_zones_.size();
|
||||
|
||||
if (nr_zones == 1) return;
|
||||
|
||||
size_t new_idx = current_time_zone_idx_ + 1;
|
||||
current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
|
||||
const auto tzSize{tzList_.size()};
|
||||
if (tzSize == 1) return;
|
||||
size_t newIdx{tzCurrIdx_ + 1lu};
|
||||
tzCurrIdx_ = (newIdx == tzSize) ? 0 : newIdx;
|
||||
}
|
||||
void waybar::modules::Clock::tz_down() {
|
||||
auto nr_zones = time_zones_.size();
|
||||
|
||||
if (nr_zones == 1) return;
|
||||
|
||||
current_time_zone_idx_ = current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1;
|
||||
}
|
||||
|
||||
auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_point* now)
|
||||
-> std::string {
|
||||
if (time_zones_.size() == 1) {
|
||||
return "";
|
||||
}
|
||||
std::stringstream os;
|
||||
for (size_t time_zone_idx = 0; time_zone_idx < time_zones_.size(); ++time_zone_idx) {
|
||||
if (static_cast<int>(time_zone_idx) == current_time_zone_idx_) {
|
||||
continue;
|
||||
}
|
||||
const date::time_zone* timezone = time_zones_[time_zone_idx];
|
||||
if (!timezone) {
|
||||
timezone = date::current_zone();
|
||||
}
|
||||
auto ztime = date::zoned_time{timezone, date::floor<std::chrono::seconds>(*now)};
|
||||
os << fmt::format(locale_, fmt::runtime(format_), ztime) << '\n';
|
||||
}
|
||||
return os.str();
|
||||
const auto tzSize{tzList_.size()};
|
||||
if (tzSize == 1) return;
|
||||
tzCurrIdx_ = (tzCurrIdx_ == 0) ? tzSize - 1 : tzCurrIdx_ - 1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LANGINFO_1STDAY
|
||||
@ -485,17 +480,41 @@ using deleting_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>;
|
||||
#endif
|
||||
|
||||
// Computations done similarly to Linux cal utility.
|
||||
auto waybar::modules::Clock::first_day_of_week() -> date::weekday {
|
||||
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 int i = (std::intptr_t)nl_langinfo_l(_NL_TIME_WEEK_1STDAY, posix_locale.get());
|
||||
auto ymd = date::year(i / 10000) / (i / 100 % 100) / (i % 100);
|
||||
auto wd = date::weekday(ymd);
|
||||
uint8_t j = *nl_langinfo_l(_NL_TIME_FIRST_WEEKDAY, posix_locale.get());
|
||||
return wd + date::days(j - 1);
|
||||
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)}};
|
||||
const auto j{(uint8_t)*nl_langinfo_l(_NL_TIME_FIRST_WEEKDAY, posix_locale.get())};
|
||||
return wd + days{j - 1};
|
||||
}
|
||||
#endif
|
||||
return date::Sunday;
|
||||
return Sunday;
|
||||
}
|
||||
|
||||
auto waybar::modules::Clock::get_ordinal_date(const year_month_day& today) -> std::string {
|
||||
auto day = static_cast<unsigned int>(today.day());
|
||||
std::stringstream res;
|
||||
res << day;
|
||||
if (day >= 11 && day <= 13) {
|
||||
res << "th";
|
||||
return res.str();
|
||||
}
|
||||
|
||||
switch (day % 10) {
|
||||
case 1:
|
||||
res << "st";
|
||||
break;
|
||||
case 2:
|
||||
res << "nd";
|
||||
break;
|
||||
case 3:
|
||||
res << "rd";
|
||||
break;
|
||||
default:
|
||||
res << "th";
|
||||
}
|
||||
return res.str();
|
||||
}
|
||||
|
||||
63
src/modules/cpu.cpp
Normal file
63
src/modules/cpu.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
#include "modules/cpu.hpp"
|
||||
|
||||
#include "modules/cpu_frequency.hpp"
|
||||
#include "modules/cpu_usage.hpp"
|
||||
#include "modules/load.hpp"
|
||||
|
||||
// In the 80000 version of fmt library authors decided to optimize imports
|
||||
// and moved declarations required for fmt::dynamic_format_arg_store in new
|
||||
// header fmt/args.h
|
||||
#if (FMT_VERSION >= 80000)
|
||||
#include <fmt/args.h>
|
||||
#else
|
||||
#include <fmt/core.h>
|
||||
#endif
|
||||
|
||||
waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "cpu", id, "{usage}%", 10) {
|
||||
thread_ = [this] {
|
||||
dp.emit();
|
||||
thread_.sleep_for(interval_);
|
||||
};
|
||||
}
|
||||
|
||||
auto waybar::modules::Cpu::update() -> void {
|
||||
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
|
||||
auto [load1, load5, load15] = Load::getLoad();
|
||||
auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_);
|
||||
auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency();
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_text(tooltip);
|
||||
}
|
||||
auto format = format_;
|
||||
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
|
||||
auto state = getState(total_usage);
|
||||
if (!state.empty() && config_["format-" + state].isString()) {
|
||||
format = config_["format-" + state].asString();
|
||||
}
|
||||
|
||||
if (format.empty()) {
|
||||
event_box_.hide();
|
||||
} else {
|
||||
event_box_.show();
|
||||
auto icons = std::vector<std::string>{state};
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(fmt::arg("load", load1));
|
||||
store.push_back(fmt::arg("usage", total_usage));
|
||||
store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
|
||||
store.push_back(fmt::arg("max_frequency", max_frequency));
|
||||
store.push_back(fmt::arg("min_frequency", min_frequency));
|
||||
store.push_back(fmt::arg("avg_frequency", avg_frequency));
|
||||
for (size_t i = 1; i < cpu_usage.size(); ++i) {
|
||||
auto core_i = i - 1;
|
||||
auto core_format = fmt::format("usage{}", core_i);
|
||||
store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i]));
|
||||
auto icon_format = fmt::format("icon{}", core_i);
|
||||
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons)));
|
||||
}
|
||||
label_.set_markup(fmt::vformat(format, store));
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
ALabel::update();
|
||||
}
|
||||
27
src/modules/cpu_frequency/bsd.cpp
Normal file
27
src/modules/cpu_frequency/bsd.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#include "modules/cpu_frequency.hpp"
|
||||
|
||||
std::vector<float> waybar::modules::CpuFrequency::parseCpuFrequencies() {
|
||||
std::vector<float> frequencies;
|
||||
char buffer[256];
|
||||
size_t len;
|
||||
int32_t freq;
|
||||
uint32_t i = 0;
|
||||
|
||||
while (true) {
|
||||
len = 4;
|
||||
snprintf(buffer, 256, "dev.cpu.%u.freq", i);
|
||||
if (sysctlbyname(buffer, &freq, &len, NULL, 0) == -1 || len <= 0) break;
|
||||
frequencies.push_back(freq);
|
||||
++i;
|
||||
}
|
||||
|
||||
if (frequencies.empty()) {
|
||||
spdlog::warn("cpu/bsd: parseCpuFrequencies failed, not found in sysctl");
|
||||
frequencies.push_back(NAN);
|
||||
}
|
||||
|
||||
return frequencies;
|
||||
}
|
||||
67
src/modules/cpu_frequency/common.cpp
Normal file
67
src/modules/cpu_frequency/common.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include "modules/cpu_frequency.hpp"
|
||||
|
||||
// In the 80000 version of fmt library authors decided to optimize imports
|
||||
// and moved declarations required for fmt::dynamic_format_arg_store in new
|
||||
// header fmt/args.h
|
||||
#if (FMT_VERSION >= 80000)
|
||||
#include <fmt/args.h>
|
||||
#else
|
||||
#include <fmt/core.h>
|
||||
#endif
|
||||
|
||||
waybar::modules::CpuFrequency::CpuFrequency(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "cpu_frequency", id, "{avg_frequency}", 10) {
|
||||
thread_ = [this] {
|
||||
dp.emit();
|
||||
thread_.sleep_for(interval_);
|
||||
};
|
||||
}
|
||||
|
||||
auto waybar::modules::CpuFrequency::update() -> void {
|
||||
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
|
||||
auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency();
|
||||
if (tooltipEnabled()) {
|
||||
auto tooltip =
|
||||
fmt::format("Minimum frequency: {}\nAverage frequency: {}\nMaximum frequency: {}\n",
|
||||
min_frequency, avg_frequency, max_frequency);
|
||||
label_.set_tooltip_text(tooltip);
|
||||
}
|
||||
auto format = format_;
|
||||
auto state = getState(avg_frequency);
|
||||
if (!state.empty() && config_["format-" + state].isString()) {
|
||||
format = config_["format-" + state].asString();
|
||||
}
|
||||
|
||||
if (format.empty()) {
|
||||
event_box_.hide();
|
||||
} else {
|
||||
event_box_.show();
|
||||
auto icons = std::vector<std::string>{state};
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(fmt::arg("icon", getIcon(avg_frequency, icons)));
|
||||
store.push_back(fmt::arg("max_frequency", max_frequency));
|
||||
store.push_back(fmt::arg("min_frequency", min_frequency));
|
||||
store.push_back(fmt::arg("avg_frequency", avg_frequency));
|
||||
label_.set_markup(fmt::vformat(format, store));
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
std::tuple<float, float, float> waybar::modules::CpuFrequency::getCpuFrequency() {
|
||||
std::vector<float> frequencies = CpuFrequency::parseCpuFrequencies();
|
||||
if (frequencies.empty()) {
|
||||
return {0.f, 0.f, 0.f};
|
||||
}
|
||||
auto [min, max] = std::minmax_element(std::begin(frequencies), std::end(frequencies));
|
||||
float avg_frequency =
|
||||
std::accumulate(std::begin(frequencies), std::end(frequencies), 0.0) / frequencies.size();
|
||||
|
||||
// Round frequencies with double decimal precision to get GHz
|
||||
float max_frequency = std::ceil(*max / 10.0) / 100.0;
|
||||
float min_frequency = std::ceil(*min / 10.0) / 100.0;
|
||||
avg_frequency = std::ceil(avg_frequency / 10.0) / 100.0;
|
||||
|
||||
return {max_frequency, min_frequency, avg_frequency};
|
||||
}
|
||||
@ -1,36 +1,8 @@
|
||||
#include <filesystem>
|
||||
|
||||
#include "modules/cpu.hpp"
|
||||
#include "modules/cpu_frequency.hpp"
|
||||
|
||||
std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
|
||||
const std::string data_dir_ = "/proc/stat";
|
||||
std::ifstream info(data_dir_);
|
||||
if (!info.is_open()) {
|
||||
throw std::runtime_error("Can't open " + data_dir_);
|
||||
}
|
||||
std::vector<std::tuple<size_t, size_t>> cpuinfo;
|
||||
std::string line;
|
||||
while (getline(info, line)) {
|
||||
if (line.substr(0, 3).compare("cpu") != 0) {
|
||||
break;
|
||||
}
|
||||
std::stringstream sline(line.substr(5));
|
||||
std::vector<size_t> times;
|
||||
for (size_t time = 0; sline >> time; times.push_back(time))
|
||||
;
|
||||
|
||||
size_t idle_time = 0;
|
||||
size_t total_time = 0;
|
||||
if (times.size() >= 4) {
|
||||
idle_time = times[3];
|
||||
total_time = std::accumulate(times.begin(), times.end(), 0);
|
||||
}
|
||||
cpuinfo.emplace_back(idle_time, total_time);
|
||||
}
|
||||
return cpuinfo;
|
||||
}
|
||||
|
||||
std::vector<float> waybar::modules::Cpu::parseCpuFrequencies() {
|
||||
std::vector<float> waybar::modules::CpuFrequency::parseCpuFrequencies() {
|
||||
const std::string file_path_ = "/proc/cpuinfo";
|
||||
std::ifstream info(file_path_);
|
||||
if (!info.is_open()) {
|
||||
@ -8,7 +8,8 @@
|
||||
#include <cmath> // NAN
|
||||
#include <cstdlib> // malloc
|
||||
|
||||
#include "modules/cpu.hpp"
|
||||
#include "modules/cpu_usage.hpp"
|
||||
#include "util/scope_guard.hpp"
|
||||
|
||||
#if defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
#include <sys/sched.h>
|
||||
@ -27,12 +28,17 @@ typedef uint64_t pcp_time_t;
|
||||
typedef long pcp_time_t;
|
||||
#endif
|
||||
|
||||
std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
|
||||
std::vector<std::tuple<size_t, size_t>> waybar::modules::CpuUsage::parseCpuinfo() {
|
||||
cp_time_t sum_cp_time[CPUSTATES];
|
||||
size_t sum_sz = sizeof(sum_cp_time);
|
||||
int ncpu = sysconf(_SC_NPROCESSORS_CONF);
|
||||
size_t sz = CPUSTATES * (ncpu + 1) * sizeof(pcp_time_t);
|
||||
pcp_time_t *cp_time = static_cast<pcp_time_t *>(malloc(sz)), *pcp_time = cp_time;
|
||||
waybar::util::ScopeGuard cp_time_deleter([cp_time]() {
|
||||
if (cp_time) {
|
||||
free(cp_time);
|
||||
}
|
||||
});
|
||||
#if defined(__NetBSD__)
|
||||
int mib[] = {
|
||||
CTL_KERN,
|
||||
@ -97,16 +103,5 @@ std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
|
||||
}
|
||||
cpuinfo.emplace_back(single_cp_time[CP_IDLE], total);
|
||||
}
|
||||
free(cp_time);
|
||||
return cpuinfo;
|
||||
}
|
||||
|
||||
std::vector<float> waybar::modules::Cpu::parseCpuFrequencies() {
|
||||
static std::vector<float> frequencies;
|
||||
if (frequencies.empty()) {
|
||||
spdlog::warn(
|
||||
"cpu/bsd: parseCpuFrequencies is not implemented, expect garbage in {*_frequency}");
|
||||
frequencies.push_back(NAN);
|
||||
}
|
||||
return frequencies;
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
#include "modules/cpu.hpp"
|
||||
#include "modules/cpu_usage.hpp"
|
||||
|
||||
// In the 80000 version of fmt library authors decided to optimize imports
|
||||
// and moved declarations required for fmt::dynamic_format_arg_store in new
|
||||
@ -9,19 +9,17 @@
|
||||
#include <fmt/core.h>
|
||||
#endif
|
||||
|
||||
waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "cpu", id, "{usage}%", 10) {
|
||||
waybar::modules::CpuUsage::CpuUsage(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "cpu_usage", id, "{usage}%", 10) {
|
||||
thread_ = [this] {
|
||||
dp.emit();
|
||||
thread_.sleep_for(interval_);
|
||||
};
|
||||
}
|
||||
|
||||
auto waybar::modules::Cpu::update() -> void {
|
||||
auto waybar::modules::CpuUsage::update() -> void {
|
||||
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
|
||||
auto cpu_load = getCpuLoad();
|
||||
auto [cpu_usage, tooltip] = getCpuUsage();
|
||||
auto [max_frequency, min_frequency, avg_frequency] = getCpuFrequency();
|
||||
auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_);
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_text(tooltip);
|
||||
}
|
||||
@ -38,12 +36,8 @@ auto waybar::modules::Cpu::update() -> void {
|
||||
event_box_.show();
|
||||
auto icons = std::vector<std::string>{state};
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(fmt::arg("load", cpu_load));
|
||||
store.push_back(fmt::arg("usage", total_usage));
|
||||
store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
|
||||
store.push_back(fmt::arg("max_frequency", max_frequency));
|
||||
store.push_back(fmt::arg("min_frequency", min_frequency));
|
||||
store.push_back(fmt::arg("avg_frequency", avg_frequency));
|
||||
for (size_t i = 1; i < cpu_usage.size(); ++i) {
|
||||
auto core_i = i - 1;
|
||||
auto core_format = fmt::format("usage{}", core_i);
|
||||
@ -58,25 +52,45 @@ auto waybar::modules::Cpu::update() -> void {
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
double waybar::modules::Cpu::getCpuLoad() {
|
||||
double load[1];
|
||||
if (getloadavg(load, 1) != -1) {
|
||||
return std::ceil(load[0] * 100.0) / 100.0;
|
||||
}
|
||||
throw std::runtime_error("Can't get Cpu load");
|
||||
}
|
||||
|
||||
std::tuple<std::vector<uint16_t>, std::string> waybar::modules::Cpu::getCpuUsage() {
|
||||
if (prev_times_.empty()) {
|
||||
prev_times_ = parseCpuinfo();
|
||||
std::tuple<std::vector<uint16_t>, std::string> waybar::modules::CpuUsage::getCpuUsage(
|
||||
std::vector<std::tuple<size_t, size_t>>& prev_times) {
|
||||
if (prev_times.empty()) {
|
||||
prev_times = CpuUsage::parseCpuinfo();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
std::vector<std::tuple<size_t, size_t>> curr_times = parseCpuinfo();
|
||||
std::vector<std::tuple<size_t, size_t>> curr_times = CpuUsage::parseCpuinfo();
|
||||
std::string tooltip;
|
||||
std::vector<uint16_t> usage;
|
||||
|
||||
if (curr_times.size() != prev_times.size()) {
|
||||
// The number of CPUs has changed, eg. due to CPU hotplug
|
||||
// We don't know which CPU came up or went down
|
||||
// so only give total usage (if we can)
|
||||
if (!curr_times.empty() && !prev_times.empty()) {
|
||||
auto [curr_idle, curr_total] = curr_times[0];
|
||||
auto [prev_idle, prev_total] = prev_times[0];
|
||||
const float delta_idle = curr_idle - prev_idle;
|
||||
const float delta_total = curr_total - prev_total;
|
||||
uint16_t tmp = 100 * (1 - delta_idle / delta_total);
|
||||
tooltip = fmt::format("Total: {}%\nCores: (pending)", tmp);
|
||||
usage.push_back(tmp);
|
||||
} else {
|
||||
tooltip = "(pending)";
|
||||
usage.push_back(0);
|
||||
}
|
||||
prev_times = curr_times;
|
||||
return {usage, tooltip};
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < curr_times.size(); ++i) {
|
||||
auto [curr_idle, curr_total] = curr_times[i];
|
||||
auto [prev_idle, prev_total] = prev_times_[i];
|
||||
auto [prev_idle, prev_total] = prev_times[i];
|
||||
if (i > 0 && (curr_total == 0 || prev_total == 0)) {
|
||||
// This CPU is offline
|
||||
tooltip = tooltip + fmt::format("\nCore{}: offline", i - 1);
|
||||
usage.push_back(0);
|
||||
continue;
|
||||
}
|
||||
const float delta_idle = curr_idle - prev_idle;
|
||||
const float delta_total = curr_total - prev_total;
|
||||
uint16_t tmp = 100 * (1 - delta_idle / delta_total);
|
||||
@ -87,23 +101,6 @@ std::tuple<std::vector<uint16_t>, std::string> waybar::modules::Cpu::getCpuUsage
|
||||
}
|
||||
usage.push_back(tmp);
|
||||
}
|
||||
prev_times_ = curr_times;
|
||||
prev_times = curr_times;
|
||||
return {usage, tooltip};
|
||||
}
|
||||
|
||||
std::tuple<float, float, float> waybar::modules::Cpu::getCpuFrequency() {
|
||||
std::vector<float> frequencies = parseCpuFrequencies();
|
||||
if (frequencies.empty()) {
|
||||
return {0.f, 0.f, 0.f};
|
||||
}
|
||||
auto [min, max] = std::minmax_element(std::begin(frequencies), std::end(frequencies));
|
||||
float avg_frequency =
|
||||
std::accumulate(std::begin(frequencies), std::end(frequencies), 0.0) / frequencies.size();
|
||||
|
||||
// Round frequencies with double decimal precision to get GHz
|
||||
float max_frequency = std::ceil(*max / 10.0) / 100.0;
|
||||
float min_frequency = std::ceil(*min / 10.0) / 100.0;
|
||||
avg_frequency = std::ceil(avg_frequency / 10.0) / 100.0;
|
||||
|
||||
return {max_frequency, min_frequency, avg_frequency};
|
||||
}
|
||||
66
src/modules/cpu_usage/linux.cpp
Normal file
66
src/modules/cpu_usage/linux.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
#include <filesystem>
|
||||
|
||||
#include "modules/cpu_usage.hpp"
|
||||
|
||||
std::vector<std::tuple<size_t, size_t>> waybar::modules::CpuUsage::parseCpuinfo() {
|
||||
// Get the "existing CPU count" from /sys/devices/system/cpu/present
|
||||
// Probably this is what the user wants the offline CPUs accounted from
|
||||
// For further details see:
|
||||
// https://www.kernel.org/doc/html/latest/core-api/cpu_hotplug.html
|
||||
const std::string sys_cpu_present_path = "/sys/devices/system/cpu/present";
|
||||
size_t cpu_present_last = 0;
|
||||
std::ifstream cpu_present_file(sys_cpu_present_path);
|
||||
std::string cpu_present_text;
|
||||
if (cpu_present_file.is_open()) {
|
||||
getline(cpu_present_file, cpu_present_text);
|
||||
// This is a comma-separated list of ranges, eg. 0,2-4,7
|
||||
size_t last_separator = cpu_present_text.find_last_of("-,");
|
||||
if (last_separator < cpu_present_text.size()) {
|
||||
std::stringstream(cpu_present_text.substr(last_separator + 1)) >> cpu_present_last;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string data_dir_ = "/proc/stat";
|
||||
std::ifstream info(data_dir_);
|
||||
if (!info.is_open()) {
|
||||
throw std::runtime_error("Can't open " + data_dir_);
|
||||
}
|
||||
std::vector<std::tuple<size_t, size_t>> cpuinfo;
|
||||
std::string line;
|
||||
size_t current_cpu_number = -1; // First line is total, second line is cpu 0
|
||||
while (getline(info, line)) {
|
||||
if (line.substr(0, 3).compare("cpu") != 0) {
|
||||
break;
|
||||
}
|
||||
size_t line_cpu_number;
|
||||
if (current_cpu_number >= 0) {
|
||||
std::stringstream(line.substr(3)) >> line_cpu_number;
|
||||
while (line_cpu_number > current_cpu_number) {
|
||||
// Fill in 0 for offline CPUs missing inside the lines of /proc/stat
|
||||
cpuinfo.emplace_back(0, 0);
|
||||
current_cpu_number++;
|
||||
}
|
||||
}
|
||||
std::stringstream sline(line.substr(5));
|
||||
std::vector<size_t> times;
|
||||
for (size_t time = 0; sline >> time; times.push_back(time));
|
||||
|
||||
size_t idle_time = 0;
|
||||
size_t total_time = 0;
|
||||
if (times.size() >= 5) {
|
||||
// idle + iowait
|
||||
idle_time = times[3] + times[4];
|
||||
total_time = std::accumulate(times.begin(), times.end(), 0);
|
||||
}
|
||||
cpuinfo.emplace_back(idle_time, total_time);
|
||||
current_cpu_number++;
|
||||
}
|
||||
|
||||
while (cpu_present_last >= current_cpu_number) {
|
||||
// Fill in 0 for offline CPUs missing after the lines of /proc/stat
|
||||
cpuinfo.emplace_back(0, 0);
|
||||
current_cpu_number++;
|
||||
}
|
||||
|
||||
return cpuinfo;
|
||||
}
|
||||
@ -2,16 +2,23 @@
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "util/scope_guard.hpp"
|
||||
|
||||
waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
|
||||
const Json::Value& config)
|
||||
const Json::Value& config, const std::string& output_name)
|
||||
: ALabel(config, "custom-" + name, id, "{}"),
|
||||
name_(name),
|
||||
output_name_(output_name),
|
||||
id_(id),
|
||||
tooltip_format_enabled_{config_["tooltip-format"].isString()},
|
||||
percentage_(0),
|
||||
fp_(nullptr),
|
||||
pid_(-1) {
|
||||
dp.emit();
|
||||
if (interval_.count() > 0) {
|
||||
if (!config_["signal"].empty() && config_["interval"].empty() &&
|
||||
config_["restart-interval"].empty()) {
|
||||
waitingWorker();
|
||||
} else if (interval_.count() > 0) {
|
||||
delayWorker();
|
||||
} else if (config_["exec"].isString()) {
|
||||
continuousWorker();
|
||||
@ -46,7 +53,7 @@ void waybar::modules::Custom::delayWorker() {
|
||||
}
|
||||
if (can_update) {
|
||||
if (config_["exec"].isString()) {
|
||||
output_ = util::command::exec(config_["exec"].asString());
|
||||
output_ = util::command::exec(config_["exec"].asString(), output_name_);
|
||||
}
|
||||
dp.emit();
|
||||
}
|
||||
@ -57,12 +64,17 @@ void waybar::modules::Custom::delayWorker() {
|
||||
void waybar::modules::Custom::continuousWorker() {
|
||||
auto cmd = config_["exec"].asString();
|
||||
pid_ = -1;
|
||||
fp_ = util::command::open(cmd, pid_);
|
||||
fp_ = util::command::open(cmd, pid_, output_name_);
|
||||
if (!fp_) {
|
||||
throw std::runtime_error("Unable to open " + cmd);
|
||||
}
|
||||
thread_ = [this, cmd] {
|
||||
char* buff = nullptr;
|
||||
waybar::util::ScopeGuard buff_deleter([&buff]() {
|
||||
if (buff) {
|
||||
free(buff);
|
||||
}
|
||||
});
|
||||
size_t len = 0;
|
||||
if (getline(&buff, &len, fp_) == -1) {
|
||||
int exit_code = 1;
|
||||
@ -78,7 +90,7 @@ void waybar::modules::Custom::continuousWorker() {
|
||||
if (config_["restart-interval"].isUInt()) {
|
||||
pid_ = -1;
|
||||
thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt()));
|
||||
fp_ = util::command::open(cmd, pid_);
|
||||
fp_ = util::command::open(cmd, pid_, output_name_);
|
||||
if (!fp_) {
|
||||
throw std::runtime_error("Unable to open " + cmd);
|
||||
}
|
||||
@ -96,7 +108,26 @@ void waybar::modules::Custom::continuousWorker() {
|
||||
output_ = {0, output};
|
||||
dp.emit();
|
||||
}
|
||||
free(buff);
|
||||
};
|
||||
}
|
||||
|
||||
void waybar::modules::Custom::waitingWorker() {
|
||||
thread_ = [this] {
|
||||
bool can_update = true;
|
||||
if (config_["exec-if"].isString()) {
|
||||
output_ = util::command::execNoRead(config_["exec-if"].asString());
|
||||
if (output_.exit_code != 0) {
|
||||
can_update = false;
|
||||
dp.emit();
|
||||
}
|
||||
}
|
||||
if (can_update) {
|
||||
if (config_["exec"].isString()) {
|
||||
output_ = util::command::exec(config_["exec"].asString(), output_name_);
|
||||
}
|
||||
dp.emit();
|
||||
}
|
||||
thread_.sleep();
|
||||
};
|
||||
}
|
||||
|
||||
@ -135,35 +166,53 @@ auto waybar::modules::Custom::update() -> void {
|
||||
} else {
|
||||
parseOutputRaw();
|
||||
}
|
||||
auto str = fmt::format(fmt::runtime(format_), text_, fmt::arg("alt", alt_),
|
||||
fmt::arg("icon", getIcon(percentage_, alt_)),
|
||||
fmt::arg("percentage", percentage_));
|
||||
if (str.empty()) {
|
||||
event_box_.hide();
|
||||
} else {
|
||||
label_.set_markup(str);
|
||||
if (tooltipEnabled()) {
|
||||
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_);
|
||||
|
||||
try {
|
||||
auto str = fmt::format(fmt::runtime(format_), fmt::arg("text", text_), fmt::arg("alt", alt_),
|
||||
fmt::arg("icon", getIcon(percentage_, alt_)),
|
||||
fmt::arg("percentage", percentage_));
|
||||
if ((config_["hide-empty-text"].asBool() && text_.empty()) || str.empty()) {
|
||||
event_box_.hide();
|
||||
} else {
|
||||
label_.set_markup(str);
|
||||
if (tooltipEnabled()) {
|
||||
if (tooltip_format_enabled_) {
|
||||
auto tooltip = config_["tooltip-format"].asString();
|
||||
tooltip = fmt::format(
|
||||
fmt::runtime(tooltip), fmt::arg("text", 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_);
|
||||
}
|
||||
}
|
||||
}
|
||||
auto style = label_.get_style_context();
|
||||
auto classes = style->list_classes();
|
||||
for (auto const& c : classes) {
|
||||
if (c == id_) continue;
|
||||
style->remove_class(c);
|
||||
}
|
||||
for (auto const& c : class_) {
|
||||
style->add_class(c);
|
||||
}
|
||||
style->add_class("flat");
|
||||
style->add_class("text-button");
|
||||
style->add_class(MODULE_CLASS);
|
||||
event_box_.show();
|
||||
}
|
||||
auto classes = label_.get_style_context()->list_classes();
|
||||
for (auto const& c : classes) {
|
||||
if (c == id_) continue;
|
||||
label_.get_style_context()->remove_class(c);
|
||||
}
|
||||
for (auto const& c : class_) {
|
||||
label_.get_style_context()->add_class(c);
|
||||
}
|
||||
label_.get_style_context()->add_class("flat");
|
||||
label_.get_style_context()->add_class("text-button");
|
||||
event_box_.show();
|
||||
} catch (const fmt::format_error& e) {
|
||||
if (std::strcmp(e.what(), "cannot switch from manual to automatic argument indexing") != 0)
|
||||
throw;
|
||||
|
||||
throw fmt::format_error(
|
||||
"mixing manual and automatic argument indexing is no longer supported; "
|
||||
"try replacing \"{}\" with \"{text}\" in your format specifier");
|
||||
}
|
||||
}
|
||||
// Call parent update
|
||||
@ -175,18 +224,29 @@ void waybar::modules::Custom::parseOutputRaw() {
|
||||
std::string line;
|
||||
int i = 0;
|
||||
while (getline(output, line)) {
|
||||
Glib::ustring validated_line = line;
|
||||
if (!validated_line.validate()) {
|
||||
validated_line = validated_line.make_valid();
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
if (config_["escape"].isBool() && config_["escape"].asBool()) {
|
||||
text_ = Glib::Markup::escape_text(line);
|
||||
text_ = Glib::Markup::escape_text(validated_line);
|
||||
tooltip_ = Glib::Markup::escape_text(validated_line);
|
||||
} else {
|
||||
text_ = line;
|
||||
text_ = validated_line;
|
||||
tooltip_ = validated_line;
|
||||
}
|
||||
tooltip_ = line;
|
||||
tooltip_ = validated_line;
|
||||
class_.clear();
|
||||
} else if (i == 1) {
|
||||
tooltip_ = 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(line);
|
||||
class_.push_back(validated_line);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@ -210,7 +270,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()) {
|
||||
|
||||
@ -11,6 +11,9 @@ waybar::modules::Disk::Disk(const std::string& id, const Json::Value& config)
|
||||
if (config["path"].isString()) {
|
||||
path_ = config["path"].asString();
|
||||
}
|
||||
if (config["unit"].isString()) {
|
||||
unit_ = config["unit"].asString();
|
||||
}
|
||||
}
|
||||
|
||||
auto waybar::modules::Disk::update() -> void {
|
||||
@ -43,6 +46,13 @@ auto waybar::modules::Disk::update() -> void {
|
||||
return;
|
||||
}
|
||||
|
||||
float specific_free, specific_used, specific_total, divisor;
|
||||
|
||||
divisor = calc_specific_divisor(unit_);
|
||||
specific_free = (stats.f_bavail * stats.f_frsize) / divisor;
|
||||
specific_used = ((stats.f_blocks - stats.f_bfree) * stats.f_frsize) / divisor;
|
||||
specific_total = (stats.f_blocks * stats.f_frsize) / divisor;
|
||||
|
||||
auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true);
|
||||
auto used = pow_format((stats.f_blocks - stats.f_bfree) * stats.f_frsize, "B", true);
|
||||
auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true);
|
||||
@ -62,7 +72,8 @@ auto waybar::modules::Disk::update() -> void {
|
||||
fmt::runtime(format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
|
||||
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
|
||||
fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),
|
||||
fmt::arg("path", path_)));
|
||||
fmt::arg("path", path_), fmt::arg("specific_free", specific_free),
|
||||
fmt::arg("specific_used", specific_used), fmt::arg("specific_total", specific_total)));
|
||||
}
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
@ -74,8 +85,31 @@ auto waybar::modules::Disk::update() -> void {
|
||||
fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
|
||||
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
|
||||
fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),
|
||||
fmt::arg("path", path_)));
|
||||
fmt::arg("path", path_), fmt::arg("specific_free", specific_free),
|
||||
fmt::arg("specific_used", specific_used), fmt::arg("specific_total", specific_total)));
|
||||
}
|
||||
// Call parent update
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
float waybar::modules::Disk::calc_specific_divisor(std::string divisor) {
|
||||
if (divisor == "kB") {
|
||||
return 1000.0;
|
||||
} else if (divisor == "kiB") {
|
||||
return 1024.0;
|
||||
} else if (divisor == "MB") {
|
||||
return 1000.0 * 1000.0;
|
||||
} else if (divisor == "MiB") {
|
||||
return 1024.0 * 1024.0;
|
||||
} else if (divisor == "GB") {
|
||||
return 1000.0 * 1000.0 * 1000.0;
|
||||
} else if (divisor == "GiB") {
|
||||
return 1024.0 * 1024.0 * 1024.0;
|
||||
} else if (divisor == "TB") {
|
||||
return 1000.0 * 1000.0 * 1000.0 * 1000.0;
|
||||
} else if (divisor == "TiB") {
|
||||
return 1024.0 * 1024.0 * 1024.0 * 1024.0;
|
||||
} else { // default to Bytes if it is anything that we don't recongnise
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -53,8 +53,8 @@ static void set_layout(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t
|
||||
// Intentionally empty
|
||||
}
|
||||
|
||||
static void appid(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *appid){
|
||||
// Intentionally empty
|
||||
static void appid(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *appid) {
|
||||
// Intentionally empty
|
||||
};
|
||||
|
||||
static const zdwl_ipc_output_v2_listener output_status_listener_impl{
|
||||
@ -93,10 +93,11 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
|
||||
status_manager_{nullptr},
|
||||
seat_{nullptr},
|
||||
bar_(bar),
|
||||
box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
|
||||
box_{bar.orientation, 0},
|
||||
output_status_{nullptr} {
|
||||
struct wl_display *display = Client::inst()->wl_display;
|
||||
struct wl_registry *registry = wl_display_get_registry(display);
|
||||
|
||||
wl_registry_add_listener(registry, ®istry_listener_impl, this);
|
||||
wl_display_roundtrip(display);
|
||||
|
||||
@ -113,6 +114,7 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
box_.get_style_context()->add_class(MODULE_CLASS);
|
||||
event_box_.add(box_);
|
||||
|
||||
// Default to 9 tags, cap at 32
|
||||
@ -154,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_);
|
||||
}
|
||||
|
||||
123
src/modules/dwl/window.cpp
Normal file
123
src/modules/dwl/window.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
#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 "glibmm/markup.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_ = Glib::Markup::escape_text(title); }
|
||||
|
||||
void Window::handle_appid(const char *appid) { appid_ = Glib::Markup::escape_text(appid); }
|
||||
|
||||
void Window::handle_layout_symbol(const char *layout_symbol) {
|
||||
layout_symbol_ = Glib::Markup::escape_text(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
|
||||
@ -1,99 +1,145 @@
|
||||
#include "modules/hyprland/backend.hpp"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
void IPC::startIPC() {
|
||||
std::filesystem::path IPC::socketFolder_;
|
||||
|
||||
std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
|
||||
static std::mutex folderMutex;
|
||||
std::unique_lock lock(folderMutex);
|
||||
|
||||
// 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_;
|
||||
}
|
||||
|
||||
IPC::IPC() {
|
||||
// will start IPC and relay events to parseIPC
|
||||
ipcThread_ = std::thread([this]() { socketListener(); });
|
||||
}
|
||||
|
||||
std::thread([&]() {
|
||||
// check for hyprland
|
||||
const char* HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
|
||||
if (!HIS) {
|
||||
spdlog::warn("Hyprland is not running, Hyprland IPC will not be available.");
|
||||
return;
|
||||
IPC::~IPC() {
|
||||
running_ = false;
|
||||
spdlog::info("Hyprland IPC stopping...");
|
||||
if (socketfd_ != -1) {
|
||||
spdlog::trace("Shutting down socket");
|
||||
if (shutdown(socketfd_, SHUT_RDWR) == -1) {
|
||||
spdlog::error("Hyprland IPC: Couldn't shutdown socket");
|
||||
}
|
||||
|
||||
if (!modulesReady) return;
|
||||
|
||||
spdlog::info("Hyprland IPC starting");
|
||||
|
||||
struct sockaddr_un addr;
|
||||
int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
if (socketfd == -1) {
|
||||
spdlog::error("Hyprland IPC: socketfd failed");
|
||||
return;
|
||||
spdlog::trace("Closing socket");
|
||||
if (close(socketfd_) == -1) {
|
||||
spdlog::error("Hyprland IPC: Couldn't close socket");
|
||||
}
|
||||
}
|
||||
ipcThread_.join();
|
||||
}
|
||||
|
||||
addr.sun_family = AF_UNIX;
|
||||
IPC& IPC::inst() {
|
||||
static IPC ipc;
|
||||
return ipc;
|
||||
}
|
||||
|
||||
// socket path
|
||||
std::string socketPath = "/tmp/hypr/" + std::string(HIS) + "/.socket2.sock";
|
||||
void IPC::socketListener() {
|
||||
// check for hyprland
|
||||
const char* his = getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
|
||||
strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
|
||||
if (his == nullptr) {
|
||||
spdlog::warn("Hyprland is not running, Hyprland IPC will not be available.");
|
||||
return;
|
||||
}
|
||||
|
||||
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
|
||||
if (!modulesReady) return;
|
||||
|
||||
int l = sizeof(struct sockaddr_un);
|
||||
spdlog::info("Hyprland IPC starting");
|
||||
|
||||
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {
|
||||
spdlog::error("Hyprland IPC: Unable to connect?");
|
||||
return;
|
||||
}
|
||||
struct sockaddr_un addr;
|
||||
socketfd_ = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
auto file = fdopen(socketfd, "r");
|
||||
if (socketfd_ == -1) {
|
||||
spdlog::error("Hyprland IPC: socketfd failed");
|
||||
return;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
// read
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
char buffer[1024]; // Hyprland socket2 events are max 1024 bytes
|
||||
auto recievedCharPtr = fgets(buffer, 1024, file);
|
||||
auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock";
|
||||
strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
|
||||
|
||||
if (!recievedCharPtr) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
continue;
|
||||
}
|
||||
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
|
||||
|
||||
callbackMutex.lock();
|
||||
int l = sizeof(struct sockaddr_un);
|
||||
|
||||
std::string messageRecieved(buffer);
|
||||
if (connect(socketfd_, (struct sockaddr*)&addr, l) == -1) {
|
||||
spdlog::error("Hyprland IPC: Unable to connect?");
|
||||
return;
|
||||
}
|
||||
auto* file = fdopen(socketfd_, "r");
|
||||
if (file == nullptr) {
|
||||
spdlog::error("Hyprland IPC: Couldn't open file descriptor");
|
||||
return;
|
||||
}
|
||||
while (running_) {
|
||||
std::array<char, 1024> buffer; // Hyprland socket2 events are max 1024 bytes
|
||||
|
||||
messageRecieved = messageRecieved.substr(0, messageRecieved.find_first_of('\n'));
|
||||
|
||||
spdlog::debug("hyprland IPC received {}", messageRecieved);
|
||||
|
||||
parseIPC(messageRecieved);
|
||||
|
||||
callbackMutex.unlock();
|
||||
auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file);
|
||||
|
||||
if (receivedCharPtr == nullptr) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
continue;
|
||||
}
|
||||
}).detach();
|
||||
|
||||
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));
|
||||
}
|
||||
spdlog::debug("Hyprland IPC stopped");
|
||||
}
|
||||
|
||||
void IPC::parseIPC(const std::string& ev) {
|
||||
// todo
|
||||
std::string request = ev.substr(0, ev.find_first_of('>'));
|
||||
std::unique_lock lock(callbackMutex_);
|
||||
|
||||
for (auto& [eventname, handler] : callbacks) {
|
||||
for (auto& [eventname, handler] : callbacks_) {
|
||||
if (eventname == request) {
|
||||
handler->onEvent(ev);
|
||||
}
|
||||
@ -101,105 +147,97 @@ 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;
|
||||
}
|
||||
callbackMutex.lock();
|
||||
|
||||
callbacks.emplace_back(std::make_pair(ev, ev_handler));
|
||||
|
||||
callbackMutex.unlock();
|
||||
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;
|
||||
}
|
||||
|
||||
callbackMutex.lock();
|
||||
std::unique_lock lock(callbackMutex_);
|
||||
|
||||
for (auto it = callbacks.begin(); it != callbacks.end();) {
|
||||
auto it_current = it;
|
||||
it++;
|
||||
auto& [eventname, handler] = *it_current;
|
||||
for (auto it = callbacks_.begin(); it != callbacks_.end();) {
|
||||
auto& [eventname, handler] = *it;
|
||||
if (handler == ev_handler) {
|
||||
callbacks.erase(it_current);
|
||||
callbacks_.erase(it++);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
callbackMutex.unlock();
|
||||
}
|
||||
|
||||
std::string IPC::getSocket1Reply(const std::string& rq) {
|
||||
// basically hyprctl
|
||||
|
||||
struct addrinfo ai_hints;
|
||||
struct addrinfo* ai_res = NULL;
|
||||
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
const auto serverSocket = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
if (SERVERSOCKET < 0) {
|
||||
spdlog::error("Hyprland IPC: Couldn't open a socket (1)");
|
||||
return "";
|
||||
}
|
||||
|
||||
memset(&ai_hints, 0, sizeof(struct addrinfo));
|
||||
ai_hints.ai_family = AF_UNSPEC;
|
||||
ai_hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
if (getaddrinfo("localhost", NULL, &ai_hints, &ai_res) != 0) {
|
||||
spdlog::error("Hyprland IPC: Couldn't get host (2)");
|
||||
return "";
|
||||
if (serverSocket < 0) {
|
||||
throw std::runtime_error("Hyprland IPC: Couldn't open a socket (1)");
|
||||
}
|
||||
|
||||
// get the instance signature
|
||||
auto instanceSig = getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
auto* instanceSig = getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
|
||||
if (!instanceSig) {
|
||||
spdlog::error("Hyprland IPC: HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?)");
|
||||
return "";
|
||||
if (instanceSig == nullptr) {
|
||||
throw std::runtime_error(
|
||||
"Hyprland IPC: HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?)");
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
strcpy(serverAddress.sun_path, socketPath.c_str());
|
||||
|
||||
if (connect(SERVERSOCKET, (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) {
|
||||
spdlog::error("Hyprland IPC: Couldn't connect to " + socketPath + ". (3)");
|
||||
return "";
|
||||
// Use snprintf to copy the socketPath string into serverAddress.sun_path
|
||||
if (snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), "%s", socketPath.c_str()) <
|
||||
0) {
|
||||
throw std::runtime_error("Hyprland IPC: Couldn't copy socket path (6)");
|
||||
}
|
||||
|
||||
auto sizeWritten = write(SERVERSOCKET, rq.c_str(), rq.length());
|
||||
if (connect(serverSocket, reinterpret_cast<sockaddr*>(&serverAddress), sizeof(serverAddress)) <
|
||||
0) {
|
||||
throw std::runtime_error("Hyprland IPC: Couldn't connect to " + socketPath + ". (3)");
|
||||
}
|
||||
|
||||
auto sizeWritten = write(serverSocket, rq.c_str(), rq.length());
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
spdlog::error("Hyprland IPC: Couldn't write (4)");
|
||||
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);
|
||||
close(serverSocket);
|
||||
return "";
|
||||
}
|
||||
response.append(buffer, sizeWritten);
|
||||
} while (sizeWritten == 8192);
|
||||
response.append(buffer.data(), sizeWritten);
|
||||
} while (sizeWritten > 0);
|
||||
|
||||
close(SERVERSOCKET);
|
||||
close(serverSocket);
|
||||
return response;
|
||||
}
|
||||
|
||||
Json::Value IPC::getSocket1JsonReply(const std::string& rq) {
|
||||
return parser_.parse(getSocket1Reply("j/" + rq));
|
||||
std::string reply = getSocket1Reply("j/" + rq);
|
||||
|
||||
if (reply.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return parser_.parse(reply);
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::hyprland
|
||||
|
||||
@ -4,20 +4,15 @@
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon/xkbregistry.h>
|
||||
|
||||
#include <util/sanitize_str.hpp>
|
||||
|
||||
#include "util/sanitize_str.hpp"
|
||||
#include "util/string.hpp"
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
Language::Language(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: ALabel(config, "language", id, "{}", 0, true), bar_(bar) {
|
||||
: ALabel(config, "language", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
|
||||
modulesReady = true;
|
||||
|
||||
if (!gIPC.get()) {
|
||||
gIPC = std::make_unique<IPC>();
|
||||
}
|
||||
|
||||
// get the active layout when open
|
||||
initLanguage();
|
||||
|
||||
@ -25,11 +20,11 @@ Language::Language(const std::string& id, const Bar& bar, const Json::Value& con
|
||||
update();
|
||||
|
||||
// register for hyprland ipc
|
||||
gIPC->registerForIPC("activelayout", this);
|
||||
m_ipc.registerForIPC("activelayout", this);
|
||||
}
|
||||
|
||||
Language::~Language() {
|
||||
gIPC->unregisterForIPC(this);
|
||||
m_ipc.unregisterForIPC(this);
|
||||
// wait for possible event handler to finish
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
}
|
||||
@ -37,8 +32,16 @@ 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)) {
|
||||
if (config_.isMember("format-" + layout_.short_description + "-" + layout_.variant)) {
|
||||
const auto propName = "format-" + layout_.short_description + "-" + layout_.variant;
|
||||
layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());
|
||||
} else if (config_.isMember("format-" + layout_.short_description)) {
|
||||
const auto propName = "format-" + layout_.short_description;
|
||||
layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());
|
||||
} else {
|
||||
@ -48,6 +51,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);
|
||||
@ -61,7 +66,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
|
||||
@ -76,7 +81,7 @@ void Language::onEvent(const std::string& ev) {
|
||||
}
|
||||
|
||||
void Language::initLanguage() {
|
||||
const auto inputDevices = gIPC->getSocket1Reply("devices");
|
||||
const auto inputDevices = m_ipc.getSocket1Reply("devices");
|
||||
|
||||
const auto kbName = config_["keyboard-name"].asString();
|
||||
|
||||
@ -94,18 +99,17 @@ void Language::initLanguage() {
|
||||
spdlog::debug("hyprland language initLanguage found {}", layout_.full_name);
|
||||
|
||||
dp.emit();
|
||||
|
||||
} catch (std::exception& e) {
|
||||
spdlog::error("hyprland language initLanguage failed with {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -114,21 +118,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");
|
||||
|
||||
|
||||
@ -2,32 +2,49 @@
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <util/sanitize_str.hpp>
|
||||
#include "util/sanitize_str.hpp"
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: ALabel(config, "submap", id, "{}", 0, true), bar_(bar) {
|
||||
: ALabel(config, "submap", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
|
||||
modulesReady = true;
|
||||
|
||||
if (!gIPC.get()) {
|
||||
gIPC = std::make_unique<IPC>();
|
||||
}
|
||||
parseConfig(config);
|
||||
|
||||
label_.hide();
|
||||
ALabel::update();
|
||||
|
||||
// Displays widget immediately if always_on_ assuming default submap
|
||||
// Needs an actual way to retrieve 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);
|
||||
m_ipc.registerForIPC("submap", this);
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Submap::~Submap() {
|
||||
gIPC->unregisterForIPC(this);
|
||||
m_ipc.unregisterForIPC(this);
|
||||
// wait for possible event handler to finish
|
||||
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_);
|
||||
|
||||
@ -51,11 +68,20 @@ void Submap::onEvent(const std::string& ev) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto submapName = ev.substr(ev.find_last_of('>') + 1);
|
||||
submapName = waybar::util::sanitize_string(submapName);
|
||||
auto submapName = ev.substr(ev.find_first_of('>') + 2 );
|
||||
|
||||
if (!submap_.empty()) {
|
||||
label_.get_style_context()->remove_class(submap_);
|
||||
}
|
||||
|
||||
submap_ = submapName;
|
||||
|
||||
if (submap_.empty() && always_on_) {
|
||||
submap_ = default_submap_;
|
||||
}
|
||||
|
||||
label_.get_style_context()->add_class(submap_);
|
||||
|
||||
spdlog::debug("hyprland submap onevent with {}", submap_);
|
||||
|
||||
dp.emit();
|
||||
|
||||
@ -1,161 +1,238 @@
|
||||
#include "modules/hyprland/window.hpp"
|
||||
|
||||
#include <glibmm/fileutils.h>
|
||||
#include <glibmm/keyfile.h>
|
||||
#include <glibmm/miscutils.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <regex>
|
||||
#include <util/sanitize_str.hpp>
|
||||
#include <shared_mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/hyprland/backend.hpp"
|
||||
#include "util/json.hpp"
|
||||
#include "util/rewrite_string.hpp"
|
||||
#include "util/sanitize_str.hpp"
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: ALabel(config, "window", id, "{}", 0, true), bar_(bar) {
|
||||
modulesReady = true;
|
||||
separate_outputs = config["separate-outputs"].asBool();
|
||||
std::shared_mutex windowIpcSmtx;
|
||||
|
||||
if (!gIPC.get()) {
|
||||
gIPC = std::make_unique<IPC>();
|
||||
}
|
||||
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
|
||||
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
|
||||
|
||||
modulesReady = true;
|
||||
separateOutputs_ = config["separate-outputs"].asBool();
|
||||
|
||||
// register for hyprland ipc
|
||||
m_ipc.registerForIPC("activewindow", this);
|
||||
m_ipc.registerForIPC("closewindow", this);
|
||||
m_ipc.registerForIPC("movewindow", this);
|
||||
m_ipc.registerForIPC("changefloatingmode", this);
|
||||
m_ipc.registerForIPC("fullscreen", this);
|
||||
|
||||
windowIpcUniqueLock.unlock();
|
||||
|
||||
queryActiveWorkspace();
|
||||
update();
|
||||
|
||||
// register for hyprland ipc
|
||||
gIPC->registerForIPC("activewindow", this);
|
||||
gIPC->registerForIPC("closewindow", this);
|
||||
gIPC->registerForIPC("movewindow", this);
|
||||
gIPC->registerForIPC("changefloatingmode", this);
|
||||
gIPC->registerForIPC("fullscreen", this);
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Window::~Window() {
|
||||
gIPC->unregisterForIPC(this);
|
||||
// wait for possible event handler to finish
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
|
||||
m_ipc.unregisterForIPC(this);
|
||||
}
|
||||
|
||||
auto Window::update() -> void {
|
||||
// fix ampersands
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx);
|
||||
|
||||
std::string window_name = waybar::util::sanitize_string(workspace_.last_window_title);
|
||||
std::string windowName = waybar::util::sanitize_string(workspace_.last_window_title);
|
||||
std::string windowAddress = workspace_.last_window;
|
||||
|
||||
if (window_name != last_title_) {
|
||||
if (window_name.empty()) {
|
||||
label_.get_style_context()->add_class("empty");
|
||||
} else {
|
||||
label_.get_style_context()->remove_class("empty");
|
||||
}
|
||||
last_title_ = window_name;
|
||||
}
|
||||
windowData_.title = windowName;
|
||||
|
||||
std::string label_text;
|
||||
if (!format_.empty()) {
|
||||
label_.show();
|
||||
label_.set_markup(fmt::format(fmt::runtime(format_),
|
||||
waybar::util::rewriteString(window_name, config_["rewrite"])));
|
||||
label_text = waybar::util::rewriteString(
|
||||
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"]);
|
||||
label_.set_markup(label_text);
|
||||
} else {
|
||||
label_.hide();
|
||||
}
|
||||
|
||||
setClass("empty", workspace_.windows == 0);
|
||||
setClass("solo", solo_);
|
||||
setClass("fullscreen", fullscreen_);
|
||||
setClass("floating", all_floating_);
|
||||
|
||||
if (!last_solo_class_.empty() && solo_class_ != last_solo_class_) {
|
||||
if (bar_.window.get_style_context()->has_class(last_solo_class_)) {
|
||||
bar_.window.get_style_context()->remove_class(last_solo_class_);
|
||||
spdlog::trace("Removing solo class: {}", last_solo_class_);
|
||||
if (tooltipEnabled()) {
|
||||
std::string tooltip_format;
|
||||
if (config_["tooltip-format"].isString()) {
|
||||
tooltip_format = config_["tooltip-format"].asString();
|
||||
}
|
||||
if (!tooltip_format.empty()) {
|
||||
label_.set_tooltip_text(
|
||||
fmt::format(fmt::runtime(tooltip_format), fmt::arg("title", windowName),
|
||||
fmt::arg("initialTitle", windowData_.initial_title),
|
||||
fmt::arg("class", windowData_.class_name),
|
||||
fmt::arg("initialClass", windowData_.initial_class_name)));
|
||||
} else if (!label_text.empty()) {
|
||||
label_.set_tooltip_text(label_text);
|
||||
}
|
||||
}
|
||||
|
||||
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 (focused_) {
|
||||
image_.show();
|
||||
} else {
|
||||
image_.hide();
|
||||
}
|
||||
last_solo_class_ = solo_class_;
|
||||
|
||||
ALabel::update();
|
||||
setClass("empty", workspace_.windows == 0);
|
||||
setClass("solo", solo_);
|
||||
setClass("floating", allFloating_);
|
||||
setClass("swallowing", swallowing_);
|
||||
setClass("fullscreen", fullscreen_);
|
||||
|
||||
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 (!soloClass_.empty() && soloClass_ != lastSoloClass_) {
|
||||
bar_.window.get_style_context()->add_class(soloClass_);
|
||||
spdlog::trace("Adding solo class: {}", soloClass_);
|
||||
}
|
||||
lastSoloClass_ = soloClass_;
|
||||
|
||||
AAppIconLabel::update();
|
||||
}
|
||||
|
||||
auto Window::getActiveWorkspace() -> Workspace {
|
||||
const auto workspace = gIPC->getSocket1JsonReply("activeworkspace");
|
||||
assert(workspace.isObject());
|
||||
return Workspace::parse(workspace);
|
||||
const auto workspace = IPC::inst().getSocket1JsonReply("activeworkspace");
|
||||
|
||||
if (workspace.isObject()) {
|
||||
return Workspace::parse(workspace);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
|
||||
const auto monitors = gIPC->getSocket1JsonReply("monitors");
|
||||
assert(monitors.isArray());
|
||||
auto monitor = std::find_if(monitors.begin(), monitors.end(),
|
||||
[&](Json::Value monitor) { return monitor["name"] == monitorName; });
|
||||
if (monitor == std::end(monitors)) {
|
||||
spdlog::warn("Monitor not found: {}", monitorName);
|
||||
return Workspace{-1, 0, "", ""};
|
||||
}
|
||||
const int id = (*monitor)["activeWorkspace"]["id"].asInt();
|
||||
const auto monitors = IPC::inst().getSocket1JsonReply("monitors");
|
||||
if (monitors.isArray()) {
|
||||
auto monitor = std::ranges::find_if(
|
||||
monitors, [&](Json::Value monitor) { return monitor["name"] == monitorName; });
|
||||
if (monitor == std::end(monitors)) {
|
||||
spdlog::warn("Monitor not found: {}", monitorName);
|
||||
return Workspace{
|
||||
.id = -1,
|
||||
.windows = 0,
|
||||
.last_window = "",
|
||||
.last_window_title = "",
|
||||
};
|
||||
}
|
||||
const int id = (*monitor)["activeWorkspace"]["id"].asInt();
|
||||
|
||||
const auto workspaces = gIPC->getSocket1JsonReply("workspaces");
|
||||
assert(workspaces.isArray());
|
||||
auto workspace = std::find_if(monitors.begin(), monitors.end(),
|
||||
[&](Json::Value workspace) { return workspace["id"] == id; });
|
||||
if (workspace == std::end(monitors)) {
|
||||
spdlog::warn("No workspace with id {}", id);
|
||||
return Workspace{-1, 0, "", ""};
|
||||
}
|
||||
return Workspace::parse(*workspace);
|
||||
const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces");
|
||||
if (workspaces.isArray()) {
|
||||
auto workspace = std::ranges::find_if(
|
||||
workspaces, [&](Json::Value workspace) { return workspace["id"] == id; });
|
||||
if (workspace == std::end(workspaces)) {
|
||||
spdlog::warn("No workspace with id {}", id);
|
||||
return Workspace{
|
||||
.id = -1,
|
||||
.windows = 0,
|
||||
.last_window = "",
|
||||
.last_window_title = "",
|
||||
};
|
||||
}
|
||||
return Workspace::parse(*workspace);
|
||||
};
|
||||
};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
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{
|
||||
.id = value["id"].asInt(),
|
||||
.windows = value["windows"].asInt(),
|
||||
.last_window = value["lastwindow"].asString(),
|
||||
.last_window_title = value["lastwindowtitle"].asString(),
|
||||
};
|
||||
}
|
||||
|
||||
auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData {
|
||||
return WindowData{.floating = value["floating"].asBool(),
|
||||
.monitor = value["monitor"].asInt(),
|
||||
.class_name = value["class"].asString(),
|
||||
.initial_class_name = value["initialClass"].asString(),
|
||||
.title = value["title"].asString(),
|
||||
.initial_title = value["initialTitle"].asString(),
|
||||
.fullscreen = value["fullscreen"].asBool(),
|
||||
.grouped = !value["grouped"].empty()};
|
||||
}
|
||||
|
||||
void Window::queryActiveWorkspace() {
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx);
|
||||
|
||||
if (separate_outputs) {
|
||||
if (separateOutputs_) {
|
||||
workspace_ = getActiveWorkspace(this->bar_.output->name);
|
||||
} else {
|
||||
workspace_ = getActiveWorkspace();
|
||||
}
|
||||
|
||||
focused_ = true;
|
||||
if (workspace_.windows > 0) {
|
||||
const auto clients = gIPC->getSocket1Reply("j/clients");
|
||||
Json::Value json = parser_.parse(clients);
|
||||
assert(json.isArray());
|
||||
auto active_window = std::find_if(json.begin(), json.end(), [&](Json::Value window) {
|
||||
return window["address"] == workspace_.last_window;
|
||||
});
|
||||
if (active_window == std::end(json)) {
|
||||
return;
|
||||
}
|
||||
const auto clients = m_ipc.getSocket1JsonReply("clients");
|
||||
if (clients.isArray()) {
|
||||
auto activeWindow = std::ranges::find_if(
|
||||
clients, [&](Json::Value window) { return window["address"] == workspace_.last_window; });
|
||||
|
||||
if (workspace_.windows == 1 && !(*active_window)["floating"].asBool()) {
|
||||
solo_class_ = (*active_window)["class"].asString();
|
||||
} else {
|
||||
solo_class_ = "";
|
||||
if (activeWindow == std::end(clients)) {
|
||||
focused_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
windowData_ = WindowData::parse(*activeWindow);
|
||||
updateAppIconName(windowData_.class_name, windowData_.initial_class_name);
|
||||
std::vector<Json::Value> workspaceWindows;
|
||||
std::ranges::copy_if(clients, std::back_inserter(workspaceWindows), [&](Json::Value window) {
|
||||
return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool();
|
||||
});
|
||||
swallowing_ = std::ranges::any_of(workspaceWindows, [&](Json::Value window) {
|
||||
return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0";
|
||||
});
|
||||
std::vector<Json::Value> visibleWindows;
|
||||
std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows),
|
||||
[&](Json::Value window) { return !window["hidden"].asBool(); });
|
||||
solo_ = 1 == std::count_if(visibleWindows.begin(), visibleWindows.end(),
|
||||
[&](Json::Value window) { return !window["floating"].asBool(); });
|
||||
allFloating_ = std::ranges::all_of(
|
||||
visibleWindows, [&](Json::Value window) { return window["floating"].asBool(); });
|
||||
fullscreen_ = windowData_.fullscreen;
|
||||
|
||||
// Fullscreen windows look like they are solo
|
||||
if (fullscreen_) {
|
||||
solo_ = true;
|
||||
}
|
||||
|
||||
if (solo_) {
|
||||
soloClass_ = windowData_.class_name;
|
||||
} else {
|
||||
soloClass_ = "";
|
||||
}
|
||||
}
|
||||
std::vector<Json::Value> workspace_windows;
|
||||
std::copy_if(json.begin(), json.end(), std::back_inserter(workspace_windows),
|
||||
[&](Json::Value window) {
|
||||
return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool();
|
||||
});
|
||||
solo_ = 1 == std::count_if(workspace_windows.begin(), workspace_windows.end(),
|
||||
[&](Json::Value window) { return !window["floating"].asBool(); });
|
||||
all_floating_ = std::all_of(workspace_windows.begin(), workspace_windows.end(),
|
||||
[&](Json::Value window) { return window["floating"].asBool(); });
|
||||
fullscreen_ = (*active_window)["fullscreen"].asBool();
|
||||
} else {
|
||||
solo_class_ = "";
|
||||
solo_ = false;
|
||||
all_floating_ = false;
|
||||
focused_ = false;
|
||||
windowData_ = WindowData{};
|
||||
allFloating_ = false;
|
||||
swallowing_ = false;
|
||||
fullscreen_ = false;
|
||||
solo_ = false;
|
||||
soloClass_ = "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
108
src/modules/hyprland/windowcreationpayload.cpp
Normal file
108
src/modules/hyprland/windowcreationpayload.cpp
Normal file
@ -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::moveToWorkspace(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
|
||||
220
src/modules/hyprland/workspace.cpp
Normal file
220
src/modules/hyprland/workspace.cpp
Normal file
@ -0,0 +1,220 @@
|
||||
#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()),
|
||||
m_ipc(IPC::inst()) {
|
||||
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()) {
|
||||
m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id()));
|
||||
} else {
|
||||
m_ipc.getSocket1Reply("dispatch workspace " + std::to_string(id()));
|
||||
}
|
||||
} else if (!isSpecial()) { // named (this includes persistent)
|
||||
if (m_workspaceManager.moveToMonitor()) {
|
||||
m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name());
|
||||
} else {
|
||||
m_ipc.getSocket1Reply("dispatch workspace name:" + name());
|
||||
}
|
||||
} else if (id() != -99) { // named special
|
||||
m_ipc.getSocket1Reply("dispatch togglespecialworkspace " + name());
|
||||
} else { // special
|
||||
m_ipc.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_payload) {
|
||||
if (!create_window_payload.isEmpty(m_workspaceManager)) {
|
||||
auto repr = create_window_payload.repr(m_workspaceManager);
|
||||
|
||||
if (!repr.empty()) {
|
||||
m_windowMap[create_window_payload.getAddress()] = repr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_payload) {
|
||||
if (create_window_payload.getWorkspaceName() == name()) {
|
||||
insertWindow(create_window_payload);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string Workspace::removeWindow(WindowAddress const &addr) {
|
||||
std::string windowRepr = m_windowMap[addr];
|
||||
m_windowMap.erase(addr);
|
||||
return windowRepr;
|
||||
}
|
||||
|
||||
std::string &Workspace::selectIcon(std::map<std::string, std::string> &icons_map) {
|
||||
spdlog::trace("Selecting icon for workspace {}", name());
|
||||
if (isUrgent()) {
|
||||
auto urgentIconIt = icons_map.find("urgent");
|
||||
if (urgentIconIt != icons_map.end()) {
|
||||
return urgentIconIt->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (isActive()) {
|
||||
auto activeIconIt = icons_map.find("active");
|
||||
if (activeIconIt != icons_map.end()) {
|
||||
return activeIconIt->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (isSpecial()) {
|
||||
auto specialIconIt = icons_map.find("special");
|
||||
if (specialIconIt != icons_map.end()) {
|
||||
return specialIconIt->second;
|
||||
}
|
||||
}
|
||||
|
||||
auto namedIconIt = icons_map.find(name());
|
||||
if (namedIconIt != icons_map.end()) {
|
||||
return namedIconIt->second;
|
||||
}
|
||||
|
||||
if (isVisible()) {
|
||||
auto visibleIconIt = icons_map.find("visible");
|
||||
if (visibleIconIt != icons_map.end()) {
|
||||
return visibleIconIt->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (isEmpty()) {
|
||||
auto emptyIconIt = icons_map.find("empty");
|
||||
if (emptyIconIt != icons_map.end()) {
|
||||
return emptyIconIt->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPersistent()) {
|
||||
auto persistentIconIt = icons_map.find("persistent");
|
||||
if (persistentIconIt != icons_map.end()) {
|
||||
return persistentIconIt->second;
|
||||
}
|
||||
}
|
||||
|
||||
auto defaultIconIt = icons_map.find("default");
|
||||
if (defaultIconIt != icons_map.end()) {
|
||||
return defaultIconIt->second;
|
||||
}
|
||||
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void Workspace::update(const std::string &format, const std::string &icon) {
|
||||
// clang-format off
|
||||
if (this->m_workspaceManager.activeOnly() && \
|
||||
!this->isActive() && \
|
||||
!this->isPersistent() && \
|
||||
!this->isVisible() && \
|
||||
!this->isSpecial()) {
|
||||
// clang-format on
|
||||
// if activeOnly is true, hide if not active, persistent, visible or special
|
||||
m_button.hide();
|
||||
return;
|
||||
}
|
||||
if (this->m_workspaceManager.specialVisibleOnly() && this->isSpecial() && !this->isVisible()) {
|
||||
m_button.hide();
|
||||
return;
|
||||
}
|
||||
m_button.show();
|
||||
|
||||
auto styleContext = m_button.get_style_context();
|
||||
addOrRemoveClass(styleContext, isActive(), "active");
|
||||
addOrRemoveClass(styleContext, isSpecial(), "special");
|
||||
addOrRemoveClass(styleContext, isEmpty(), "empty");
|
||||
addOrRemoveClass(styleContext, isPersistent(), "persistent");
|
||||
addOrRemoveClass(styleContext, isUrgent(), "urgent");
|
||||
addOrRemoveClass(styleContext, isVisible(), "visible");
|
||||
addOrRemoveClass(styleContext, m_workspaceManager.getBarOutput() == output(), "hosting-monitor");
|
||||
|
||||
std::string windows;
|
||||
auto windowSeparator = m_workspaceManager.getWindowSeparator();
|
||||
|
||||
bool isNotFirst = false;
|
||||
|
||||
for (auto &[_pid, window_repr] : m_windowMap) {
|
||||
if (isNotFirst) {
|
||||
windows.append(windowSeparator);
|
||||
}
|
||||
isNotFirst = true;
|
||||
windows.append(window_repr);
|
||||
}
|
||||
|
||||
m_label.set_markup(fmt::format(fmt::runtime(format), fmt::arg("id", id()),
|
||||
fmt::arg("name", name()), fmt::arg("icon", icon),
|
||||
fmt::arg("windows", windows)));
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::hyprland
|
||||
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
box_.get_style_context()->add_class(MODULE_CLASS);
|
||||
event_box_.add(box_);
|
||||
|
||||
dp.emit();
|
||||
@ -41,31 +42,37 @@ void waybar::modules::Image::refresh(int sig) {
|
||||
}
|
||||
|
||||
auto waybar::modules::Image::update() -> void {
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||
if (config_["path"].isString()) {
|
||||
path_ = config_["path"].asString();
|
||||
} else if (config_["exec"].isString()) {
|
||||
output_ = util::command::exec(config_["exec"].asString());
|
||||
output_ = util::command::exec(config_["exec"].asString(), "");
|
||||
parseOutputRaw();
|
||||
} else {
|
||||
path_ = "";
|
||||
}
|
||||
if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS))
|
||||
pixbuf = Gdk::Pixbuf::create_from_file(path_, size_, size_);
|
||||
else
|
||||
pixbuf = {};
|
||||
|
||||
if (pixbuf) {
|
||||
if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS)) {
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||
|
||||
int scaled_icon_size = size_ * image_.get_scale_factor();
|
||||
pixbuf = Gdk::Pixbuf::create_from_file(path_, scaled_icon_size, scaled_icon_size);
|
||||
|
||||
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(),
|
||||
image_.get_window());
|
||||
image_.set(surface);
|
||||
image_.show();
|
||||
|
||||
if (tooltipEnabled() && !tooltip_.empty()) {
|
||||
if (box_.get_tooltip_markup() != tooltip_) {
|
||||
box_.set_tooltip_markup(tooltip_);
|
||||
}
|
||||
}
|
||||
image_.set(pixbuf);
|
||||
image_.show();
|
||||
|
||||
box_.get_style_context()->remove_class("empty");
|
||||
} else {
|
||||
image_.clear();
|
||||
image_.hide();
|
||||
box_.get_style_context()->add_class("empty");
|
||||
}
|
||||
|
||||
AModule::update();
|
||||
|
||||
@ -81,7 +81,7 @@ auto supportsLockStates(const libevdev* dev) -> bool {
|
||||
waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar,
|
||||
const Json::Value& config)
|
||||
: AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()),
|
||||
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||
box_(bar.orientation, 0),
|
||||
numlock_label_(""),
|
||||
capslock_label_(""),
|
||||
numlock_format_(config_["format"].isString() ? config_["format"].asString()
|
||||
@ -132,6 +132,7 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
box_.get_style_context()->add_class(MODULE_CLASS);
|
||||
event_box_.add(box_);
|
||||
|
||||
if (config_["device-path"].isString()) {
|
||||
@ -142,6 +143,21 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
|
||||
}
|
||||
}
|
||||
|
||||
auto keys = config_["binding-keys"];
|
||||
if (keys.isArray()) {
|
||||
for (const auto& key : keys) {
|
||||
if (key.isInt()) {
|
||||
binding_keys.insert(key.asInt());
|
||||
} else {
|
||||
spdlog::warn("Cannot read key binding {} as int.", key.asString());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding_keys.insert(KEY_CAPSLOCK);
|
||||
binding_keys.insert(KEY_NUMLOCK);
|
||||
binding_keys.insert(KEY_SCROLLLOCK);
|
||||
}
|
||||
|
||||
DIR* dev_dir = opendir(devices_path_.c_str());
|
||||
if (dev_dir == nullptr) {
|
||||
throw errno_error(errno, "Failed to open " + devices_path_);
|
||||
@ -171,14 +187,8 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
|
||||
auto state = libinput_event_keyboard_get_key_state(keyboard_event);
|
||||
if (state == LIBINPUT_KEY_STATE_RELEASED) {
|
||||
uint32_t key = libinput_event_keyboard_get_key(keyboard_event);
|
||||
switch (key) {
|
||||
case KEY_CAPSLOCK:
|
||||
case KEY_NUMLOCK:
|
||||
case KEY_SCROLLLOCK:
|
||||
dp.emit();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (binding_keys.contains(key)) {
|
||||
dp.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
61
src/modules/load.cpp
Normal file
61
src/modules/load.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
#include "modules/load.hpp"
|
||||
|
||||
// In the 80000 version of fmt library authors decided to optimize imports
|
||||
// and moved declarations required for fmt::dynamic_format_arg_store in new
|
||||
// header fmt/args.h
|
||||
#if (FMT_VERSION >= 80000)
|
||||
#include <fmt/args.h>
|
||||
#else
|
||||
#include <fmt/core.h>
|
||||
#endif
|
||||
|
||||
waybar::modules::Load::Load(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "load", id, "{load1}", 10) {
|
||||
thread_ = [this] {
|
||||
dp.emit();
|
||||
thread_.sleep_for(interval_);
|
||||
};
|
||||
}
|
||||
|
||||
auto waybar::modules::Load::update() -> void {
|
||||
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
|
||||
auto [load1, load5, load15] = Load::getLoad();
|
||||
if (tooltipEnabled()) {
|
||||
auto tooltip = fmt::format("Load 1: {}\nLoad 5: {}\nLoad 15: {}", load1, load5, load15);
|
||||
label_.set_tooltip_text(tooltip);
|
||||
}
|
||||
auto format = format_;
|
||||
auto state = getState(load1);
|
||||
if (!state.empty() && config_["format-" + state].isString()) {
|
||||
format = config_["format-" + state].asString();
|
||||
}
|
||||
|
||||
if (format.empty()) {
|
||||
event_box_.hide();
|
||||
} else {
|
||||
event_box_.show();
|
||||
auto icons = std::vector<std::string>{state};
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(fmt::arg("load1", load1));
|
||||
store.push_back(fmt::arg("load5", load5));
|
||||
store.push_back(fmt::arg("load15", load15));
|
||||
store.push_back(fmt::arg("icon1", getIcon(load1, icons)));
|
||||
store.push_back(fmt::arg("icon5", getIcon(load5, icons)));
|
||||
store.push_back(fmt::arg("icon15", getIcon(load15, icons)));
|
||||
label_.set_markup(fmt::vformat(format, store));
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
std::tuple<double, double, double> waybar::modules::Load::getLoad() {
|
||||
double load[3];
|
||||
if (getloadavg(load, 3) != -1) {
|
||||
double load1 = std::ceil(load[0] * 100.0) / 100.0;
|
||||
double load5 = std::ceil(load[1] * 100.0) / 100.0;
|
||||
double load15 = std::ceil(load[2] * 100.0) / 100.0;
|
||||
return {load1, load5, load15};
|
||||
}
|
||||
throw std::runtime_error("Can't get system load");
|
||||
}
|
||||
@ -21,13 +21,13 @@ static uint64_t get_total_memory() {
|
||||
u_long physmem;
|
||||
#endif
|
||||
int mib[] = {
|
||||
CTL_HW,
|
||||
CTL_HW,
|
||||
#if defined(HW_MEMSIZE)
|
||||
HW_MEMSIZE,
|
||||
HW_MEMSIZE,
|
||||
#elif defined(HW_PHYSMEM64)
|
||||
HW_PHYSMEM64,
|
||||
HW_PHYSMEM64,
|
||||
#else
|
||||
HW_PHYSMEM,
|
||||
HW_PHYSMEM,
|
||||
#endif
|
||||
};
|
||||
u_int miblen = sizeof(mib) / sizeof(mib[0]);
|
||||
|
||||
@ -60,6 +60,7 @@ auto waybar::modules::Memory::update() -> void {
|
||||
fmt::arg("icon", getIcon(used_ram_percentage, icons)),
|
||||
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
|
||||
fmt::arg("percentage", used_ram_percentage),
|
||||
fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"),
|
||||
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
|
||||
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
|
||||
fmt::arg("swapAvail", available_swap_gigabytes)));
|
||||
@ -72,6 +73,7 @@ auto waybar::modules::Memory::update() -> void {
|
||||
fmt::runtime(tooltip_format), used_ram_percentage,
|
||||
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
|
||||
fmt::arg("percentage", used_ram_percentage),
|
||||
fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"),
|
||||
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
|
||||
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
|
||||
fmt::arg("swapAvail", available_swap_gigabytes)));
|
||||
|
||||
@ -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(); }
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "util/scope_guard.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include <playerctl/playerctl.h>
|
||||
}
|
||||
@ -18,7 +20,7 @@ namespace waybar::modules::mpris {
|
||||
const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}";
|
||||
|
||||
Mpris::Mpris(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "mpris", id, DEFAULT_FORMAT, 5, false, true),
|
||||
: ALabel(config, "mpris", id, DEFAULT_FORMAT, 0, false, true),
|
||||
tooltip_(DEFAULT_FORMAT),
|
||||
artist_len_(-1),
|
||||
album_len_(-1),
|
||||
@ -83,7 +85,9 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
|
||||
// "dynamic-priority" has been kept for backward compatibility
|
||||
if (config_["dynamic-importance-order"].isArray() || config_["dynamic-priority"].isArray()) {
|
||||
dynamic_prio_.clear();
|
||||
const auto& dynamic_priority = config_["dynamic-importance-order"].isArray() ? config_["dynamic-importance-order"] : config_["dynamic-priority"];
|
||||
const auto& dynamic_priority = config_["dynamic-importance-order"].isArray()
|
||||
? config_["dynamic-importance-order"]
|
||||
: config_["dynamic-priority"];
|
||||
for (const auto& value : dynamic_priority) {
|
||||
if (value.isString()) {
|
||||
dynamic_prio_.push_back(value.asString());
|
||||
@ -92,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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -106,15 +110,19 @@ 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GError* error = nullptr;
|
||||
waybar::util::ScopeGuard error_deleter([error]() {
|
||||
if (error) {
|
||||
g_error_free(error);
|
||||
}
|
||||
});
|
||||
manager = playerctl_player_manager_new(&error);
|
||||
if (error) {
|
||||
throw std::runtime_error(fmt::format("unable to create MPRIS client: {}", error->message));
|
||||
@ -134,13 +142,11 @@ Mpris::Mpris(const std::string& id, const Json::Value& config)
|
||||
} else {
|
||||
GList* players = playerctl_list_players(&error);
|
||||
if (error) {
|
||||
auto e = fmt::format("unable to list players: {}", error->message);
|
||||
g_error_free(error);
|
||||
throw std::runtime_error(e);
|
||||
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;
|
||||
@ -173,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 "";
|
||||
}
|
||||
@ -198,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
|
||||
@ -262,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 {
|
||||
@ -270,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 {
|
||||
@ -299,9 +302,9 @@ auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) ->
|
||||
"position") != dynamic_order_.end());
|
||||
|
||||
if (truncated && dynamic_len_ >= 0) {
|
||||
//Since the first element doesn't present a separator and we don't know a priori which one
|
||||
//it will be, we add a "virtual separatorLen" to the dynamicLen, since we are adding the
|
||||
//separatorLen to all the other lengths.
|
||||
// Since the first element doesn't present a separator and we don't know a priori which one
|
||||
// it will be, we add a "virtual separatorLen" to the dynamicLen, since we are adding the
|
||||
// separatorLen to all the other lengths.
|
||||
size_t separatorLen = utf8_width(dynamic_separator_);
|
||||
size_t dynamicLen = dynamic_len_ + separatorLen;
|
||||
if (showArtist) artistLen += separatorLen;
|
||||
@ -312,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) {
|
||||
@ -361,12 +364,9 @@ auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) ->
|
||||
std::string previousOrder = "";
|
||||
|
||||
for (const std::string& order : dynamic_order_) {
|
||||
if ((order == "artist" && showArtist) ||
|
||||
(order == "album" && showAlbum) ||
|
||||
if ((order == "artist" && showArtist) || (order == "album" && showAlbum) ||
|
||||
(order == "title" && showTitle)) {
|
||||
if (previousShown &&
|
||||
previousOrder != "length" &&
|
||||
previousOrder != "position") {
|
||||
if (previousShown && previousOrder != "length" && previousOrder != "position") {
|
||||
dynamic << dynamic_separator_;
|
||||
}
|
||||
|
||||
@ -402,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);
|
||||
@ -411,8 +411,7 @@ auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlaye
|
||||
return;
|
||||
}
|
||||
|
||||
GError* error = nullptr;
|
||||
mpris->player = playerctl_player_new_from_name(player_name, &error);
|
||||
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",
|
||||
@ -423,19 +422,22 @@ 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);
|
||||
spdlog::debug("mpris: name-vanished callback: {}", player_name->name);
|
||||
|
||||
if (std::string(player_name->name) == mpris->player_) {
|
||||
if (mpris->player_ == "playerctld") {
|
||||
mpris->dp.emit();
|
||||
} else if (mpris->player_ == player_name->name) {
|
||||
mpris->player = nullptr;
|
||||
mpris->event_box_.set_visible(false);
|
||||
mpris->dp.emit();
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
@ -444,7 +446,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");
|
||||
@ -453,7 +455,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");
|
||||
@ -465,7 +467,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");
|
||||
@ -479,6 +481,11 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
|
||||
}
|
||||
|
||||
GError* error = nullptr;
|
||||
waybar::util::ScopeGuard error_deleter([error]() {
|
||||
if (error) {
|
||||
g_error_free(error);
|
||||
}
|
||||
});
|
||||
|
||||
char* player_status = nullptr;
|
||||
auto player_playback_status = PLAYERCTL_PLAYBACK_STATUS_STOPPED;
|
||||
@ -488,14 +495,13 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
|
||||
if (player_name == "playerctld") {
|
||||
GList* players = playerctl_list_players(&error);
|
||||
if (error) {
|
||||
auto e = fmt::format("unable to list players: {}", error->message);
|
||||
g_error_free(error);
|
||||
throw std::runtime_error(e);
|
||||
throw std::runtime_error(fmt::format("unable to list players: {}", error->message));
|
||||
}
|
||||
// > get the list of players [..] in order of activity
|
||||
// https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249
|
||||
players = g_list_first(players);
|
||||
if (players) player_name = static_cast<PlayerctlPlayerName*>(players->data)->name;
|
||||
else return std::nullopt; // no players found, hide the widget
|
||||
}
|
||||
|
||||
if (std::any_of(ignored_players_.begin(), ignored_players_.end(),
|
||||
@ -517,30 +523,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);
|
||||
@ -557,7 +563,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);
|
||||
@ -568,13 +574,25 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
|
||||
return info;
|
||||
|
||||
errorexit:
|
||||
spdlog::error("mpris[{}]: {}", info.name, error->message);
|
||||
g_error_free(error);
|
||||
std::string errorMsg = error->message;
|
||||
// When mpris checks for active player sessions periodically(5 secs), NoActivePlayer error
|
||||
// message is
|
||||
// thrown when there are no active sessions. This error message is spamming logs without having
|
||||
// any value addition. Log the error only if the error we recceived is not NoActivePlayer.
|
||||
if (errorMsg.rfind("GDBus.Error:com.github.altdesktop.playerctld.NoActivePlayer") ==
|
||||
std::string::npos) {
|
||||
spdlog::error("mpris[{}]: {}", info.name, error->message);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool Mpris::handleToggle(GdkEventButton* const& e) {
|
||||
GError* error = nullptr;
|
||||
waybar::util::ScopeGuard error_deleter([error]() {
|
||||
if (error) {
|
||||
g_error_free(error);
|
||||
}
|
||||
});
|
||||
|
||||
auto info = getPlayerInfo();
|
||||
if (!info) return false;
|
||||
@ -588,13 +606,13 @@ bool Mpris::handleToggle(GdkEventButton* const& e) {
|
||||
playerctl_player_play_pause(player, &error);
|
||||
break;
|
||||
case 2: // middle-click
|
||||
if (config_["on-middle-click"].isString()) {
|
||||
if (config_["on-click-middle"].isString()) {
|
||||
return ALabel::handleToggle(e);
|
||||
}
|
||||
playerctl_player_previous(player, &error);
|
||||
break;
|
||||
case 3: // right-click
|
||||
if (config_["on-right-click"].isString()) {
|
||||
if (config_["on-click-right"].isString()) {
|
||||
return ALabel::handleToggle(e);
|
||||
}
|
||||
playerctl_player_next(player, &error);
|
||||
@ -604,7 +622,6 @@ bool Mpris::handleToggle(GdkEventButton* const& e) {
|
||||
if (error) {
|
||||
spdlog::error("mpris[{}]: error running builtin on-click action: {}", (*info).name,
|
||||
error->message);
|
||||
g_error_free(error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@ -80,7 +80,7 @@ waybar::modules::Network::readBandwidthUsage() {
|
||||
waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
|
||||
: ALabel(config, "network", id, DEFAULT_FORMAT, 60),
|
||||
ifid_(-1),
|
||||
family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET),
|
||||
addr_pref_(IPV4),
|
||||
efd_(-1),
|
||||
ev_fd_(-1),
|
||||
want_route_dump_(false),
|
||||
@ -89,6 +89,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
|
||||
dump_in_progress_(false),
|
||||
is_p2p_(false),
|
||||
cidr_(0),
|
||||
cidr6_(0),
|
||||
signal_strength_dbm_(0),
|
||||
signal_strength_(0),
|
||||
#ifdef WANT_RFKILL
|
||||
@ -102,6 +103,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
|
||||
// the module start with no text, but the event_box_ is shown.
|
||||
label_.set_markup("<s></s>");
|
||||
|
||||
if (config_["family"] == "ipv6") {
|
||||
addr_pref_ = IPV6;
|
||||
} else if (config["family"] == "ipv4_6") {
|
||||
addr_pref_ = IPV4_6;
|
||||
}
|
||||
|
||||
auto bandwidth = readBandwidthUsage();
|
||||
if (bandwidth.has_value()) {
|
||||
bandwidth_down_total_ = (*bandwidth).first;
|
||||
@ -141,12 +148,7 @@ waybar::modules::Network::~Network() {
|
||||
close(efd_);
|
||||
}
|
||||
if (ev_sock_ != nullptr) {
|
||||
nl_socket_drop_membership(ev_sock_, RTNLGRP_LINK);
|
||||
if (family_ == AF_INET) {
|
||||
nl_socket_drop_membership(ev_sock_, RTNLGRP_IPV4_IFADDR);
|
||||
} else {
|
||||
nl_socket_drop_membership(ev_sock_, RTNLGRP_IPV6_IFADDR);
|
||||
}
|
||||
nl_socket_drop_memberships(ev_sock_, RTNLGRP_LINK, RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV6_IFADDR);
|
||||
nl_close(ev_sock_);
|
||||
nl_socket_free(ev_sock_);
|
||||
}
|
||||
@ -161,7 +163,7 @@ void waybar::modules::Network::createEventSocket() {
|
||||
nl_socket_disable_seq_check(ev_sock_);
|
||||
nl_socket_modify_cb(ev_sock_, NL_CB_VALID, NL_CB_CUSTOM, handleEvents, this);
|
||||
nl_socket_modify_cb(ev_sock_, NL_CB_FINISH, NL_CB_CUSTOM, handleEventsDone, this);
|
||||
auto groups = RTMGRP_LINK | (family_ == AF_INET ? RTMGRP_IPV4_IFADDR : RTMGRP_IPV6_IFADDR);
|
||||
auto groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
|
||||
nl_join_groups(ev_sock_, groups); // Deprecated
|
||||
if (nl_connect(ev_sock_, NETLINK_ROUTE) != 0) {
|
||||
throw std::runtime_error("Can't connect network socket");
|
||||
@ -169,18 +171,9 @@ void waybar::modules::Network::createEventSocket() {
|
||||
if (nl_socket_set_nonblocking(ev_sock_)) {
|
||||
throw std::runtime_error("Can't set non-blocking on network socket");
|
||||
}
|
||||
nl_socket_add_membership(ev_sock_, RTNLGRP_LINK);
|
||||
if (family_ == AF_INET) {
|
||||
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV4_IFADDR);
|
||||
} else {
|
||||
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_IFADDR);
|
||||
}
|
||||
nl_socket_add_memberships(ev_sock_, RTNLGRP_LINK, RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV6_IFADDR, 0);
|
||||
if (!config_["interface"].isString()) {
|
||||
if (family_ == AF_INET) {
|
||||
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV4_ROUTE);
|
||||
} else {
|
||||
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_ROUTE);
|
||||
}
|
||||
nl_socket_add_memberships(ev_sock_, RTNLGRP_IPV4_ROUTE, RTNLGRP_IPV6_ROUTE, 0);
|
||||
}
|
||||
|
||||
efd_ = epoll_create1(EPOLL_CLOEXEC);
|
||||
@ -230,8 +223,8 @@ void waybar::modules::Network::worker() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (ifid_ > 0) {
|
||||
getInfo();
|
||||
dp.emit();
|
||||
}
|
||||
dp.emit();
|
||||
}
|
||||
thread_timer_.sleep_for(interval_);
|
||||
};
|
||||
@ -278,14 +271,14 @@ void waybar::modules::Network::worker() {
|
||||
}
|
||||
|
||||
const std::string waybar::modules::Network::getNetworkState() const {
|
||||
if (ifid_ == -1) {
|
||||
#ifdef WANT_RFKILL
|
||||
if (rfkill_.getState()) return "disabled";
|
||||
if (rfkill_.getState()) return "disabled";
|
||||
#endif
|
||||
if (ifid_ == -1) {
|
||||
return "disconnected";
|
||||
}
|
||||
if (!carrier_) return "disconnected";
|
||||
if (ipaddr_.empty()) return "linked";
|
||||
if (ipaddr_.empty() && ipaddr6_.empty()) return "linked";
|
||||
if (essid_.empty()) return "ethernet";
|
||||
return "wifi";
|
||||
}
|
||||
@ -331,12 +324,24 @@ auto waybar::modules::Network::update() -> void {
|
||||
}
|
||||
getState(signal_strength_);
|
||||
|
||||
std::string final_ipaddr_;
|
||||
if (addr_pref_ == ip_addr_pref::IPV4) {
|
||||
final_ipaddr_ = ipaddr_;
|
||||
} else if (addr_pref_ == ip_addr_pref::IPV6) {
|
||||
final_ipaddr_ = ipaddr6_;
|
||||
} else if (addr_pref_ == ip_addr_pref::IPV4_6) {
|
||||
final_ipaddr_ = ipaddr_;
|
||||
final_ipaddr_ += '\n';
|
||||
final_ipaddr_ += ipaddr6_;
|
||||
}
|
||||
|
||||
auto text = fmt::format(
|
||||
fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_),
|
||||
fmt::arg("signalStrength", signal_strength_),
|
||||
fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_),
|
||||
fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_),
|
||||
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
|
||||
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_),
|
||||
fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
|
||||
fmt::arg("netmask", netmask_), fmt::arg("netmask6", netmask6_),
|
||||
fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_),
|
||||
fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
|
||||
fmt::arg("icon", getIcon(signal_strength_, state_)),
|
||||
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
|
||||
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
|
||||
@ -364,11 +369,12 @@ auto waybar::modules::Network::update() -> void {
|
||||
}
|
||||
if (!tooltip_format.empty()) {
|
||||
auto tooltip_text = fmt::format(
|
||||
fmt::runtime(tooltip_format), fmt::arg("essid", essid_),
|
||||
fmt::runtime(tooltip_format), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_),
|
||||
fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_),
|
||||
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
|
||||
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_),
|
||||
fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
|
||||
fmt::arg("netmask", netmask_), fmt::arg("netmask6", netmask6_),
|
||||
fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_),
|
||||
fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
|
||||
fmt::arg("icon", getIcon(signal_strength_, state_)),
|
||||
fmt::arg("bandwidthDownBits",
|
||||
pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
|
||||
@ -407,11 +413,15 @@ void waybar::modules::Network::clearIface() {
|
||||
ifid_ = -1;
|
||||
ifname_.clear();
|
||||
essid_.clear();
|
||||
bssid_.clear();
|
||||
ipaddr_.clear();
|
||||
ipaddr6_.clear();
|
||||
gwaddr_.clear();
|
||||
netmask_.clear();
|
||||
netmask6_.clear();
|
||||
carrier_ = false;
|
||||
cidr_ = 0;
|
||||
cidr6_ = 0;
|
||||
signal_strength_dbm_ = 0;
|
||||
signal_strength_ = 0;
|
||||
signal_strength_app_.clear();
|
||||
@ -481,6 +491,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
} else {
|
||||
// clear state related to WiFi connection
|
||||
net->essid_.clear();
|
||||
net->bssid_.clear();
|
||||
net->signal_strength_dbm_ = 0;
|
||||
net->signal_strength_ = 0;
|
||||
net->signal_strength_app_.clear();
|
||||
@ -529,16 +540,11 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
return NL_OK;
|
||||
}
|
||||
|
||||
if (ifa->ifa_family != net->family_) {
|
||||
return NL_OK;
|
||||
}
|
||||
|
||||
// We ignore address mark as scope for the link or host,
|
||||
// which should leave scope global addresses.
|
||||
if (ifa->ifa_scope >= RT_SCOPE_LINK) {
|
||||
return NL_OK;
|
||||
}
|
||||
|
||||
for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) {
|
||||
switch (ifa_rta->rta_type) {
|
||||
case IFA_ADDRESS:
|
||||
@ -546,8 +552,20 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
case IFA_LOCAL:
|
||||
char ipaddr[INET6_ADDRSTRLEN];
|
||||
if (!is_del_event) {
|
||||
net->ipaddr_ = inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
|
||||
net->cidr_ = ifa->ifa_prefixlen;
|
||||
if ((net->addr_pref_ == ip_addr_pref::IPV4 ||
|
||||
net->addr_pref_ == ip_addr_pref::IPV4_6) &&
|
||||
net->cidr_ == 0 && ifa->ifa_family == AF_INET) {
|
||||
net->ipaddr_ =
|
||||
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
|
||||
net->cidr_ = ifa->ifa_prefixlen;
|
||||
} else if ((net->addr_pref_ == ip_addr_pref::IPV6 ||
|
||||
net->addr_pref_ == ip_addr_pref::IPV4_6) &&
|
||||
net->cidr6_ == 0 && ifa->ifa_family == AF_INET6) {
|
||||
net->ipaddr6_ =
|
||||
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
|
||||
net->cidr6_ = ifa->ifa_prefixlen;
|
||||
}
|
||||
|
||||
switch (ifa->ifa_family) {
|
||||
case AF_INET: {
|
||||
struct in_addr netmask;
|
||||
@ -555,21 +573,24 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof(ipaddr));
|
||||
}
|
||||
case AF_INET6: {
|
||||
struct in6_addr netmask;
|
||||
struct in6_addr netmask6;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int v = (i + 1) * 8 - ifa->ifa_prefixlen;
|
||||
if (v < 0) v = 0;
|
||||
if (v > 8) v = 8;
|
||||
netmask.s6_addr[i] = ~0 << v;
|
||||
netmask6.s6_addr[i] = ~0 << v;
|
||||
}
|
||||
net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof(ipaddr));
|
||||
net->netmask6_ = inet_ntop(ifa->ifa_family, &netmask6, ipaddr, sizeof(ipaddr));
|
||||
}
|
||||
}
|
||||
spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_);
|
||||
} else {
|
||||
net->ipaddr_.clear();
|
||||
net->ipaddr6_.clear();
|
||||
net->cidr_ = 0;
|
||||
net->cidr6_ = 0;
|
||||
net->netmask_.clear();
|
||||
net->netmask6_.clear();
|
||||
spdlog::debug("network: {} addr deleted {}/{}", net->ifname_,
|
||||
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)),
|
||||
ifa->ifa_prefixlen);
|
||||
@ -589,6 +610,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
// to find the interface used to reach the outside world
|
||||
|
||||
struct rtmsg *rtm = static_cast<struct rtmsg *>(NLMSG_DATA(nh));
|
||||
int family = rtm->rtm_family;
|
||||
ssize_t attrlen = RTM_PAYLOAD(nh);
|
||||
struct rtattr *attr = RTM_RTA(rtm);
|
||||
bool has_gateway = false;
|
||||
@ -616,14 +638,14 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
* If someone ever needs to figure out the gateway address as well,
|
||||
* it's here as the attribute payload.
|
||||
*/
|
||||
inet_ntop(net->family_, RTA_DATA(attr), temp_gw_addr, sizeof(temp_gw_addr));
|
||||
inet_ntop(family, RTA_DATA(attr), temp_gw_addr, sizeof(temp_gw_addr));
|
||||
has_gateway = true;
|
||||
break;
|
||||
case RTA_DST: {
|
||||
/* The destination address.
|
||||
* Should be either missing, or maybe all 0s. Accept both.
|
||||
*/
|
||||
const uint32_t nr_zeroes = (net->family_ == AF_INET) ? 4 : 16;
|
||||
const uint32_t nr_zeroes = (family == AF_INET) ? 4 : 16;
|
||||
unsigned char c = 0;
|
||||
size_t dstlen = RTA_PAYLOAD(attr);
|
||||
if (dstlen != nr_zeroes) {
|
||||
@ -655,8 +677,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
higher priority. Disable router -> RTA_GATEWAY -> up new router -> set higher priority added
|
||||
checking route id
|
||||
**/
|
||||
if (!is_del_event &&
|
||||
((net->ifid_ == -1) || (priority < net->route_priority) || (net->ifid_ != temp_idx))) {
|
||||
if (!is_del_event && ((net->ifid_ == -1) || (priority < net->route_priority))) {
|
||||
// Clear if's state for the case were there is a higher priority
|
||||
// route on a different interface.
|
||||
net->clearIface();
|
||||
@ -716,7 +737,6 @@ void waybar::modules::Network::askForStateDump(void) {
|
||||
};
|
||||
|
||||
if (want_route_dump_) {
|
||||
rt_hdr.rtgen_family = family_;
|
||||
nl_send_simple(ev_sock_, RTM_GETROUTE, NLM_F_DUMP, &rt_hdr, sizeof(rt_hdr));
|
||||
want_route_dump_ = false;
|
||||
dump_in_progress_ = true;
|
||||
@ -727,7 +747,6 @@ void waybar::modules::Network::askForStateDump(void) {
|
||||
dump_in_progress_ = true;
|
||||
|
||||
} else if (want_addr_dump_) {
|
||||
rt_hdr.rtgen_family = family_;
|
||||
nl_send_simple(ev_sock_, RTM_GETADDR, NLM_F_DUMP, &rt_hdr, sizeof(rt_hdr));
|
||||
want_addr_dump_ = false;
|
||||
dump_in_progress_ = true;
|
||||
@ -773,6 +792,7 @@ int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) {
|
||||
net->parseEssid(bss);
|
||||
net->parseSignal(bss);
|
||||
net->parseFreq(bss);
|
||||
net->parseBssid(bss);
|
||||
return NL_OK;
|
||||
}
|
||||
|
||||
@ -838,6 +858,17 @@ void waybar::modules::Network::parseFreq(struct nlattr **bss) {
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::modules::Network::parseBssid(struct nlattr **bss) {
|
||||
if (bss[NL80211_BSS_BSSID] != nullptr) {
|
||||
auto bssid = static_cast<uint8_t *>(nla_data(bss[NL80211_BSS_BSSID]));
|
||||
auto bssid_len = nla_len(bss[NL80211_BSS_BSSID]);
|
||||
if (bssid_len == 6) {
|
||||
bssid_ = fmt::format("{:x}:{:x}:{:x}:{:x}:{:x}:{:x}", bssid[0], bssid[1], bssid[2], bssid[3],
|
||||
bssid[4], bssid[5]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool waybar::modules::Network::associatedOrJoined(struct nlattr **bss) {
|
||||
if (bss[NL80211_BSS_STATUS] == nullptr) {
|
||||
return false;
|
||||
|
||||
261
src/modules/niri/backend.cpp
Normal file
261
src/modules/niri/backend.cpp
Normal file
@ -0,0 +1,261 @@
|
||||
#include "modules/niri/backend.hpp"
|
||||
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "giomm/datainputstream.h"
|
||||
#include "giomm/dataoutputstream.h"
|
||||
#include "giomm/unixinputstream.h"
|
||||
#include "giomm/unixoutputstream.h"
|
||||
|
||||
namespace waybar::modules::niri {
|
||||
|
||||
int IPC::connectToSocket() {
|
||||
const char *socket_path = getenv("NIRI_SOCKET");
|
||||
|
||||
if (socket_path == nullptr) {
|
||||
spdlog::warn("Niri is not running, niri IPC will not be available.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_un addr;
|
||||
int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
if (socketfd == -1) {
|
||||
throw std::runtime_error("socketfd failed");
|
||||
}
|
||||
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
|
||||
|
||||
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
|
||||
|
||||
int l = sizeof(struct sockaddr_un);
|
||||
|
||||
if (connect(socketfd, (struct sockaddr *)&addr, l) == -1) {
|
||||
close(socketfd);
|
||||
throw std::runtime_error("unable to connect");
|
||||
}
|
||||
|
||||
return socketfd;
|
||||
}
|
||||
|
||||
void IPC::startIPC() {
|
||||
// will start IPC and relay events to parseIPC
|
||||
|
||||
std::thread([&]() {
|
||||
int socketfd;
|
||||
try {
|
||||
socketfd = connectToSocket();
|
||||
} catch (std::exception &e) {
|
||||
spdlog::error("Niri IPC: failed to start, reason: {}", e.what());
|
||||
return;
|
||||
}
|
||||
if (socketfd == -1) return;
|
||||
|
||||
spdlog::info("Niri IPC starting");
|
||||
|
||||
auto unix_istream = Gio::UnixInputStream::create(socketfd, true);
|
||||
auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false);
|
||||
auto istream = Gio::DataInputStream::create(unix_istream);
|
||||
auto ostream = Gio::DataOutputStream::create(unix_ostream);
|
||||
|
||||
if (!ostream->put_string("\"EventStream\"\n") || !ostream->flush()) {
|
||||
spdlog::error("Niri IPC: failed to start event stream");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
if (!istream->read_line(line) || line != R"({"Ok":"Handled"})") {
|
||||
spdlog::error("Niri IPC: failed to start event stream");
|
||||
return;
|
||||
}
|
||||
|
||||
while (istream->read_line(line)) {
|
||||
spdlog::debug("Niri IPC: received {}", line);
|
||||
|
||||
try {
|
||||
parseIPC(line);
|
||||
} catch (std::exception &e) {
|
||||
spdlog::warn("Failed to parse IPC message: {}, reason: {}", line, e.what());
|
||||
} catch (...) {
|
||||
throw;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void IPC::parseIPC(const std::string &line) {
|
||||
const auto ev = parser_.parse(line);
|
||||
const auto members = ev.getMemberNames();
|
||||
if (members.size() != 1) throw std::runtime_error("Event must have a single member");
|
||||
|
||||
{
|
||||
auto lock = lockData();
|
||||
|
||||
if (const auto &payload = ev["WorkspacesChanged"]) {
|
||||
workspaces_.clear();
|
||||
const auto &values = payload["workspaces"];
|
||||
std::copy(values.begin(), values.end(), std::back_inserter(workspaces_));
|
||||
|
||||
std::sort(workspaces_.begin(), workspaces_.end(), [](const auto &a, const auto &b) {
|
||||
const auto &aOutput = a["output"].asString();
|
||||
const auto &bOutput = b["output"].asString();
|
||||
const auto aIdx = a["idx"].asUInt();
|
||||
const auto bIdx = b["idx"].asUInt();
|
||||
if (aOutput == bOutput) return aIdx < bIdx;
|
||||
return aOutput < bOutput;
|
||||
});
|
||||
} else if (const auto &payload = ev["WorkspaceActivated"]) {
|
||||
const auto id = payload["id"].asUInt64();
|
||||
const auto focused = payload["focused"].asBool();
|
||||
auto it = std::find_if(workspaces_.begin(), workspaces_.end(),
|
||||
[id](const auto &ws) { return ws["id"].asUInt64() == id; });
|
||||
if (it != workspaces_.end()) {
|
||||
const auto &ws = *it;
|
||||
const auto &output = ws["output"].asString();
|
||||
for (auto &ws : workspaces_) {
|
||||
const auto got_activated = (ws["id"].asUInt64() == id);
|
||||
if (ws["output"] == output) ws["is_active"] = got_activated;
|
||||
|
||||
if (focused) ws["is_focused"] = got_activated;
|
||||
}
|
||||
} else {
|
||||
spdlog::error("Activated unknown workspace");
|
||||
}
|
||||
} else if (const auto &payload = ev["WorkspaceActiveWindowChanged"]) {
|
||||
const auto workspaceId = payload["workspace_id"].asUInt64();
|
||||
auto it = std::find_if(workspaces_.begin(), workspaces_.end(), [workspaceId](const auto &ws) {
|
||||
return ws["id"].asUInt64() == workspaceId;
|
||||
});
|
||||
if (it != workspaces_.end()) {
|
||||
auto &ws = *it;
|
||||
ws["active_window_id"] = payload["active_window_id"];
|
||||
} else {
|
||||
spdlog::error("Active window changed on unknown workspace");
|
||||
}
|
||||
} else if (const auto &payload = ev["KeyboardLayoutsChanged"]) {
|
||||
const auto &layouts = payload["keyboard_layouts"];
|
||||
const auto &names = layouts["names"];
|
||||
keyboardLayoutCurrent_ = layouts["current_idx"].asUInt();
|
||||
|
||||
keyboardLayoutNames_.clear();
|
||||
for (const auto &fullName : names) keyboardLayoutNames_.push_back(fullName.asString());
|
||||
} else if (const auto &payload = ev["KeyboardLayoutSwitched"]) {
|
||||
keyboardLayoutCurrent_ = payload["idx"].asUInt();
|
||||
} else if (const auto &payload = ev["WindowsChanged"]) {
|
||||
windows_.clear();
|
||||
const auto &values = payload["windows"];
|
||||
std::copy(values.begin(), values.end(), std::back_inserter(windows_));
|
||||
} else if (const auto &payload = ev["WindowOpenedOrChanged"]) {
|
||||
const auto &window = payload["window"];
|
||||
const auto id = window["id"].asUInt64();
|
||||
auto it = std::find_if(windows_.begin(), windows_.end(),
|
||||
[id](const auto &win) { return win["id"].asUInt64() == id; });
|
||||
if (it == windows_.end()) {
|
||||
windows_.push_back(window);
|
||||
|
||||
if (window["is_focused"].asBool()) {
|
||||
for (auto &win : windows_) {
|
||||
win["is_focused"] = win["id"].asUInt64() == id;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*it = window;
|
||||
}
|
||||
} else if (const auto &payload = ev["WindowClosed"]) {
|
||||
const auto id = payload["id"].asUInt64();
|
||||
auto it = std::find_if(windows_.begin(), windows_.end(),
|
||||
[id](const auto &win) { return win["id"].asUInt64() == id; });
|
||||
if (it != windows_.end()) {
|
||||
windows_.erase(it);
|
||||
} else {
|
||||
spdlog::error("Unknown window closed");
|
||||
}
|
||||
} else if (const auto &payload = ev["WindowFocusChanged"]) {
|
||||
const auto focused = !payload["id"].isNull();
|
||||
const auto id = payload["id"].asUInt64();
|
||||
for (auto &win : windows_) {
|
||||
win["is_focused"] = focused && win["id"].asUInt64() == id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_lock lock(callbackMutex_);
|
||||
|
||||
for (auto &[eventname, handler] : callbacks_) {
|
||||
if (eventname == members[0]) {
|
||||
handler->onEvent(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IPC::registerForIPC(const std::string &ev, EventHandler *ev_handler) {
|
||||
if (ev_handler == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock lock(callbackMutex_);
|
||||
callbacks_.emplace_back(ev, ev_handler);
|
||||
}
|
||||
|
||||
void IPC::unregisterForIPC(EventHandler *ev_handler) {
|
||||
if (ev_handler == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock lock(callbackMutex_);
|
||||
|
||||
for (auto it = callbacks_.begin(); it != callbacks_.end();) {
|
||||
auto &[eventname, handler] = *it;
|
||||
if (handler == ev_handler) {
|
||||
it = callbacks_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value IPC::send(const Json::Value &request) {
|
||||
int socketfd = connectToSocket();
|
||||
if (socketfd == -1) throw std::runtime_error("Niri is not running");
|
||||
|
||||
auto unix_istream = Gio::UnixInputStream::create(socketfd, true);
|
||||
auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false);
|
||||
auto istream = Gio::DataInputStream::create(unix_istream);
|
||||
auto ostream = Gio::DataOutputStream::create(unix_ostream);
|
||||
|
||||
// Niri needs the request on a single line.
|
||||
Json::StreamWriterBuilder builder;
|
||||
builder["indentation"] = "";
|
||||
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
|
||||
std::ostringstream oss;
|
||||
writer->write(request, &oss);
|
||||
oss << '\n';
|
||||
|
||||
if (!ostream->put_string(oss.str()) || !ostream->flush())
|
||||
throw std::runtime_error("error writing to niri socket");
|
||||
|
||||
std::string line;
|
||||
if (!istream->read_line(line)) throw std::runtime_error("error reading from niri socket");
|
||||
|
||||
std::istringstream iss(std::move(line));
|
||||
Json::Value response;
|
||||
iss >> response;
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::niri
|
||||
136
src/modules/niri/language.cpp
Normal file
136
src/modules/niri/language.cpp
Normal file
@ -0,0 +1,136 @@
|
||||
#include "modules/niri/language.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon/xkbregistry.h>
|
||||
|
||||
#include "util/string.hpp"
|
||||
|
||||
namespace waybar::modules::niri {
|
||||
|
||||
Language::Language(const std::string &id, const Bar &bar, const Json::Value &config)
|
||||
: ALabel(config, "language", id, "{}", 0, false), bar_(bar) {
|
||||
label_.hide();
|
||||
|
||||
if (!gIPC) gIPC = std::make_unique<IPC>();
|
||||
|
||||
gIPC->registerForIPC("KeyboardLayoutsChanged", this);
|
||||
gIPC->registerForIPC("KeyboardLayoutSwitched", this);
|
||||
|
||||
updateFromIPC();
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Language::~Language() {
|
||||
gIPC->unregisterForIPC(this);
|
||||
// wait for possible event handler to finish
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
}
|
||||
|
||||
void Language::updateFromIPC() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto ipcLock = gIPC->lockData();
|
||||
|
||||
layouts_.clear();
|
||||
for (const auto &fullName : gIPC->keyboardLayoutNames()) layouts_.push_back(getLayout(fullName));
|
||||
|
||||
current_idx_ = gIPC->keyboardLayoutCurrent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Language::doUpdate - update workspaces in UI thread.
|
||||
*
|
||||
* Note: some member fields are modified by both UI thread and event listener thread, use mutex_ to
|
||||
* protect these member fields, and lock should released before calling ALabel::update().
|
||||
*/
|
||||
void Language::doUpdate() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (layouts_.size() <= current_idx_) {
|
||||
spdlog::error("niri language layout index out of bounds");
|
||||
label_.hide();
|
||||
return;
|
||||
}
|
||||
const auto &layout = layouts_[current_idx_];
|
||||
|
||||
spdlog::debug("niri language update with full name {}", layout.full_name);
|
||||
spdlog::debug("niri language update with short name {}", layout.short_name);
|
||||
spdlog::debug("niri language update with short description {}", layout.short_description);
|
||||
spdlog::debug("niri 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;
|
||||
layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());
|
||||
} else if (config_.isMember("format-" + layout.short_description)) {
|
||||
const auto propName = "format-" + layout.short_description;
|
||||
layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());
|
||||
} else {
|
||||
layoutName = trim(fmt::format(fmt::runtime(format_), fmt::arg("long", layout.full_name),
|
||||
fmt::arg("short", layout.short_name),
|
||||
fmt::arg("shortDescription", layout.short_description),
|
||||
fmt::arg("variant", layout.variant)));
|
||||
}
|
||||
|
||||
spdlog::debug("niri language formatted layout name {}", layoutName);
|
||||
|
||||
if (!format_.empty()) {
|
||||
label_.show();
|
||||
label_.set_markup(layoutName);
|
||||
} else {
|
||||
label_.hide();
|
||||
}
|
||||
}
|
||||
|
||||
void Language::update() {
|
||||
doUpdate();
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
void Language::onEvent(const Json::Value &ev) {
|
||||
if (ev["KeyboardLayoutsChanged"]) {
|
||||
updateFromIPC();
|
||||
} else if (ev["KeyboardLayoutSwitched"]) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto ipcLock = gIPC->lockData();
|
||||
current_idx_ = gIPC->keyboardLayoutCurrent();
|
||||
}
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Language::Layout Language::getLayout(const std::string &fullName) {
|
||||
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 != nullptr) {
|
||||
std::string nameOfLayout = rxkb_layout_get_description(layout);
|
||||
|
||||
if (nameOfLayout != fullName) {
|
||||
layout = rxkb_layout_next(layout);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto name = std::string(rxkb_layout_get_name(layout));
|
||||
const auto *variantPtr = rxkb_layout_get_variant(layout);
|
||||
std::string variant = variantPtr == nullptr ? "" : std::string(variantPtr);
|
||||
|
||||
const auto *descriptionPtr = rxkb_layout_get_brief(layout);
|
||||
std::string description = descriptionPtr == nullptr ? "" : std::string(descriptionPtr);
|
||||
|
||||
Layout info = Layout{nameOfLayout, name, variant, description};
|
||||
|
||||
rxkb_context_unref(context);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
rxkb_context_unref(context);
|
||||
|
||||
spdlog::debug("niri language didn't find matching layout for {}", fullName);
|
||||
|
||||
return Layout{"", "", "", ""};
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::niri
|
||||
109
src/modules/niri/window.cpp
Normal file
109
src/modules/niri/window.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
#include "modules/niri/window.hpp"
|
||||
|
||||
#include <gtkmm/button.h>
|
||||
#include <gtkmm/label.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "util/rewrite_string.hpp"
|
||||
#include "util/sanitize_str.hpp"
|
||||
|
||||
namespace waybar::modules::niri {
|
||||
|
||||
Window::Window(const std::string &id, const Bar &bar, const Json::Value &config)
|
||||
: AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) {
|
||||
if (!gIPC) gIPC = std::make_unique<IPC>();
|
||||
|
||||
gIPC->registerForIPC("WindowsChanged", this);
|
||||
gIPC->registerForIPC("WindowOpenedOrChanged", this);
|
||||
gIPC->registerForIPC("WindowClosed", this);
|
||||
gIPC->registerForIPC("WindowFocusChanged", this);
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Window::~Window() { gIPC->unregisterForIPC(this); }
|
||||
|
||||
void Window::onEvent(const Json::Value &ev) { dp.emit(); }
|
||||
|
||||
void Window::doUpdate() {
|
||||
auto ipcLock = gIPC->lockData();
|
||||
|
||||
const auto &windows = gIPC->windows();
|
||||
const auto &workspaces = gIPC->workspaces();
|
||||
|
||||
const auto separateOutputs = config_["separate-outputs"].asBool();
|
||||
const auto ws_it = std::find_if(workspaces.cbegin(), workspaces.cend(), [&](const auto &ws) {
|
||||
if (separateOutputs) {
|
||||
return ws["is_active"].asBool() && ws["output"].asString() == bar_.output->name;
|
||||
}
|
||||
|
||||
return ws["is_focused"].asBool();
|
||||
});
|
||||
|
||||
std::vector<Json::Value>::const_iterator it;
|
||||
if (ws_it == workspaces.cend() || (*ws_it)["active_window_id"].isNull()) {
|
||||
it = windows.cend();
|
||||
} else {
|
||||
const auto id = (*ws_it)["active_window_id"].asUInt64();
|
||||
it = std::find_if(windows.cbegin(), windows.cend(),
|
||||
[id](const auto &win) { return win["id"].asUInt64() == id; });
|
||||
}
|
||||
|
||||
setClass("empty", ws_it == workspaces.cend() || (*ws_it)["active_window_id"].isNull());
|
||||
|
||||
if (it != windows.cend()) {
|
||||
const auto &window = *it;
|
||||
|
||||
const auto title = window["title"].asString();
|
||||
const auto appId = window["app_id"].asString();
|
||||
const auto sanitizedTitle = waybar::util::sanitize_string(title);
|
||||
const auto sanitizedAppId = waybar::util::sanitize_string(appId);
|
||||
|
||||
label_.show();
|
||||
label_.set_markup(waybar::util::rewriteString(
|
||||
fmt::format(fmt::runtime(format_), fmt::arg("title", sanitizedTitle),
|
||||
fmt::arg("app_id", sanitizedAppId)),
|
||||
config_["rewrite"]));
|
||||
|
||||
updateAppIconName(appId, "");
|
||||
|
||||
if (tooltipEnabled()) label_.set_tooltip_text(title);
|
||||
|
||||
const auto id = window["id"].asUInt64();
|
||||
const auto workspaceId = window["workspace_id"].asUInt64();
|
||||
const auto isSolo = std::none_of(windows.cbegin(), windows.cend(), [&](const auto &win) {
|
||||
return win["id"].asUInt64() != id && win["workspace_id"].asUInt64() == workspaceId;
|
||||
});
|
||||
setClass("solo", isSolo);
|
||||
if (!appId.empty()) setClass(appId, isSolo);
|
||||
|
||||
if (oldAppId_ != appId) {
|
||||
if (!oldAppId_.empty()) setClass(oldAppId_, false);
|
||||
oldAppId_ = appId;
|
||||
}
|
||||
} else {
|
||||
label_.hide();
|
||||
updateAppIconName("", "");
|
||||
setClass("solo", false);
|
||||
if (!oldAppId_.empty()) setClass(oldAppId_, false);
|
||||
oldAppId_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::update() {
|
||||
doUpdate();
|
||||
AAppIconLabel::update();
|
||||
}
|
||||
|
||||
void Window::setClass(const std::string &className, bool enable) {
|
||||
auto styleContext = bar_.window.get_style_context();
|
||||
if (enable) {
|
||||
if (!styleContext->has_class(className)) {
|
||||
styleContext->add_class(className);
|
||||
}
|
||||
} else {
|
||||
styleContext->remove_class(className);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::niri
|
||||
186
src/modules/niri/workspaces.cpp
Normal file
186
src/modules/niri/workspaces.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
#include "modules/niri/workspaces.hpp"
|
||||
|
||||
#include <gtkmm/button.h>
|
||||
#include <gtkmm/label.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace waybar::modules::niri {
|
||||
|
||||
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
|
||||
: AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) {
|
||||
box_.set_name("workspaces");
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
box_.get_style_context()->add_class(MODULE_CLASS);
|
||||
event_box_.add(box_);
|
||||
|
||||
if (!gIPC) gIPC = std::make_unique<IPC>();
|
||||
|
||||
gIPC->registerForIPC("WorkspacesChanged", this);
|
||||
gIPC->registerForIPC("WorkspaceActivated", this);
|
||||
gIPC->registerForIPC("WorkspaceActiveWindowChanged", this);
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Workspaces::~Workspaces() { gIPC->unregisterForIPC(this); }
|
||||
|
||||
void Workspaces::onEvent(const Json::Value &ev) { dp.emit(); }
|
||||
|
||||
void Workspaces::doUpdate() {
|
||||
auto ipcLock = gIPC->lockData();
|
||||
|
||||
const auto alloutputs = config_["all-outputs"].asBool();
|
||||
std::vector<Json::Value> my_workspaces;
|
||||
const auto &workspaces = gIPC->workspaces();
|
||||
std::copy_if(workspaces.cbegin(), workspaces.cend(), std::back_inserter(my_workspaces),
|
||||
[&](const auto &ws) {
|
||||
if (alloutputs) return true;
|
||||
return ws["output"].asString() == bar_.output->name;
|
||||
});
|
||||
|
||||
// Remove buttons for removed workspaces.
|
||||
for (auto it = buttons_.begin(); it != buttons_.end();) {
|
||||
auto ws = std::find_if(my_workspaces.begin(), my_workspaces.end(),
|
||||
[it](const auto &ws) { return ws["id"].asUInt64() == it->first; });
|
||||
if (ws == my_workspaces.end()) {
|
||||
it = buttons_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// Add buttons for new workspaces, update existing ones.
|
||||
for (const auto &ws : my_workspaces) {
|
||||
auto bit = buttons_.find(ws["id"].asUInt64());
|
||||
auto &button = bit == buttons_.end() ? addButton(ws) : bit->second;
|
||||
auto style_context = button.get_style_context();
|
||||
|
||||
if (ws["is_focused"].asBool())
|
||||
style_context->add_class("focused");
|
||||
else
|
||||
style_context->remove_class("focused");
|
||||
|
||||
if (ws["is_active"].asBool())
|
||||
style_context->add_class("active");
|
||||
else
|
||||
style_context->remove_class("active");
|
||||
|
||||
if (ws["output"]) {
|
||||
if (ws["output"].asString() == bar_.output->name)
|
||||
style_context->add_class("current_output");
|
||||
else
|
||||
style_context->remove_class("current_output");
|
||||
} else {
|
||||
style_context->remove_class("current_output");
|
||||
}
|
||||
|
||||
if (ws["active_window_id"].isNull())
|
||||
style_context->add_class("empty");
|
||||
else
|
||||
style_context->remove_class("empty");
|
||||
|
||||
std::string name;
|
||||
if (ws["name"]) {
|
||||
name = ws["name"].asString();
|
||||
} else {
|
||||
name = std::to_string(ws["idx"].asUInt());
|
||||
}
|
||||
button.set_name("niri-workspace-" + name);
|
||||
|
||||
if (config_["format"].isString()) {
|
||||
auto format = config_["format"].asString();
|
||||
name = fmt::format(fmt::runtime(format), fmt::arg("icon", getIcon(name, ws)),
|
||||
fmt::arg("value", name), fmt::arg("name", ws["name"].asString()),
|
||||
fmt::arg("index", ws["idx"].asUInt()),
|
||||
fmt::arg("output", ws["output"].asString()));
|
||||
}
|
||||
if (!config_["disable-markup"].asBool()) {
|
||||
static_cast<Gtk::Label *>(button.get_children()[0])->set_markup(name);
|
||||
} else {
|
||||
button.set_label(name);
|
||||
}
|
||||
|
||||
if (config_["current-only"].asBool()) {
|
||||
const auto *property = alloutputs ? "is_focused" : "is_active";
|
||||
if (ws[property].asBool())
|
||||
button.show();
|
||||
else
|
||||
button.hide();
|
||||
} else {
|
||||
button.show();
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the button order.
|
||||
for (auto it = my_workspaces.cbegin(); it != my_workspaces.cend(); ++it) {
|
||||
const auto &ws = *it;
|
||||
|
||||
auto pos = ws["idx"].asUInt() - 1;
|
||||
if (alloutputs) pos = it - my_workspaces.cbegin();
|
||||
|
||||
auto &button = buttons_[ws["id"].asUInt64()];
|
||||
box_.reorder_child(button, pos);
|
||||
}
|
||||
}
|
||||
|
||||
void Workspaces::update() {
|
||||
doUpdate();
|
||||
AModule::update();
|
||||
}
|
||||
|
||||
Gtk::Button &Workspaces::addButton(const Json::Value &ws) {
|
||||
std::string name;
|
||||
if (ws["name"]) {
|
||||
name = ws["name"].asString();
|
||||
} else {
|
||||
name = std::to_string(ws["idx"].asUInt());
|
||||
}
|
||||
|
||||
auto pair = buttons_.emplace(ws["id"].asUInt64(), name);
|
||||
auto &&button = pair.first->second;
|
||||
box_.pack_start(button, false, false, 0);
|
||||
button.set_relief(Gtk::RELIEF_NONE);
|
||||
if (!config_["disable-click"].asBool()) {
|
||||
const auto id = ws["id"].asUInt64();
|
||||
button.signal_pressed().connect([=] {
|
||||
try {
|
||||
// {"Action":{"FocusWorkspace":{"reference":{"Id":1}}}}
|
||||
Json::Value request(Json::objectValue);
|
||||
auto &action = (request["Action"] = Json::Value(Json::objectValue));
|
||||
auto &focusWorkspace = (action["FocusWorkspace"] = Json::Value(Json::objectValue));
|
||||
auto &reference = (focusWorkspace["reference"] = Json::Value(Json::objectValue));
|
||||
reference["Id"] = id;
|
||||
|
||||
IPC::send(request);
|
||||
} catch (const std::exception &e) {
|
||||
spdlog::error("Error switching workspace: {}", e.what());
|
||||
}
|
||||
});
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws) {
|
||||
const auto &icons = config_["format-icons"];
|
||||
if (!icons) return value;
|
||||
|
||||
if (ws["is_focused"].asBool() && icons["focused"]) return icons["focused"].asString();
|
||||
|
||||
if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString();
|
||||
|
||||
if (ws["name"]) {
|
||||
const auto &name = ws["name"].asString();
|
||||
if (icons[name]) return icons[name].asString();
|
||||
}
|
||||
|
||||
const auto idx = ws["idx"].asString();
|
||||
if (icons[idx]) return icons[idx].asString();
|
||||
|
||||
if (icons["default"]) return icons["default"].asString();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::niri
|
||||
213
src/modules/power_profiles_daemon.cpp
Normal file
213
src/modules/power_profiles_daemon.cpp
Normal file
@ -0,0 +1,213 @@
|
||||
#include "modules/power_profiles_daemon.hpp"
|
||||
|
||||
#include <fmt/args.h>
|
||||
#include <glibmm.h>
|
||||
#include <glibmm/variant.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace waybar::modules {
|
||||
|
||||
PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "power-profiles-daemon", id, "{icon}", 0, false, true), connected_(false) {
|
||||
if (config_["tooltip-format"].isString()) {
|
||||
tooltipFormat_ = config_["tooltip-format"].asString();
|
||||
} else {
|
||||
tooltipFormat_ = "Power profile: {profile}\nDriver: {driver}";
|
||||
}
|
||||
// Fasten your seatbelt, we're up for quite a ride. The rest of the
|
||||
// init is performed asynchronously. There's 2 callbacks involved.
|
||||
// Here's the overall idea:
|
||||
// 1. Async connect to the system bus.
|
||||
// 2. In the system bus connect callback, try to call
|
||||
// org.freedesktop.DBus.Properties.GetAll to see if
|
||||
// power-profiles-daemon is able to respond.
|
||||
// 3. In the GetAll callback, connect the activeProfile monitoring
|
||||
// callback, consider the init to be successful. Meaning start
|
||||
// drawing the module.
|
||||
//
|
||||
// There's sadly no other way around that, we have to try to call a
|
||||
// method on the proxy to see whether or not something's responding
|
||||
// on the other side.
|
||||
|
||||
// NOTE: the DBus addresses are under migration. They should be
|
||||
// changed to org.freedesktop.UPower.PowerProfiles at some point.
|
||||
//
|
||||
// See
|
||||
// https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/releases/0.20
|
||||
//
|
||||
// The old name is still announced for now. Let's rather use the old
|
||||
// addresses 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 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
|
||||
210
src/modules/privacy/privacy.cpp
Normal file
210
src/modules/privacy/privacy.cpp
Normal file
@ -0,0 +1,210 @@
|
||||
#include "modules/privacy/privacy.hpp"
|
||||
|
||||
#include <json/value.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "AModule.hpp"
|
||||
#include "modules/privacy/privacy_item.hpp"
|
||||
|
||||
namespace waybar::modules::privacy {
|
||||
|
||||
using util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_INPUT;
|
||||
using util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT;
|
||||
using util::PipewireBackend::PRIVACY_NODE_TYPE_NONE;
|
||||
using util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT;
|
||||
|
||||
Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientation orientation,
|
||||
const std::string& pos)
|
||||
: AModule(config, "privacy", id),
|
||||
nodes_screenshare(),
|
||||
nodes_audio_in(),
|
||||
nodes_audio_out(),
|
||||
visibility_conn(),
|
||||
box_(orientation, 0) {
|
||||
box_.set_name(name_);
|
||||
|
||||
event_box_.add(box_);
|
||||
|
||||
// Icon Spacing
|
||||
if (config_["icon-spacing"].isUInt()) {
|
||||
iconSpacing = config_["icon-spacing"].asUInt();
|
||||
}
|
||||
box_.set_spacing(iconSpacing);
|
||||
|
||||
// Icon Size
|
||||
if (config_["icon-size"].isUInt()) {
|
||||
iconSize = config_["icon-size"].asUInt();
|
||||
}
|
||||
|
||||
// Transition Duration
|
||||
if (config_["transition-duration"].isUInt()) {
|
||||
transition_duration = config_["transition-duration"].asUInt();
|
||||
}
|
||||
|
||||
// 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.empty()) {
|
||||
modules = Json::Value(Json::arrayValue);
|
||||
for (const auto& type : {"screenshare", "audio-in"}) {
|
||||
Json::Value obj = Json::Value(Json::objectValue);
|
||||
obj["type"] = type;
|
||||
modules.append(obj);
|
||||
}
|
||||
}
|
||||
|
||||
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, orientation, pos,
|
||||
iconSize, transition_duration);
|
||||
box_.add(*item);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& ignore_item : config_["ignore"]) {
|
||||
if (!ignore_item.isObject() || !ignore_item["type"].isString() ||
|
||||
!ignore_item["name"].isString())
|
||||
continue;
|
||||
const std::string type = ignore_item["type"].asString();
|
||||
const std::string name = ignore_item["name"].asString();
|
||||
|
||||
auto iter = typeMap.find(type);
|
||||
if (iter != typeMap.end()) {
|
||||
auto& [_, nodeType] = iter->second;
|
||||
ignore.emplace(nodeType, std::move(name));
|
||||
}
|
||||
}
|
||||
|
||||
if (config_["ignore-monitor"].isBool()) {
|
||||
ignore_monitor = config_["ignore-monitor"].asBool();
|
||||
}
|
||||
|
||||
backend = util::PipewireBackend::PipewireBackend::getInstance();
|
||||
backend->privacy_nodes_changed_signal_event.connect(
|
||||
sigc::mem_fun(*this, &Privacy::onPrivacyNodesChanged));
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
void Privacy::onPrivacyNodesChanged() {
|
||||
mutex_.lock();
|
||||
nodes_audio_out.clear();
|
||||
nodes_audio_in.clear();
|
||||
nodes_screenshare.clear();
|
||||
|
||||
for (auto& node : backend->privacy_nodes) {
|
||||
if (ignore_monitor && node.second->is_monitor) continue;
|
||||
|
||||
auto iter = ignore.find(std::pair(node.second->type, node.second->node_name));
|
||||
if (iter != ignore.end()) continue;
|
||||
|
||||
switch (node.second->state) {
|
||||
case PW_NODE_STATE_RUNNING:
|
||||
switch (node.second->type) {
|
||||
case PRIVACY_NODE_TYPE_VIDEO_INPUT:
|
||||
nodes_screenshare.push_back(node.second);
|
||||
break;
|
||||
case PRIVACY_NODE_TYPE_AUDIO_INPUT:
|
||||
nodes_audio_in.push_back(node.second);
|
||||
break;
|
||||
case PRIVACY_NODE_TYPE_AUDIO_OUTPUT:
|
||||
nodes_audio_out.push_back(node.second);
|
||||
break;
|
||||
case PRIVACY_NODE_TYPE_NONE:
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_.unlock();
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
auto Privacy::update() -> void {
|
||||
// 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()) {
|
||||
auto* module = dynamic_cast<PrivacyItem*>(widget);
|
||||
if (module == nullptr) continue;
|
||||
switch (module->privacy_type) {
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT:
|
||||
setScreenshare = true;
|
||||
useScreenshare = !nodes_screenshare.empty();
|
||||
module->set_in_use(useScreenshare);
|
||||
break;
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_INPUT:
|
||||
setAudioIn = true;
|
||||
useAudioIn = !nodes_audio_in.empty();
|
||||
module->set_in_use(useAudioIn);
|
||||
break;
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT:
|
||||
setAudioOut = true;
|
||||
useAudioOut = !nodes_audio_out.empty();
|
||||
module->set_in_use(useAudioOut);
|
||||
break;
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_.unlock();
|
||||
|
||||
// Hide the whole widget if none are in use
|
||||
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 (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, setScreenshare, setAudioOut, setAudioIn]() {
|
||||
mutex_.lock();
|
||||
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(visible);
|
||||
return false;
|
||||
},
|
||||
*this),
|
||||
transition_duration);
|
||||
}
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
AModule::update();
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::privacy
|
||||
164
src/modules/privacy/privacy_item.cpp
Normal file
164
src/modules/privacy/privacy_item.cpp
Normal file
@ -0,0 +1,164 @@
|
||||
#include "modules/privacy/privacy_item.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "glibmm/main.h"
|
||||
#include "gtkmm/label.h"
|
||||
#include "gtkmm/revealer.h"
|
||||
#include "gtkmm/tooltip.h"
|
||||
#include "util/pipewire/privacy_node_info.hpp"
|
||||
|
||||
namespace waybar::modules::privacy {
|
||||
|
||||
PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_,
|
||||
std::list<PrivacyNodeInfo *> *nodes_, Gtk::Orientation orientation,
|
||||
const std::string &pos, const uint icon_size,
|
||||
const uint transition_duration)
|
||||
: Gtk::Revealer(),
|
||||
privacy_type(privacy_type_),
|
||||
nodes(nodes_),
|
||||
signal_conn(),
|
||||
tooltip_window(Gtk::ORIENTATION_VERTICAL, 0),
|
||||
box_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||
icon_() {
|
||||
switch (privacy_type) {
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_INPUT:
|
||||
box_.get_style_context()->add_class("audio-in");
|
||||
iconName = "waybar-privacy-audio-input-symbolic";
|
||||
break;
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT:
|
||||
box_.get_style_context()->add_class("audio-out");
|
||||
iconName = "waybar-privacy-audio-output-symbolic";
|
||||
break;
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT:
|
||||
box_.get_style_context()->add_class("screenshare");
|
||||
iconName = "waybar-privacy-screen-share-symbolic";
|
||||
break;
|
||||
default:
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_NONE:
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the reveal transition to not look weird when sliding in
|
||||
if (pos == "modules-left") {
|
||||
set_transition_type(orientation == Gtk::ORIENTATION_HORIZONTAL
|
||||
? Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT
|
||||
: Gtk::REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
|
||||
} else if (pos == "modules-center") {
|
||||
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_CROSSFADE);
|
||||
} else if (pos == "modules-right") {
|
||||
set_transition_type(orientation == Gtk::ORIENTATION_HORIZONTAL
|
||||
? Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT
|
||||
: Gtk::REVEALER_TRANSITION_TYPE_SLIDE_UP);
|
||||
}
|
||||
set_transition_duration(transition_duration);
|
||||
|
||||
box_.set_name("privacy-item");
|
||||
|
||||
// We use `set_center_widget` instead of `add` to make sure the icon is
|
||||
// centered even if the orientation is vertical
|
||||
box_.set_center_widget(icon_);
|
||||
|
||||
icon_.set_pixel_size(icon_size);
|
||||
add(box_);
|
||||
|
||||
// Icon Name
|
||||
if (config_["icon-name"].isString()) {
|
||||
iconName = config_["icon-name"].asString();
|
||||
}
|
||||
icon_.set_from_icon_name(iconName, Gtk::ICON_SIZE_INVALID);
|
||||
|
||||
// Tooltip Icon Size
|
||||
if (config_["tooltip-icon-size"].isUInt()) {
|
||||
tooltipIconSize = config_["tooltip-icon-size"].asUInt();
|
||||
}
|
||||
// Tooltip
|
||||
if (config_["tooltip"].isString()) {
|
||||
tooltip = config_["tooltip"].asBool();
|
||||
}
|
||||
set_has_tooltip(tooltip);
|
||||
if (tooltip) {
|
||||
// Sets the window to use when showing the tooltip
|
||||
update_tooltip();
|
||||
this->signal_query_tooltip().connect(sigc::track_obj(
|
||||
[this](int x, int y, bool keyboard_tooltip, const Glib::RefPtr<Gtk::Tooltip> &tooltip) {
|
||||
tooltip->set_custom(tooltip_window);
|
||||
return true;
|
||||
},
|
||||
*this));
|
||||
}
|
||||
|
||||
// Don't show by default
|
||||
set_reveal_child(true);
|
||||
set_visible(false);
|
||||
}
|
||||
|
||||
void PrivacyItem::update_tooltip() {
|
||||
// Removes all old nodes
|
||||
for (auto *child : tooltip_window.get_children()) {
|
||||
tooltip_window.remove(*child);
|
||||
// despite the remove, still needs a delete to prevent memory leak. Speculating that this might
|
||||
// work differently in GTK4.
|
||||
delete child;
|
||||
}
|
||||
for (auto *node : *nodes) {
|
||||
auto *box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL, 4);
|
||||
|
||||
// Set device icon
|
||||
auto *node_icon = Gtk::make_managed<Gtk::Image>();
|
||||
node_icon->set_pixel_size(tooltipIconSize);
|
||||
node_icon->set_from_icon_name(node->getIconName(), Gtk::ICON_SIZE_INVALID);
|
||||
box->add(*node_icon);
|
||||
|
||||
// Set model
|
||||
auto *nodeName = Gtk::make_managed<Gtk::Label>(node->getName());
|
||||
box->add(*nodeName);
|
||||
|
||||
tooltip_window.add(*box);
|
||||
}
|
||||
|
||||
tooltip_window.show_all();
|
||||
}
|
||||
|
||||
void PrivacyItem::set_in_use(bool in_use) {
|
||||
if (in_use) {
|
||||
update_tooltip();
|
||||
}
|
||||
|
||||
if (this->in_use == in_use && init) return;
|
||||
|
||||
if (init) {
|
||||
// Disconnect any previous connection so that it doesn't get activated in
|
||||
// the future, hiding the module when it should be visible
|
||||
signal_conn.disconnect();
|
||||
|
||||
this->in_use = in_use;
|
||||
guint duration = 0;
|
||||
if (this->in_use) {
|
||||
set_visible(true);
|
||||
} else {
|
||||
set_reveal_child(false);
|
||||
duration = get_transition_duration();
|
||||
}
|
||||
|
||||
signal_conn = Glib::signal_timeout().connect(sigc::track_obj(
|
||||
[this] {
|
||||
if (this->in_use) {
|
||||
set_reveal_child(true);
|
||||
} else {
|
||||
set_visible(false);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
*this),
|
||||
duration);
|
||||
} else {
|
||||
set_visible(false);
|
||||
set_reveal_child(false);
|
||||
}
|
||||
this->init = true;
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::privacy
|
||||
@ -1,74 +1,12 @@
|
||||
#include "modules/pulseaudio.hpp"
|
||||
|
||||
waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value &config)
|
||||
: ALabel(config, "pulseaudio", id, "{volume}%"),
|
||||
mainloop_(nullptr),
|
||||
mainloop_api_(nullptr),
|
||||
context_(nullptr),
|
||||
sink_idx_(0),
|
||||
volume_(0),
|
||||
muted_(false),
|
||||
source_idx_(0),
|
||||
source_volume_(0),
|
||||
source_muted_(false) {
|
||||
mainloop_ = pa_threaded_mainloop_new();
|
||||
if (mainloop_ == nullptr) {
|
||||
throw std::runtime_error("pa_mainloop_new() failed.");
|
||||
}
|
||||
pa_threaded_mainloop_lock(mainloop_);
|
||||
mainloop_api_ = pa_threaded_mainloop_get_api(mainloop_);
|
||||
context_ = pa_context_new(mainloop_api_, "waybar");
|
||||
if (context_ == nullptr) {
|
||||
throw std::runtime_error("pa_context_new() failed.");
|
||||
}
|
||||
if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOFAIL, nullptr) < 0) {
|
||||
auto err =
|
||||
fmt::format("pa_context_connect() failed: {}", pa_strerror(pa_context_errno(context_)));
|
||||
throw std::runtime_error(err);
|
||||
}
|
||||
pa_context_set_state_callback(context_, contextStateCb, this);
|
||||
if (pa_threaded_mainloop_start(mainloop_) < 0) {
|
||||
throw std::runtime_error("pa_mainloop_run() failed.");
|
||||
}
|
||||
pa_threaded_mainloop_unlock(mainloop_);
|
||||
: ALabel(config, "pulseaudio", id, "{volume}%") {
|
||||
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
||||
event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Pulseaudio::handleScroll));
|
||||
}
|
||||
|
||||
waybar::modules::Pulseaudio::~Pulseaudio() {
|
||||
pa_context_disconnect(context_);
|
||||
mainloop_api_->quit(mainloop_api_, 0);
|
||||
pa_threaded_mainloop_stop(mainloop_);
|
||||
pa_threaded_mainloop_free(mainloop_);
|
||||
}
|
||||
|
||||
void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data) {
|
||||
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
|
||||
switch (pa_context_get_state(c)) {
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
pa->mainloop_api_->quit(pa->mainloop_api_, 0);
|
||||
break;
|
||||
case PA_CONTEXT_READY:
|
||||
pa_context_get_server_info(c, serverInfoCb, data);
|
||||
pa_context_set_subscribe_callback(c, subscribeCb, data);
|
||||
pa_context_subscribe(c,
|
||||
static_cast<enum pa_subscription_mask>(
|
||||
static_cast<int>(PA_SUBSCRIPTION_MASK_SERVER) |
|
||||
static_cast<int>(PA_SUBSCRIPTION_MASK_SINK) |
|
||||
static_cast<int>(PA_SUBSCRIPTION_MASK_SINK_INPUT) |
|
||||
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE) |
|
||||
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)),
|
||||
nullptr, nullptr);
|
||||
break;
|
||||
case PA_CONTEXT_FAILED:
|
||||
pa->mainloop_api_->quit(pa->mainloop_api_, 1);
|
||||
break;
|
||||
case PA_CONTEXT_CONNECTING:
|
||||
case PA_CONTEXT_AUTHORIZING:
|
||||
case PA_CONTEXT_SETTING_NAME:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
backend = util::AudioBackend::getInstance([this] { this->dp.emit(); });
|
||||
backend->setIgnoredSinks(config_["ignored-sinks"]);
|
||||
}
|
||||
|
||||
bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
|
||||
@ -81,9 +19,6 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
|
||||
if (dir == SCROLL_DIR::NONE) {
|
||||
return true;
|
||||
}
|
||||
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
|
||||
pa_volume_t change = volume_tick;
|
||||
pa_cvolume pa_volume = pa_volume_;
|
||||
int max_volume = 100;
|
||||
double step = 1;
|
||||
// isDouble returns true for integers as well, just in case
|
||||
@ -91,176 +26,59 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
|
||||
step = config_["scroll-step"].asDouble();
|
||||
}
|
||||
if (config_["max-volume"].isInt()) {
|
||||
max_volume = std::min(config_["max-volume"].asInt(), static_cast<int>(PA_VOLUME_UI_MAX));
|
||||
max_volume = config_["max-volume"].asInt();
|
||||
}
|
||||
|
||||
if (dir == SCROLL_DIR::UP) {
|
||||
if (volume_ < max_volume) {
|
||||
if (volume_ + step > max_volume) {
|
||||
change = round((max_volume - volume_) * volume_tick);
|
||||
} else {
|
||||
change = round(step * volume_tick);
|
||||
}
|
||||
pa_cvolume_inc(&pa_volume, change);
|
||||
}
|
||||
} else if (dir == SCROLL_DIR::DOWN) {
|
||||
if (volume_ > 0) {
|
||||
if (volume_ - step < 0) {
|
||||
change = round(volume_ * volume_tick);
|
||||
} else {
|
||||
change = round(step * volume_tick);
|
||||
}
|
||||
pa_cvolume_dec(&pa_volume, change);
|
||||
}
|
||||
}
|
||||
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
|
||||
auto change_type = (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::RIGHT)
|
||||
? util::ChangeType::Increase
|
||||
: util::ChangeType::Decrease;
|
||||
|
||||
backend->changeVolume(change_type, step, max_volume);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when an event we subscribed to occurs.
|
||||
*/
|
||||
void waybar::modules::Pulseaudio::subscribeCb(pa_context *context,
|
||||
pa_subscription_event_type_t type, uint32_t idx,
|
||||
void *data) {
|
||||
unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
|
||||
unsigned operation = type & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
|
||||
if (operation != PA_SUBSCRIPTION_EVENT_CHANGE) {
|
||||
return;
|
||||
}
|
||||
if (facility == PA_SUBSCRIPTION_EVENT_SERVER) {
|
||||
pa_context_get_server_info(context, serverInfoCb, data);
|
||||
} else if (facility == PA_SUBSCRIPTION_EVENT_SINK) {
|
||||
pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data);
|
||||
} else if (facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
|
||||
pa_context_get_sink_info_list(context, sinkInfoCb, data);
|
||||
} else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) {
|
||||
pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data);
|
||||
} else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) {
|
||||
pa_context_get_source_info_list(context, sourceInfoCb, data);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Called in response to a volume change request
|
||||
*/
|
||||
void waybar::modules::Pulseaudio::volumeModifyCb(pa_context *c, int success, void *data) {
|
||||
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
|
||||
if (success != 0) {
|
||||
pa_context_get_sink_info_by_index(pa->context_, pa->sink_idx_, sinkInfoCb, data);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when the requested source information is ready.
|
||||
*/
|
||||
void waybar::modules::Pulseaudio::sourceInfoCb(pa_context * /*context*/, const pa_source_info *i,
|
||||
int /*eol*/, void *data) {
|
||||
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
|
||||
if (i != nullptr && pa->default_source_name_ == i->name) {
|
||||
auto source_volume = static_cast<float>(pa_cvolume_avg(&(i->volume))) / float{PA_VOLUME_NORM};
|
||||
pa->source_volume_ = std::round(source_volume * 100.0F);
|
||||
pa->source_idx_ = i->index;
|
||||
pa->source_muted_ = i->mute != 0;
|
||||
pa->source_desc_ = i->description;
|
||||
pa->source_port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown";
|
||||
pa->dp.emit();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when the requested sink information is ready.
|
||||
*/
|
||||
void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i,
|
||||
int /*eol*/, void *data) {
|
||||
if (i == nullptr) return;
|
||||
|
||||
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
|
||||
|
||||
if (pa->config_["ignored-sinks"].isArray()) {
|
||||
for (const auto &ignored_sink : pa->config_["ignored-sinks"]) {
|
||||
if (ignored_sink.asString() == i->description) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pa->current_sink_name_ == i->name) {
|
||||
if (i->state != PA_SINK_RUNNING) {
|
||||
pa->current_sink_running_ = false;
|
||||
} else {
|
||||
pa->current_sink_running_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pa->current_sink_running_ && i->state == PA_SINK_RUNNING) {
|
||||
pa->current_sink_name_ = i->name;
|
||||
pa->current_sink_running_ = true;
|
||||
}
|
||||
|
||||
if (pa->current_sink_name_ == i->name) {
|
||||
pa->pa_volume_ = i->volume;
|
||||
float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) / float{PA_VOLUME_NORM};
|
||||
pa->sink_idx_ = i->index;
|
||||
pa->volume_ = std::round(volume * 100.0F);
|
||||
pa->muted_ = i->mute != 0;
|
||||
pa->desc_ = i->description;
|
||||
pa->monitor_ = i->monitor_source_name;
|
||||
pa->port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown";
|
||||
if (auto ff = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_FORM_FACTOR)) {
|
||||
pa->form_factor_ = ff;
|
||||
} else {
|
||||
pa->form_factor_ = "";
|
||||
}
|
||||
pa->dp.emit();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when the requested information on the server is ready. This is
|
||||
* used to find the default PulseAudio sink.
|
||||
*/
|
||||
void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, const pa_server_info *i,
|
||||
void *data) {
|
||||
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
|
||||
pa->current_sink_name_ = i->default_sink_name;
|
||||
pa->default_source_name_ = i->default_source_name;
|
||||
|
||||
pa_context_get_sink_info_list(context, sinkInfoCb, data);
|
||||
pa_context_get_source_info_list(context, sourceInfoCb, data);
|
||||
}
|
||||
|
||||
static const std::array<std::string, 9> ports = {
|
||||
"headphone", "speaker", "hdmi", "headset", "hands-free", "portable", "car", "hifi", "phone",
|
||||
};
|
||||
|
||||
const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {
|
||||
std::vector<std::string> res = {current_sink_name_, default_source_name_};
|
||||
std::string nameLC = port_name_ + form_factor_;
|
||||
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;
|
||||
}
|
||||
|
||||
auto waybar::modules::Pulseaudio::update() -> void {
|
||||
auto format = format_;
|
||||
std::string tooltip_format;
|
||||
auto sink_volume = backend->getSinkVolume();
|
||||
if (!alt_) {
|
||||
std::string format_name = "format";
|
||||
if (monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio
|
||||
monitor_.find("a2dp-sink") != std::string::npos || // PipeWire
|
||||
monitor_.find("bluez") != std::string::npos) {
|
||||
if (backend->isBluetooth()) {
|
||||
format_name = format_name + "-bluetooth";
|
||||
label_.get_style_context()->add_class("bluetooth");
|
||||
} else {
|
||||
label_.get_style_context()->remove_class("bluetooth");
|
||||
}
|
||||
if (muted_) {
|
||||
if (backend->getSinkMuted()) {
|
||||
// Check muted bluetooth format exist, otherwise fallback to default muted format
|
||||
if (format_name != "format" && !config_[format_name + "-muted"].isString()) {
|
||||
format_name = "format";
|
||||
@ -272,7 +90,7 @@ auto waybar::modules::Pulseaudio::update() -> void {
|
||||
label_.get_style_context()->remove_class("muted");
|
||||
label_.get_style_context()->remove_class("sink-muted");
|
||||
}
|
||||
auto state = getState(volume_, true);
|
||||
auto state = getState(sink_volume, true);
|
||||
if (!state.empty() && config_[format_name + "-" + state].isString()) {
|
||||
format = config_[format_name + "-" + state].asString();
|
||||
} else if (config_[format_name].isString()) {
|
||||
@ -281,22 +99,27 @@ auto waybar::modules::Pulseaudio::update() -> void {
|
||||
}
|
||||
// TODO: find a better way to split source/sink
|
||||
std::string format_source = "{volume}%";
|
||||
if (source_muted_) {
|
||||
if (backend->getSourceMuted()) {
|
||||
label_.get_style_context()->add_class("source-muted");
|
||||
if (config_["format-source-muted"].isString()) {
|
||||
format_source = config_["format-source-muted"].asString();
|
||||
}
|
||||
} else {
|
||||
label_.get_style_context()->remove_class("source-muted");
|
||||
if (config_["format-source-muted"].isString()) {
|
||||
if (config_["format-source"].isString()) {
|
||||
format_source = config_["format-source"].asString();
|
||||
}
|
||||
}
|
||||
format_source = fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_volume_));
|
||||
|
||||
auto source_volume = backend->getSourceVolume();
|
||||
auto sink_desc = backend->getSinkDesc();
|
||||
auto source_desc = backend->getSourceDesc();
|
||||
|
||||
format_source = fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_volume));
|
||||
auto text = fmt::format(
|
||||
fmt::runtime(format), fmt::arg("desc", desc_), fmt::arg("volume", volume_),
|
||||
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
|
||||
fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())));
|
||||
fmt::runtime(format), fmt::arg("desc", sink_desc), fmt::arg("volume", sink_volume),
|
||||
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume),
|
||||
fmt::arg("source_desc", source_desc), fmt::arg("icon", getIcon(sink_volume, getPulseIcon())));
|
||||
if (text.empty()) {
|
||||
label_.hide();
|
||||
} else {
|
||||
@ -310,12 +133,12 @@ auto waybar::modules::Pulseaudio::update() -> void {
|
||||
}
|
||||
if (!tooltip_format.empty()) {
|
||||
label_.set_tooltip_text(fmt::format(
|
||||
fmt::runtime(tooltip_format), fmt::arg("desc", desc_), fmt::arg("volume", volume_),
|
||||
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
|
||||
fmt::arg("source_desc", source_desc_),
|
||||
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
|
||||
fmt::runtime(tooltip_format), fmt::arg("desc", sink_desc),
|
||||
fmt::arg("volume", sink_volume), fmt::arg("format_source", format_source),
|
||||
fmt::arg("source_volume", source_volume), fmt::arg("source_desc", source_desc),
|
||||
fmt::arg("icon", getIcon(sink_volume, getPulseIcon()))));
|
||||
} else {
|
||||
label_.set_tooltip_text(desc_);
|
||||
label_.set_tooltip_text(sink_desc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
82
src/modules/pulseaudio_slider.cpp
Normal file
82
src/modules/pulseaudio_slider.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
#include "modules/pulseaudio_slider.hpp"
|
||||
|
||||
namespace waybar::modules {
|
||||
|
||||
PulseaudioSlider::PulseaudioSlider(const std::string& id, const Json::Value& config)
|
||||
: ASlider(config, "pulseaudio-slider", id) {
|
||||
backend = util::AudioBackend::getInstance([this] { this->dp.emit(); });
|
||||
backend->setIgnoredSinks(config_["ignored-sinks"]);
|
||||
|
||||
if (config_["target"].isString()) {
|
||||
std::string target = config_["target"].asString();
|
||||
if (target == "sink") {
|
||||
this->target = PulseaudioSliderTarget::Sink;
|
||||
} else if (target == "source") {
|
||||
this->target = PulseaudioSliderTarget::Source;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PulseaudioSlider::update() {
|
||||
switch (target) {
|
||||
case PulseaudioSliderTarget::Sink:
|
||||
if (backend->getSinkMuted()) {
|
||||
scale_.set_value(min_);
|
||||
} else {
|
||||
scale_.set_value(backend->getSinkVolume());
|
||||
}
|
||||
break;
|
||||
|
||||
case PulseaudioSliderTarget::Source:
|
||||
if (backend->getSourceMuted()) {
|
||||
scale_.set_value(min_);
|
||||
} else {
|
||||
scale_.set_value(backend->getSourceVolume());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PulseaudioSlider::onValueChanged() {
|
||||
bool is_mute = false;
|
||||
|
||||
switch (target) {
|
||||
case PulseaudioSliderTarget::Sink:
|
||||
if (backend->getSinkMuted()) {
|
||||
is_mute = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case PulseaudioSliderTarget::Source:
|
||||
if (backend->getSourceMuted()) {
|
||||
is_mute = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
uint16_t volume = scale_.get_value();
|
||||
|
||||
if (is_mute) {
|
||||
// Avoid setting sink/source to volume 0 if the user muted if via another mean.
|
||||
if (volume == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the sink/source is mute, but the user clicked the slider, unmute it!
|
||||
else {
|
||||
switch (target) {
|
||||
case PulseaudioSliderTarget::Sink:
|
||||
backend->toggleSinkMute(false);
|
||||
break;
|
||||
|
||||
case PulseaudioSliderTarget::Source:
|
||||
backend->toggleSourceMute(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
backend->changeVolume(volume, min_, max_);
|
||||
}
|
||||
|
||||
} // namespace waybar::modules
|
||||
@ -87,7 +87,7 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
|
||||
control_{nullptr},
|
||||
seat_{nullptr},
|
||||
bar_(bar),
|
||||
box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
|
||||
box_{bar.orientation, 0},
|
||||
output_status_{nullptr} {
|
||||
struct wl_display *display = Client::inst()->wl_display;
|
||||
struct wl_registry *registry = wl_display_get_registry(display);
|
||||
@ -111,6 +111,7 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
box_.get_style_context()->add_class(MODULE_CLASS);
|
||||
event_box_.add(box_);
|
||||
|
||||
// Default to 9 tags, cap at 32
|
||||
@ -188,10 +189,20 @@ bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) {
|
||||
}
|
||||
|
||||
void Tags::handle_focused_tags(uint32_t tags) {
|
||||
auto hide_vacant = config_["hide-vacant"].asBool();
|
||||
for (size_t i = 0; i < buttons_.size(); ++i) {
|
||||
bool visible = buttons_[i].is_visible();
|
||||
bool occupied = buttons_[i].get_style_context()->has_class("occupied");
|
||||
bool urgent = buttons_[i].get_style_context()->has_class("urgent");
|
||||
if ((1 << i) & tags) {
|
||||
if (hide_vacant && !visible) {
|
||||
buttons_[i].set_visible(true);
|
||||
}
|
||||
buttons_[i].get_style_context()->add_class("focused");
|
||||
} else {
|
||||
if (hide_vacant && !(occupied || urgent)) {
|
||||
buttons_[i].set_visible(false);
|
||||
}
|
||||
buttons_[i].get_style_context()->remove_class("focused");
|
||||
}
|
||||
}
|
||||
@ -204,20 +215,40 @@ void Tags::handle_view_tags(struct wl_array *view_tags) {
|
||||
for (; view_tag < end; ++view_tag) {
|
||||
tags |= *view_tag;
|
||||
}
|
||||
auto hide_vacant = config_["hide-vacant"].asBool();
|
||||
for (size_t i = 0; i < buttons_.size(); ++i) {
|
||||
bool visible = buttons_[i].is_visible();
|
||||
bool focused = buttons_[i].get_style_context()->has_class("focused");
|
||||
bool urgent = buttons_[i].get_style_context()->has_class("urgent");
|
||||
if ((1 << i) & tags) {
|
||||
if (hide_vacant && !visible) {
|
||||
buttons_[i].set_visible(true);
|
||||
}
|
||||
buttons_[i].get_style_context()->add_class("occupied");
|
||||
} else {
|
||||
if (hide_vacant && !(focused || urgent)) {
|
||||
buttons_[i].set_visible(false);
|
||||
}
|
||||
buttons_[i].get_style_context()->remove_class("occupied");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tags::handle_urgent_tags(uint32_t tags) {
|
||||
auto hide_vacant = config_["hide-vacant"].asBool();
|
||||
for (size_t i = 0; i < buttons_.size(); ++i) {
|
||||
bool visible = buttons_[i].is_visible();
|
||||
bool occupied = buttons_[i].get_style_context()->has_class("occupied");
|
||||
bool focused = buttons_[i].get_style_context()->has_class("focused");
|
||||
if ((1 << i) & tags) {
|
||||
if (hide_vacant && !visible) {
|
||||
buttons_[i].set_visible(true);
|
||||
}
|
||||
buttons_[i].get_style_context()->add_class("urgent");
|
||||
} else {
|
||||
if (hide_vacant && !(occupied || focused)) {
|
||||
buttons_[i].set_visible(false);
|
||||
}
|
||||
buttons_[i].get_style_context()->remove_class("urgent");
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,13 +18,13 @@ auto waybar::modules::Clock::update() -> void {
|
||||
tzset(); // Update timezone information
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
|
||||
auto text = fmt::format(format_, localtime);
|
||||
auto text = fmt::format(fmt::runtime(format_), localtime);
|
||||
label_.set_markup(text);
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
if (config_["tooltip-format"].isString()) {
|
||||
auto tooltip_format = config_["tooltip-format"].asString();
|
||||
auto tooltip_text = fmt::format(tooltip_format, localtime);
|
||||
auto tooltip_text = fmt::format(fmt::runtime(tooltip_format), localtime);
|
||||
label_.set_tooltip_text(tooltip_text);
|
||||
} else {
|
||||
label_.set_tooltip_text(text);
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "util/scope_guard.hpp"
|
||||
|
||||
namespace waybar::modules::SNI {
|
||||
|
||||
Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar,
|
||||
@ -19,9 +21,13 @@ Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar,
|
||||
|
||||
Host::~Host() {
|
||||
if (bus_name_id_ > 0) {
|
||||
Gio::DBus::unwatch_name(bus_name_id_);
|
||||
Gio::DBus::unown_name(bus_name_id_);
|
||||
bus_name_id_ = 0;
|
||||
}
|
||||
if (watcher_id_ > 0) {
|
||||
Gio::DBus::unwatch_name(watcher_id_);
|
||||
watcher_id_ = 0;
|
||||
}
|
||||
g_cancellable_cancel(cancellable_);
|
||||
g_clear_object(&cancellable_);
|
||||
g_clear_object(&watcher_);
|
||||
@ -53,17 +59,20 @@ void Host::nameVanished(const Glib::RefPtr<Gio::DBus::Connection>& conn, const G
|
||||
|
||||
void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) {
|
||||
GError* error = nullptr;
|
||||
waybar::util::ScopeGuard error_deleter([error]() {
|
||||
if (error != nullptr) {
|
||||
g_error_free(error);
|
||||
}
|
||||
});
|
||||
SnWatcher* watcher = sn_watcher_proxy_new_finish(res, &error);
|
||||
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
|
||||
spdlog::error("Host: {}", error->message);
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
auto host = static_cast<SNI::Host*>(data);
|
||||
host->watcher_ = watcher;
|
||||
if (error != nullptr) {
|
||||
spdlog::error("Host: {}", error->message);
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
sn_watcher_call_register_host(host->watcher_, host->object_path_.c_str(), host->cancellable_,
|
||||
@ -72,16 +81,19 @@ void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) {
|
||||
|
||||
void Host::registerHost(GObject* src, GAsyncResult* res, gpointer data) {
|
||||
GError* error = nullptr;
|
||||
waybar::util::ScopeGuard error_deleter([error]() {
|
||||
if (error != nullptr) {
|
||||
g_error_free(error);
|
||||
}
|
||||
});
|
||||
sn_watcher_call_register_host_finish(SN_WATCHER(src), res, &error);
|
||||
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
|
||||
spdlog::error("Host: {}", error->message);
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
auto host = static_cast<SNI::Host*>(data);
|
||||
if (error != nullptr) {
|
||||
spdlog::error("Host: {}", error->message);
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
g_signal_connect(host->watcher_, "item-registered", G_CALLBACK(&Host::itemRegistered), data);
|
||||
|
||||
@ -5,24 +5,27 @@
|
||||
#include <gtkmm/tooltip.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
|
||||
#include "gdk/gdk.h"
|
||||
#include "modules/sni/icon_manager.hpp"
|
||||
#include "util/format.hpp"
|
||||
#include "util/gtk_icon.hpp"
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Glib::VariantBase> : formatter<std::string> {
|
||||
bool is_printable(const Glib::VariantBase& value) {
|
||||
bool is_printable(const Glib::VariantBase& value) const {
|
||||
auto type = value.get_type_string();
|
||||
/* Print only primitive (single character excluding 'v') and short complex types */
|
||||
return (type.length() == 1 && islower(type[0]) && type[0] != 'v') || value.get_size() <= 32;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const Glib::VariantBase& value, FormatContext& ctx) {
|
||||
auto format(const Glib::VariantBase& value, FormatContext& ctx) const {
|
||||
if (is_printable(value)) {
|
||||
return formatter<std::string>::format(value.print(), ctx);
|
||||
return formatter<std::string>::format(static_cast<std::string>(value.print()), ctx);
|
||||
} else {
|
||||
return formatter<std::string>::format(value.get_type_string(), ctx);
|
||||
}
|
||||
@ -39,7 +42,8 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf
|
||||
object_path(op),
|
||||
icon_size(16),
|
||||
effective_icon_size(0),
|
||||
icon_theme(Gtk::IconTheme::create()) {
|
||||
icon_theme(Gtk::IconTheme::create()),
|
||||
bar_(bar) {
|
||||
if (config["icon-size"].isUInt()) {
|
||||
icon_size = config["icon-size"].asUInt();
|
||||
}
|
||||
@ -56,6 +60,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_);
|
||||
@ -68,6 +74,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) {
|
||||
@ -110,7 +126,8 @@ ToolTip get_variant<ToolTip>(const Glib::VariantBase& value) {
|
||||
result.text = get_variant<Glib::ustring>(container.get_child(2));
|
||||
auto description = get_variant<Glib::ustring>(container.get_child(3));
|
||||
if (!description.empty()) {
|
||||
result.text = fmt::format("<b>{}</b>\n{}", result.text, description);
|
||||
auto escapedDescription = Glib::Markup::escape_text(description);
|
||||
result.text = fmt::format("<b>{}</b>\n{}", result.text, escapedDescription);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -123,6 +140,7 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
|
||||
category = get_variant<std::string>(value);
|
||||
} else if (name == "Id") {
|
||||
id = get_variant<std::string>(value);
|
||||
setCustomIcon(id);
|
||||
} else if (name == "Title") {
|
||||
title = get_variant<std::string>(value);
|
||||
if (tooltip.text.empty()) {
|
||||
@ -184,6 +202,19 @@ void Item::setStatus(const Glib::ustring& value) {
|
||||
style->add_class(lower);
|
||||
}
|
||||
|
||||
void Item::setCustomIcon(const std::string& id) {
|
||||
std::string custom_icon = IconManager::instance().getIconForApp(id);
|
||||
if (!custom_icon.empty()) {
|
||||
if (std::filesystem::exists(custom_icon)) {
|
||||
Glib::RefPtr<Gdk::Pixbuf> custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon);
|
||||
icon_name = ""; // icon_name has priority over pixmap
|
||||
icon_pixmap = custom_pixbuf;
|
||||
} else { // if file doesn't exist it's most likely an icon_name
|
||||
icon_name = custom_icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Item::getUpdatedProperties() {
|
||||
auto params = Glib::VariantContainerBase::create_tuple(
|
||||
{Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)});
|
||||
@ -355,33 +386,19 @@ Glib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() {
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) {
|
||||
int tmp_size = 0;
|
||||
icon_theme->rescan_if_needed();
|
||||
auto sizes = icon_theme->get_icon_sizes(name.c_str());
|
||||
|
||||
for (auto const& size : sizes) {
|
||||
// -1 == scalable
|
||||
if (size == request_size || size == -1) {
|
||||
tmp_size = request_size;
|
||||
break;
|
||||
} else if (size < request_size) {
|
||||
tmp_size = size;
|
||||
} else if (size > tmp_size && tmp_size > 0) {
|
||||
tmp_size = request_size;
|
||||
break;
|
||||
if (!icon_theme_path.empty()) {
|
||||
auto icon_info = icon_theme->lookup_icon(name.c_str(), request_size,
|
||||
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
|
||||
if (icon_info) {
|
||||
bool is_sym = false;
|
||||
return icon_info.load_symbolic(event_box.get_style_context(), is_sym);
|
||||
}
|
||||
}
|
||||
if (tmp_size == 0) {
|
||||
tmp_size = request_size;
|
||||
}
|
||||
if (!icon_theme_path.empty() &&
|
||||
icon_theme->lookup_icon(name.c_str(), tmp_size,
|
||||
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE)) {
|
||||
return icon_theme->load_icon(name.c_str(), tmp_size,
|
||||
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
|
||||
}
|
||||
return DefaultGtkIconThemeWrapper::load_icon(name.c_str(), tmp_size,
|
||||
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
|
||||
return DefaultGtkIconThemeWrapper::load_icon(name.c_str(), request_size,
|
||||
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE,
|
||||
event_box.get_style_context());
|
||||
}
|
||||
|
||||
double Item::getScaledIconSize() {
|
||||
@ -410,7 +427,8 @@ void Item::makeMenu() {
|
||||
|
||||
bool Item::handleClick(GdkEventButton* const& ev) {
|
||||
auto parameters = Glib::VariantContainerBase::create_tuple(
|
||||
{Glib::Variant<int>::create(ev->x), Glib::Variant<int>::create(ev->y)});
|
||||
{Glib::Variant<int>::create(ev->x_root + bar_.x_global),
|
||||
Glib::Variant<int>::create(ev->y_root + bar_.y_global)});
|
||||
if ((ev->button == 1 && item_is_menu) || ev->button == 3) {
|
||||
makeMenu();
|
||||
if (gtk_menu != nullptr) {
|
||||
|
||||
@ -2,11 +2,13 @@
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "modules/sni/icon_manager.hpp"
|
||||
|
||||
namespace waybar::modules::SNI {
|
||||
|
||||
Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: AModule(config, "tray", id),
|
||||
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||
box_(bar.orientation, 0),
|
||||
watcher_(SNI::Watcher::getInstance()),
|
||||
host_(nb_hosts_, config, bar, std::bind(&Tray::onAdd, this, std::placeholders::_1),
|
||||
std::bind(&Tray::onRemove, this, std::placeholders::_1)) {
|
||||
@ -15,10 +17,14 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
box_.get_style_context()->add_class(MODULE_CLASS);
|
||||
if (config_["spacing"].isUInt()) {
|
||||
box_.set_spacing(config_["spacing"].asUInt());
|
||||
}
|
||||
nb_hosts_ += 1;
|
||||
if (config_["icons"].isObject()) {
|
||||
IconManager::instance().setIconsConfig(config_["icons"]);
|
||||
}
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
@ -38,7 +44,7 @@ void Tray::onRemove(std::unique_ptr<Item>& item) {
|
||||
|
||||
auto Tray::update() -> void {
|
||||
// Show tray only when items are available
|
||||
box_.set_visible(!box_.get_children().empty());
|
||||
event_box_.set_visible(!box_.get_children().empty());
|
||||
// Call parent update
|
||||
AModule::update();
|
||||
}
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "util/scope_guard.hpp"
|
||||
|
||||
using namespace waybar::modules::SNI;
|
||||
|
||||
Watcher::Watcher()
|
||||
@ -14,6 +16,10 @@ Watcher::Watcher()
|
||||
watcher_(sn_watcher_skeleton_new()) {}
|
||||
|
||||
Watcher::~Watcher() {
|
||||
if (hosts_ != nullptr) {
|
||||
g_slist_free_full(hosts_, gfWatchFree);
|
||||
hosts_ = nullptr;
|
||||
}
|
||||
if (items_ != nullptr) {
|
||||
g_slist_free_full(items_, gfWatchFree);
|
||||
items_ = nullptr;
|
||||
@ -25,6 +31,11 @@ Watcher::~Watcher() {
|
||||
|
||||
void Watcher::busAcquired(const Glib::RefPtr<Gio::DBus::Connection>& conn, Glib::ustring name) {
|
||||
GError* error = nullptr;
|
||||
waybar::util::ScopeGuard error_deleter([error]() {
|
||||
if (error) {
|
||||
g_error_free(error);
|
||||
}
|
||||
});
|
||||
g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(watcher_), conn->gobj(),
|
||||
"/StatusNotifierWatcher", &error);
|
||||
if (error != nullptr) {
|
||||
@ -32,7 +43,6 @@ void Watcher::busAcquired(const Glib::RefPtr<Gio::DBus::Connection>& conn, Glib:
|
||||
if (error->code != 2) {
|
||||
spdlog::error("Watcher: {}", error->message);
|
||||
}
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
g_signal_connect_swapped(watcher_, "handle-register-item",
|
||||
@ -57,10 +67,9 @@ gboolean Watcher::handleRegisterHost(Watcher* obj, GDBusMethodInvocation* invoca
|
||||
}
|
||||
auto watch = gfWatchFind(obj->hosts_, bus_name, object_path);
|
||||
if (watch != nullptr) {
|
||||
g_dbus_method_invocation_return_error(
|
||||
invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
|
||||
"Status Notifier Host with bus name '%s' and object path '%s' is already registered",
|
||||
bus_name, object_path);
|
||||
g_warning("Status Notifier Host with bus name '%s' and object path '%s' is already registered",
|
||||
bus_name, object_path);
|
||||
sn_watcher_complete_register_item(obj->watcher_, invocation);
|
||||
return TRUE;
|
||||
}
|
||||
watch = gfWatchNew(GF_WATCH_TYPE_HOST, service, bus_name, object_path, obj);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#include "modules/sway/ipc/client.hpp"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
@ -17,12 +18,16 @@ Ipc::~Ipc() {
|
||||
|
||||
if (fd_ > 0) {
|
||||
// To fail the IPC header
|
||||
write(fd_, "close-sway-ipc", 14);
|
||||
if (write(fd_, "close-sway-ipc", 14) == -1) {
|
||||
spdlog::error("Failed to close sway IPC");
|
||||
}
|
||||
close(fd_);
|
||||
fd_ = -1;
|
||||
}
|
||||
if (fd_event_ > 0) {
|
||||
write(fd_event_, "close-sway-ipc", 14);
|
||||
if (write(fd_event_, "close-sway-ipc", 14) == -1) {
|
||||
spdlog::error("Failed to close sway IPC event handler");
|
||||
}
|
||||
close(fd_event_);
|
||||
fd_event_ = -1;
|
||||
}
|
||||
|
||||
@ -19,12 +19,13 @@ 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);
|
||||
displayed_short_flag |= static_cast<std::byte>(DisplayedShortFlag::ShortName);
|
||||
}
|
||||
if (format_.find("{shortDescription}") != std::string::npos) {
|
||||
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortDescription);
|
||||
displayed_short_flag |= static_cast<std::byte>(DisplayedShortFlag::ShortDescription);
|
||||
}
|
||||
if (config.isMember("tooltip-format")) {
|
||||
tooltip_format_ = config["tooltip-format"].asString();
|
||||
@ -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),
|
||||
|
||||
@ -17,13 +17,7 @@
|
||||
namespace waybar::modules::sway {
|
||||
|
||||
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: AIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) {
|
||||
// Icon size
|
||||
if (config_["icon-size"].isUInt()) {
|
||||
app_icon_size_ = config["icon-size"].asUInt();
|
||||
}
|
||||
image_.set_pixel_size(app_icon_size_);
|
||||
|
||||
: AAppIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) {
|
||||
ipc_.subscribe(R"(["window","workspace"])");
|
||||
ipc_.signal_event.connect(sigc::mem_fun(*this, &Window::onEvent));
|
||||
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Window::onCmd));
|
||||
@ -49,7 +43,7 @@ void Window::onCmd(const struct Ipc::ipc_response& res) {
|
||||
auto output = payload["output"].isString() ? payload["output"].asString() : "";
|
||||
std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_) =
|
||||
getFocusedNode(payload["nodes"], output);
|
||||
updateAppIconName();
|
||||
updateAppIconName(app_id_, app_class_);
|
||||
dp.emit();
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("Window: {}", e.what());
|
||||
@ -57,105 +51,6 @@ void Window::onCmd(const struct Ipc::ipc_response& res) {
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> getDesktopFilePath(const std::string& app_id,
|
||||
const std::string& app_class) {
|
||||
const auto data_dirs = Glib::get_system_data_dirs();
|
||||
for (const auto& data_dir : data_dirs) {
|
||||
const auto data_app_dir = data_dir + "applications/";
|
||||
auto desktop_file_path = data_app_dir + app_id + ".desktop";
|
||||
if (std::filesystem::exists(desktop_file_path)) {
|
||||
return desktop_file_path;
|
||||
}
|
||||
if (!app_class.empty()) {
|
||||
desktop_file_path = data_app_dir + app_class + ".desktop";
|
||||
if (std::filesystem::exists(desktop_file_path)) {
|
||||
return desktop_file_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<Glib::ustring> getIconName(const std::string& app_id, const std::string& app_class) {
|
||||
const auto desktop_file_path = getDesktopFilePath(app_id, app_class);
|
||||
if (!desktop_file_path.has_value()) {
|
||||
// Try some heuristics to find a matching icon
|
||||
|
||||
if (DefaultGtkIconThemeWrapper::has_icon(app_id)) {
|
||||
return app_id;
|
||||
}
|
||||
|
||||
const auto app_id_desktop = app_id + "-desktop";
|
||||
if (DefaultGtkIconThemeWrapper::has_icon(app_id_desktop)) {
|
||||
return app_id_desktop;
|
||||
}
|
||||
|
||||
const auto to_lower = [](const std::string& str) {
|
||||
auto str_cpy = str;
|
||||
std::transform(str_cpy.begin(), str_cpy.end(), str_cpy.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
return str;
|
||||
};
|
||||
|
||||
const auto first_space = app_id.find_first_of(' ');
|
||||
if (first_space != std::string::npos) {
|
||||
const auto first_word = to_lower(app_id.substr(0, first_space));
|
||||
if (DefaultGtkIconThemeWrapper::has_icon(first_word)) {
|
||||
return first_word;
|
||||
}
|
||||
}
|
||||
|
||||
const auto first_dash = app_id.find_first_of('-');
|
||||
if (first_dash != std::string::npos) {
|
||||
const auto first_word = to_lower(app_id.substr(0, first_dash));
|
||||
if (DefaultGtkIconThemeWrapper::has_icon(first_word)) {
|
||||
return first_word;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
Glib::KeyFile desktop_file;
|
||||
desktop_file.load_from_file(desktop_file_path.value());
|
||||
return desktop_file.get_string("Desktop Entry", "Icon");
|
||||
} catch (Glib::FileError& error) {
|
||||
spdlog::warn("Error while loading desktop file {}: {}", desktop_file_path.value(),
|
||||
error.what().c_str());
|
||||
} catch (Glib::KeyFileError& error) {
|
||||
spdlog::warn("Error while loading desktop file {}: {}", desktop_file_path.value(),
|
||||
error.what().c_str());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void Window::updateAppIconName() {
|
||||
if (!iconEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto icon_name = getIconName(app_id_, app_class_);
|
||||
if (icon_name.has_value()) {
|
||||
app_icon_name_ = icon_name.value();
|
||||
} else {
|
||||
app_icon_name_ = "";
|
||||
}
|
||||
update_app_icon_ = true;
|
||||
}
|
||||
|
||||
void Window::updateAppIcon() {
|
||||
if (update_app_icon_) {
|
||||
update_app_icon_ = false;
|
||||
if (app_icon_name_.empty()) {
|
||||
image_.set_visible(false);
|
||||
} else {
|
||||
image_.set_from_icon_name(app_icon_name_, Gtk::ICON_SIZE_INVALID);
|
||||
image_.set_visible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Window::update() -> void {
|
||||
spdlog::trace("workspace layout {}, tiled count {}, floating count {}", layout_, app_nb_,
|
||||
floating_count_);
|
||||
@ -210,7 +105,7 @@ auto Window::update() -> void {
|
||||
updateAppIcon();
|
||||
|
||||
// Call parent update
|
||||
AIconLabel::update();
|
||||
AAppIconLabel::update();
|
||||
}
|
||||
|
||||
void Window::setClass(std::string classname, bool enable) {
|
||||
@ -250,6 +145,40 @@ std::pair<int, int> leafNodesInWorkspace(const Json::Value& node) {
|
||||
return {sum, floating_sum};
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<const Json::Value>> getSingleChildNode(
|
||||
const Json::Value& node) {
|
||||
auto const& nodes = node["nodes"];
|
||||
if (nodes.empty()) {
|
||||
if (node["type"].asString() == "workspace")
|
||||
return {};
|
||||
else if (node["type"].asString() == "floating_con") {
|
||||
return {};
|
||||
} else {
|
||||
return {std::cref(node)};
|
||||
}
|
||||
}
|
||||
auto it = std::cbegin(nodes);
|
||||
if (it == std::cend(nodes)) {
|
||||
return {};
|
||||
}
|
||||
auto const& child = *it;
|
||||
++it;
|
||||
if (it != std::cend(nodes)) {
|
||||
return {};
|
||||
}
|
||||
return {getSingleChildNode(child)};
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string, std::string> getWindowInfo(const Json::Value& node) {
|
||||
const auto app_id = node["app_id"].isString() ? node["app_id"].asString()
|
||||
: node["window_properties"]["instance"].asString();
|
||||
const auto app_class = node["window_properties"]["class"].isString()
|
||||
? node["window_properties"]["class"].asString()
|
||||
: "";
|
||||
const auto shell = node["shell"].isString() ? node["shell"].asString() : "";
|
||||
return {app_id, app_class, shell};
|
||||
}
|
||||
|
||||
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
|
||||
gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Value& config_,
|
||||
const Bar& bar_, Json::Value& parentWorkspace,
|
||||
@ -286,12 +215,7 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
|
||||
// found node
|
||||
spdlog::trace("actual output {}, output found {}, node (focused) found {}", bar_.output->name,
|
||||
output, node["name"].asString());
|
||||
auto app_id = node["app_id"].isString() ? node["app_id"].asString()
|
||||
: node["window_properties"]["instance"].asString();
|
||||
const auto app_class = node["window_properties"]["class"].isString()
|
||||
? node["window_properties"]["class"].asString()
|
||||
: "";
|
||||
const auto shell = node["shell"].isString() ? node["shell"].asString() : "";
|
||||
const auto [app_id, app_class, shell] = getWindowInfo(node);
|
||||
int nb = node.size();
|
||||
int floating_count = 0;
|
||||
std::string workspace_layout = "";
|
||||
@ -331,15 +255,24 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
|
||||
std::pair all_leaf_nodes = leafNodesInWorkspace(immediateParent);
|
||||
// using an empty string as default ensures that no window depending styles are set due to the
|
||||
// checks above for !name.empty()
|
||||
std::string app_id = "";
|
||||
std::string app_class = "";
|
||||
std::string workspace_layout = "";
|
||||
if (all_leaf_nodes.first == 1) {
|
||||
const auto single_child = getSingleChildNode(immediateParent);
|
||||
if (single_child.has_value()) {
|
||||
std::tie(app_id, app_class, workspace_layout) = getWindowInfo(single_child.value());
|
||||
}
|
||||
}
|
||||
return {all_leaf_nodes.first,
|
||||
all_leaf_nodes.second,
|
||||
0,
|
||||
(all_leaf_nodes.first > 0 || all_leaf_nodes.second > 0)
|
||||
? config_["offscreen-css-text"].asString()
|
||||
: "",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
app_id,
|
||||
app_class,
|
||||
workspace_layout,
|
||||
immediateParent["layout"].asString()};
|
||||
}
|
||||
|
||||
|
||||
@ -11,32 +11,69 @@ namespace waybar::modules::sway {
|
||||
// Helper function to assign a number to a workspace, just like sway. In fact
|
||||
// this is taken quite verbatim from `sway/ipc-json.c`.
|
||||
int Workspaces::convertWorkspaceNameToNum(std::string name) {
|
||||
if (isdigit(name[0])) {
|
||||
if (isdigit(name[0]) != 0) {
|
||||
errno = 0;
|
||||
char *endptr = NULL;
|
||||
char *endptr = nullptr;
|
||||
long long parsed_num = strtoll(name.c_str(), &endptr, 10);
|
||||
if (errno != 0 || parsed_num > INT32_MAX || parsed_num < 0 || endptr == name.c_str()) {
|
||||
return -1;
|
||||
} else {
|
||||
return (int)parsed_num;
|
||||
}
|
||||
return (int)parsed_num;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) {
|
||||
// Rules that match against title are prioritized
|
||||
// Rules that don't specify if they're matching against either title or class are deprioritized
|
||||
bool const hasTitle = window_rule.find("title") != std::string::npos;
|
||||
bool const hasClass = window_rule.find("class") != std::string::npos;
|
||||
|
||||
if (hasTitle && hasClass) {
|
||||
return 3;
|
||||
}
|
||||
if (hasTitle) {
|
||||
return 2;
|
||||
}
|
||||
if (hasClass) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
|
||||
: AModule(config, "workspaces", id, false, !config["disable-scroll"].asBool()),
|
||||
bar_(bar),
|
||||
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) {
|
||||
box_(bar.orientation, 0) {
|
||||
if (config["format-icons"]["high-priority-named"].isArray()) {
|
||||
for (const auto &it : config["format-icons"]["high-priority-named"]) {
|
||||
high_priority_named_.push_back(it.asString());
|
||||
}
|
||||
}
|
||||
box_.set_name("workspaces");
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
box_.get_style_context()->add_class(MODULE_CLASS);
|
||||
event_box_.add(box_);
|
||||
if (config_["format-window-separator"].isString()) {
|
||||
m_formatWindowSeparator = config_["format-window-separator"].asString();
|
||||
} else {
|
||||
m_formatWindowSeparator = " ";
|
||||
}
|
||||
const Json::Value &windowRewrite = config["window-rewrite"];
|
||||
if (windowRewrite.isObject()) {
|
||||
const Json::Value &windowRewriteDefaultConfig = config["window-rewrite-default"];
|
||||
std::string windowRewriteDefault =
|
||||
windowRewriteDefaultConfig.isString() ? windowRewriteDefaultConfig.asString() : "?";
|
||||
m_windowRewriteRules = waybar::util::RegexCollection(
|
||||
windowRewrite, std::move(windowRewriteDefault), windowRewritePriorityFunction);
|
||||
}
|
||||
ipc_.subscribe(R"(["workspace"])");
|
||||
ipc_.subscribe(R"(["window"])");
|
||||
ipc_.signal_event.connect(sigc::mem_fun(*this, &Workspaces::onEvent));
|
||||
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Workspaces::onCmd));
|
||||
ipc_.sendCmd(IPC_GET_WORKSPACES);
|
||||
ipc_.sendCmd(IPC_GET_TREE);
|
||||
if (config["enable-bar-scroll"].asBool()) {
|
||||
auto &window = const_cast<Bar &>(bar_).window;
|
||||
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
||||
@ -54,39 +91,52 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
|
||||
|
||||
void Workspaces::onEvent(const struct Ipc::ipc_response &res) {
|
||||
try {
|
||||
ipc_.sendCmd(IPC_GET_WORKSPACES);
|
||||
ipc_.sendCmd(IPC_GET_TREE);
|
||||
} catch (const std::exception &e) {
|
||||
spdlog::error("Workspaces: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
|
||||
if (res.type == IPC_GET_WORKSPACES) {
|
||||
if (res.type == IPC_GET_TREE) {
|
||||
try {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto payload = parser_.parse(res.payload);
|
||||
workspaces_.clear();
|
||||
std::copy_if(payload.begin(), payload.end(), std::back_inserter(workspaces_),
|
||||
[&](const auto &workspace) {
|
||||
return !config_["all-outputs"].asBool()
|
||||
? workspace["output"].asString() == bar_.output->name
|
||||
: true;
|
||||
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 &output) {
|
||||
if (alloutputs && output["name"].asString() != "__i3") {
|
||||
return true;
|
||||
}
|
||||
if (output["name"].asString() == bar_.output->name) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
for (auto &output : outputs) {
|
||||
std::copy(output["nodes"].begin(), output["nodes"].end(),
|
||||
std::back_inserter(workspaces_));
|
||||
std::copy(output["floating_nodes"].begin(), output["floating_nodes"].end(),
|
||||
std::back_inserter(workspaces_));
|
||||
}
|
||||
|
||||
// adding persistent workspaces (as per the config file)
|
||||
if (config_["persistent_workspaces"].isObject()) {
|
||||
const Json::Value &p_workspaces = config_["persistent_workspaces"];
|
||||
if (config_["persistent-workspaces"].isObject()) {
|
||||
const Json::Value &p_workspaces = config_["persistent-workspaces"];
|
||||
const std::vector<std::string> p_workspaces_names = p_workspaces.getMemberNames();
|
||||
|
||||
for (const std::string &p_w_name : p_workspaces_names) {
|
||||
const Json::Value &p_w = p_workspaces[p_w_name];
|
||||
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
|
||||
}
|
||||
|
||||
@ -189,6 +239,49 @@ bool Workspaces::filterButtons() {
|
||||
return needReorder;
|
||||
}
|
||||
|
||||
bool Workspaces::hasFlag(const Json::Value &node, const std::string &flag) {
|
||||
if (node[flag].asBool()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std::any_of(node["nodes"].begin(), node["nodes"].end(),
|
||||
[&](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) {
|
||||
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);
|
||||
std::string windowClass = node["app_id"].isString()
|
||||
? node["app_id"].asString()
|
||||
: node["window_properties"]["class"].asString();
|
||||
|
||||
// Only add window rewrites that can be looked up
|
||||
if (!windowClass.empty()) {
|
||||
std::string windowReprKey = fmt::format("class<{}> title<{}>", windowClass, title);
|
||||
std::string window = m_windowRewriteRules.get(windowReprKey);
|
||||
// allow result to have formatting
|
||||
window = fmt::format(fmt::runtime(window), fmt::arg("name", title),
|
||||
fmt::arg("class", windowClass));
|
||||
windows.append(window);
|
||||
windows.append(m_formatWindowSeparator);
|
||||
}
|
||||
}
|
||||
for (const Json::Value &child : node["nodes"]) {
|
||||
updateWindows(child, windows);
|
||||
}
|
||||
for (const Json::Value &child : node["floating_nodes"]) {
|
||||
updateWindows(child, windows);
|
||||
}
|
||||
}
|
||||
|
||||
auto Workspaces::update() -> void {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
bool needReorder = filterButtons();
|
||||
@ -198,17 +291,21 @@ auto Workspaces::update() -> void {
|
||||
needReorder = true;
|
||||
}
|
||||
auto &button = bit == buttons_.end() ? addButton(*it) : bit->second;
|
||||
if ((*it)["focused"].asBool()) {
|
||||
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 ((*it)["visible"].asBool()) {
|
||||
if (hasFlag((*it), "visible") || ((*it)["output"].isString() && noNodes)) {
|
||||
button.get_style_context()->add_class("visible");
|
||||
} else {
|
||||
button.get_style_context()->remove_class("visible");
|
||||
}
|
||||
if ((*it)["urgent"].asBool()) {
|
||||
if (hasFlag((*it), "urgent")) {
|
||||
button.get_style_context()->add_class("urgent");
|
||||
} else {
|
||||
button.get_style_context()->remove_class("urgent");
|
||||
@ -218,6 +315,11 @@ auto Workspaces::update() -> void {
|
||||
} 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");
|
||||
@ -227,16 +329,19 @@ auto Workspaces::update() -> void {
|
||||
} else {
|
||||
button.get_style_context()->remove_class("current_output");
|
||||
}
|
||||
if (needReorder) {
|
||||
box_.reorder_child(button, it - workspaces_.begin());
|
||||
}
|
||||
std::string output = (*it)["name"].asString();
|
||||
std::string windows = "";
|
||||
if (config_["window-format"].isString()) {
|
||||
updateWindows((*it), windows);
|
||||
}
|
||||
if (config_["format"].isString()) {
|
||||
auto format = config_["format"].asString();
|
||||
output = fmt::format(fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)),
|
||||
fmt::arg("value", output), fmt::arg("name", trimWorkspaceName(output)),
|
||||
fmt::arg("index", (*it)["num"].asString()),
|
||||
fmt::arg("output", (*it)["output"].asString()));
|
||||
output = fmt::format(
|
||||
fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), fmt::arg("value", output),
|
||||
fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString()),
|
||||
fmt::arg("windows",
|
||||
windows.substr(0, windows.length() - m_formatWindowSeparator.length())),
|
||||
fmt::arg("output", (*it)["output"].asString()));
|
||||
}
|
||||
if (!config_["disable-markup"].asBool()) {
|
||||
static_cast<Gtk::Label *>(button.get_children()[0])->set_markup(output);
|
||||
@ -279,13 +384,28 @@ Gtk::Button &Workspaces::addButton(const Json::Value &node) {
|
||||
}
|
||||
|
||||
std::string Workspaces::getIcon(const std::string &name, const Json::Value &node) {
|
||||
std::vector<std::string> keys = {"urgent", "focused", name, "visible", "default"};
|
||||
std::vector<std::string> keys = {"high-priority-named", "urgent", "focused", name, "default"};
|
||||
for (auto const &key : keys) {
|
||||
if (key == "focused" || key == "visible" || key == "urgent") {
|
||||
if (config_["format-icons"][key].isString() && node[key].asBool()) {
|
||||
if (key == "high-priority-named") {
|
||||
auto it = std::find_if(high_priority_named_.begin(), high_priority_named_.end(),
|
||||
[&](const std::string &member) { return member == name; });
|
||||
if (it != high_priority_named_.end()) {
|
||||
return config_["format-icons"][name].asString();
|
||||
}
|
||||
|
||||
it = std::find_if(high_priority_named_.begin(), high_priority_named_.end(),
|
||||
[&](const std::string &member) {
|
||||
return trimWorkspaceName(member) == trimWorkspaceName(name);
|
||||
});
|
||||
if (it != high_priority_named_.end()) {
|
||||
return config_["format-icons"][trimWorkspaceName(name)].asString();
|
||||
}
|
||||
}
|
||||
if (key == "focused" || key == "urgent") {
|
||||
if (config_["format-icons"][key].isString() && hasFlag(node, key)) {
|
||||
return config_["format-icons"][key].asString();
|
||||
}
|
||||
} else if (config_["format_icons"]["persistent"].isString() &&
|
||||
} else if (config_["format-icons"]["persistent"].isString() &&
|
||||
node["target_output"].isString()) {
|
||||
return config_["format-icons"]["persistent"].asString();
|
||||
} else if (config_["format-icons"][key].isString()) {
|
||||
@ -298,7 +418,7 @@ std::string Workspaces::getIcon(const std::string &name, const Json::Value &node
|
||||
}
|
||||
|
||||
bool Workspaces::handleScroll(GdkEventScroll *e) {
|
||||
if (gdk_event_get_pointer_emulated((GdkEvent *)e)) {
|
||||
if (gdk_event_get_pointer_emulated((GdkEvent *)e) != 0) {
|
||||
/**
|
||||
* Ignore emulated scroll events on window
|
||||
*/
|
||||
@ -310,9 +430,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;
|
||||
}
|
||||
@ -327,22 +454,21 @@ bool Workspaces::handleScroll(GdkEventScroll *e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!config_["warp-on-scroll"].asBool()) {
|
||||
ipc_.sendCmd(IPC_COMMAND, fmt::format("mouse_warping none"));
|
||||
if (!config_["warp-on-scroll"].isNull() && !config_["warp-on-scroll"].asBool()) {
|
||||
ipc_.sendCmd(IPC_COMMAND, fmt::format("mouse_warping none"));
|
||||
}
|
||||
try {
|
||||
ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, "--no-auto-back-and-forth", name));
|
||||
} catch (const std::exception &e) {
|
||||
spdlog::error("Workspaces: {}", e.what());
|
||||
}
|
||||
if (!config_["warp-on-scroll"].asBool()) {
|
||||
ipc_.sendCmd(IPC_COMMAND, fmt::format("mouse_warping container"));
|
||||
if (!config_["warp-on-scroll"].isNull() && !config_["warp-on-scroll"].asBool()) {
|
||||
ipc_.sendCmd(IPC_COMMAND, fmt::format("mouse_warping container"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string Workspaces::getCycleWorkspace(std::vector<Json::Value>::iterator it,
|
||||
bool prev) const {
|
||||
std::string Workspaces::getCycleWorkspace(std::vector<Json::Value>::iterator it, bool prev) const {
|
||||
if (prev && it == workspaces_.begin() && !config_["disable-scroll-wraparound"].asBool()) {
|
||||
return (*(--workspaces_.end()))["name"].asString();
|
||||
}
|
||||
@ -368,9 +494,34 @@ std::string Workspaces::trimWorkspaceName(std::string name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
bool is_focused_recursive(const Json::Value &node) {
|
||||
// If a workspace has a focused container then get_tree will say
|
||||
// that the workspace itself isn't focused. Therefore we need to
|
||||
// check if any of its nodes are focused as well.
|
||||
// some layouts like tabbed have many nested nodes
|
||||
// all nested nodes must be checked for focused flag
|
||||
if (node["focused"].asBool()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto &child : node["nodes"]) {
|
||||
if (is_focused_recursive(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &child : node["floating_nodes"]) {
|
||||
if (is_focused_recursive(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) {
|
||||
if (config_["current-only"].asBool()) {
|
||||
if (node["focused"].asBool()) {
|
||||
if (is_focused_recursive(node)) {
|
||||
button.show();
|
||||
} else {
|
||||
button.hide();
|
||||
|
||||
162
src/modules/systemd_failed_units.cpp
Normal file
162
src/modules/systemd_failed_units.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
#include "modules/systemd_failed_units.hpp"
|
||||
|
||||
#include <giomm/dbusproxy.h>
|
||||
#include <glibmm/variant.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
static const unsigned UPDATE_DEBOUNCE_TIME_MS = 1000;
|
||||
|
||||
namespace waybar::modules {
|
||||
|
||||
SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "systemd-failed-units", id, "{nr_failed} failed", 1),
|
||||
hide_on_ok(true),
|
||||
update_pending(false),
|
||||
nr_failed_system(0),
|
||||
nr_failed_user(0),
|
||||
nr_failed(0),
|
||||
last_status() {
|
||||
if (config["hide-on-ok"].isBool()) {
|
||||
hide_on_ok = config["hide-on-ok"].asBool();
|
||||
}
|
||||
if (config["format-ok"].isString()) {
|
||||
format_ok = config["format-ok"].asString();
|
||||
} else {
|
||||
format_ok = format_;
|
||||
}
|
||||
|
||||
/* Default to enable both "system" and "user". */
|
||||
if (!config["system"].isBool() || config["system"].asBool()) {
|
||||
system_proxy = Gio::DBus::Proxy::create_for_bus_sync(
|
||||
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.systemd1",
|
||||
"/org/freedesktop/systemd1", "org.freedesktop.DBus.Properties");
|
||||
if (!system_proxy) {
|
||||
throw std::runtime_error("Unable to connect to systemwide systemd DBus!");
|
||||
}
|
||||
system_proxy->signal_signal().connect(sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb));
|
||||
}
|
||||
if (!config["user"].isBool() || config["user"].asBool()) {
|
||||
user_proxy = Gio::DBus::Proxy::create_for_bus_sync(
|
||||
Gio::DBus::BusType::BUS_TYPE_SESSION, "org.freedesktop.systemd1",
|
||||
"/org/freedesktop/systemd1", "org.freedesktop.DBus.Properties");
|
||||
if (!user_proxy) {
|
||||
throw std::runtime_error("Unable to connect to user systemd DBus!");
|
||||
}
|
||||
user_proxy->signal_signal().connect(sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb));
|
||||
}
|
||||
|
||||
updateData();
|
||||
/* Always update for the first time. */
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
SystemdFailedUnits::~SystemdFailedUnits() {
|
||||
if (system_proxy) system_proxy.reset();
|
||||
if (user_proxy) user_proxy.reset();
|
||||
}
|
||||
|
||||
auto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name,
|
||||
const Glib::ustring& signal_name,
|
||||
const Glib::VariantContainerBase& arguments) -> void {
|
||||
if (signal_name == "PropertiesChanged" && !update_pending) {
|
||||
update_pending = true;
|
||||
/* The fail count may fluctuate due to restarting. */
|
||||
Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &SystemdFailedUnits::updateData),
|
||||
UPDATE_DEBOUNCE_TIME_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void SystemdFailedUnits::RequestSystemState() {
|
||||
auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> std::string {
|
||||
try {
|
||||
if (!proxy) return "unknown";
|
||||
auto parameters = Glib::VariantContainerBase(
|
||||
g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "SystemState"));
|
||||
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
|
||||
if (data && data.is_of_type(Glib::VariantType("(v)"))) {
|
||||
Glib::VariantBase variant;
|
||||
g_variant_get(data.gobj_copy(), "(v)", &variant);
|
||||
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_STRING)) {
|
||||
return g_variant_get_string(variant.gobj_copy(), NULL);
|
||||
}
|
||||
}
|
||||
} catch (Glib::Error& e) {
|
||||
spdlog::error("Failed to get {} state: {}", kind, e.what().c_str());
|
||||
}
|
||||
return "unknown";
|
||||
};
|
||||
|
||||
system_state = load("systemwide", system_proxy);
|
||||
user_state = load("user", user_proxy);
|
||||
if (system_state == "running" && user_state == "running")
|
||||
overall_state = "ok";
|
||||
else
|
||||
overall_state = "degraded";
|
||||
}
|
||||
|
||||
void SystemdFailedUnits::RequestFailedUnits() {
|
||||
auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> uint32_t {
|
||||
try {
|
||||
if (!proxy) return 0;
|
||||
auto parameters = Glib::VariantContainerBase(
|
||||
g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "NFailedUnits"));
|
||||
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
|
||||
if (data && data.is_of_type(Glib::VariantType("(v)"))) {
|
||||
Glib::VariantBase variant;
|
||||
g_variant_get(data.gobj_copy(), "(v)", &variant);
|
||||
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_UINT32)) {
|
||||
return g_variant_get_uint32(variant.gobj_copy());
|
||||
}
|
||||
}
|
||||
} catch (Glib::Error& e) {
|
||||
spdlog::error("Failed to get {} failed units: {}", kind, e.what().c_str());
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
nr_failed_system = load("systemwide", system_proxy);
|
||||
nr_failed_user = load("user", user_proxy);
|
||||
nr_failed = nr_failed_system + nr_failed_user;
|
||||
}
|
||||
|
||||
void SystemdFailedUnits::updateData() {
|
||||
update_pending = false;
|
||||
|
||||
RequestSystemState();
|
||||
if (overall_state == "degraded") RequestFailedUnits();
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
auto SystemdFailedUnits::update() -> void {
|
||||
if (last_status == overall_state) return;
|
||||
|
||||
// Hide if needed.
|
||||
if (overall_state == "ok" && hide_on_ok) {
|
||||
event_box_.set_visible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
event_box_.set_visible(true);
|
||||
|
||||
// Set state class.
|
||||
if (!last_status.empty() && label_.get_style_context()->has_class(last_status)) {
|
||||
label_.get_style_context()->remove_class(last_status);
|
||||
}
|
||||
if (!label_.get_style_context()->has_class(overall_state)) {
|
||||
label_.get_style_context()->add_class(overall_state);
|
||||
}
|
||||
|
||||
last_status = overall_state;
|
||||
|
||||
label_.set_markup(fmt::format(
|
||||
fmt::runtime(nr_failed == 0 ? format_ok : format_), fmt::arg("nr_failed", nr_failed),
|
||||
fmt::arg("nr_failed_system", nr_failed_system), fmt::arg("nr_failed_user", nr_failed_user),
|
||||
fmt::arg("system_state", system_state), fmt::arg("user_state", user_state),
|
||||
fmt::arg("overall_state", overall_state)));
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
} // namespace waybar::modules
|
||||
@ -1,6 +1,7 @@
|
||||
#include "modules/temperature.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#if defined(__FreeBSD__)
|
||||
#include <sys/sysctl.h>
|
||||
@ -9,34 +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()) {
|
||||
file_path_ = (*std::filesystem::directory_iterator(config_["hwmon-path-abs"].asString()))
|
||||
.path()
|
||||
.string() +
|
||||
"/" + config_["input-filename"].asString();
|
||||
} else {
|
||||
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 retrieve 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_);
|
||||
@ -49,21 +69,26 @@ auto waybar::modules::Temperature::update() -> void {
|
||||
uint16_t temperature_f = std::round(temperature * 1.8 + 32);
|
||||
uint16_t temperature_k = std::round(temperature + 273.15);
|
||||
auto critical = isCritical(temperature_c);
|
||||
auto warning = isWarning(temperature_c);
|
||||
auto format = format_;
|
||||
if (critical) {
|
||||
format = config_["format-critical"].isString() ? config_["format-critical"].asString() : format;
|
||||
label_.get_style_context()->add_class("critical");
|
||||
} else if (warning) {
|
||||
format = config_["format-warning"].isString() ? config_["format-warning"].asString() : format;
|
||||
label_.get_style_context()->add_class("warning");
|
||||
} else {
|
||||
label_.get_style_context()->remove_class("critical");
|
||||
label_.get_style_context()->remove_class("warning");
|
||||
}
|
||||
|
||||
if (format.empty()) {
|
||||
event_box_.hide();
|
||||
return;
|
||||
} else {
|
||||
event_box_.show();
|
||||
}
|
||||
|
||||
event_box_.show();
|
||||
|
||||
auto max_temp = config_["critical-threshold"].isInt() ? config_["critical-threshold"].asInt() : 0;
|
||||
label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("temperatureC", temperature_c),
|
||||
fmt::arg("temperatureF", temperature_f),
|
||||
@ -88,14 +113,18 @@ 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");
|
||||
// First, try with dev.cpu
|
||||
if ((sysctlbyname(fmt::format("dev.cpu.{}.temperature", zone).c_str(), &temp, &size, NULL, 0) ==
|
||||
0) ||
|
||||
(sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size,
|
||||
NULL, 0) == 0)) {
|
||||
auto temperature_c = ((float)temp - 2732) / 10;
|
||||
return temperature_c;
|
||||
}
|
||||
auto temperature_c = ((float)temp - 2732) / 10;
|
||||
return temperature_c;
|
||||
|
||||
throw std::runtime_error(fmt::format(
|
||||
"sysctl hw.acpi.thermal.tz{}.temperature and dev.cpu.{}.temperature failed", zone, zone));
|
||||
|
||||
#else // Linux
|
||||
std::ifstream temp(file_path_);
|
||||
@ -105,6 +134,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;
|
||||
@ -112,6 +144,11 @@ float waybar::modules::Temperature::getTemperature() {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool waybar::modules::Temperature::isWarning(uint16_t temperature_c) {
|
||||
return config_["warning-threshold"].isInt() &&
|
||||
temperature_c >= config_["warning-threshold"].asInt();
|
||||
}
|
||||
|
||||
bool waybar::modules::Temperature::isCritical(uint16_t temperature_c) {
|
||||
return config_["critical-threshold"].isInt() &&
|
||||
temperature_c >= config_["critical-threshold"].asInt();
|
||||
|
||||
498
src/modules/upower.cpp
Normal file
498
src/modules/upower.cpp
Normal file
@ -0,0 +1,498 @@
|
||||
#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 (upDevice_.upDevice != NULL) {
|
||||
g_object_unref(upDevice_.upDevice);
|
||||
upDevice_.upDevice = NULL;
|
||||
}
|
||||
|
||||
if (nativePath_.empty() && model_.empty()) {
|
||||
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 it exists
|
||||
if (displayDevice.upDevice != NULL) {
|
||||
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,384 +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) {
|
||||
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 = new 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);
|
||||
|
||||
GError* error = NULL;
|
||||
client = up_client_new_full(NULL, &error);
|
||||
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, &error);
|
||||
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 (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) 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,161 +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(),
|
||||
iconSize(iconSize_),
|
||||
tooltipSpacing(tooltipSpacing_),
|
||||
tooltipPadding(tooltipPadding_) {
|
||||
contentBox = new Gtk::Box(Gtk::ORIENTATION_VERTICAL);
|
||||
|
||||
// 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
|
||||
@ -4,6 +4,8 @@
|
||||
|
||||
bool isValidNodeId(uint32_t id) { return id > 0 && id < G_MAXUINT32; }
|
||||
|
||||
std::list<waybar::modules::Wireplumber*> waybar::modules::Wireplumber::modules;
|
||||
|
||||
waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config)
|
||||
: ALabel(config, "wireplumber", id, "{volume}%"),
|
||||
wp_core_(nullptr),
|
||||
@ -16,87 +18,95 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
|
||||
muted_(false),
|
||||
volume_(0.0),
|
||||
min_step_(0.0),
|
||||
node_id_(0) {
|
||||
node_id_(0),
|
||||
type_(nullptr) {
|
||||
waybar::modules::Wireplumber::modules.push_back(this);
|
||||
|
||||
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();
|
||||
type_ = g_strdup(config_["node-type"].isString() ? config_["node-type"].asString().c_str()
|
||||
: "Audio/Sink");
|
||||
|
||||
loadRequiredApiModules();
|
||||
prepare(this);
|
||||
|
||||
spdlog::debug("[{}]: connecting to pipewire...", this->name_);
|
||||
spdlog::debug("[{}]: connecting to pipewire: '{}'...", name_, type_);
|
||||
|
||||
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_, type_);
|
||||
throw std::runtime_error("Could not connect to PipeWire\n");
|
||||
}
|
||||
|
||||
spdlog::debug("[{}]: connected!", this->name_);
|
||||
spdlog::debug("[{}]: {} connected!", name_, type_);
|
||||
|
||||
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() {
|
||||
waybar::modules::Wireplumber::modules.remove(this);
|
||||
wp_core_disconnect(wp_core_);
|
||||
g_clear_pointer(&apis_, g_ptr_array_unref);
|
||||
g_clear_object(&om_);
|
||||
g_clear_object(&wp_core_);
|
||||
g_clear_object(&mixer_api_);
|
||||
g_clear_object(&def_nodes_api_);
|
||||
g_free(default_node_name_);
|
||||
g_free(type_);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self, uint32_t id) {
|
||||
spdlog::debug("[{}]: updating node name with node.id {}", self->name_, id);
|
||||
spdlog::debug("[{}]: updating '{}' node name with node.id {}", self->name_, self->type_, id);
|
||||
|
||||
if (!isValidNodeId(id)) {
|
||||
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node name update.", self->name_, id);
|
||||
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring '{}' node name update.", self->name_,
|
||||
id, self->type_);
|
||||
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;
|
||||
spdlog::debug("[{}]: Updating node name to: {}", self->name_, self->node_name_);
|
||||
self->node_name_ = nick != nullptr ? nick
|
||||
: description != nullptr ? description
|
||||
: "Unknown node name";
|
||||
spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, 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);
|
||||
spdlog::error("[{}]: '{}' is not a valid '{}' node ID. Ignoring volume update.", self->name_,
|
||||
id, self->type_);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
@ -111,13 +121,22 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id) {
|
||||
spdlog::debug("[{}]: (onMixerChanged) - id: {}", self->name_, id);
|
||||
|
||||
g_autoptr(WpNode) node = static_cast<WpNode*>(wp_object_manager_lookup(
|
||||
self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, NULL));
|
||||
self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, nullptr));
|
||||
|
||||
if (!node) {
|
||||
spdlog::warn("[{}]: (onMixerChanged) - Object with id {} not found", self->name_, id);
|
||||
if (node == nullptr) {
|
||||
// log a warning only if no other widget is targeting the id.
|
||||
// this reduces log spam when multiple instances of the module are used on different node types.
|
||||
if (id != self->node_id_) {
|
||||
for (auto const& module : waybar::modules::Wireplumber::modules) {
|
||||
if (module->node_id_ == id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spdlog::warn("[{}]: (onMixerChanged: {}) - Object with id {} not found", self->name_,
|
||||
self->type_, id);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -125,63 +144,66 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber*
|
||||
|
||||
if (self->node_id_ != id) {
|
||||
spdlog::debug(
|
||||
"[{}]: (onMixerChanged) - ignoring mixer update for node: id: {}, name: {} as it is not "
|
||||
"the default node: {} with id: {}",
|
||||
self->name_, id, name, self->default_node_name_, self->node_id_);
|
||||
"[{}]: (onMixerChanged: {}) - ignoring mixer update for node: id: {}, name: {} as it is "
|
||||
"not the default node: {} with id: {}",
|
||||
self->name_, self->type_, id, name, self->default_node_name_, self->node_id_);
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::debug("[{}]: (onMixerChanged) - Need to update volume for node with id {} and name {}",
|
||||
self->name_, id, name);
|
||||
spdlog::debug(
|
||||
"[{}]: (onMixerChanged: {}) - Need to update volume for node with id {} and name {}",
|
||||
self->name_, self->type_, id, name);
|
||||
updateVolume(self, id);
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wireplumber* self) {
|
||||
spdlog::debug("[{}]: (onDefaultNodesApiChanged)", self->name_);
|
||||
spdlog::debug("[{}]: (onDefaultNodesApiChanged: {})", self->name_, self->type_);
|
||||
|
||||
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", self->type_, &defaultNodeId);
|
||||
|
||||
if (!isValidNodeId(default_node_id)) {
|
||||
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node change.", self->name_,
|
||||
default_node_id);
|
||||
if (!isValidNodeId(defaultNodeId)) {
|
||||
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring '{}' node change.", self->name_,
|
||||
defaultNodeId, self->type_);
|
||||
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) {
|
||||
spdlog::warn("[{}]: (onDefaultNodesApiChanged) - Object with id {} not found", self->name_,
|
||||
default_node_id);
|
||||
if (node == nullptr) {
|
||||
spdlog::warn("[{}]: (onDefaultNodesApiChanged: {}) - Object with id {} not found", self->name_,
|
||||
self->type_, 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);
|
||||
"[{}]: (onDefaultNodesApiChanged: {}) - got the following default node: Node(name: {}, id: "
|
||||
"{})",
|
||||
self->name_, self->type_, defaultNodeName, defaultNodeId);
|
||||
|
||||
if (g_strcmp0(self->default_node_name_, default_node_name) == 0) {
|
||||
if (g_strcmp0(self->default_node_name_, defaultNodeName) == 0 &&
|
||||
self->node_id_ == defaultNodeId) {
|
||||
spdlog::debug(
|
||||
"[{}]: (onDefaultNodesApiChanged) - Default node has not changed. Node(name: {}, id: {}). "
|
||||
"Ignoring.",
|
||||
self->name_, self->default_node_name_, default_node_id);
|
||||
"[{}]: (onDefaultNodesApiChanged: {}) - Default node has not changed. Node(name: {}, id: "
|
||||
"{}). Ignoring.",
|
||||
self->name_, self->type_, self->default_node_name_, defaultNodeId);
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::debug(
|
||||
"[{}]: (onDefaultNodesApiChanged) - Default node changed to -> Node(name: {}, id: {})",
|
||||
self->name_, default_node_name, default_node_id);
|
||||
"[{}]: (onDefaultNodesApiChanged: {}) - Default node changed to -> Node(name: {}, id: {})",
|
||||
self->name_, self->type_, defaultNodeName, defaultNodeId);
|
||||
|
||||
g_free(self->default_node_name_);
|
||||
self->default_node_name_ = g_strdup(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) {
|
||||
@ -189,25 +211,26 @@ 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");
|
||||
}
|
||||
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", "Audio/Sink",
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", self->type_,
|
||||
&self->default_node_name_);
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &self->node_id_);
|
||||
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &self->node_id_);
|
||||
|
||||
if (self->default_node_name_) {
|
||||
spdlog::debug("[{}]: (onObjectManagerInstalled) - default configured node name: {} and id: {}",
|
||||
self->name_, self->default_node_name_, self->node_id_);
|
||||
if (self->default_node_name_ != nullptr) {
|
||||
spdlog::debug(
|
||||
"[{}]: (onObjectManagerInstalled: {}) - default configured node name: {} and id: {}",
|
||||
self->name_, self->type_, self->default_node_name_, self->node_id_);
|
||||
}
|
||||
|
||||
updateVolume(self, self->node_id_);
|
||||
@ -220,11 +243,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);
|
||||
}
|
||||
@ -239,42 +262,75 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::modules::Wireplumber::prepare() {
|
||||
spdlog::debug("[{}]: preparing object manager", name_);
|
||||
void waybar::modules::Wireplumber::prepare(waybar::modules::Wireplumber* self) {
|
||||
spdlog::debug("[{}]: preparing object manager: '{}'", name_, self->type_);
|
||||
wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
|
||||
"=s", "Audio/Sink", NULL);
|
||||
"=s", self->type_, 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, &error);
|
||||
|
||||
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;
|
||||
@ -291,12 +347,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 {
|
||||
@ -316,38 +372,31 @@ bool waybar::modules::Wireplumber::handleScroll(GdkEventScroll* e) {
|
||||
if (dir == SCROLL_DIR::NONE) {
|
||||
return true;
|
||||
}
|
||||
if (config_["reverse-scrolling"].asInt() == 1) {
|
||||
if (dir == SCROLL_DIR::UP) {
|
||||
dir = SCROLL_DIR::DOWN;
|
||||
} else if (dir == SCROLL_DIR::DOWN) {
|
||||
dir = SCROLL_DIR::UP;
|
||||
}
|
||||
}
|
||||
double 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);
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
#include "glibmm/fileutils.h"
|
||||
#include "glibmm/refptr.h"
|
||||
#include "util/format.hpp"
|
||||
#include "util/gtk_icon.hpp"
|
||||
#include "util/rewrite_string.hpp"
|
||||
#include "util/string.hpp"
|
||||
|
||||
@ -29,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/");
|
||||
@ -46,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;
|
||||
@ -182,11 +183,21 @@ bool Task::image_load_icon(Gtk::Image &image, const Glib::RefPtr<Gtk::IconTheme>
|
||||
|
||||
try {
|
||||
pixbuf = icon_theme->load_icon(ret_icon_name, scaled_icon_size, Gtk::ICON_LOOKUP_FORCE_SIZE);
|
||||
spdlog::debug("{} Loaded icon '{}'", repr(), ret_icon_name);
|
||||
} catch (...) {
|
||||
if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS))
|
||||
if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS)) {
|
||||
pixbuf = load_icon_from_file(ret_icon_name, scaled_icon_size);
|
||||
else
|
||||
pixbuf = {};
|
||||
spdlog::debug("{} Loaded icon from file '{}'", repr(), ret_icon_name);
|
||||
} else {
|
||||
try {
|
||||
pixbuf = DefaultGtkIconThemeWrapper::load_icon(
|
||||
"image-missing", scaled_icon_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
|
||||
spdlog::debug("{} Loaded icon from resource", repr());
|
||||
} catch (...) {
|
||||
pixbuf = {};
|
||||
spdlog::debug("{} Unable to load icon.", repr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pixbuf) {
|
||||
@ -266,7 +277,7 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
|
||||
handle_{tl_handle},
|
||||
seat_{seat},
|
||||
id_{global_id++},
|
||||
content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} {
|
||||
content_{bar.orientation, 0} {
|
||||
zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this);
|
||||
|
||||
button.set_relief(Gtk::RELIEF_NONE);
|
||||
@ -304,6 +315,10 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
|
||||
with_icon_ = true;
|
||||
}
|
||||
|
||||
if (app_id_.empty()) {
|
||||
handle_app_id("unknown");
|
||||
}
|
||||
|
||||
/* Strip spaces at the beginning and end of the format strings */
|
||||
format_tooltip_.clear();
|
||||
if (!config_["tooltip"].isBool() || config_["tooltip"].asBool()) {
|
||||
@ -319,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);
|
||||
@ -370,8 +383,43 @@ std::string Task::state_string(bool shortened) const {
|
||||
}
|
||||
|
||||
void Task::handle_title(const char *title) {
|
||||
if (title_.empty()) {
|
||||
spdlog::debug(fmt::format("Task ({}) setting title to {}", id_, title_));
|
||||
} else {
|
||||
spdlog::debug(fmt::format("Task ({}) overwriting title '{}' with '{}'", id_, title_, title));
|
||||
}
|
||||
title_ = title;
|
||||
hide_if_ignored();
|
||||
|
||||
if (!with_icon_ && !with_name_ || app_info_) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_app_info_from_app_id_list(title_);
|
||||
name_ = app_info_ ? app_info_->get_display_name() : title;
|
||||
|
||||
if (!with_icon_) {
|
||||
return;
|
||||
}
|
||||
|
||||
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
|
||||
bool found = false;
|
||||
for (auto &icon_theme : tbar_->icon_themes()) {
|
||||
if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
icon_.show();
|
||||
else
|
||||
spdlog::debug("Couldn't find icon for {}", title_);
|
||||
}
|
||||
|
||||
void Task::set_minimize_hint() {
|
||||
zwlr_foreign_toplevel_handle_v1_set_rectangle(handle_, bar_.surface, minimize_hint.x,
|
||||
minimize_hint.y, minimize_hint.w, minimize_hint.h);
|
||||
}
|
||||
|
||||
void Task::hide_if_ignored() {
|
||||
@ -392,6 +440,11 @@ void Task::hide_if_ignored() {
|
||||
}
|
||||
|
||||
void Task::handle_app_id(const char *app_id) {
|
||||
if (app_id_.empty()) {
|
||||
spdlog::debug(fmt::format("Task ({}) setting app_id to {}", id_, app_id));
|
||||
} else {
|
||||
spdlog::debug(fmt::format("Task ({}) overwriting app_id '{}' with '{}'", id_, app_id_, app_id));
|
||||
}
|
||||
app_id_ = app_id;
|
||||
hide_if_ignored();
|
||||
|
||||
@ -429,6 +482,13 @@ void Task::handle_app_id(const char *app_id) {
|
||||
spdlog::debug("Couldn't find icon for {}", app_id_);
|
||||
}
|
||||
|
||||
void Task::on_button_size_allocated(Gtk::Allocation &alloc) {
|
||||
gtk_widget_translate_coordinates(GTK_WIDGET(button.gobj()), GTK_WIDGET(bar_.window.gobj()), 0, 0,
|
||||
&minimize_hint.x, &minimize_hint.y);
|
||||
minimize_hint.w = button.get_width();
|
||||
minimize_hint.h = button.get_height();
|
||||
}
|
||||
|
||||
void Task::handle_output_enter(struct wl_output *output) {
|
||||
if (ignored_) {
|
||||
spdlog::debug("{} is ignored", repr());
|
||||
@ -439,6 +499,8 @@ void Task::handle_output_enter(struct wl_output *output) {
|
||||
|
||||
if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) {
|
||||
/* The task entered the output of the current bar make the button visible */
|
||||
button.signal_size_allocate().connect_notify(
|
||||
sigc::mem_fun(this, &Task::on_button_size_allocated));
|
||||
tbar_->add_button(button);
|
||||
button.show();
|
||||
button_visible_ = true;
|
||||
@ -507,17 +569,17 @@ void Task::handle_closed() {
|
||||
spdlog::debug("{} closed", repr());
|
||||
zwlr_foreign_toplevel_handle_v1_destroy(handle_);
|
||||
handle_ = nullptr;
|
||||
tbar_->remove_task(id_);
|
||||
if (button_visible_) {
|
||||
tbar_->remove_button(button);
|
||||
button_visible_ = false;
|
||||
}
|
||||
tbar_->remove_task(id_);
|
||||
}
|
||||
|
||||
bool Task::handle_clicked(GdkEventButton *bt) {
|
||||
/* filter out additional events for double/triple clicks */
|
||||
if (bt->type == GDK_BUTTON_PRESS) {
|
||||
/* save where the button press ocurred in case it becomes a drag */
|
||||
/* save where the button press occurred in case it becomes a drag */
|
||||
drag_start_button = bt->button;
|
||||
drag_start_x = bt->x;
|
||||
drag_start_y = bt->y;
|
||||
@ -535,9 +597,11 @@ bool Task::handle_clicked(GdkEventButton *bt) {
|
||||
return true;
|
||||
else if (action == "activate")
|
||||
activate();
|
||||
else if (action == "minimize")
|
||||
else if (action == "minimize") {
|
||||
set_minimize_hint();
|
||||
minimize(!minimized());
|
||||
else if (action == "minimize-raise") {
|
||||
} else if (action == "minimize-raise") {
|
||||
set_minimize_hint();
|
||||
if (minimized())
|
||||
minimize(false);
|
||||
else if (active())
|
||||
@ -553,12 +617,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) {
|
||||
@ -710,13 +770,14 @@ static const wl_registry_listener registry_listener_impl = {.global = handle_glo
|
||||
Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
|
||||
: waybar::AModule(config, "taskbar", id, false, false),
|
||||
bar_(bar),
|
||||
box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
|
||||
box_{bar.orientation, 0},
|
||||
manager_{nullptr},
|
||||
seat_{nullptr} {
|
||||
box_.set_name("taskbar");
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
box_.get_style_context()->add_class(MODULE_CLASS);
|
||||
box_.get_style_context()->add_class("empty");
|
||||
event_box_.add(box_);
|
||||
|
||||
@ -773,6 +834,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() {
|
||||
@ -879,7 +944,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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,9 +21,7 @@ std::map<std::string, std::string> Workspace::icons_map_;
|
||||
|
||||
WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar,
|
||||
const Json::Value &config)
|
||||
: waybar::AModule(config, "workspaces", id, false, false),
|
||||
bar_(bar),
|
||||
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) {
|
||||
: waybar::AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) {
|
||||
auto config_sort_by_name = config_["sort-by-name"];
|
||||
if (config_sort_by_name.isBool()) {
|
||||
sort_by_name_ = config_sort_by_name.asBool();
|
||||
@ -54,6 +52,7 @@ WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
box_.get_style_context()->add_class(MODULE_CLASS);
|
||||
event_box_.add(box_);
|
||||
|
||||
add_registry_listener(this);
|
||||
@ -209,8 +208,17 @@ WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::fill_persistent_workspaces() -> void {
|
||||
if (config_["persistent_workspaces"].isObject() && !workspace_manager_.all_outputs()) {
|
||||
const Json::Value &p_workspaces = config_["persistent_workspaces"];
|
||||
if (config_["persistent_workspaces"].isObject()) {
|
||||
spdlog::warn(
|
||||
"persistent_workspaces is deprecated. Please change config to use persistent-workspaces.");
|
||||
}
|
||||
|
||||
if ((config_["persistent-workspaces"].isObject() ||
|
||||
config_["persistent_workspaces"].isObject()) &&
|
||||
!workspace_manager_.all_outputs()) {
|
||||
const Json::Value &p_workspaces = config_["persistent-workspaces"].isObject()
|
||||
? config_["persistent-workspaces"]
|
||||
: config_["persistent_workspaces"];
|
||||
const std::vector<std::string> p_workspaces_names = p_workspaces.getMemberNames();
|
||||
|
||||
for (const std::string &p_w_name : p_workspaces_names) {
|
||||
|
||||
Reference in New Issue
Block a user