Merge remote-tracking branch 'origin/master' into pr/pulseaudio_mapping

# Conflicts:
#	src/util/audio_backend.cpp
This commit is contained in:
Alex
2026-07-03 22:41:19 +02:00
306 changed files with 13895 additions and 5044 deletions
+220 -65
View File
@@ -1,9 +1,12 @@
#include "util/audio_backend.hpp"
#include <fmt/core.h>
#include <pulse/def.h>
#include <pulse/error.h>
#include <pulse/introspect.h>
#include <pulse/subscribe.h>
#include <pulse/volume.h>
#include <spdlog/spdlog.h>
#include <algorithm>
#include <cmath>
@@ -21,6 +24,8 @@ AudioBackend::AudioBackend(std::function<void()> on_updated_cb, private_construc
source_volume_(0),
source_muted_(false),
on_updated_cb_(std::move(on_updated_cb)) {
// Initialize pa_volume_ with safe defaults
pa_cvolume_init(&pa_volume_);
mainloop_ = pa_threaded_mainloop_new();
if (mainloop_ == nullptr) {
throw std::runtime_error("pa_mainloop_new() failed.");
@@ -35,12 +40,16 @@ AudioBackend::AudioBackend(std::function<void()> on_updated_cb, private_construc
}
AudioBackend::~AudioBackend() {
if (context_ != nullptr) {
pa_context_disconnect(context_);
}
if (mainloop_ != nullptr) {
mainloop_api_->quit(mainloop_api_, 0);
// Lock the mainloop so we can safely disconnect the context.
// This must be done before stopping the thread.
pa_threaded_mainloop_lock(mainloop_);
if (context_ != nullptr) {
pa_context_disconnect(context_);
pa_context_unref(context_);
context_ = nullptr;
}
pa_threaded_mainloop_unlock(mainloop_);
pa_threaded_mainloop_stop(mainloop_);
pa_threaded_mainloop_free(mainloop_);
}
@@ -64,11 +73,18 @@ void AudioBackend::connectContext() {
}
}
void AudioBackend::contextStateCb(pa_context *c, void *data) {
auto *backend = static_cast<AudioBackend *>(data);
void AudioBackend::contextStateCb(pa_context* c, void* data) {
auto* backend = static_cast<AudioBackend*>(data);
switch (pa_context_get_state(c)) {
case PA_CONTEXT_TERMINATED:
backend->mainloop_api_->quit(backend->mainloop_api_, 0);
// Only quit the mainloop if this is still the active context.
// During reconnection, the old context fires TERMINATED after the new one
// has already been created; quitting in that case would kill the new context.
// Note: context_ is only written from PA callbacks (while the mainloop lock is
// held), so this comparison is safe within any PA callback.
if (backend->context_ == nullptr || backend->context_ == c) {
backend->mainloop_api_->quit(backend->mainloop_api_, 0);
}
break;
case PA_CONTEXT_READY:
pa_context_get_server_info(c, serverInfoCb, data);
@@ -83,13 +99,17 @@ void AudioBackend::contextStateCb(pa_context *c, void *data) {
nullptr, nullptr);
break;
case PA_CONTEXT_FAILED:
// When pulseaudio server restarts, the connection is "failed". Try to reconnect.
// pa_threaded_mainloop_lock is already acquired in callback threads.
// So there is no need to lock it again.
if (backend->context_ != nullptr) {
pa_context_disconnect(backend->context_);
if (pa_context_errno(c) != PA_ERR_CONNECTIONREFUSED) {
// When pulseaudio server restarts, the connection is "failed". Try to reconnect.
// pa_threaded_mainloop_lock is already acquired in callback threads.
// So there is no need to lock it again.
if (backend->context_ != nullptr) {
pa_context_disconnect(backend->context_);
pa_context_unref(backend->context_);
backend->context_ = nullptr;
}
backend->connectContext();
}
backend->connectContext();
break;
case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING:
@@ -102,8 +122,8 @@ void AudioBackend::contextStateCb(pa_context *c, void *data) {
/*
* Called when an event we subscribed to occurs.
*/
void AudioBackend::subscribeCb(pa_context *context, pa_subscription_event_type_t type, uint32_t idx,
void *data) {
void AudioBackend::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) {
@@ -125,24 +145,33 @@ void AudioBackend::subscribeCb(pa_context *context, pa_subscription_event_type_t
/*
* Called in response to a volume change request
*/
void AudioBackend::volumeModifyCb(pa_context *c, int success, void *data) {
auto *backend = static_cast<AudioBackend *>(data);
void AudioBackend::volumeModifyCb(pa_context* c, int success, void* data) {
auto* backend = static_cast<AudioBackend*>(data);
if (success != 0) {
pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, sinkInfoCb, data);
if ((backend->context_ != nullptr) &&
pa_context_get_state(backend->context_) == PA_CONTEXT_READY) {
pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, sinkInfoCb, data);
}
} else {
spdlog::debug("Volume modification failed");
}
}
/*
* Called when the requested sink information is ready.
*/
void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, int /*eol*/,
void *data) {
void AudioBackend::sinkInfoCb(pa_context* /*context*/, const pa_sink_info* i, int /*eol*/,
void* data) {
if (i == nullptr) return;
auto *backend = static_cast<AudioBackend *>(data);
auto running = i->state == PA_SINK_RUNNING;
auto idle = i->state == PA_SINK_IDLE;
spdlog::trace("Sink name {} Running:[{}] Idle:[{}]", i->name, running, idle);
auto* backend = static_cast<AudioBackend*>(data);
if (!backend->ignored_sinks_.empty()) {
for (const auto &ignored_sink : backend->ignored_sinks_) {
for (const auto& ignored_sink : backend->ignored_sinks_) {
if (ignored_sink == i->description) {
if (i->name == backend->current_sink_name_) {
// If the current sink happens to be ignored it is never considered running
@@ -162,26 +191,44 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i
}
}
if (backend->current_sink_name_ == i->name) {
backend->current_sink_running_ = i->state == PA_SINK_RUNNING;
backend->default_sink_running_ = backend->default_sink_name == i->name &&
(i->state == PA_SINK_RUNNING || i->state == PA_SINK_IDLE);
if (i->name != backend->default_sink_name && i->name != backend->current_sink_name_ &&
!backend->default_sink_running_) {
return;
}
if (!backend->current_sink_running_ && i->state == PA_SINK_RUNNING) {
if (backend->current_sink_name_ == i->name) {
backend->current_sink_running_ = (i->state == PA_SINK_RUNNING || i->state == PA_SINK_IDLE);
}
if (!backend->current_sink_running_ &&
(i->state == PA_SINK_RUNNING || i->state == PA_SINK_IDLE)) {
backend->current_sink_name_ = i->name;
backend->current_sink_running_ = true;
}
if (backend->current_sink_name_ == i->name) {
backend->pa_volume_ = i->volume;
float volume =
static_cast<float>(pa_cvolume_avg(&(backend->pa_volume_))) / float{PA_VOLUME_NORM};
backend->sink_idx_ = i->index;
backend->volume_ = std::round(volume * 100.0F);
// Safely copy the volume structure
if (pa_cvolume_valid(&i->volume) != 0) {
backend->pa_volume_ = i->volume;
float volume =
static_cast<float>(pa_cvolume_avg(&(backend->pa_volume_))) / float{PA_VOLUME_NORM};
backend->sink_idx_ = i->index;
backend->volume_ = std::round(volume * 100.0F);
} else {
spdlog::error("Invalid volume structure received from PulseAudio");
// Initialize with safe defaults
pa_cvolume_init(&backend->pa_volume_);
backend->volume_ = 0;
}
backend->muted_ = i->mute != 0;
backend->desc_ = i->description;
backend->monitor_ = i->monitor_source_name;
backend->port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown";
if (const auto *ff = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_FORM_FACTOR)) {
if (const auto* ff = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_FORM_FACTOR)) {
backend->form_factor_ = ff;
} else {
backend->form_factor_ = "";
@@ -193,9 +240,9 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i
/*
* Called when the requested source information is ready.
*/
void AudioBackend::sourceInfoCb(pa_context * /*context*/, const pa_source_info *i, int /*eol*/,
void *data) {
auto *backend = static_cast<AudioBackend *>(data);
void AudioBackend::sourceInfoCb(pa_context* /*context*/, const pa_source_info* i, int /*eol*/,
void* data) {
auto* backend = static_cast<AudioBackend*>(data);
if (i != nullptr && backend->default_source_name_ == i->name) {
auto source_volume = static_cast<float>(pa_cvolume_avg(&(i->volume))) / float{PA_VOLUME_NORM};
backend->source_volume_ = std::round(source_volume * 100.0F);
@@ -211,76 +258,184 @@ void AudioBackend::sourceInfoCb(pa_context * /*context*/, const pa_source_info *
* Called when the requested information on the server is ready. This is
* used to find the default PulseAudio sink.
*/
void AudioBackend::serverInfoCb(pa_context *context, const pa_server_info *i, void *data) {
auto *backend = static_cast<AudioBackend *>(data);
backend->current_sink_name_ = i->default_sink_name;
backend->default_source_name_ = i->default_source_name;
void AudioBackend::serverInfoCb(pa_context* context, const pa_server_info* i, void* data) {
auto* backend = static_cast<AudioBackend*>(data);
if (i == nullptr) return;
backend->current_sink_name_ = i->default_sink_name ? i->default_sink_name : "";
backend->default_sink_name = i->default_sink_name ? i->default_sink_name : "";
backend->default_source_name_ = i->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);
}
uint16_t AudioBackend::getVolume(PulseaudioTarget target) const {
if (target == PulseaudioTarget::Source) {
return source_volume_;
} else {
return volume_;
}
}
void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume) {
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_cvolume pa_volume = pa_volume_;
// Early return if context is not ready
if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) {
spdlog::error("PulseAudio context not ready");
return;
}
// Prepare volume structure
pa_cvolume pa_volume;
pa_cvolume_init(&pa_volume);
// Use existing volume structure if valid, otherwise create a safe default
if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) {
pa_volume = pa_volume_;
} else {
// Set stereo as a safe default
pa_volume.channels = 2;
spdlog::debug("Using default stereo volume structure");
}
// Set the volume safely
volume = std::clamp(volume, min_volume, max_volume);
pa_cvolume_set(&pa_volume, pa_volume_.channels, volume * volume_tick);
pa_volume_t vol = volume * (static_cast<double>(PA_VOLUME_NORM) / 100);
// Set all channels to the same volume manually to avoid pa_cvolume_set
for (uint8_t i = 0; i < pa_volume.channels; i++) {
pa_volume.values[i] = vol;
}
// Apply the volume change
pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
pa_threaded_mainloop_unlock(mainloop_);
}
void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t max_volume) {
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_volume_t change = volume_tick;
pa_cvolume pa_volume = pa_volume_;
// Early return if context is not ready
if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) {
spdlog::error("PulseAudio context not ready");
return;
}
// Prepare volume structure
pa_cvolume pa_volume;
pa_cvolume_init(&pa_volume);
// Use existing volume structure if valid, otherwise create a safe default
if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) {
pa_volume = pa_volume_;
} else {
// Set stereo as a safe default
pa_volume.channels = 2;
spdlog::debug("Using default stereo volume structure");
// Initialize all channels to current volume level
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_volume_t vol = volume_ * volume_tick;
for (uint8_t i = 0; i < pa_volume.channels; i++) {
pa_volume.values[i] = vol;
}
// No need to continue with volume change if we had to create a new structure
pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
pa_threaded_mainloop_unlock(mainloop_);
return;
}
// Calculate volume change
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_volume_t change;
max_volume = std::min(max_volume, static_cast<uint16_t>(PA_VOLUME_UI_MAX));
if (change_type == ChangeType::Increase) {
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);
if (change_type == ChangeType::Increase && volume_ < max_volume) {
// Calculate how much to increase
if (volume_ + step > max_volume) {
change = round((max_volume - volume_) * volume_tick);
} else {
change = round(step * volume_tick);
}
} else if (change_type == ChangeType::Decrease) {
if (volume_ > 0) {
if (volume_ - step < 0) {
change = round(volume_ * volume_tick);
} else {
change = round(step * volume_tick);
}
pa_cvolume_dec(&pa_volume, change);
// Manually increase each channel's volume
for (uint8_t i = 0; i < pa_volume.channels; i++) {
pa_volume.values[i] = std::min(pa_volume.values[i] + change, PA_VOLUME_MAX);
}
} else if (change_type == ChangeType::Decrease && volume_ > 0) {
// Calculate how much to decrease
if (volume_ - step < 0) {
change = round(volume_ * volume_tick);
} else {
change = round(step * volume_tick);
}
// Manually decrease each channel's volume
for (uint8_t i = 0; i < pa_volume.channels; i++) {
pa_volume.values[i] = (pa_volume.values[i] > change) ? (pa_volume.values[i] - change) : 0;
}
} else {
// No change needed
return;
}
// Apply the volume change
pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
pa_threaded_mainloop_unlock(mainloop_);
}
bool AudioBackend::getMuted(PulseaudioTarget target) const {
if (target == PulseaudioTarget::Source) {
return source_muted_;
} else {
return muted_;
}
}
void AudioBackend::toggleSinkMute() {
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
muted_ = !muted_;
pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast<int>(muted_), nullptr,
nullptr);
pa_threaded_mainloop_unlock(mainloop_);
}
void AudioBackend::toggleSinkMute(bool mute) {
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
muted_ = mute;
pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast<int>(muted_), nullptr,
nullptr);
pa_threaded_mainloop_unlock(mainloop_);
}
void AudioBackend::toggleSourceMute() {
source_muted_ = !muted_;
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
source_muted_ = !source_muted_;
pa_threaded_mainloop_lock(mainloop_);
pa_context_set_source_mute_by_index(context_, source_idx_, static_cast<int>(source_muted_),
nullptr, nullptr);
pa_threaded_mainloop_unlock(mainloop_);
}
void AudioBackend::toggleSourceMute(bool mute) {
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
source_muted_ = mute;
pa_threaded_mainloop_lock(mainloop_);
pa_context_set_source_mute_by_index(context_, source_idx_, static_cast<int>(source_muted_),
nullptr, nullptr);
pa_threaded_mainloop_unlock(mainloop_);
}
void AudioBackend::unmute(PulseaudioTarget target) {
if (target == PulseaudioTarget::Source) {
toggleSourceMute(false);
} else {
toggleSinkMute(false);
}
}
bool AudioBackend::isBluetooth() {
@@ -289,9 +444,9 @@ bool AudioBackend::isBluetooth() {
monitor_.find("bluez") != std::string::npos;
}
void AudioBackend::setIgnoredSinks(const Json::Value &config) {
void AudioBackend::setIgnoredSinks(const Json::Value& config) {
if (config.isArray()) {
for (const auto &ignored_sink : config) {
for (const auto& ignored_sink : config) {
if (ignored_sink.isString()) {
ignored_sinks_.push_back(ignored_sink.asString());
}
@@ -299,7 +454,7 @@ void AudioBackend::setIgnoredSinks(const Json::Value &config) {
}
}
void AudioBackend::setSinkMapping(const Json::Value &config) {
void AudioBackend::setSinkMapping(const Json::Value& config) {
if (config.isObject()) {
for (auto it = config.begin(); it != config.end(); ++it) {
if (it.key().isString() && it->isString()) {
+77 -55
View File
@@ -8,14 +8,16 @@
#include <optional>
#include <utility>
#include "util/udev_deleter.hpp"
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(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) {
@@ -29,43 +31,27 @@ class FileDescriptor {
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: ") {
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: ") {
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 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: ") {
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") {
void check_nn(const void* ptr, const char* message = "ptr was null") {
if (ptr == nullptr) {
throw std::runtime_error(message);
}
@@ -75,48 +61,68 @@ void check_nn(const void *ptr, const char *message = "ptr was null") {
namespace waybar::util {
static void upsert_device(std::vector<BacklightDevice> &devices, udev_device *dev) {
const char *name = udev_device_get_sysname(dev);
static void upsert_device(std::vector<BacklightDevice>& devices, udev_device* dev) {
const char* name = udev_device_get_sysname(dev);
check_nn(name);
const char *actual_brightness_attr =
const char* actual_brightness_attr =
strncmp(name, "amdgpu_bl", 9) == 0 || strcmp(name, "apple-panel-bl") == 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");
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(devices.begin(), devices.end(), [name](const BacklightDevice &device) {
auto found = std::find_if(devices.begin(), devices.end(), [name](const BacklightDevice& device) {
return device.name() == name;
});
if (found != devices.end()) {
if (actual != nullptr) {
found->set_actual(std::stoi(actual));
try {
found->set_actual(std::stoi(actual));
} catch (const std::exception&) {
}
}
if (max != nullptr) {
found->set_max(std::stoi(max));
try {
found->set_max(std::stoi(max));
} catch (const std::exception&) {
}
}
if (power != nullptr) {
found->set_powered(std::stoi(power) == 0);
try {
found->set_powered(std::stoi(power) == 0);
} catch (const std::exception&) {
}
}
} 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;
int actual_int = 0, max_int = 0;
bool power_bool = true;
try {
if (actual != nullptr) actual_int = std::stoi(actual);
} catch (const std::exception&) {
}
try {
if (max != nullptr) max_int = std::stoi(max);
} catch (const std::exception&) {
}
try {
if (power != nullptr) power_bool = std::stoi(power) == 0;
} catch (const std::exception&) {
}
devices.emplace_back(name, actual_int, max_int, power_bool);
}
}
static void enumerate_devices(std::vector<BacklightDevice> &devices, udev *udev) {
static void enumerate_devices(std::vector<BacklightDevice>& devices, 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* 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);
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(devices, dev.get());
@@ -150,10 +156,18 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval,
throw std::runtime_error("No backlight found");
}
#ifdef HAVE_LOGIN_PROXY
// 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");
"/org/freedesktop/login1/session/auto", "org.freedesktop.login1.Session");
if (!login_proxy_) {
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");
}
#endif
udev_thread_ = [this] {
std::unique_ptr<udev, UdevDeleter> udev{udev_new()};
@@ -190,10 +204,12 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval,
devices = devices_;
}
for (int i = 0; i < event_count; ++i) {
const auto &event = events[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");
if (!dev) {
continue;
}
upsert_device(devices, dev.get());
}
@@ -210,27 +226,27 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval,
};
}
const BacklightDevice *BacklightBackend::best_device(const std::vector<BacklightDevice> &devices,
const BacklightDevice* BacklightBackend::best_device(const std::vector<BacklightDevice>& devices,
std::string_view preferred_device) {
const auto found = std::find_if(
devices.begin(), devices.end(),
[preferred_device](const BacklightDevice &dev) { return dev.name() == preferred_device; });
[preferred_device](const BacklightDevice& dev) { return dev.name() == preferred_device; });
if (found != devices.end()) {
return &(*found);
}
const auto max = std::max_element(
devices.begin(), devices.end(),
[](const BacklightDevice &l, const BacklightDevice &r) { return l.get_max() < r.get_max(); });
[](const BacklightDevice& l, const BacklightDevice& r) { return l.get_max() < r.get_max(); });
return max == devices.end() ? nullptr : &(*max);
}
const BacklightDevice *BacklightBackend::get_previous_best_device() {
const BacklightDevice* BacklightBackend::get_previous_best_device() {
return previous_best_.has_value() ? &(*previous_best_) : nullptr;
}
void BacklightBackend::set_previous_best_device(const BacklightDevice *device) {
void BacklightBackend::set_previous_best_device(const BacklightDevice* device) {
if (device == nullptr) {
previous_best_ = std::nullopt;
} else {
@@ -238,7 +254,7 @@ void BacklightBackend::set_previous_best_device(const BacklightDevice *device) {
}
}
void BacklightBackend::set_scaled_brightness(const std::string &preferred_device, int brightness) {
void BacklightBackend::set_scaled_brightness(const std::string& preferred_device, int brightness) {
GET_BEST_DEVICE(best, (*this), preferred_device);
if (best != nullptr) {
@@ -248,7 +264,7 @@ void BacklightBackend::set_scaled_brightness(const std::string &preferred_device
}
}
void BacklightBackend::set_brightness(const std::string &preferred_device, ChangeType change_type,
void BacklightBackend::set_brightness(const std::string& preferred_device, ChangeType change_type,
double step) {
GET_BEST_DEVICE(best, (*this), preferred_device);
@@ -263,8 +279,13 @@ void BacklightBackend::set_brightness(const std::string &preferred_device, Chang
}
}
void BacklightBackend::set_brightness_internal(const std::string &device_name, int brightness,
void BacklightBackend::set_brightness_internal(const std::string& device_name, int brightness,
int max_brightness) {
if (!login_proxy_) {
spdlog::error("Login proxy not available, cannot set brightness");
return;
}
brightness = std::clamp(brightness, 0, max_brightness);
auto call_args = Glib::VariantContainerBase(
@@ -273,11 +294,12 @@ void BacklightBackend::set_brightness_internal(const std::string &device_name, i
login_proxy_->call_sync("SetBrightness", call_args);
}
int BacklightBackend::get_scaled_brightness(const std::string &preferred_device) {
int BacklightBackend::get_scaled_brightness(const std::string& preferred_device) {
GET_BEST_DEVICE(best, (*this), preferred_device);
if (best != nullptr) {
return best->get_actual() * 100 / best->get_max();
if (best->get_max() == 0) return 0;
return static_cast<int>(std::round(best->get_actual() * 100.0F / best->get_max()));
}
return 0;
+20 -3
View File
@@ -2,7 +2,13 @@
#include <poll.h>
#include <spdlog/spdlog.h>
#ifndef __OpenBSD__
#include <sys/inotify.h>
#else
#include <sys/event.h>
#include <sys/time.h>
#include <sys/types.h>
#endif
#include <filesystem>
#include <fstream>
@@ -17,7 +23,8 @@ namespace {
const std::regex IMPORT_REGEX(R"(@import\s+(?:url\()?(?:"|')([^"')]+)(?:"|')\)?;)");
}
waybar::CssReloadHelper::CssReloadHelper(std::string cssFile, std::function<void()> callback)
waybar::CssReloadHelper::CssReloadHelper(std::string cssFile,
std::function<void(const std::string&)> callback)
: m_cssFile(std::move(cssFile)), m_callback(std::move(callback)) {}
std::string waybar::CssReloadHelper::getFileContents(const std::string& filename) {
@@ -82,6 +89,12 @@ void waybar::CssReloadHelper::monitorChanges() {
}
}
void waybar::CssReloadHelper::changeCssFile(const std::string& newCssFile) {
m_fileMonitors.clear();
m_cssFile = newCssFile;
monitorChanges();
}
void waybar::CssReloadHelper::handleFileChange(Glib::RefPtr<Gio::File> const& file,
Glib::RefPtr<Gio::File> const& other_type,
Gio::FileMonitorEvent event_type) {
@@ -89,7 +102,7 @@ void waybar::CssReloadHelper::handleFileChange(Glib::RefPtr<Gio::File> const& fi
// fire for one
if (event_type == Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) {
spdlog::debug("Reloading style, file changed: {}", file->get_path());
m_callback();
m_callback(m_cssFile);
}
}
@@ -109,11 +122,15 @@ std::vector<std::string> waybar::CssReloadHelper::parseImports(const std::string
auto maxIterations = 100U;
do {
previousSize = imports.size();
std::vector<std::string> to_parse;
for (const auto& [file, parsed] : imports) {
if (!parsed) {
parseImports(file, imports);
to_parse.push_back(file);
}
}
for (const auto& file : to_parse) {
parseImports(file, imports);
}
} while (imports.size() > previousSize && maxIterations-- > 0);
+2
View File
@@ -41,5 +41,7 @@ EnumType EnumParser<EnumType>::parseStringToEnum(const std::string& str,
// Explicit instantiations for specific EnumType types you intend to use
// Add explicit instantiations for all relevant EnumType types
template struct EnumParser<modules::hyprland::Workspaces::SortMethod>;
template struct EnumParser<modules::hyprland::Workspaces::ActiveWindowPosition>;
template struct EnumParser<util::KillSignalAction>;
} // namespace waybar::util
+16 -4
View File
@@ -15,11 +15,23 @@ bool DefaultGtkIconThemeWrapper::has_icon(const std::string& value) {
return Gtk::IconTheme::get_default()->has_icon(value);
}
Glib::RefPtr<Gdk::Pixbuf> DefaultGtkIconThemeWrapper::load_icon(const char* name, int tmp_size,
Gtk::IconLookupFlags flags) {
Glib::RefPtr<Gdk::Pixbuf> DefaultGtkIconThemeWrapper::load_icon(
const char* name, int tmp_size, Gtk::IconLookupFlags flags,
Glib::RefPtr<Gtk::StyleContext> style) {
const std::lock_guard<std::mutex> lock(default_theme_mutex);
auto default_theme = Gtk::IconTheme::get_default();
default_theme->rescan_if_needed();
return default_theme->load_icon(name, tmp_size, flags);
auto icon_info = default_theme->lookup_icon(name, tmp_size, flags);
if (icon_info == nullptr) {
return default_theme->load_icon(name, tmp_size, flags);
}
if (style.get() == nullptr) {
return icon_info.load_icon();
}
bool is_sym = false;
return icon_info.load_symbolic(style, is_sym);
}
+210
View File
@@ -0,0 +1,210 @@
#include "util/icon_loader.hpp"
#include "util/string.hpp"
std::vector<std::string> IconLoader::search_prefix() {
std::vector<std::string> prefixes = {""};
const char* home_env = std::getenv("HOME");
std::string home_dir = home_env ? home_env : "";
if (!home_dir.empty()) {
prefixes.push_back(home_dir + "/.local/share/");
}
auto xdg_data_dirs = std::getenv("XDG_DATA_DIRS");
if (!xdg_data_dirs) {
prefixes.emplace_back("/usr/share/");
prefixes.emplace_back("/usr/local/share/");
} else {
std::string xdg_data_dirs_str(xdg_data_dirs);
size_t start = 0;
size_t end = 0;
do {
end = xdg_data_dirs_str.find(':', start);
auto p = xdg_data_dirs_str.substr(start, end - start);
prefixes.push_back(trim(p) + "/");
start = end == std::string::npos ? end : end + 1;
} while (end != std::string::npos);
}
for (auto& p : prefixes) spdlog::debug("Using 'desktop' search path prefix: {}", p);
return prefixes;
}
Glib::RefPtr<Gio::DesktopAppInfo> IconLoader::get_app_info_by_name(const std::string& app_id) {
static std::vector<std::string> prefixes = search_prefix();
std::vector<std::string> app_folders = {"", "applications/", "applications/kde/",
"applications/org.kde."};
std::vector<std::string> suffixes = {"", ".desktop"};
for (auto const& prefix : prefixes) {
for (auto const& folder : app_folders) {
for (auto const& suffix : suffixes) {
auto app_info_ =
Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
if (!app_info_) {
continue;
}
return app_info_;
}
}
}
return {};
}
Glib::RefPtr<Gio::DesktopAppInfo> IconLoader::get_desktop_app_info(const std::string& app_id) {
auto app_info = get_app_info_by_name(app_id);
if (app_info) {
return app_info;
}
std::string desktop_file = "";
gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str());
if (desktop_list != nullptr && desktop_list[0] != nullptr) {
for (size_t i = 0; desktop_list[0][i]; i++) {
if (desktop_file == "") {
desktop_file = desktop_list[0][i];
} else {
auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]);
if (!tmp_info)
// see https://github.com/Alexays/Waybar/issues/1446
continue;
auto startup_class = tmp_info->get_startup_wm_class();
if (startup_class == app_id) {
desktop_file = desktop_list[0][i];
break;
}
}
}
g_strfreev(desktop_list[0]);
}
g_free(desktop_list);
return get_app_info_by_name(desktop_file);
}
Glib::RefPtr<Gdk::Pixbuf> IconLoader::load_icon_from_file(std::string const& icon_path, int size) {
try {
auto pb = Gdk::Pixbuf::create_from_file(icon_path, size, size);
return pb;
} catch (...) {
return {};
}
}
std::string IconLoader::get_icon_name_from_icon_theme(
const Glib::RefPtr<Gtk::IconTheme>& icon_theme, const std::string& app_id) {
if (icon_theme->lookup_icon(app_id, 24)) return app_id;
return "";
}
bool IconLoader::image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size) {
std::string ret_icon_name = "unknown";
if (app_info) {
std::string icon_name =
get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class());
if (!icon_name.empty()) {
ret_icon_name = icon_name;
} else {
if (app_info->get_icon()) {
ret_icon_name = app_info->get_icon()->to_string();
}
}
}
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
auto scaled_icon_size = size * image.get_scale_factor();
try {
pixbuf = icon_theme->load_icon(ret_icon_name, scaled_icon_size, Gtk::ICON_LOOKUP_FORCE_SIZE);
} catch (...) {
if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS)) {
pixbuf = load_icon_from_file(ret_icon_name, scaled_icon_size);
} else {
try {
pixbuf = DefaultGtkIconThemeWrapper::load_icon(
"image-missing", scaled_icon_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
} catch (...) {
pixbuf = {};
}
}
}
if (pixbuf) {
if (pixbuf->get_width() != scaled_icon_size && pixbuf->get_height() > 0) {
int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();
pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
}
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image.get_scale_factor(),
image.get_window());
image.set(surface);
return true;
}
return false;
}
void IconLoader::add_custom_icon_theme(const std::string& theme_name) {
auto icon_theme = Gtk::IconTheme::create();
icon_theme->set_custom_theme(theme_name);
custom_icon_themes_.push_back(icon_theme);
spdlog::debug("Use custom icon theme: {}", theme_name);
}
bool IconLoader::image_load_icon(Gtk::Image& image, Glib::RefPtr<Gio::DesktopAppInfo> app_info,
int size) const {
for (auto& icon_theme : custom_icon_themes_) {
if (image_load_icon(image, icon_theme, app_info, size)) {
return true;
}
}
return image_load_icon(image, default_icon_theme_, app_info, size);
}
Glib::RefPtr<Gio::DesktopAppInfo> IconLoader::get_app_info_from_app_id_list(
const std::string& app_id_list) {
std::string app_id;
std::istringstream stream(app_id_list);
Glib::RefPtr<Gio::DesktopAppInfo> app_info_;
/* Wayfire sends a list of app-id's in space separated format, other compositors
* send a single app-id, but in any case this works fine */
while (stream >> app_id) {
app_info_ = get_desktop_app_info(app_id);
if (app_info_) {
return app_info_;
}
auto lower_app_id = app_id;
std::ranges::transform(lower_app_id, lower_app_id.begin(),
[](char c) { return std::tolower(c); });
app_info_ = get_desktop_app_info(lower_app_id);
if (app_info_) {
return app_info_;
}
size_t start = 0, end = app_id.size();
start = app_id.rfind(".", end);
std::string app_name = app_id.substr(start + 1, app_id.size());
app_info_ = get_desktop_app_info(app_name);
if (app_info_) {
return app_info_;
}
start = app_id.find("-");
app_name = app_id.substr(0, start);
app_info_ = get_desktop_app_info(app_name);
}
return app_info_;
}
+29 -18
View File
@@ -4,11 +4,11 @@
namespace waybar::util::PipewireBackend {
static void getNodeInfo(void *data_, const struct pw_node_info *info) {
auto *pNodeInfo = static_cast<PrivacyNodeInfo *>(data_);
static void getNodeInfo(void* data_, const struct pw_node_info* info) {
auto* pNodeInfo = static_cast<PrivacyNodeInfo*>(data_);
pNodeInfo->handleNodeEventInfo(info);
static_cast<PipewireBackend *>(pNodeInfo->data)->privacy_nodes_changed_signal_event.emit();
static_cast<PipewireBackend*>(pNodeInfo->data)->privacy_nodes_changed_signal_event.emit();
}
static const struct pw_node_events NODE_EVENTS = {
@@ -16,8 +16,8 @@ static const struct pw_node_events NODE_EVENTS = {
.info = getNodeInfo,
};
static void proxyDestroy(void *data) {
static_cast<PrivacyNodeInfo *>(data)->handleProxyEventDestroy();
static void proxyDestroy(void* data) {
static_cast<PrivacyNodeInfo*>(data)->handleProxyEventDestroy();
}
static const struct pw_proxy_events PROXY_EVENTS = {
@@ -25,14 +25,14 @@ static const struct pw_proxy_events PROXY_EVENTS = {
.destroy = proxyDestroy,
};
static void registryEventGlobal(void *_data, uint32_t id, uint32_t permissions, const char *type,
uint32_t version, const struct spa_dict *props) {
static_cast<PipewireBackend *>(_data)->handleRegistryEventGlobal(id, permissions, type, version,
props);
static void registryEventGlobal(void* _data, uint32_t id, uint32_t permissions, const char* type,
uint32_t version, const struct spa_dict* props) {
static_cast<PipewireBackend*>(_data)->handleRegistryEventGlobal(id, permissions, type, version,
props);
}
static void registryEventGlobalRemove(void *_data, uint32_t id) {
static_cast<PipewireBackend *>(_data)->handleRegistryEventGlobalRemove(id);
static void registryEventGlobalRemove(void* _data, uint32_t id) {
static_cast<PipewireBackend*>(_data)->handleRegistryEventGlobalRemove(id);
}
static const struct pw_registry_events REGISTRY_EVENTS = {
@@ -54,11 +54,17 @@ PipewireBackend::PipewireBackend(PrivateConstructorTag tag)
context_ = pw_context_new(pw_thread_loop_get_loop(mainloop_), nullptr, 0);
if (context_ == nullptr) {
pw_thread_loop_unlock(mainloop_);
pw_thread_loop_destroy(mainloop_);
mainloop_ = nullptr;
throw std::runtime_error("pa_context_new() failed.");
}
core_ = pw_context_connect(context_, nullptr, 0);
if (core_ == nullptr) {
pw_thread_loop_unlock(mainloop_);
pw_context_destroy(context_);
context_ = nullptr;
pw_thread_loop_destroy(mainloop_);
mainloop_ = nullptr;
throw std::runtime_error("pw_context_connect() failed");
}
registry_ = pw_core_get_registry(core_, PW_VERSION_REGISTRY, 0);
@@ -78,7 +84,7 @@ PipewireBackend::~PipewireBackend() {
}
if (registry_ != nullptr) {
pw_proxy_destroy((struct pw_proxy *)registry_);
pw_proxy_destroy((struct pw_proxy*)registry_);
}
spa_zero(registryListener_);
@@ -103,11 +109,11 @@ std::shared_ptr<PipewireBackend> PipewireBackend::getInstance() {
return std::make_shared<PipewireBackend>(tag);
}
void PipewireBackend::handleRegistryEventGlobal(uint32_t id, uint32_t permissions, const char *type,
uint32_t version, const struct spa_dict *props) {
void PipewireBackend::handleRegistryEventGlobal(uint32_t id, uint32_t permissions, const char* type,
uint32_t version, const struct spa_dict* props) {
if (props == nullptr || strcmp(type, PW_TYPE_INTERFACE_Node) != 0) return;
const char *lookupStr = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
const char* lookupStr = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
if (lookupStr == nullptr) return;
std::string mediaClass = lookupStr;
enum PrivacyNodeType mediaType = PRIVACY_NODE_TYPE_NONE;
@@ -121,11 +127,12 @@ void PipewireBackend::handleRegistryEventGlobal(uint32_t id, uint32_t permission
return;
}
auto *proxy = (pw_proxy *)pw_registry_bind(registry_, id, type, version, sizeof(PrivacyNodeInfo));
auto* proxy = (pw_proxy*)pw_registry_bind(registry_, id, type, version, sizeof(PrivacyNodeInfo));
if (proxy == nullptr) return;
auto *pNodeInfo = (PrivacyNodeInfo *)pw_proxy_get_user_data(proxy);
auto* pNodeInfo = (PrivacyNodeInfo*)pw_proxy_get_user_data(proxy);
new (pNodeInfo) PrivacyNodeInfo{};
pNodeInfo->id = id;
pNodeInfo->data = this;
pNodeInfo->type = mediaType;
@@ -135,13 +142,17 @@ void PipewireBackend::handleRegistryEventGlobal(uint32_t id, uint32_t permission
pw_proxy_add_object_listener(proxy, &pNodeInfo->object_listener, &NODE_EVENTS, pNodeInfo);
privacy_nodes.insert_or_assign(id, pNodeInfo);
{
std::lock_guard<std::mutex> lock(mutex_);
privacy_nodes.insert_or_assign(id, pNodeInfo);
}
}
void PipewireBackend::handleRegistryEventGlobalRemove(uint32_t id) {
mutex_.lock();
auto iter = privacy_nodes.find(id);
if (iter != privacy_nodes.end()) {
privacy_nodes[id]->~PrivacyNodeInfo();
privacy_nodes.erase(id);
}
mutex_.unlock();
+9 -7
View File
@@ -3,9 +3,9 @@
namespace waybar::util::PipewireBackend {
std::string PrivacyNodeInfo::getName() {
const std::vector<std::string *> names{&application_name, &node_name};
const std::vector<std::string*> names{&application_name, &node_name};
std::string name = "Unknown Application";
for (const auto &item : names) {
for (const auto& item : names) {
if (item != nullptr && !item->empty()) {
name = *item;
name[0] = toupper(name[0]);
@@ -16,10 +16,10 @@ std::string PrivacyNodeInfo::getName() {
}
std::string PrivacyNodeInfo::getIconName() {
const std::vector<std::string *> names{&application_icon_name, &pipewire_access_portal_app_id,
&application_name, &node_name};
const std::vector<std::string*> names{&application_icon_name, &pipewire_access_portal_app_id,
&application_name, &node_name};
std::string name = "application-x-executable-symbolic";
for (const auto &item : names) {
for (const auto& item : names) {
if (item != nullptr && !item->empty() && DefaultGtkIconThemeWrapper::has_icon(*item)) {
return *item;
}
@@ -32,10 +32,10 @@ void PrivacyNodeInfo::handleProxyEventDestroy() {
spa_hook_remove(&object_listener);
}
void PrivacyNodeInfo::handleNodeEventInfo(const struct pw_node_info *info) {
void PrivacyNodeInfo::handleNodeEventInfo(const struct pw_node_info* info) {
state = info->state;
const struct spa_dict_item *item;
const struct spa_dict_item* item;
spa_dict_for_each(item, info->props) {
if (strcmp(item->key, PW_KEY_CLIENT_ID) == 0) {
client_id = strtoul(item->value, nullptr, 10);
@@ -49,6 +49,8 @@ void PrivacyNodeInfo::handleNodeEventInfo(const struct pw_node_info *info) {
pipewire_access_portal_app_id = item->value;
} else if (strcmp(item->key, PW_KEY_APP_ICON_NAME) == 0) {
application_icon_name = item->value;
} else if (strcmp(item->key, "stream.monitor") == 0) {
is_monitor = strcmp(item->value, "true") == 0;
}
}
}
+21 -17
View File
@@ -17,8 +17,6 @@ static constexpr const char* PORTAL_NAMESPACE = "org.freedesktop.appearance";
static constexpr const char* PORTAL_KEY = "color-scheme";
} // namespace waybar
using namespace Gio;
auto fmt::formatter<waybar::Appearance>::format(waybar::Appearance c, format_context& ctx) const {
string_view name;
switch (c) {
@@ -36,8 +34,8 @@ auto fmt::formatter<waybar::Appearance>::format(waybar::Appearance c, format_con
}
waybar::Portal::Portal()
: DBus::Proxy(DBus::Connection::get_sync(DBus::BusType::BUS_TYPE_SESSION), PORTAL_BUS_NAME,
PORTAL_OBJ_PATH, PORTAL_INTERFACE),
: Gio::DBus::Proxy(Gio::DBus::Connection::get_sync(Gio::DBus::BusType::BUS_TYPE_SESSION),
PORTAL_BUS_NAME, PORTAL_OBJ_PATH, PORTAL_INTERFACE),
currentMode(Appearance::UNKNOWN) {
refreshAppearance();
};
@@ -60,21 +58,27 @@ void waybar::Portal::refreshAppearance() {
// xdg-desktop-portal 1.17 will fix this issue with a new `ReadOne` method,
// but this version is not yet released.
// TODO(xdg-desktop-portal v1.17): switch to ReadOne
auto container = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(response);
Glib::VariantBase modev;
container.get_child(modev, 0);
auto mode =
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::Variant<Glib::Variant<uint32_t>>>>(modev)
.get()
.get()
.get();
auto newMode = Appearance(mode);
if (newMode == currentMode) {
try {
auto container = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(response);
Glib::VariantBase modev;
container.get_child(modev, 0);
auto mode =
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::Variant<Glib::Variant<uint32_t>>>>(
modev)
.get()
.get()
.get();
auto newMode = Appearance(mode);
if (newMode == currentMode) {
return;
}
spdlog::info("Discovered appearance '{}'", newMode);
currentMode = newMode;
m_signal_appearance_changed.emit(currentMode);
} catch (const std::bad_cast& e) {
spdlog::error("Unexpected appearance variant format: {}", e.what());
return;
}
spdlog::info("Discovered appearance '{}'", newMode);
currentMode = newMode;
m_signal_appearance_changed.emit(currentMode);
}
waybar::Appearance waybar::Portal::getAppearance() { return currentMode; };
+7 -7
View File
@@ -18,21 +18,21 @@ class PrepareForSleep {
}
}
static void prepareForSleep_cb(GDBusConnection *system_bus, const gchar *sender_name,
const gchar *object_path, const gchar *interface_name,
const gchar *signal_name, GVariant *parameters,
static void prepareForSleep_cb(GDBusConnection* system_bus, const gchar* sender_name,
const gchar* object_path, const gchar* interface_name,
const gchar* signal_name, GVariant* parameters,
gpointer user_data) {
if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(b)")) != 0) {
gboolean sleeping;
g_variant_get(parameters, "(b)", &sleeping);
auto *self = static_cast<PrepareForSleep *>(user_data);
auto* self = static_cast<PrepareForSleep*>(user_data);
self->signal.emit(sleeping);
}
}
public:
static PrepareForSleep &GetInstance() {
static PrepareForSleep& GetInstance() {
static PrepareForSleep instance;
return instance;
}
@@ -40,10 +40,10 @@ class PrepareForSleep {
private:
guint login1_id;
GDBusConnection *login1_connection;
GDBusConnection* login1_connection;
};
} // namespace
waybar::SafeSignal<bool> &waybar::util::prepare_for_sleep() {
waybar::SafeSignal<bool>& waybar::util::prepare_for_sleep() {
return PrepareForSleep::GetInstance().signal;
}
+1
View File
@@ -3,6 +3,7 @@
#include <json/value.h>
#include <spdlog/spdlog.h>
#include <algorithm>
#include <utility>
namespace waybar::util {
+1 -1
View File
@@ -18,7 +18,7 @@ std::string rewriteString(const std::string& value, const Json::Value& rules) {
// malformated regexes will cause an exception.
// in this case, log error and try the next rule.
const std::regex rule{it.key().asString(), std::regex_constants::icase};
if (std::regex_match(value, rule)) {
if (std::regex_match(res, rule)) {
res = std::regex_replace(res, rule, it->asString());
}
} catch (const std::regex_error& e) {
+60
View File
@@ -0,0 +1,60 @@
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <regex>
#include <sstream>
namespace fs = std::filesystem;
struct TransformResult {
std::string css;
bool was_transformed;
};
TransformResult transform_8bit_to_hex(const std::string& file_path) {
std::ifstream f(file_path, std::ios::in | std::ios::binary);
const auto size = fs::file_size(file_path);
std::string result(size, '\0');
if (!f.is_open() || !f.good()) {
throw std::runtime_error("Cannot open file: " + file_path);
}
if (size == 0) {
return {.css = result, .was_transformed = false};
}
f.read(result.data(), size);
static std::regex pattern(
R"(\#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2}))");
std::string final_output;
auto it = std::sregex_iterator(result.begin(), result.end(), pattern);
auto eof = std::sregex_iterator();
if (it == eof) {
return {.css = result, .was_transformed = false};
}
std::smatch match;
while (it != eof) {
match = *it;
final_output += match.prefix().str();
int r = stoi(match[1].str(), nullptr, 16);
int g = stoi(match[2].str(), nullptr, 16);
int b = stoi(match[3].str(), nullptr, 16);
double a = (stoi(match[4].str(), nullptr, 16) / 255.0);
std::stringstream ss;
ss << "rgba(" << r << "," << g << "," << b << "," << std::fixed << std::setprecision(2) << a
<< ")";
final_output += ss.str();
++it;
}
final_output += match.suffix().str();
return {.css = final_output, .was_transformed = true};
}
+1 -1
View File
@@ -1,6 +1,6 @@
#include "util/ustring_clen.hpp"
int ustring_clen(const Glib::ustring &str) {
int ustring_clen(const Glib::ustring& str) {
int total = 0;
for (unsigned int i : str) {
total += g_unichar_iswide(i) + 1;
+62
View File
@@ -0,0 +1,62 @@
#include <glib.h>
#include <string>
#include <util/utf8_string.hpp>
namespace waybar::util {
namespace {
// Wide characters count as two, zero-width characters count as zero
// Modifies str in-place (unless width = std::string::npos)
// Returns the total width of the string pre-truncating
size_t measure_and_truncate(std::string& str, size_t width = std::string::npos) {
if (str.length() == 0) return 0;
const gchar* trunc_end = nullptr;
size_t total_width = 0;
for (gchar *data = str.data(), *end = data + str.size(); data != nullptr;) {
gunichar c = g_utf8_get_char_validated(data, end - data);
if (c == -1U || c == -2U) {
// invalid unicode, treat string as ascii
if (width != std::string::npos && str.length() > width) str.resize(width);
return str.length();
} else if (g_unichar_iswide(c)) {
total_width += 2;
} else if (!g_unichar_iszerowidth(c) && c != 0xAD) { // neither zero-width nor soft hyphen
total_width += 1;
}
data = g_utf8_find_next_char(data, end);
if (width != std::string::npos && total_width <= width && !g_unichar_isspace(c))
trunc_end = data;
}
if (trunc_end) str.resize(trunc_end - str.data());
return total_width;
}
} // namespace
size_t utf8_width(const std::string& str) {
return measure_and_truncate(const_cast<std::string&>(str));
}
void utf8_truncate(std::string& s, const std::string& ellipsis, size_t max_len) {
if (max_len == 0) {
s.resize(0);
return;
}
size_t len = measure_and_truncate(s, max_len);
if (len > max_len) {
size_t ellipsis_len = utf8_width(ellipsis);
if (max_len >= ellipsis_len) {
if (ellipsis_len) measure_and_truncate(s, max_len - ellipsis_len);
s += ellipsis;
} else {
s.resize(0);
}
}
}
} // namespace waybar::util