Merge branch 'Alexays:master' into master

This commit is contained in:
ico277
2025-07-17 15:04:15 +02:00
committed by GitHub
163 changed files with 3911 additions and 936 deletions
+17 -6
View File
@@ -273,14 +273,18 @@ waybar::modules::Battery::getInfos() {
// Scale these by the voltage to get μW/μWh.
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;
@@ -324,11 +328,15 @@ waybar::modules::Battery::getInfos() {
}
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;
@@ -679,8 +687,11 @@ auto waybar::modules::Battery::update() -> void {
std::string tooltip_text_default;
std::string tooltip_format = "{timeTo}";
if (time_remaining != 0) {
std::string time_to = std::string("Time to ") + ((time_remaining > 0) ? "empty" : "full");
tooltip_text_default = time_to + ": " + time_remaining_formatted;
if (time_remaining > 0) {
tooltip_text_default = std::string("Empty in ") + time_remaining_formatted;
} else {
tooltip_text_default = std::string("Full in ") + time_remaining_formatted;
}
} else {
tooltip_text_default = status_pretty;
}
@@ -693,7 +704,7 @@ auto waybar::modules::Battery::update() -> void {
} else if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
label_.set_tooltip_text(
label_.set_tooltip_markup(
fmt::format(fmt::runtime(tooltip_format), fmt::arg("timeTo", tooltip_text_default),
fmt::arg("power", power), fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted), fmt::arg("cycles", cycles),
+9 -8
View File
@@ -49,8 +49,8 @@ auto getBoolProperty(GDBusProxy* proxy, const char* property_name) -> bool {
return false;
}
auto getOptionalStringProperty(GDBusProxy* proxy,
const char* property_name) -> std::optional<std::string> {
auto getOptionalStringProperty(GDBusProxy* proxy, const char* property_name)
-> std::optional<std::string> {
auto gvar = g_dbus_proxy_get_cached_property(proxy, property_name);
if (gvar) {
std::string property_value = g_variant_get_string(gvar, NULL);
@@ -345,8 +345,8 @@ auto waybar::modules::Bluetooth::onInterfaceAddedOrRemoved(GDBusObjectManager* m
auto waybar::modules::Bluetooth::onInterfaceProxyPropertiesChanged(
GDBusObjectManagerClient* manager, GDBusObjectProxy* object_proxy, GDBusProxy* interface_proxy,
GVariant* changed_properties, const gchar* const* invalidated_properties,
gpointer user_data) -> void {
GVariant* changed_properties, const gchar* const* invalidated_properties, gpointer user_data)
-> void {
std::string interface_name = g_dbus_proxy_get_interface_name(interface_proxy);
std::string object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object_proxy));
@@ -395,8 +395,8 @@ auto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object)
return std::nullopt;
}
auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object,
DeviceInfo& device_info) -> bool {
auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, DeviceInfo& device_info)
-> bool {
GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1"));
if (proxy_device != NULL) {
@@ -462,8 +462,9 @@ auto waybar::modules::Bluetooth::findCurController() -> std::optional<Controller
return controller_info;
}
auto waybar::modules::Bluetooth::findConnectedDevices(
const std::string& cur_controller_path, std::vector<DeviceInfo>& connected_devices) -> void {
auto waybar::modules::Bluetooth::findConnectedDevices(const std::string& cur_controller_path,
std::vector<DeviceInfo>& connected_devices)
-> void {
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);
+2 -2
View File
@@ -139,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_;
@@ -147,7 +147,7 @@ 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);
+8 -4
View File
@@ -28,7 +28,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
}
// Fetch functions
if (*wbcffi_version == 1) {
if (*wbcffi_version == 1 || *wbcffi_version == 2) {
// Mandatory functions
hooks_.init = reinterpret_cast<InitFn*>(dlsym(handle, "wbcffi_init"));
if (!hooks_.init) {
@@ -58,10 +58,14 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co
const auto& keys = config.getMemberNames();
for (size_t i = 0; i < keys.size(); i++) {
const auto& value = config[keys[i]];
if (value.isConvertibleTo(Json::ValueType::stringValue)) {
config_entries_stringstor.push_back(config[keys[i]].asString());
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(config[keys[i]].toStyledString());
config_entries_stringstor.push_back(value.toStyledString());
}
}
+26 -10
View File
@@ -1,5 +1,6 @@
#include "modules/clock.hpp"
#include <glib.h>
#include <gtkmm/tooltip.h>
#include <spdlog/spdlog.h>
@@ -16,6 +17,7 @@
#include <clocale>
#endif
using namespace date;
namespace fmt_lib = waybar::util::date::format;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
@@ -25,6 +27,7 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
m_tooltip_{new Gtk::Label()},
cldInTooltip_{m_tlpFmt_.find("{" + kCldPlaceholder + "}") != std::string::npos},
cldYearShift_{January / 1 / 1900},
cldMonShift_{year(1900) / January},
tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos},
tzCurrIdx_{0},
ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} {
@@ -199,8 +202,8 @@ 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(const year_month& ym, const weekday& firstdow,
const unsigned line) -> const year_month_weekday {
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];
@@ -348,9 +351,9 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
m_locale_, fmtMap_[4],
fmt_lib::make_format_args(
(line == 2)
? static_cast<const date::zoned_seconds&&>(
? static_cast<const zoned_seconds&&>(
zoned_seconds{tz, local_days{ymTmp / 1}})
: static_cast<const date::zoned_seconds&&>(zoned_seconds{
: static_cast<const zoned_seconds&&>(zoned_seconds{
tz, local_days{cldGetWeekForLine(ymTmp, firstdow, line)}})))
<< ' ';
} else
@@ -358,10 +361,23 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
}
}
os << Glib::ustring::format((cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right,
std::setfill(L' '),
std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ : 0)),
getCalendarLine(today, ymTmp, line, firstdow, &m_locale_));
// Count wide characters to avoid extra padding
size_t wideCharCount = 0;
std::string calendarLine = getCalendarLine(today, ymTmp, line, firstdow, &m_locale_);
if (line < 2) {
for (gchar *data = calendarLine.data(), *end = data + calendarLine.size();
data != nullptr;) {
gunichar c = g_utf8_get_char_validated(data, end - data);
if (g_unichar_iswide(c)) {
wideCharCount++;
}
data = g_utf8_find_next_char(data, end);
}
}
os << Glib::ustring::format(
(cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right, std::setfill(L' '),
std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ - wideCharCount : 0)),
calendarLine);
// Week numbers on the right
if (cldWPos_ == WS::RIGHT && line > 0) {
@@ -371,9 +387,9 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea
<< fmt_lib::vformat(
m_locale_, fmtMap_[4],
fmt_lib::make_format_args(
(line == 2) ? static_cast<const date::zoned_seconds&&>(
(line == 2) ? static_cast<const zoned_seconds&&>(
zoned_seconds{tz, local_days{ymTmp / 1}})
: static_cast<const date::zoned_seconds&&>(
: static_cast<const zoned_seconds&&>(
zoned_seconds{tz, local_days{cldGetWeekForLine(
ymTmp, firstdow, line)}})));
else
+8 -1
View File
@@ -35,6 +35,13 @@ waybar::modules::Custom::~Custom() {
void waybar::modules::Custom::delayWorker() {
thread_ = [this] {
for (int i : this->pid_children_) {
int status;
waitpid(i, &status, 0);
}
this->pid_children_.clear();
bool can_update = true;
if (config_["exec-if"].isString()) {
output_ = util::command::execNoRead(config_["exec-if"].asString());
@@ -62,7 +69,7 @@ void waybar::modules::Custom::continuousWorker() {
}
thread_ = [this, cmd] {
char* buff = nullptr;
waybar::util::ScopeGuard buff_deleter([buff]() {
waybar::util::ScopeGuard buff_deleter([&buff]() {
if (buff) {
free(buff);
}
+6 -3
View File
@@ -9,6 +9,7 @@
#include "client.hpp"
#include "dwl-ipc-unstable-v2-client-protocol.h"
#include "glibmm/markup.h"
#include "util/rewrite_string.hpp"
namespace waybar::modules::dwl {
@@ -97,11 +98,13 @@ Window::~Window() {
}
}
void Window::handle_title(const char *title) { title_ = title; }
void Window::handle_title(const char *title) { title_ = Glib::Markup::escape_text(title); }
void Window::handle_appid(const char *appid) { appid_ = appid; }
void Window::handle_appid(const char *appid) { appid_ = Glib::Markup::escape_text(appid); }
void Window::handle_layout_symbol(const char *layout_symbol) { layout_symbol_ = layout_symbol; }
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; }
+216
View File
@@ -0,0 +1,216 @@
#include "modules/gps.hpp"
#include <gps.h>
#include <spdlog/spdlog.h>
#include <cmath>
#include <cstdio>
// In the 80000 version of fmt library authors decided to optimize imports
// and moved declarations required for fmt::dynamic_format_arg_store in new
// header fmt/args.h
#if (FMT_VERSION >= 80000)
#include <fmt/args.h>
#else
#include <fmt/core.h>
#endif
namespace {
using namespace waybar::util;
constexpr const char* DEFAULT_FORMAT = "{mode}";
} // namespace
waybar::modules::Gps::Gps(const std::string& id, const Json::Value& config)
: ALabel(config, "gps", id, "{}", 5)
#ifdef WANT_RFKILL
,
rfkill_{RFKILL_TYPE_GPS}
#endif
{
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
if (0 != gps_open("localhost", "2947", &gps_data_)) {
throw std::runtime_error("Can't open gpsd socket");
}
if (config_["hide-disconnected"].isBool()) {
hideDisconnected = config_["hide-disconnected"].asBool();
}
if (config_["hide-no-fix"].isBool()) {
hideNoFix = config_["hide-no-fix"].asBool();
}
gps_thread_ = [this] {
dp.emit();
gps_stream(&gps_data_, WATCH_ENABLE, NULL);
int last_gps_mode = 0;
while (gps_waiting(&gps_data_, 5000000)) {
if (gps_read(&gps_data_, NULL, 0) == -1) {
throw std::runtime_error("Can't read data from gpsd.");
}
if (MODE_SET != (MODE_SET & gps_data_.set)) {
// did not even get mode, nothing to see here
continue;
}
if (gps_data_.fix.mode != last_gps_mode) {
// significant update
dp.emit();
}
last_gps_mode = gps_data_.fix.mode;
}
};
#ifdef WANT_RFKILL
rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Gps::update)));
#endif
}
const std::string waybar::modules::Gps::getFixModeName() const {
switch (gps_data_.fix.mode) {
case MODE_NO_FIX:
return "fix-none";
case MODE_2D:
return "fix-2d";
case MODE_3D:
return "fix-3d";
default:
#ifdef WANT_RFKILL
if (rfkill_.getState()) return "disabled";
#endif
return "disconnected";
}
}
const std::string waybar::modules::Gps::getFixModeString() const {
switch (gps_data_.fix.mode) {
case MODE_NO_FIX:
return "No fix";
case MODE_2D:
return "2D Fix";
case MODE_3D:
return "3D Fix";
default:
return "Disconnected";
}
}
const std::string waybar::modules::Gps::getFixStatusString() const {
switch (gps_data_.fix.status) {
case STATUS_GPS:
return "GPS";
case STATUS_DGPS:
return "DGPS";
case STATUS_RTK_FIX:
return "RTK Fixed";
case STATUS_RTK_FLT:
return "RTK Float";
case STATUS_DR:
return "Dead Reckoning";
case STATUS_GNSSDR:
return "GNSS + Dead Reckoning";
case STATUS_TIME:
return "Time Only";
case STATUS_PPS_FIX:
return "PPS Fix";
default:
#ifdef WANT_RFKILL
if (rfkill_.getState()) return "Disabled";
#endif
return "Unknown";
}
}
auto waybar::modules::Gps::update() -> void {
sleep(0); // Wait for gps status change
if ((gps_data_.fix.mode == MODE_NOT_SEEN && hideDisconnected) ||
(gps_data_.fix.mode == MODE_NO_FIX && hideNoFix)) {
event_box_.set_visible(false);
return;
}
// Show the module
if (!event_box_.get_visible()) event_box_.set_visible(true);
std::string tooltip_format;
if (!alt_) {
auto state = getFixModeName();
if (!state_.empty() && label_.get_style_context()->has_class(state_)) {
label_.get_style_context()->remove_class(state_);
}
if (config_["format-" + state].isString()) {
default_format_ = config_["format-" + state].asString();
} else if (config_["format"].isString()) {
default_format_ = config_["format"].asString();
} else {
default_format_ = DEFAULT_FORMAT;
}
if (config_["tooltip-format-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + state].asString();
}
if (!label_.get_style_context()->has_class(state)) {
label_.get_style_context()->add_class(state);
}
format_ = default_format_;
state_ = state;
}
auto format = format_;
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(fmt::arg("mode", getFixModeString()));
store.push_back(fmt::arg("status", getFixStatusString()));
store.push_back(fmt::arg("latitude", gps_data_.fix.latitude));
store.push_back(fmt::arg("latitude_error", gps_data_.fix.epy));
store.push_back(fmt::arg("longitude", gps_data_.fix.longitude));
store.push_back(fmt::arg("longitude_error", gps_data_.fix.epx));
store.push_back(fmt::arg("altitude_hae", gps_data_.fix.altHAE));
store.push_back(fmt::arg("altitude_msl", gps_data_.fix.altMSL));
store.push_back(fmt::arg("altitude_error", gps_data_.fix.epv));
store.push_back(fmt::arg("speed", gps_data_.fix.speed));
store.push_back(fmt::arg("speed_error", gps_data_.fix.eps));
store.push_back(fmt::arg("climb", gps_data_.fix.climb));
store.push_back(fmt::arg("climb_error", gps_data_.fix.epc));
store.push_back(fmt::arg("satellites_used", gps_data_.satellites_used));
store.push_back(fmt::arg("satellites_visible", gps_data_.satellites_visible));
auto text = fmt::vformat(format, store);
if (tooltipEnabled()) {
if (tooltip_format.empty() && config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
auto tooltip_text = fmt::vformat(tooltip_format, store);
if (label_.get_tooltip_text() != tooltip_text) {
label_.set_tooltip_markup(tooltip_text);
}
} else if (label_.get_tooltip_text() != text) {
label_.set_tooltip_markup(text);
}
}
label_.set_markup(text);
// Call parent update
ALabel::update();
}
waybar::modules::Gps::~Gps() {
gps_stream(&gps_data_, WATCH_DISABLE, NULL);
gps_close(&gps_data_);
}
+72 -48
View File
@@ -11,7 +11,6 @@
#include <filesystem>
#include <string>
#include <thread>
namespace waybar::modules::hyprland {
@@ -44,71 +43,96 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
return socketFolder_;
}
void IPC::startIPC() {
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 == nullptr) {
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;
}
auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock";
strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
void IPC::socketListener() {
// check for hyprland
const char* his = getenv("HYPRLAND_INSTANCE_SIGNATURE");
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
if (his == nullptr) {
spdlog::warn("Hyprland is not running, Hyprland IPC will not be available.");
return;
}
int l = sizeof(struct sockaddr_un);
if (!modulesReady) return;
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {
spdlog::error("Hyprland IPC: Unable to connect?");
return;
}
spdlog::info("Hyprland IPC starting");
auto* file = fdopen(socketfd, "r");
struct sockaddr_un addr;
socketfd_ = socket(AF_UNIX, SOCK_STREAM, 0);
while (true) {
std::array<char, 1024> buffer; // Hyprland socket2 events are max 1024 bytes
if (socketfd_ == -1) {
spdlog::error("Hyprland IPC: socketfd failed");
return;
}
auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file);
addr.sun_family = AF_UNIX;
if (receivedCharPtr == nullptr) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
}
auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock";
strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
std::string messageReceived(buffer.data());
messageReceived = messageReceived.substr(0, messageReceived.find_first_of('\n'));
spdlog::debug("hyprland IPC received {}", messageReceived);
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
try {
parseIPC(messageReceived);
} catch (std::exception& e) {
spdlog::warn("Failed to parse IPC message: {}, reason: {}", messageReceived, e.what());
} catch (...) {
throw;
}
int l = sizeof(struct sockaddr_un);
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
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) {
+16 -9
View File
@@ -10,13 +10,9 @@
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) {
gIPC = std::make_unique<IPC>();
}
// get the active layout when open
initLanguage();
@@ -24,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_);
}
@@ -70,7 +66,18 @@ auto Language::update() -> void {
void Language::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lg(mutex_);
std::string kbName(begin(ev) + ev.find_last_of('>') + 1, begin(ev) + ev.find_first_of(','));
auto layoutName = ev.substr(ev.find_last_of(',') + 1);
// Last comma before variants parenthesis, eg:
// activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,
// with dead keys)
std::string beforeParenthesis;
auto parenthesisPos = ev.find_last_of('(');
if (parenthesisPos == std::string::npos) {
beforeParenthesis = ev;
} else {
beforeParenthesis = std::string(begin(ev), begin(ev) + parenthesisPos);
}
auto layoutName = ev.substr(beforeParenthesis.find_last_of(',') + 1);
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
return; // ignore
@@ -85,7 +92,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();
+5 -10
View File
@@ -7,32 +7,28 @@
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;
parseConfig(config);
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
label_.hide();
ALabel::update();
// Displays widget immediately if always_on_ assuming default submap
// Needs an actual way to retrive current submap on startup
// 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_);
}
@@ -72,8 +68,7 @@ void Submap::onEvent(const std::string& ev) {
return;
}
auto submapName = ev.substr(ev.find_last_of('>') + 1);
submapName = waybar::util::sanitize_string(submapName);
auto submapName = ev.substr(ev.find_first_of('>') + 2);
if (!submap_.empty()) {
label_.get_style_context()->remove_class(submap_);
+83 -61
View File
@@ -6,6 +6,7 @@
#include <spdlog/spdlog.h>
#include <algorithm>
#include <shared_mutex>
#include <vector>
#include "modules/hyprland/backend.hpp"
@@ -14,54 +15,72 @@
namespace waybar::modules::hyprland {
std::shared_mutex windowIpcSmtx;
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) {
: 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();
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
// 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();
dp.emit();
// register for hyprland ipc
gIPC->registerForIPC("activewindow", this);
gIPC->registerForIPC("closewindow", this);
gIPC->registerForIPC("movewindow", this);
gIPC->registerForIPC("changefloatingmode", this);
gIPC->registerForIPC("fullscreen", this);
}
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 windowName = waybar::util::sanitize_string(workspace_.last_window_title);
std::string windowAddress = workspace_.last_window;
windowData_.title = windowName;
std::string label_text;
if (!format_.empty()) {
label_.show();
label_.set_markup(waybar::util::rewriteString(
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"]));
config_["rewrite"]);
label_.set_markup(label_text);
} else {
label_.hide();
}
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 (focused_) {
image_.show();
} else {
@@ -91,7 +110,7 @@ auto Window::update() -> void {
}
auto Window::getActiveWorkspace() -> Workspace {
const auto workspace = gIPC->getSocket1JsonReply("activeworkspace");
const auto workspace = IPC::inst().getSocket1JsonReply("activeworkspace");
if (workspace.isObject()) {
return Workspace::parse(workspace);
@@ -101,24 +120,33 @@ auto Window::getActiveWorkspace() -> Workspace {
}
auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
const auto monitors = gIPC->getSocket1JsonReply("monitors");
const auto monitors = IPC::inst().getSocket1JsonReply("monitors");
if (monitors.isArray()) {
auto monitor = std::find_if(monitors.begin(), monitors.end(), [&](Json::Value monitor) {
return monitor["name"] == monitorName;
});
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{-1, 0, "", ""};
return Workspace{
.id = -1,
.windows = 0,
.last_window = "",
.last_window_title = "",
};
}
const int id = (*monitor)["activeWorkspace"]["id"].asInt();
const auto workspaces = gIPC->getSocket1JsonReply("workspaces");
const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces");
if (workspaces.isArray()) {
auto workspace = std::find_if(workspaces.begin(), workspaces.end(),
[&](Json::Value workspace) { return workspace["id"] == id; });
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{-1, 0, "", ""};
return Workspace{
.id = -1,
.windows = 0,
.last_window = "",
.last_window_title = "",
};
}
return Workspace::parse(*workspace);
};
@@ -129,22 +157,26 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
auto Window::Workspace::parse(const Json::Value& value) -> Window::Workspace {
return Workspace{
value["id"].asInt(),
value["windows"].asInt(),
value["lastwindow"].asString(),
value["lastwindowtitle"].asString(),
.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{value["floating"].asBool(), value["monitor"].asInt(),
value["class"].asString(), value["initialClass"].asString(),
value["title"].asString(), value["initialTitle"].asString(),
value["fullscreen"].asBool(), !value["grouped"].empty()};
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 (separateOutputs_) {
workspace_ = getActiveWorkspace(this->bar_.output->name);
@@ -154,11 +186,10 @@ void Window::queryActiveWorkspace() {
focused_ = true;
if (workspace_.windows > 0) {
const auto clients = gIPC->getSocket1JsonReply("clients");
const auto clients = m_ipc.getSocket1JsonReply("clients");
if (clients.isArray()) {
auto activeWindow = std::find_if(clients.begin(), clients.end(), [&](Json::Value window) {
return window["address"] == workspace_.last_window;
});
auto activeWindow = std::ranges::find_if(
clients, [&](Json::Value window) { return window["address"] == workspace_.last_window; });
if (activeWindow == std::end(clients)) {
focused_ = false;
@@ -168,22 +199,19 @@ void Window::queryActiveWorkspace() {
windowData_ = WindowData::parse(*activeWindow);
updateAppIconName(windowData_.class_name, windowData_.initial_class_name);
std::vector<Json::Value> workspaceWindows;
std::copy_if(clients.begin(), clients.end(), std::back_inserter(workspaceWindows),
[&](Json::Value window) {
return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool();
});
swallowing_ =
std::any_of(workspaceWindows.begin(), workspaceWindows.end(), [&](Json::Value window) {
return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0";
});
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::copy_if(workspaceWindows.begin(), workspaceWindows.end(),
std::back_inserter(visibleWindows),
[&](Json::Value window) { return !window["hidden"].asBool(); });
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::all_of(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
@@ -191,18 +219,12 @@ void Window::queryActiveWorkspace() {
solo_ = true;
}
// Grouped windows have a tab bar and therefore don't look fullscreen or solo
if (windowData_.grouped) {
fullscreen_ = false;
solo_ = false;
}
if (solo_) {
soloClass_ = windowData_.class_name;
} else {
soloClass_ = "";
}
};
}
} else {
focused_ = false;
windowData_ = WindowData{};
+142
View File
@@ -0,0 +1,142 @@
#include "modules/hyprland/windowcount.hpp"
#include <glibmm/fileutils.h>
#include <glibmm/keyfile.h>
#include <glibmm/miscutils.h>
#include <spdlog/spdlog.h>
#include <algorithm>
#include <vector>
#include "modules/hyprland/backend.hpp"
#include "util/sanitize_str.hpp"
namespace waybar::modules::hyprland {
WindowCount::WindowCount(const std::string& id, const Bar& bar, const Json::Value& config)
: AAppIconLabel(config, "windowcount", id, "{count}", 0, true), bar_(bar) {
modulesReady = true;
separateOutputs_ =
config.isMember("separate-outputs") ? config["separate-outputs"].asBool() : true;
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
queryActiveWorkspace();
update();
dp.emit();
// register for hyprland ipc
gIPC->registerForIPC("fullscreen", this);
gIPC->registerForIPC("workspace", this);
gIPC->registerForIPC("focusedmon", this);
gIPC->registerForIPC("openwindow", this);
gIPC->registerForIPC("closewindow", this);
gIPC->registerForIPC("movewindow", this);
}
WindowCount::~WindowCount() {
gIPC->unregisterForIPC(this);
// wait for possible event handler to finish
std::lock_guard<std::mutex> lg(mutex_);
}
auto WindowCount::update() -> void {
std::lock_guard<std::mutex> lg(mutex_);
std::string format = config_["format"].asString();
std::string formatEmpty = config_["format-empty"].asString();
std::string formatWindowed = config_["format-windowed"].asString();
std::string formatFullscreen = config_["format-fullscreen"].asString();
setClass("empty", workspace_.windows == 0);
setClass("fullscreen", workspace_.hasfullscreen);
if (workspace_.windows == 0 && !formatEmpty.empty()) {
label_.set_markup(fmt::format(fmt::runtime(formatEmpty), workspace_.windows));
} else if (!workspace_.hasfullscreen && !formatWindowed.empty()) {
label_.set_markup(fmt::format(fmt::runtime(formatWindowed), workspace_.windows));
} else if (workspace_.hasfullscreen && !formatFullscreen.empty()) {
label_.set_markup(fmt::format(fmt::runtime(formatFullscreen), workspace_.windows));
} else if (!format.empty()) {
label_.set_markup(fmt::format(fmt::runtime(format), workspace_.windows));
} else {
label_.set_text(fmt::format("{}", workspace_.windows));
}
label_.show();
AAppIconLabel::update();
}
auto WindowCount::getActiveWorkspace() -> Workspace {
const auto workspace = gIPC->getSocket1JsonReply("activeworkspace");
if (workspace.isObject()) {
return Workspace::parse(workspace);
}
return {};
}
auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspace {
const auto monitors = gIPC->getSocket1JsonReply("monitors");
if (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, false};
}
const int id = (*monitor)["activeWorkspace"]["id"].asInt();
const auto workspaces = gIPC->getSocket1JsonReply("workspaces");
if (workspaces.isArray()) {
auto workspace = std::find_if(workspaces.begin(), workspaces.end(),
[&](Json::Value workspace) { return workspace["id"] == id; });
if (workspace == std::end(workspaces)) {
spdlog::warn("No workspace with id {}", id);
return Workspace{-1, 0, false};
}
return Workspace::parse(*workspace);
};
};
return {};
}
auto WindowCount::Workspace::parse(const Json::Value& value) -> WindowCount::Workspace {
return Workspace{
value["id"].asInt(),
value["windows"].asInt(),
value["hasfullscreen"].asBool(),
};
}
void WindowCount::queryActiveWorkspace() {
std::lock_guard<std::mutex> lg(mutex_);
if (separateOutputs_) {
workspace_ = getActiveWorkspace(this->bar_.output->name);
} else {
workspace_ = getActiveWorkspace();
}
}
void WindowCount::onEvent(const std::string& ev) {
queryActiveWorkspace();
dp.emit();
}
void WindowCount::setClass(const std::string& classname, bool enable) {
if (enable) {
if (!bar_.window.get_style_context()->has_class(classname)) {
bar_.window.get_style_context()->add_class(classname);
}
} else {
bar_.window.get_style_context()->remove_class(classname);
}
}
} // namespace waybar::modules::hyprland
@@ -88,7 +88,7 @@ bool WindowCreationPayload::isEmpty(Workspaces &workspace_manager) {
int WindowCreationPayload::incrementTimeSpentUncreated() { return m_timeSpentUncreated++; }
void WindowCreationPayload::moveToWorksace(std::string &new_workspace_name) {
void WindowCreationPayload::moveToWorkspace(std::string &new_workspace_name) {
m_workspaceName = new_workspace_name;
}
+19 -14
View File
@@ -18,7 +18,8 @@ Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_ma
m_windows(workspace_data["windows"].asInt()),
m_isActive(true),
m_isPersistentRule(workspace_data["persistent-rule"].asBool()),
m_isPersistentConfig(workspace_data["persistent-config"].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")) {
@@ -58,20 +59,20 @@ bool Workspace::handleClicked(GdkEventButton *bt) const {
try {
if (id() > 0) { // normal
if (m_workspaceManager.moveToMonitor()) {
gIPC->getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id()));
m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id()));
} else {
gIPC->getSocket1Reply("dispatch workspace " + std::to_string(id()));
m_ipc.getSocket1Reply("dispatch workspace " + std::to_string(id()));
}
} else if (!isSpecial()) { // named (this includes persistent)
if (m_workspaceManager.moveToMonitor()) {
gIPC->getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name());
m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name());
} else {
gIPC->getSocket1Reply("dispatch workspace name:" + name());
m_ipc.getSocket1Reply("dispatch workspace name:" + name());
}
} else if (id() != -99) { // named special
gIPC->getSocket1Reply("dispatch togglespecialworkspace " + name());
m_ipc.getSocket1Reply("dispatch togglespecialworkspace " + name());
} else { // special
gIPC->getSocket1Reply("dispatch togglespecialworkspace");
m_ipc.getSocket1Reply("dispatch togglespecialworkspace");
}
return true;
} catch (const std::exception &e) {
@@ -90,19 +91,19 @@ void Workspace::initializeWindowMap(const Json::Value &clients_data) {
}
}
void Workspace::insertWindow(WindowCreationPayload create_window_paylod) {
if (!create_window_paylod.isEmpty(m_workspaceManager)) {
auto repr = create_window_paylod.repr(m_workspaceManager);
void Workspace::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_paylod.getAddress()] = repr;
m_windowMap[create_window_payload.getAddress()] = repr;
}
}
};
bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_paylod) {
if (create_window_paylod.getWorkspaceName() == name()) {
insertWindow(create_window_paylod);
bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_payload) {
if (create_window_payload.getWorkspaceName() == name()) {
insertWindow(create_window_payload);
return true;
}
return false;
@@ -172,6 +173,10 @@ std::string &Workspace::selectIcon(std::map<std::string, std::string> &icons_map
}
void Workspace::update(const std::string &format, const std::string &icon) {
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
m_button.hide();
return;
}
// clang-format off
if (this->m_workspaceManager.activeOnly() && \
!this->isActive() && \
+337 -208
View File
@@ -5,6 +5,7 @@
#include <algorithm>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
@@ -13,7 +14,10 @@
namespace waybar::modules::hyprland {
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
: AModule(config, "workspaces", id, false, false), m_bar(bar), m_box(bar.orientation, 0) {
: AModule(config, "workspaces", id, false, false),
m_bar(bar),
m_box(bar.orientation, 0),
m_ipc(IPC::inst()) {
modulesReady = true;
parseConfig(config);
@@ -24,23 +28,19 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
m_box.get_style_context()->add_class(MODULE_CLASS);
event_box_.add(m_box);
if (!gIPC) {
gIPC = std::make_unique<IPC>();
}
setCurrentMonitorId();
init();
registerIpc();
}
Workspaces::~Workspaces() {
gIPC->unregisterForIPC(this);
m_ipc.unregisterForIPC(this);
// wait for possible event handler to finish
std::lock_guard<std::mutex> lg(m_mutex);
}
void Workspaces::init() {
m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString();
m_activeWorkspaceId = m_ipc.getSocket1JsonReply("activeworkspace")["id"].asInt();
initializeWorkspaces();
dp.emit();
@@ -50,13 +50,12 @@ Json::Value Workspaces::createMonitorWorkspaceData(std::string const &name,
std::string const &monitor) {
spdlog::trace("Creating persistent workspace: {} on monitor {}", name, monitor);
Json::Value workspaceData;
try {
// numbered persistent workspaces get the name as ID
workspaceData["id"] = name == "special" ? -99 : std::stoi(name);
} catch (const std::exception &e) {
// named persistent workspaces start with ID=0
workspaceData["id"] = 0;
auto workspaceId = parseWorkspaceId(name);
if (!workspaceId.has_value()) {
workspaceId = 0;
}
workspaceData["id"] = *workspaceId;
workspaceData["name"] = name;
workspaceData["monitor"] = monitor;
workspaceData["windows"] = 0;
@@ -66,12 +65,15 @@ Json::Value Workspaces::createMonitorWorkspaceData(std::string const &name,
void Workspaces::createWorkspace(Json::Value const &workspace_data,
Json::Value const &clients_data) {
auto workspaceName = workspace_data["name"].asString();
auto workspaceId = workspace_data["id"].asInt();
spdlog::debug("Creating workspace {}", workspaceName);
// avoid recreating existing workspaces
auto workspace = std::find_if(
m_workspaces.begin(), m_workspaces.end(),
[workspaceName](std::unique_ptr<Workspace> const &w) {
auto workspace =
std::ranges::find_if(m_workspaces, [&](std::unique_ptr<Workspace> const &w) {
if (workspaceId > 0) {
return w->id() == workspaceId;
}
return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) ||
workspaceName == w->name();
});
@@ -81,14 +83,14 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data,
const auto keys = workspace_data.getMemberNames();
const auto *k = "persistent-rule";
if (std::find(keys.begin(), keys.end(), k) != keys.end()) {
if (std::ranges::find(keys, k) != keys.end()) {
spdlog::debug("Set dynamic persistency of workspace {} to: {}", workspaceName,
workspace_data[k].asBool() ? "true" : "false");
(*workspace)->setPersistentRule(workspace_data[k].asBool());
}
k = "persistent-config";
if (std::find(keys.begin(), keys.end(), k) != keys.end()) {
if (std::ranges::find(keys, k) != keys.end()) {
spdlog::debug("Set config persistency of workspace {} to: {}", workspaceName,
workspace_data[k].asBool() ? "true" : "false");
(*workspace)->setPersistentConfig(workspace_data[k].asBool());
@@ -159,18 +161,18 @@ std::string Workspaces::getRewrite(std::string window_class, std::string window_
fmt::arg("title", window_title));
}
std::vector<std::string> Workspaces::getVisibleWorkspaces() {
std::vector<std::string> visibleWorkspaces;
auto monitors = gIPC->getSocket1JsonReply("monitors");
std::vector<int> Workspaces::getVisibleWorkspaces() {
std::vector<int> visibleWorkspaces;
auto monitors = IPC::inst().getSocket1JsonReply("monitors");
for (const auto &monitor : monitors) {
auto ws = monitor["activeWorkspace"];
if (ws.isObject() && ws["name"].isString()) {
visibleWorkspaces.push_back(ws["name"].asString());
if (ws.isObject() && ws["id"].isInt()) {
visibleWorkspaces.push_back(ws["id"].asInt());
}
auto sws = monitor["specialWorkspace"];
auto name = sws["name"].asString();
if (sws.isObject() && sws["name"].isString() && !name.empty()) {
visibleWorkspaces.push_back(!name.starts_with("special:") ? name : name.substr(8));
if (sws.isObject() && sws["id"].isInt() && !name.empty()) {
visibleWorkspaces.push_back(sws["id"].asInt());
}
}
return visibleWorkspaces;
@@ -181,12 +183,12 @@ void Workspaces::initializeWorkspaces() {
// if the workspace rules changed since last initialization, make sure we reset everything:
for (auto &workspace : m_workspaces) {
m_workspacesToRemove.push_back(workspace->name());
m_workspacesToRemove.push_back(std::to_string(workspace->id()));
}
// get all current workspaces
auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces");
auto const clientsJson = gIPC->getSocket1JsonReply("clients");
auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
auto const clientsJson = m_ipc.getSocket1JsonReply("clients");
for (Json::Value workspaceJson : workspacesJson) {
std::string workspaceName = workspaceJson["name"].asString();
@@ -233,7 +235,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs
std::vector<std::string> persistentWorkspacesToCreate;
const std::string currentMonitor = m_bar.output->name;
const bool monitorInConfig = std::find(keys.begin(), keys.end(), currentMonitor) != keys.end();
const bool monitorInConfig = std::ranges::find(keys, currentMonitor) != keys.end();
for (const std::string &key : keys) {
// only add if either:
// 1. key is the current monitor name
@@ -248,17 +250,15 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs
int amount = value.asInt();
spdlog::debug("Creating {} persistent workspaces for monitor {}", amount, currentMonitor);
for (int i = 0; i < amount; i++) {
persistentWorkspacesToCreate.emplace_back(std::to_string(m_monitorId * amount + i + 1));
persistentWorkspacesToCreate.emplace_back(std::to_string((m_monitorId * amount) + i + 1));
}
}
} else if (value.isArray() && !value.empty()) {
// value is an array => create defined workspaces for this monitor
if (canCreate) {
for (const Json::Value &workspace : value) {
if (workspace.isInt()) {
spdlog::debug("Creating workspace {} on monitor {}", workspace, currentMonitor);
persistentWorkspacesToCreate.emplace_back(std::to_string(workspace.asInt()));
}
spdlog::debug("Creating workspace {} on monitor {}", workspace, currentMonitor);
persistentWorkspacesToCreate.emplace_back(workspace.asString());
}
} else {
// key is the workspace and value is array of monitors to create on
@@ -285,7 +285,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs
void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &clientsJson) {
spdlog::info("Loading persistent workspaces from Hyprland workspace rules");
auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules");
auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules");
for (Json::Value const &rule : workspaceRules) {
if (!rule["workspaceString"].isString()) {
spdlog::warn("Workspace rules: invalid workspaceString, skipping: {}", rule);
@@ -294,7 +294,8 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c
if (!rule["persistent"].asBool()) {
continue;
}
auto const &workspace = rule["workspaceString"].asString();
auto const &workspace = rule.isMember("defaultName") ? rule["defaultName"].asString()
: rule["workspaceString"].asString();
auto const &monitor = rule["monitor"].asString();
// create this workspace persistently if:
// 1. the allOutputs config option is enabled
@@ -306,6 +307,7 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c
workspaceData["persistent-rule"] = true;
m_workspacesToCreate.emplace_back(workspaceData, clientsJson);
} else {
// This can be any workspace selector.
m_workspacesToRemove.emplace_back(workspace);
}
}
@@ -316,29 +318,29 @@ void Workspaces::onEvent(const std::string &ev) {
std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>'));
std::string payload = ev.substr(eventName.size() + 2);
if (eventName == "workspace") {
if (eventName == "workspacev2") {
onWorkspaceActivated(payload);
} else if (eventName == "activespecial") {
onSpecialWorkspaceActivated(payload);
} else if (eventName == "destroyworkspace") {
} else if (eventName == "destroyworkspacev2") {
onWorkspaceDestroyed(payload);
} else if (eventName == "createworkspace") {
} else if (eventName == "createworkspacev2") {
onWorkspaceCreated(payload);
} else if (eventName == "focusedmon") {
} else if (eventName == "focusedmonv2") {
onMonitorFocused(payload);
} else if (eventName == "moveworkspace") {
} else if (eventName == "moveworkspacev2") {
onWorkspaceMoved(payload);
} else if (eventName == "openwindow") {
onWindowOpened(payload);
} else if (eventName == "closewindow") {
onWindowClosed(payload);
} else if (eventName == "movewindow") {
} else if (eventName == "movewindowv2") {
onWindowMoved(payload);
} else if (eventName == "urgent") {
setUrgentWorkspace(payload);
} else if (eventName == "renameworkspace") {
onWorkspaceRenamed(payload);
} else if (eventName == "windowtitle") {
} else if (eventName == "windowtitlev2") {
onWindowTitleEvent(payload);
} else if (eventName == "configreloaded") {
onConfigReloaded();
@@ -348,7 +350,11 @@ void Workspaces::onEvent(const std::string &ev) {
}
void Workspaces::onWorkspaceActivated(std::string const &payload) {
m_activeWorkspaceName = payload;
const auto [workspaceIdStr, workspaceName] = splitDoublePayload(payload);
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (workspaceId.has_value()) {
m_activeWorkspaceId = *workspaceId;
}
}
void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) {
@@ -357,39 +363,55 @@ void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) {
}
void Workspaces::onWorkspaceDestroyed(std::string const &payload) {
if (!isDoubleSpecial(payload)) {
m_workspacesToRemove.push_back(payload);
const auto [workspaceId, workspaceName] = splitDoublePayload(payload);
if (!isDoubleSpecial(workspaceName)) {
m_workspacesToRemove.push_back(workspaceId);
}
}
void Workspaces::onWorkspaceCreated(std::string const &workspaceName,
Json::Value const &clientsData) {
spdlog::debug("Workspace created: {}", workspaceName);
auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces");
void Workspaces::onWorkspaceCreated(std::string const &payload, Json::Value const &clientsData) {
spdlog::debug("Workspace created: {}", payload);
if (!isWorkspaceIgnored(workspaceName)) {
auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules");
for (Json::Value workspaceJson : workspacesJson) {
std::string name = workspaceJson["name"].asString();
if (name == workspaceName) {
if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) &&
(showSpecial() || !name.starts_with("special")) && !isDoubleSpecial(workspaceName)) {
for (Json::Value const &rule : workspaceRules) {
if (rule["workspaceString"].asString() == workspaceName) {
workspaceJson["persistent-rule"] = rule["persistent"].asBool();
break;
}
}
const auto [workspaceIdStr, _] = splitDoublePayload(payload);
m_workspacesToCreate.emplace_back(workspaceJson, clientsData);
break;
}
} else {
extendOrphans(workspaceJson["id"].asInt(), clientsData);
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (!workspaceId.has_value()) {
return;
}
auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules");
auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
for (Json::Value workspaceJson : workspacesJson) {
const auto currentId = workspaceJson["id"].asInt();
if (currentId == *workspaceId) {
std::string workspaceName = workspaceJson["name"].asString();
// This workspace name is more up-to-date than the one in the event payload.
if (isWorkspaceIgnored(workspaceName)) {
spdlog::trace("Not creating workspace because it is ignored: id={} name={}", *workspaceId,
workspaceName);
break;
}
if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) &&
(showSpecial() || !workspaceName.starts_with("special")) &&
!isDoubleSpecial(workspaceName)) {
for (Json::Value const &rule : workspaceRules) {
auto ruleWorkspaceName = rule.isMember("defaultName")
? rule["defaultName"].asString()
: rule["workspaceString"].asString();
if (ruleWorkspaceName == workspaceName) {
workspaceJson["persistent-rule"] = rule["persistent"].asBool();
break;
}
}
m_workspacesToCreate.emplace_back(workspaceJson, clientsData);
break;
}
} else {
extendOrphans(*workspaceId, clientsData);
}
} else {
spdlog::trace("Not creating workspace because it is ignored: {}", workspaceName);
}
}
@@ -397,32 +419,34 @@ void Workspaces::onWorkspaceMoved(std::string const &payload) {
spdlog::debug("Workspace moved: {}", payload);
// Update active workspace
m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString();
m_activeWorkspaceId = (m_ipc.getSocket1JsonReply("activeworkspace"))["id"].asInt();
if (allOutputs()) return;
std::string workspaceName = payload.substr(0, payload.find(','));
std::string monitorName = payload.substr(payload.find(',') + 1);
const auto [workspaceIdStr, workspaceName, monitorName] = splitTriplePayload(payload);
const auto subPayload = makePayload(workspaceIdStr, workspaceName);
if (m_bar.output->name == monitorName) {
Json::Value clientsData = gIPC->getSocket1JsonReply("clients");
onWorkspaceCreated(workspaceName, clientsData);
Json::Value clientsData = m_ipc.getSocket1JsonReply("clients");
onWorkspaceCreated(subPayload, clientsData);
} else {
spdlog::debug("Removing workspace because it was moved to another monitor: {}");
onWorkspaceDestroyed(workspaceName);
spdlog::debug("Removing workspace because it was moved to another monitor: {}", subPayload);
onWorkspaceDestroyed(subPayload);
}
}
void Workspaces::onWorkspaceRenamed(std::string const &payload) {
spdlog::debug("Workspace renamed: {}", payload);
std::string workspaceIdStr = payload.substr(0, payload.find(','));
int workspaceId = workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr);
std::string newName = payload.substr(payload.find(',') + 1);
const auto [workspaceIdStr, newName] = splitDoublePayload(payload);
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (!workspaceId.has_value()) {
return;
}
for (auto &workspace : m_workspaces) {
if (workspace->id() == workspaceId) {
if (workspace->name() == m_activeWorkspaceName) {
m_activeWorkspaceName = newName;
}
if (workspace->id() == *workspaceId) {
workspace->setName(newName);
break;
}
@@ -432,11 +456,19 @@ void Workspaces::onWorkspaceRenamed(std::string const &payload) {
void Workspaces::onMonitorFocused(std::string const &payload) {
spdlog::trace("Monitor focused: {}", payload);
m_activeWorkspaceName = payload.substr(payload.find(',') + 1);
for (Json::Value &monitor : gIPC->getSocket1JsonReply("monitors")) {
if (monitor["name"].asString() == payload.substr(0, payload.find(','))) {
auto name = monitor["specialWorkspace"]["name"].asString();
const auto [monitorName, workspaceIdStr] = splitDoublePayload(payload);
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (!workspaceId.has_value()) {
return;
}
m_activeWorkspaceId = *workspaceId;
for (Json::Value &monitor : m_ipc.getSocket1JsonReply("monitors")) {
if (monitor["name"].asString() == monitorName) {
const auto name = monitor["specialWorkspace"]["name"].asString();
m_activeSpecialWorkspaceName = !name.starts_with("special:") ? name : name.substr(8);
}
}
@@ -475,11 +507,7 @@ void Workspaces::onWindowClosed(std::string const &addr) {
void Workspaces::onWindowMoved(std::string const &payload) {
spdlog::trace("Window moved: {}", payload);
updateWindowCount();
size_t lastCommaIdx = 0;
size_t nextCommaIdx = payload.find(',');
std::string windowAddress = payload.substr(lastCommaIdx, nextCommaIdx - lastCommaIdx);
std::string workspaceName = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx);
auto [windowAddress, _, workspaceName] = splitTriplePayload(payload);
std::string windowRepr;
@@ -487,7 +515,7 @@ void Workspaces::onWindowMoved(std::string const &payload) {
// and exit
for (auto &window : m_windowsToCreate) {
if (window.getAddress() == windowAddress) {
window.moveToWorksace(workspaceName);
window.moveToWorkspace(workspaceName);
return;
}
}
@@ -515,13 +543,15 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
spdlog::trace("Window title changed: {}", payload);
std::optional<std::function<void(WindowCreationPayload)>> inserter;
const auto [windowAddress, _] = splitDoublePayload(payload);
// If the window was an orphan, rename it at the orphan's vector
if (m_orphanWindowMap.contains(payload)) {
if (m_orphanWindowMap.contains(windowAddress)) {
inserter = [this](WindowCreationPayload wcp) { this->registerOrphanWindow(std::move(wcp)); };
} else {
auto windowWorkspace =
std::find_if(m_workspaces.begin(), m_workspaces.end(),
[payload](auto &workspace) { return workspace->containsWindow(payload); });
auto windowWorkspace = std::ranges::find_if(m_workspaces, [windowAddress](auto &workspace) {
return workspace->containsWindow(windowAddress);
});
// If the window exists on a workspace, rename it at the workspace's window
// map
@@ -530,9 +560,9 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
(*windowWorkspace)->insertWindow(std::move(wcp));
};
} else {
auto queuedWindow = std::find_if(
m_windowsToCreate.begin(), m_windowsToCreate.end(),
[payload](auto &windowPayload) { return windowPayload.getAddress() == payload; });
auto queuedWindow = std::ranges::find_if(m_windowsToCreate, [payload](auto &windowPayload) {
return windowPayload.getAddress() == payload;
});
// If the window was queued, rename it in the queue
if (queuedWindow != m_windowsToCreate.end()) {
@@ -542,15 +572,14 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
}
if (inserter.has_value()) {
Json::Value clientsData = gIPC->getSocket1JsonReply("clients");
Json::Value clientsData = m_ipc.getSocket1JsonReply("clients");
std::string jsonWindowAddress = fmt::format("0x{}", payload);
auto client =
std::find_if(clientsData.begin(), clientsData.end(), [jsonWindowAddress](auto &client) {
return client["address"].asString() == jsonWindowAddress;
});
auto client = std::ranges::find_if(clientsData, [jsonWindowAddress](auto &client) {
return client["address"].asString() == jsonWindowAddress;
});
if (!client->empty()) {
if (client != clientsData.end() && !client->empty()) {
(*inserter)({*client});
}
}
@@ -573,6 +602,7 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void {
populateBoolConfig(config, "all-outputs", m_allOutputs);
populateBoolConfig(config, "show-special", m_showSpecial);
populateBoolConfig(config, "special-visible-only", m_specialVisibleOnly);
populateBoolConfig(config, "persistent-only", m_persistentOnly);
populateBoolConfig(config, "active-only", m_activeOnly);
populateBoolConfig(config, "move-to-monitor", m_moveToMonitor);
@@ -590,8 +620,8 @@ auto Workspaces::populateIconsMap(const Json::Value &formatIcons) -> void {
m_iconsMap.emplace("", "");
}
auto Workspaces::populateBoolConfig(const Json::Value &config, const std::string &key,
bool &member) -> void {
auto Workspaces::populateBoolConfig(const Json::Value &config, const std::string &key, bool &member)
-> void {
const auto &configValue = config[key];
if (configValue.isBool()) {
member = configValue.asBool();
@@ -660,40 +690,58 @@ void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payloa
}
auto Workspaces::registerIpc() -> void {
gIPC->registerForIPC("workspace", this);
gIPC->registerForIPC("activespecial", this);
gIPC->registerForIPC("createworkspace", this);
gIPC->registerForIPC("destroyworkspace", this);
gIPC->registerForIPC("focusedmon", this);
gIPC->registerForIPC("moveworkspace", this);
gIPC->registerForIPC("renameworkspace", this);
gIPC->registerForIPC("openwindow", this);
gIPC->registerForIPC("closewindow", this);
gIPC->registerForIPC("movewindow", this);
gIPC->registerForIPC("urgent", this);
gIPC->registerForIPC("configreloaded", this);
m_ipc.registerForIPC("workspacev2", this);
m_ipc.registerForIPC("activespecial", this);
m_ipc.registerForIPC("createworkspacev2", this);
m_ipc.registerForIPC("destroyworkspacev2", this);
m_ipc.registerForIPC("focusedmonv2", this);
m_ipc.registerForIPC("moveworkspacev2", this);
m_ipc.registerForIPC("renameworkspace", this);
m_ipc.registerForIPC("openwindow", this);
m_ipc.registerForIPC("closewindow", this);
m_ipc.registerForIPC("movewindowv2", this);
m_ipc.registerForIPC("urgent", this);
m_ipc.registerForIPC("configreloaded", this);
if (windowRewriteConfigUsesTitle()) {
spdlog::info(
"Registering for Hyprland's 'windowtitle' events because a user-defined window "
"Registering for Hyprland's 'windowtitlev2' events because a user-defined window "
"rewrite rule uses the 'title' field.");
gIPC->registerForIPC("windowtitle", this);
m_ipc.registerForIPC("windowtitlev2", this);
}
}
void Workspaces::removeWorkspacesToRemove() {
for (const auto &workspaceName : m_workspacesToRemove) {
removeWorkspace(workspaceName);
for (const auto &workspaceString : m_workspacesToRemove) {
removeWorkspace(workspaceString);
}
m_workspacesToRemove.clear();
}
void Workspaces::removeWorkspace(std::string const &name) {
spdlog::debug("Removing workspace {}", name);
auto workspace =
std::find_if(m_workspaces.begin(), m_workspaces.end(), [&](std::unique_ptr<Workspace> &x) {
return (name.starts_with("special:") && name.substr(8) == x->name()) || name == x->name();
});
void Workspaces::removeWorkspace(std::string const &workspaceString) {
spdlog::debug("Removing workspace {}", workspaceString);
// If this succeeds, we have a workspace ID.
const auto workspaceId = parseWorkspaceId(workspaceString);
std::string name;
// TODO: At some point we want to support all workspace selectors
// This is just a subset.
// https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors
if (workspaceString.starts_with("special:")) {
name = workspaceString.substr(8);
} else if (workspaceString.starts_with("name:")) {
name = workspaceString.substr(5);
} else {
name = workspaceString;
}
const auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr<Workspace> &x) {
if (workspaceId.has_value()) {
return *workspaceId == x->id();
}
return name == x->name();
});
if (workspace == m_workspaces.end()) {
// happens when a workspace on another monitor is destroyed
@@ -701,7 +749,8 @@ void Workspaces::removeWorkspace(std::string const &name) {
}
if ((*workspace)->isPersistentConfig()) {
spdlog::trace("Not removing config persistent workspace {}", name);
spdlog::trace("Not removing config persistent workspace id={} name={}", (*workspace)->id(),
(*workspace)->name());
return;
}
@@ -712,10 +761,10 @@ void Workspaces::removeWorkspace(std::string const &name) {
void Workspaces::setCurrentMonitorId() {
// get monitor ID from name (used by persistent workspaces)
m_monitorId = 0;
auto monitors = gIPC->getSocket1JsonReply("monitors");
auto currentMonitor = std::find_if(
monitors.begin(), monitors.end(),
[this](const Json::Value &m) { return m["name"].asString() == m_bar.output->name; });
auto monitors = m_ipc.getSocket1JsonReply("monitors");
auto currentMonitor = std::ranges::find_if(monitors, [this](const Json::Value &m) {
return m["name"].asString() == m_bar.output->name;
});
if (currentMonitor == monitors.end()) {
spdlog::error("Monitor '{}' does not have an ID? Using 0", m_bar.output->name);
} else {
@@ -724,63 +773,101 @@ void Workspaces::setCurrentMonitorId() {
}
}
void Workspaces::sortSpecialCentered() {
std::vector<std::unique_ptr<Workspace>> specialWorkspaces;
std::vector<std::unique_ptr<Workspace>> hiddenWorkspaces;
std::vector<std::unique_ptr<Workspace>> normalWorkspaces;
for (auto &workspace : m_workspaces) {
if (workspace->isSpecial()) {
specialWorkspaces.push_back(std::move(workspace));
} else {
if (workspace->button().is_visible()) {
normalWorkspaces.push_back(std::move(workspace));
} else {
hiddenWorkspaces.push_back(std::move(workspace));
}
}
}
m_workspaces.clear();
size_t center = normalWorkspaces.size() / 2;
m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(normalWorkspaces.begin()),
std::make_move_iterator(normalWorkspaces.begin() + center));
m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(specialWorkspaces.begin()),
std::make_move_iterator(specialWorkspaces.end()));
m_workspaces.insert(m_workspaces.end(),
std::make_move_iterator(normalWorkspaces.begin() + center),
std::make_move_iterator(normalWorkspaces.end()));
m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(hiddenWorkspaces.begin()),
std::make_move_iterator(hiddenWorkspaces.end()));
}
void Workspaces::sortWorkspaces() {
std::sort(m_workspaces.begin(), m_workspaces.end(),
[&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
// Helper comparisons
auto isIdLess = a->id() < b->id();
auto isNameLess = a->name() < b->name();
std::ranges::sort( //
m_workspaces, [&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
// Helper comparisons
auto isIdLess = a->id() < b->id();
auto isNameLess = a->name() < b->name();
switch (m_sortBy) {
case SortMethod::ID:
return isIdLess;
case SortMethod::NAME:
return isNameLess;
case SortMethod::NUMBER:
try {
return std::stoi(a->name()) < std::stoi(b->name());
} catch (const std::invalid_argument &) {
// Handle the exception if necessary.
break;
}
case SortMethod::DEFAULT:
default:
// Handle the default case here.
// normal -> named persistent -> named -> special -> named special
switch (m_sortBy) {
case SortMethod::ID:
return isIdLess;
case SortMethod::NAME:
return isNameLess;
case SortMethod::NUMBER:
try {
return std::stoi(a->name()) < std::stoi(b->name());
} catch (const std::invalid_argument &) {
// Handle the exception if necessary.
break;
}
case SortMethod::DEFAULT:
default:
// Handle the default case here.
// normal -> named persistent -> named -> special -> named special
// both normal (includes numbered persistent) => sort by ID
if (a->id() > 0 && b->id() > 0) {
return isIdLess;
}
// both normal (includes numbered persistent) => sort by ID
if (a->id() > 0 && b->id() > 0) {
return isIdLess;
}
// one normal, one special => normal first
if ((a->isSpecial()) ^ (b->isSpecial())) {
return b->isSpecial();
}
// one normal, one special => normal first
if ((a->isSpecial()) ^ (b->isSpecial())) {
return b->isSpecial();
}
// only one normal, one named
if ((a->id() > 0) ^ (b->id() > 0)) {
return a->id() > 0;
}
// only one normal, one named
if ((a->id() > 0) ^ (b->id() > 0)) {
return a->id() > 0;
}
// both special
if (a->isSpecial() && b->isSpecial()) {
// if one is -99 => put it last
if (a->id() == -99 || b->id() == -99) {
return b->id() == -99;
}
// both are 0 (not yet named persistents) / named specials (-98 <= ID <= -1)
return isNameLess;
}
// sort non-special named workspaces by name (ID <= -1377)
return isNameLess;
break;
// both special
if (a->isSpecial() && b->isSpecial()) {
// if one is -99 => put it last
if (a->id() == -99 || b->id() == -99) {
return b->id() == -99;
}
// both are 0 (not yet named persistents) / named specials
// (-98 <= ID <= -1)
return isNameLess;
}
// Return a default value if none of the cases match.
return isNameLess; // You can adjust this to your specific needs.
});
// sort non-special named workspaces by name (ID <= -1377)
return isNameLess;
break;
}
// Return a default value if none of the cases match.
return isNameLess; // You can adjust this to your specific needs.
});
if (m_sortBy == SortMethod::SPECIAL_CENTERED) {
this->sortSpecialCentered();
}
for (size_t i = 0; i < m_workspaces.size(); ++i) {
m_box.reorder_child(m_workspaces[i]->button(), i);
@@ -788,7 +875,7 @@ void Workspaces::sortWorkspaces() {
}
void Workspaces::setUrgentWorkspace(std::string const &windowaddress) {
const Json::Value clientsJson = gIPC->getSocket1JsonReply("clients");
const Json::Value clientsJson = m_ipc.getSocket1JsonReply("clients");
int workspaceId = -1;
for (Json::Value clientJson : clientsJson) {
@@ -798,9 +885,9 @@ void Workspaces::setUrgentWorkspace(std::string const &windowaddress) {
}
}
auto workspace =
std::find_if(m_workspaces.begin(), m_workspaces.end(),
[workspaceId](std::unique_ptr<Workspace> &x) { return x->id() == workspaceId; });
auto workspace = std::ranges::find_if(m_workspaces, [workspaceId](std::unique_ptr<Workspace> &x) {
return x->id() == workspaceId;
});
if (workspace != m_workspaces.end()) {
workspace->get()->setUrgent();
}
@@ -812,13 +899,12 @@ auto Workspaces::update() -> void {
}
void Workspaces::updateWindowCount() {
const Json::Value workspacesJson = gIPC->getSocket1JsonReply("workspaces");
const Json::Value workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
for (auto &workspace : m_workspaces) {
auto workspaceJson =
std::find_if(workspacesJson.begin(), workspacesJson.end(), [&](Json::Value const &x) {
return x["name"].asString() == workspace->name() ||
(workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name());
});
auto workspaceJson = std::ranges::find_if(workspacesJson, [&](Json::Value const &x) {
return x["name"].asString() == workspace->name() ||
(workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name());
});
uint32_t count = 0;
if (workspaceJson != workspacesJson.end()) {
try {
@@ -858,26 +944,34 @@ bool Workspaces::updateWindowsToCreate() {
}
void Workspaces::updateWorkspaceStates() {
const std::vector<std::string> visibleWorkspaces = getVisibleWorkspaces();
auto updatedWorkspaces = gIPC->getSocket1JsonReply("workspaces");
const std::vector<int> visibleWorkspaces = getVisibleWorkspaces();
auto updatedWorkspaces = m_ipc.getSocket1JsonReply("workspaces");
auto currentWorkspace = m_ipc.getSocket1JsonReply("activeworkspace");
std::string currentWorkspaceName =
currentWorkspace.isMember("name") ? currentWorkspace["name"].asString() : "";
for (auto &workspace : m_workspaces) {
workspace->setActive(workspace->name() == m_activeWorkspaceName ||
workspace->name() == m_activeSpecialWorkspaceName);
if (workspace->name() == m_activeWorkspaceName && workspace->isUrgent()) {
bool isActiveByName =
!currentWorkspaceName.empty() && workspace->name() == currentWorkspaceName;
workspace->setActive(
workspace->id() == m_activeWorkspaceId || isActiveByName ||
(workspace->isSpecial() && workspace->name() == m_activeSpecialWorkspaceName));
if (workspace->isActive() && workspace->isUrgent()) {
workspace->setUrgent(false);
}
workspace->setVisible(std::find(visibleWorkspaces.begin(), visibleWorkspaces.end(),
workspace->name()) != visibleWorkspaces.end());
workspace->setVisible(std::ranges::find(visibleWorkspaces, workspace->id()) !=
visibleWorkspaces.end());
std::string &workspaceIcon = m_iconsMap[""];
if (m_withIcon) {
workspaceIcon = workspace->selectIcon(m_iconsMap);
}
auto updatedWorkspace = std::find_if(
updatedWorkspaces.begin(), updatedWorkspaces.end(), [&workspace](const auto &w) {
auto wNameRaw = w["name"].asString();
auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw;
return wName == workspace->name();
});
auto updatedWorkspace = std::ranges::find_if(updatedWorkspaces, [&workspace](const auto &w) {
auto wNameRaw = w["name"].asString();
auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw;
return wName == workspace->name();
});
if (updatedWorkspace != updatedWorkspaces.end()) {
workspace->setOutput((*updatedWorkspace)["monitor"].asString());
}
@@ -905,4 +999,39 @@ int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) {
return 0;
}
template <typename... Args>
std::string Workspaces::makePayload(Args const &...args) {
std::ostringstream result;
bool first = true;
((result << (first ? "" : ",") << args, first = false), ...);
return result.str();
}
std::pair<std::string, std::string> Workspaces::splitDoublePayload(std::string const &payload) {
const std::string part1 = payload.substr(0, payload.find(','));
const std::string part2 = payload.substr(part1.size() + 1);
return {part1, part2};
}
std::tuple<std::string, std::string, std::string> Workspaces::splitTriplePayload(
std::string const &payload) {
const size_t firstComma = payload.find(',');
const size_t secondComma = payload.find(',', firstComma + 1);
const std::string part1 = payload.substr(0, firstComma);
const std::string part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1));
const std::string part3 = payload.substr(secondComma + 1);
return {part1, part2, part3};
}
std::optional<int> Workspaces::parseWorkspaceId(std::string const &workspaceIdStr) {
try {
return workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr);
} catch (std::exception const &e) {
spdlog::error("Failed to parse workspace ID: {}", e.what());
return std::nullopt;
}
}
} // namespace waybar::modules::hyprland
+2
View File
@@ -60,6 +60,7 @@ auto waybar::modules::Memory::update() -> void {
fmt::arg("icon", getIcon(used_ram_percentage, icons)),
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"),
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
fmt::arg("swapAvail", available_swap_gigabytes)));
@@ -72,6 +73,7 @@ auto waybar::modules::Memory::update() -> void {
fmt::runtime(tooltip_format), used_ram_percentage,
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"),
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
fmt::arg("swapAvail", available_swap_gigabytes)));
+38 -27
View File
@@ -14,7 +14,6 @@ extern "C" {
#include <glib.h>
#include <spdlog/spdlog.h>
namespace waybar::modules::mpris {
const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}";
@@ -425,9 +424,11 @@ auto Mpris::onPlayerNameVanished(PlayerctlPlayerManager* manager, PlayerctlPlaye
auto* mpris = static_cast<Mpris*>(data);
if (!mpris) return;
spdlog::debug("mpris: player-vanished callback: {}", player_name->name);
spdlog::debug("mpris: name-vanished callback: {}", player_name->name);
if (std::string(player_name->name) == mpris->player_) {
if (mpris->player_ == "playerctld") {
mpris->dp.emit();
} else if (mpris->player_ == player_name->name) {
mpris->player = nullptr;
mpris->event_box_.set_visible(false);
mpris->dp.emit();
@@ -498,7 +499,10 @@ auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
// > get the list of players [..] in order of activity
// https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249
players = g_list_first(players);
if (players) player_name = static_cast<PlayerctlPlayerName*>(players->data)->name;
if (players)
player_name = static_cast<PlayerctlPlayerName*>(players->data)->name;
else
return std::nullopt; // no players found, hide the widget
}
if (std::any_of(ignored_players_.begin(), ignored_players_.end(),
@@ -584,38 +588,45 @@ errorexit:
}
bool Mpris::handleToggle(GdkEventButton* const& e) {
if (!e || e->type != GdkEventType::GDK_BUTTON_PRESS) {
return false;
}
auto info = getPlayerInfo();
if (!info) return false;
struct ButtonAction {
guint button;
const char* config_key;
std::function<void()> builtin_action;
};
GError* error = nullptr;
waybar::util::ScopeGuard error_deleter([error]() {
waybar::util::ScopeGuard error_deleter([&error]() {
if (error) {
g_error_free(error);
}
});
auto info = getPlayerInfo();
if (!info) return false;
// Command pattern: encapsulate each button's action
const ButtonAction actions[] = {
{1, "on-click", [&]() { playerctl_player_play_pause(player, &error); }},
{2, "on-click-middle", [&]() { playerctl_player_previous(player, &error); }},
{3, "on-click-right", [&]() { playerctl_player_next(player, &error); }},
{8, "on-click-backward", [&]() { playerctl_player_previous(player, &error); }},
{9, "on-click-forward", [&]() { playerctl_player_next(player, &error); }},
};
if (e->type == GdkEventType::GDK_BUTTON_PRESS) {
switch (e->button) {
case 1: // left-click
if (config_["on-click"].isString()) {
return ALabel::handleToggle(e);
}
playerctl_player_play_pause(player, &error);
break;
case 2: // middle-click
if (config_["on-click-middle"].isString()) {
return ALabel::handleToggle(e);
}
playerctl_player_previous(player, &error);
break;
case 3: // right-click
if (config_["on-click-right"].isString()) {
return ALabel::handleToggle(e);
}
playerctl_player_next(player, &error);
break;
for (const auto& action : actions) {
if (e->button == action.button) {
if (config_[action.config_key].isString()) {
return ALabel::handleToggle(e);
}
action.builtin_action();
break;
}
}
if (error) {
spdlog::error("mpris[{}]: error running builtin on-click action: {}", (*info).name,
error->message);
+59 -43
View File
@@ -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,12 @@ 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() && ifid_ == -1) return "disabled";
#endif
return "disconnected";
}
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 +322,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("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")),
@@ -367,8 +370,9 @@ auto waybar::modules::Network::update() -> void {
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")),
@@ -409,10 +413,13 @@ void waybar::modules::Network::clearIface() {
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();
@@ -531,16 +538,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:
@@ -548,8 +550,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;
@@ -557,21 +571,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);
@@ -591,6 +608,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;
@@ -618,14 +636,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) {
@@ -717,7 +735,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;
@@ -728,7 +745,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;
+11
View File
@@ -147,6 +147,17 @@ void IPC::parseIPC(const std::string &line) {
} else {
spdlog::error("Active window changed on unknown workspace");
}
} else if (const auto &payload = ev["WorkspaceUrgencyChanged"]) {
const auto id = payload["id"].asUInt64();
const auto urgent = payload["urgent"].asBool();
auto it = std::find_if(workspaces_.begin(), workspaces_.end(),
[id](const auto &ws) { return ws["id"].asUInt64() == id; });
if (it != workspaces_.end()) {
auto &ws = *it;
ws["is_urgent"] = urgent;
} else {
spdlog::error("Urgency changed for unknown workspace");
}
} else if (const auto &payload = ev["KeyboardLayoutsChanged"]) {
const auto &layouts = payload["keyboard_layouts"];
const auto &names = layouts["names"];
+1 -1
View File
@@ -9,7 +9,7 @@
namespace waybar::modules::niri {
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, false), bar_(bar) {
label_.hide();
if (!gIPC) gIPC = std::make_unique<IPC>();
+10
View File
@@ -20,6 +20,7 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
gIPC->registerForIPC("WorkspacesChanged", this);
gIPC->registerForIPC("WorkspaceActivated", this);
gIPC->registerForIPC("WorkspaceActiveWindowChanged", this);
gIPC->registerForIPC("WorkspaceUrgencyChanged", this);
dp.emit();
}
@@ -67,6 +68,11 @@ void Workspaces::doUpdate() {
else
style_context->remove_class("active");
if (ws["is_urgent"].asBool())
style_context->add_class("urgent");
else
style_context->remove_class("urgent");
if (ws["output"]) {
if (ws["output"].asString() == bar_.output->name)
style_context->add_class("current_output");
@@ -166,6 +172,10 @@ std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws)
const auto &icons = config_["format-icons"];
if (!icons) return value;
if (ws["is_urgent"].asBool() && icons["urgent"]) return icons["urgent"].asString();
if (ws["active_window_id"].isNull() && icons["empty"]) return icons["empty"].asString();
if (ws["is_focused"].asBool() && icons["focused"]) return icons["focused"].asString();
if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString();
+2 -2
View File
@@ -29,14 +29,14 @@ PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Valu
// method on the proxy to see whether or not something's responding
// on the other side.
// NOTE: the DBus adresses are under migration. They should be
// NOTE: the DBus addresses are under migration. They should be
// changed to org.freedesktop.UPower.PowerProfiles at some point.
//
// See
// https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/releases/0.20
//
// The old name is still announced for now. Let's rather use the old
// adresses for compatibility sake.
// addresses for compatibility sake.
//
// Revisit this in 2026, systems should be updated by then.
+28 -4
View File
@@ -15,13 +15,14 @@ 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, const std::string& pos)
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_(Gtk::ORIENTATION_HORIZONTAL, 0) {
box_(orientation, 0) {
box_.set_name(name_);
event_box_.add(box_);
@@ -67,12 +68,30 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, const std::st
auto iter = typeMap.find(type);
if (iter != typeMap.end()) {
auto& [nodePtr, nodeType] = iter->second;
auto* item = Gtk::make_managed<PrivacyItem>(module, nodeType, nodePtr, pos, iconSize,
transition_duration);
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));
@@ -87,6 +106,11 @@ void Privacy::onPrivacyNodesChanged() {
nodes_screenshare.clear();
for (auto& node : backend->privacy_nodes) {
if (ignore_monitor && node.second->is_monitor) continue;
auto iter = ignore.find(std::pair(node.second->type, node.second->node_name));
if (iter != ignore.end()) continue;
switch (node.second->state) {
case PW_NODE_STATE_RUNNING:
switch (node.second->type) {
+22 -9
View File
@@ -1,5 +1,7 @@
#include "modules/privacy/privacy_item.hpp"
#include <spdlog/spdlog.h>
#include <string>
#include "glibmm/main.h"
@@ -11,8 +13,9 @@
namespace waybar::modules::privacy {
PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_,
std::list<PrivacyNodeInfo *> *nodes_, const std::string &pos,
const uint icon_size, const uint transition_duration)
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_),
@@ -40,16 +43,24 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac
// Set the reveal transition to not look weird when sliding in
if (pos == "modules-left") {
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT);
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(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT);
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");
box_.add(icon_);
// 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_);
@@ -87,20 +98,22 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac
void PrivacyItem::update_tooltip() {
// Removes all old nodes
for (auto *child : tooltip_window.get_children()) {
tooltip_window.remove(*child);
// despite the remove, still needs a delete to prevent memory leak. Speculating that this might
// work differently in GTK4.
delete child;
}
for (auto *node : *nodes) {
Gtk::Box *box = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4);
auto *box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL, 4);
// Set device icon
Gtk::Image *node_icon = new Gtk::Image();
auto *node_icon = Gtk::make_managed<Gtk::Image>();
node_icon->set_pixel_size(tooltipIconSize);
node_icon->set_from_icon_name(node->getIconName(), Gtk::ICON_SIZE_INVALID);
box->add(*node_icon);
// Set model
auto *nodeName = new Gtk::Label(node->getName());
auto *nodeName = Gtk::make_managed<Gtk::Label>(node->getName());
box->add(*nodeName);
tooltip_window.add(*box);
+1 -1
View File
@@ -132,7 +132,7 @@ auto waybar::modules::Pulseaudio::update() -> void {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(
label_.set_tooltip_markup(fmt::format(
fmt::runtime(tooltip_format), fmt::arg("desc", sink_desc),
fmt::arg("volume", sink_volume), fmt::arg("format_source", format_source),
fmt::arg("source_volume", source_volume), fmt::arg("source_desc", source_desc),
+30
View File
@@ -189,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");
}
}
@@ -205,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");
}
}
+27 -7
View File
@@ -5,10 +5,12 @@
#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"
@@ -124,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;
}
@@ -137,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()) {
@@ -198,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)});
@@ -371,14 +388,17 @@ Glib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() {
Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) {
icon_theme->rescan_if_needed();
if (!icon_theme_path.empty() &&
icon_theme->lookup_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE)) {
return icon_theme->load_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
if (!icon_theme_path.empty()) {
auto icon_info = icon_theme->lookup_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
if (icon_info) {
bool is_sym = false;
return icon_info.load_symbolic(event_box.get_style_context(), is_sym);
}
}
return DefaultGtkIconThemeWrapper::load_icon(name.c_str(), request_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE,
event_box.get_style_context());
}
double Item::getScaledIconSize() {
+5
View File
@@ -2,6 +2,8 @@
#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)
@@ -20,6 +22,9 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
box_.set_spacing(config_["spacing"].asUInt());
}
nb_hosts_ += 1;
if (config_["icons"].isObject()) {
IconManager::instance().setIconsConfig(config_["icons"]);
}
dp.emit();
}
+2 -2
View File
@@ -22,10 +22,10 @@ Language::Language(const std::string& id, const Json::Value& config)
hide_single_ = config["hide-single-layout"].isBool() && config["hide-single-layout"].asBool();
is_variant_displayed = format_.find("{variant}") != std::string::npos;
if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) {
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortName);
displayed_short_flag |= static_cast<std::byte>(DisplayedShortFlag::ShortName);
}
if (format_.find("{shortDescription}") != std::string::npos) {
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortDescription);
displayed_short_flag |= static_cast<std::byte>(DisplayedShortFlag::ShortDescription);
}
if (config.isMember("tooltip-format")) {
tooltip_format_ = config["tooltip-format"].asString();
+39 -18
View File
@@ -41,8 +41,8 @@ void Window::onCmd(const struct Ipc::ipc_response& res) {
std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload);
auto output = payload["output"].isString() ? payload["output"].asString() : "";
std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_) =
getFocusedNode(payload["nodes"], output);
std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_,
marks_) = getFocusedNode(payload["nodes"], output);
updateAppIconName(app_id_, app_class_);
dp.emit();
} catch (const std::exception& e) {
@@ -96,7 +96,7 @@ auto Window::update() -> void {
label_.set_markup(waybar::util::rewriteString(
fmt::format(fmt::runtime(format_), fmt::arg("title", window_), fmt::arg("app_id", app_id_),
fmt::arg("shell", shell_)),
fmt::arg("shell", shell_), fmt::arg("marks", marks_)),
config_["rewrite"]));
if (tooltipEnabled()) {
label_.set_tooltip_text(window_);
@@ -108,7 +108,7 @@ auto Window::update() -> void {
AAppIconLabel::update();
}
void Window::setClass(std::string classname, bool enable) {
void Window::setClass(const std::string& classname, bool enable) {
if (enable) {
if (!bar_.window.get_style_context()->has_class(classname)) {
bar_.window.get_style_context()->add_class(classname);
@@ -169,17 +169,31 @@ std::optional<std::reference_wrapper<const Json::Value>> getSingleChildNode(
return {getSingleChildNode(child)};
}
std::tuple<std::string, std::string, std::string> getWindowInfo(const Json::Value& node) {
std::tuple<std::string, std::string, std::string, std::string> getWindowInfo(
const Json::Value& node, bool showHidden) {
const auto app_id = node["app_id"].isString() ? node["app_id"].asString()
: node["window_properties"]["instance"].asString();
const auto app_class = node["window_properties"]["class"].isString()
? node["window_properties"]["class"].asString()
: "";
const auto shell = node["shell"].isString() ? node["shell"].asString() : "";
return {app_id, app_class, shell};
std::string marks = "";
if (node["marks"].isArray()) {
for (const auto& m : node["marks"]) {
if (!m.isString() || (!showHidden && m.asString().at(0) == '_')) {
continue;
}
if (!marks.empty()) {
marks += ',';
}
marks += m.asString();
}
}
return {app_id, app_class, shell, marks};
}
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string,
std::string>
gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Value& config_,
const Bar& bar_, Json::Value& parentWorkspace,
const Json::Value& immediateParent) {
@@ -207,7 +221,8 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
"",
"",
"",
node["layout"].asString()};
node["layout"].asString(),
""};
}
parentWorkspace = node;
} else if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") &&
@@ -215,7 +230,8 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
// found node
spdlog::trace("actual output {}, output found {}, node (focused) found {}", bar_.output->name,
output, node["name"].asString());
const auto [app_id, app_class, shell] = getWindowInfo(node);
const auto [app_id, app_class, shell, marks] =
getWindowInfo(node, config_["show-hidden-marks"].asBool());
int nb = node.size();
int floating_count = 0;
std::string workspace_layout = "";
@@ -232,20 +248,21 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
app_id,
app_class,
shell,
workspace_layout};
workspace_layout,
marks};
}
// iterate
auto [nb, f, id, name, app_id, app_class, shell, workspace_layout] =
auto [nb, f, id, name, app_id, app_class, shell, workspace_layout, marks] =
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace, node);
auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2] =
auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2, marks2] =
gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace, node);
// if ((id > 0 || ((id2 < 0 || name2.empty()) && id > -1)) && !name.empty()) {
if ((id > 0) || (id2 < 0 && id > -1)) {
return {nb, f, id, name, app_id, app_class, shell, workspace_layout};
return {nb, f, id, name, app_id, app_class, shell, workspace_layout, marks};
} else if (id2 > 0 && !name2.empty()) {
return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2};
return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2, marks2};
}
}
@@ -258,10 +275,12 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
std::string app_id = "";
std::string app_class = "";
std::string workspace_layout = "";
std::string marks = "";
if (all_leaf_nodes.first == 1) {
const auto single_child = getSingleChildNode(immediateParent);
if (single_child.has_value()) {
std::tie(app_id, app_class, workspace_layout) = getWindowInfo(single_child.value());
std::tie(app_id, app_class, workspace_layout, marks) =
getWindowInfo(single_child.value(), config_["show-hidden-marks"].asBool());
}
}
return {all_leaf_nodes.first,
@@ -273,13 +292,15 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu
app_id,
app_class,
workspace_layout,
immediateParent["layout"].asString()};
immediateParent["layout"].asString(),
marks};
}
return {0, 0, -1, "", "", "", "", ""};
return {0, 0, -1, "", "", "", "", "", ""};
}
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string,
std::string>
Window::getFocusedNode(const Json::Value& nodes, std::string& output) {
Json::Value placeholder = Json::Value::null;
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder);
+40 -21
View File
@@ -57,19 +57,18 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
box_.get_style_context()->add_class(MODULE_CLASS);
event_box_.add(box_);
if (config_["format-window-separator"].isString()) {
m_formatWindowSeperator = config_["format-window-separator"].asString();
m_formatWindowSeparator = config_["format-window-separator"].asString();
} else {
m_formatWindowSeperator = " ";
m_formatWindowSeparator = " ";
}
const Json::Value &windowRewrite = config["window-rewrite"];
const Json::Value &windowRewriteDefaultConfig = config["window-rewrite-default"];
m_windowRewriteDefault =
windowRewriteDefaultConfig.isString() ? windowRewriteDefaultConfig.asString() : "?";
m_windowRewriteRules = waybar::util::RegexCollection(
windowRewrite, m_windowRewriteDefault,
[](std::string &window_rule) { return windowRewritePriorityFunction(window_rule); });
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));
@@ -260,7 +259,9 @@ 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"].asString();
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()) {
@@ -270,7 +271,7 @@ void Workspaces::updateWindows(const Json::Value &node, std::string &windows) {
window = fmt::format(fmt::runtime(window), fmt::arg("name", title),
fmt::arg("class", windowClass));
windows.append(window);
windows.append(m_formatWindowSeperator);
windows.append(m_formatWindowSeparator);
}
}
for (const Json::Value &child : node["nodes"]) {
@@ -339,7 +340,7 @@ auto Workspaces::update() -> void {
fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), fmt::arg("value", output),
fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString()),
fmt::arg("windows",
windows.substr(0, windows.length() - m_formatWindowSeperator.length())),
windows.substr(0, windows.length() - m_formatWindowSeparator.length())),
fmt::arg("output", (*it)["output"].asString()));
}
if (!config_["disable-markup"].asBool()) {
@@ -493,16 +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 a workspace has a focused container then get_tree will say
// that the workspace itself isn't focused. Therefore we need to
// check if any of its nodes are focused as well.
bool focused = node["focused"].asBool() ||
std::any_of(node["nodes"].begin(), node["nodes"].end(),
[](const auto &child) { return child["focused"].asBool(); });
if (focused) {
if (is_focused_recursive(node)) {
button.show();
} else {
button.hide();
+53 -21
View File
@@ -16,6 +16,7 @@ SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value&
update_pending(false),
nr_failed_system(0),
nr_failed_user(0),
nr_failed(0),
last_status() {
if (config["hide-on-ok"].isBool()) {
hide_on_ok = config["hide-on-ok"].asBool();
@@ -67,11 +68,38 @@ auto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name,
}
}
void SystemdFailedUnits::updateData() {
update_pending = false;
void SystemdFailedUnits::RequestSystemState() {
auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> std::string {
try {
if (!proxy) return "unknown";
auto parameters = Glib::VariantContainerBase(
g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "SystemState"));
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
if (data && data.is_of_type(Glib::VariantType("(v)"))) {
Glib::VariantBase variant;
g_variant_get(data.gobj_copy(), "(v)", &variant);
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_STRING)) {
return g_variant_get_string(variant.gobj_copy(), NULL);
}
}
} catch (Glib::Error& e) {
spdlog::error("Failed to get {} state: {}", kind, e.what().c_str());
}
return "unknown";
};
system_state = load("systemwide", system_proxy);
user_state = load("user", user_proxy);
if (system_state == "running" && user_state == "running")
overall_state = "ok";
else
overall_state = "degraded";
}
void SystemdFailedUnits::RequestFailedUnits() {
auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> uint32_t {
try {
if (!proxy) return 0;
auto parameters = Glib::VariantContainerBase(
g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "NFailedUnits"));
Glib::VariantContainerBase data = proxy->call_sync("Get", parameters);
@@ -79,9 +107,7 @@ void SystemdFailedUnits::updateData() {
Glib::VariantBase variant;
g_variant_get(data.gobj_copy(), "(v)", &variant);
if (variant && variant.is_of_type(Glib::VARIANT_TYPE_UINT32)) {
uint32_t value = 0;
g_variant_get(variant.gobj_copy(), "u", &value);
return value;
return g_variant_get_uint32(variant.gobj_copy());
}
}
} catch (Glib::Error& e) {
@@ -90,40 +116,46 @@ void SystemdFailedUnits::updateData() {
return 0;
};
if (system_proxy) {
nr_failed_system = load("systemwide", system_proxy);
}
if (user_proxy) {
nr_failed_user = load("user", user_proxy);
}
nr_failed_system = load("systemwide", system_proxy);
nr_failed_user = load("user", user_proxy);
nr_failed = nr_failed_system + nr_failed_user;
}
void SystemdFailedUnits::updateData() {
update_pending = false;
RequestSystemState();
if (overall_state == "degraded") RequestFailedUnits();
dp.emit();
}
auto SystemdFailedUnits::update() -> void {
uint32_t nr_failed = nr_failed_system + nr_failed_user;
if (last_status == overall_state) return;
// Hide if needed.
if (nr_failed == 0 && hide_on_ok) {
if (overall_state == "ok" && hide_on_ok) {
event_box_.set_visible(false);
return;
}
if (!event_box_.get_visible()) {
event_box_.set_visible(true);
}
event_box_.set_visible(true);
// Set state class.
const std::string status = nr_failed == 0 ? "ok" : "degraded";
if (!last_status.empty() && label_.get_style_context()->has_class(last_status)) {
label_.get_style_context()->remove_class(last_status);
}
if (!label_.get_style_context()->has_class(status)) {
label_.get_style_context()->add_class(status);
if (!label_.get_style_context()->has_class(overall_state)) {
label_.get_style_context()->add_class(overall_state);
}
last_status = status;
last_status = overall_state;
label_.set_markup(fmt::format(
fmt::runtime(nr_failed == 0 ? format_ok : format_), fmt::arg("nr_failed", nr_failed),
fmt::arg("nr_failed_system", nr_failed_system), fmt::arg("nr_failed_user", nr_failed_user)));
fmt::arg("nr_failed_system", nr_failed_system), fmt::arg("nr_failed_user", nr_failed_user),
fmt::arg("system_state", system_state), fmt::arg("user_state", user_state),
fmt::arg("overall_state", overall_state)));
ALabel::update();
}
+21 -7
View File
@@ -75,7 +75,7 @@ waybar::modules::Temperature::Temperature(const std::string& id, const Json::Val
file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone);
}
// check if file_path_ can be used to retrive the temperature
// check if file_path_ can be used to retrieve the temperature
std::ifstream temp(file_path_);
if (!temp.is_open()) {
throw std::runtime_error("Can't open " + file_path_);
@@ -99,12 +99,17 @@ 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()) {
@@ -139,13 +144,17 @@ float waybar::modules::Temperature::getTemperature() {
auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0;
if (sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size,
NULL, 0) != 0) {
throw std::runtime_error(fmt::format(
"sysctl hw.acpi.thermal.tz{}.temperature or dev.cpu.{}.temperature failed", zone, zone));
// 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_);
@@ -165,6 +174,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();
+5 -4
View File
@@ -358,10 +358,12 @@ void UPower::resetDevices() {
void UPower::setDisplayDevice() {
std::lock_guard<std::mutex> guard{mutex_};
if (nativePath_.empty() && model_.empty()) {
// Unref current upDevice
if (upDevice_.upDevice != NULL) g_object_unref(upDevice_.upDevice);
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 {
@@ -386,7 +388,6 @@ void UPower::setDisplayDevice() {
}
// Unref current upDevice if it exists
if (displayDevice.upDevice != NULL) {
if (thisPtr->upDevice_.upDevice != NULL) g_object_unref(thisPtr->upDevice_.upDevice);
thisPtr->upDevice_ = displayDevice;
}
},
+445
View File
@@ -0,0 +1,445 @@
#include "modules/wayfire/backend.hpp"
#include <json/json.h>
#include <spdlog/spdlog.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <algorithm>
#include <bit>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <ranges>
#include <thread>
namespace waybar::modules::wayfire {
std::weak_ptr<IPC> IPC::instance;
// C++23: std::byteswap
inline auto byteswap(uint32_t x) -> uint32_t {
return (x & 0xff000000) >> 24 | (x & 0x00ff0000) >> 8 | (x & 0x0000ff00) << 8 |
(x & 0x000000ff) << 24;
}
auto pack_and_write(Sock& sock, std::string&& buf) -> void {
uint32_t len = buf.size();
if constexpr (std::endian::native != std::endian::little) len = byteswap(len);
(void)write(sock.fd, &len, 4);
(void)write(sock.fd, buf.data(), buf.size());
}
auto read_exact(Sock& sock, size_t n) -> std::string {
auto buf = std::string(n, 0);
for (size_t i = 0; i < n;) i += read(sock.fd, &buf[i], n - i);
return buf;
}
// https://github.com/WayfireWM/pywayfire/blob/69b7c21/wayfire/ipc.py#L438
inline auto is_mapped_toplevel_view(const Json::Value& view) -> bool {
return view["mapped"].asBool() && view["role"] != "desktop-environment" &&
view["pid"].asInt() != -1;
}
auto State::Wset::count_ws(const Json::Value& pos) -> Workspace& {
auto x = pos["x"].asInt();
auto y = pos["y"].asInt();
return wss.at(ws_w * y + x);
}
auto State::Wset::locate_ws(const Json::Value& geo) -> Workspace& {
return const_cast<Workspace&>(std::as_const(*this).locate_ws(geo));
}
auto State::Wset::locate_ws(const Json::Value& geo) const -> const Workspace& {
const auto& out = output.value().get();
auto [qx, rx] = std::div(geo["x"].asInt(), out.w);
auto [qy, ry] = std::div(geo["y"].asInt(), out.h);
auto x = std::max(0, (int)ws_x + qx - int{rx < 0});
auto y = std::max(0, (int)ws_y + qy - int{ry < 0});
return wss.at(ws_w * y + x);
}
auto State::update_view(const Json::Value& view) -> void {
auto id = view["id"].asUInt();
// erase old view information
if (views.contains(id)) {
auto& old_view = views.at(id);
auto& ws = wsets.at(old_view["wset-index"].asUInt()).locate_ws(old_view["geometry"]);
ws.num_views--;
if (old_view["sticky"].asBool()) ws.num_sticky_views--;
views.erase(id);
}
// insert or assign new view information
if (is_mapped_toplevel_view(view)) {
try {
// view["wset-index"] could be messed up
auto& ws = wsets.at(view["wset-index"].asUInt()).locate_ws(view["geometry"]);
ws.num_views++;
if (view["sticky"].asBool()) ws.num_sticky_views++;
views.emplace(id, view);
} catch (const std::exception&) {
}
}
}
auto IPC::get_instance() -> std::shared_ptr<IPC> {
auto p = instance.lock();
if (!p) instance = p = std::shared_ptr<IPC>(new IPC);
return p;
}
auto IPC::connect() -> Sock {
auto* path = std::getenv("WAYFIRE_SOCKET");
if (path == nullptr) {
throw std::runtime_error{"Wayfire IPC: ipc not available"};
}
auto sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1) {
throw std::runtime_error{"Wayfire IPC: socket() failed"};
}
auto addr = sockaddr_un{.sun_family = AF_UNIX};
std::strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
if (::connect(sock, (const sockaddr*)&addr, sizeof(addr)) == -1) {
close(sock);
throw std::runtime_error{"Wayfire IPC: connect() failed"};
}
return {sock};
}
auto IPC::receive(Sock& sock) -> Json::Value {
auto len = *reinterpret_cast<uint32_t*>(read_exact(sock, 4).data());
if constexpr (std::endian::native != std::endian::little) len = byteswap(len);
auto buf = read_exact(sock, len);
Json::Value json;
std::string err;
auto* reader = reader_builder.newCharReader();
if (!reader->parse(&*buf.begin(), &*buf.end(), &json, &err)) {
throw std::runtime_error{"Wayfire IPC: parse json failed: " + err};
}
return json;
}
auto IPC::send(const std::string& method, Json::Value&& data) -> Json::Value {
spdlog::debug("Wayfire IPC: send method \"{}\"", method);
auto sock = connect();
Json::Value json;
json["method"] = method;
json["data"] = std::move(data);
pack_and_write(sock, Json::writeString(writer_builder, json));
auto res = receive(sock);
root_event_handler(method, res);
return res;
}
auto IPC::start() -> void {
spdlog::info("Wayfire IPC: starting");
// init state
send("window-rules/list-outputs", {});
send("window-rules/list-wsets", {});
send("window-rules/list-views", {});
send("window-rules/get-focused-view", {});
send("window-rules/get-focused-output", {});
std::thread([&] {
auto sock = connect();
{
Json::Value json;
json["method"] = "window-rules/events/watch";
pack_and_write(sock, Json::writeString(writer_builder, json));
if (receive(sock)["result"] != "ok") {
spdlog::error(
"Wayfire IPC: method \"window-rules/events/watch\""
" have failed");
return;
}
}
while (auto json = receive(sock)) {
auto ev = json["event"].asString();
spdlog::debug("Wayfire IPC: received event \"{}\"", ev);
root_event_handler(ev, json);
}
}).detach();
}
auto IPC::register_handler(const std::string& event, const EventHandler& handler) -> void {
auto _ = std::lock_guard{handlers_mutex};
handlers.emplace_back(event, handler);
}
auto IPC::unregister_handler(EventHandler& handler) -> void {
auto _ = std::lock_guard{handlers_mutex};
handlers.remove_if([&](auto& e) { return &e.second.get() == &handler; });
}
auto IPC::root_event_handler(const std::string& event, const Json::Value& data) -> void {
bool new_output_detected;
{
auto _ = lock_state();
update_state_handler(event, data);
new_output_detected = state.new_output_detected;
state.new_output_detected = false;
}
if (new_output_detected) {
send("window-rules/list-outputs", {});
send("window-rules/list-wsets", {});
}
{
auto _ = std::lock_guard{handlers_mutex};
for (const auto& [_event, handler] : handlers)
if (_event == event) handler(event);
}
}
auto IPC::update_state_handler(const std::string& event, const Json::Value& data) -> void {
// IPC events
// https://github.com/WayfireWM/wayfire/blob/053b222/plugins/ipc-rules/ipc-events.hpp#L108-L125
/*
[x] view-mapped
[x] view-unmapped
[-] view-set-output // for detect new output
[ ] view-geometry-changed // -> view-workspace-changed
[x] view-wset-changed
[x] view-focused
[x] view-title-changed
[x] view-app-id-changed
[x] plugin-activation-state-changed
[x] output-gain-focus
[ ] view-tiled
[ ] view-minimized
[ ] view-fullscreened
[x] view-sticky
[x] view-workspace-changed
[x] output-wset-changed
[x] wset-workspace-changed
*/
if (event == "view-mapped") {
// data: { event, view }
state.update_view(data["view"]);
return;
}
if (event == "view-unmapped") {
// data: { event, view }
try {
// data["view"]["wset-index"] could be messed up
state.update_view(data["view"]);
state.maybe_empty_focus_wset_idx = data["view"]["wset-index"].asUInt();
} catch (const std::exception&) {
}
return;
}
if (event == "view-set-output") {
// data: { event, output?, view }
// new output event
if (!state.outputs.contains(data["view"]["output-name"].asString())) {
state.new_output_detected = true;
}
return;
}
if (event == "view-wset-changed") {
// data: { event, old-wset: wset, new-wset: wset, view }
state.maybe_empty_focus_wset_idx = data["old-wset"]["index"].asUInt();
state.update_view(data["view"]);
return;
}
if (event == "view-focused") {
// data: { event, view? }
if (const auto& view = data["view"]) {
try {
// view["wset-index"] could be messed up
auto& wset = state.wsets.at(view["wset-index"].asUInt());
wset.focused_view_id = view["id"].asUInt();
} catch (const std::exception&) {
}
} else {
// focused to null
if (state.wsets.contains(state.maybe_empty_focus_wset_idx))
state.wsets.at(state.maybe_empty_focus_wset_idx).focused_view_id = {};
}
return;
}
if (event == "view-title-changed" || event == "view-app-id-changed" || event == "view-sticky") {
// data: { event, view }
state.update_view(data["view"]);
return;
}
if (event == "plugin-activation-state-changed") {
// data: { event, plugin: name, state: bool, output: id, output-data: output }
auto plugin = data["plugin"].asString();
auto plugin_state = data["state"].asBool();
if (plugin == "vswitch") {
state.vswitching = plugin_state;
if (plugin_state) {
state.maybe_empty_focus_wset_idx = data["output-data"]["wset-index"].asUInt();
}
}
return;
}
if (event == "output-gain-focus") {
// data: { event, output }
state.focused_output_name = data["output"]["name"].asString();
return;
}
if (event == "view-workspace-changed") {
// data: { event, from: point, to: point, view }
if (state.vswitching) {
if (state.vswitch_sticky_view_id == 0) {
auto& wset = state.wsets.at(data["view"]["wset-index"].asUInt());
auto& old_ws = wset.locate_ws(state.views.at(data["view"]["id"].asUInt())["geometry"]);
auto& new_ws = wset.count_ws(data["to"]);
old_ws.num_views--;
new_ws.num_views++;
if (data["view"]["sticky"].asBool()) {
old_ws.num_sticky_views--;
new_ws.num_sticky_views++;
}
state.update_view(data["view"]);
state.vswitch_sticky_view_id = data["view"]["id"].asUInt();
} else {
state.vswitch_sticky_view_id = {};
}
return;
}
state.update_view(data["view"]);
return;
}
if (event == "output-wset-changed") {
// data: { event, new-wset: wset.name, output: id, new-wset-data: wset, output-data: output }
auto& output = state.outputs.at(data["output-data"]["name"].asString());
auto wset_idx = data["new-wset-data"]["index"].asUInt();
state.wsets.at(wset_idx).output = output;
output.wset_idx = wset_idx;
return;
}
if (event == "wset-workspace-changed") {
// data: { event, previous-workspace: point, new-workspace: point,
// output: id, wset: wset.name, output-data: output, wset-data: wset }
auto wset_idx = data["wset-data"]["index"].asUInt();
auto& wset = state.wsets.at(wset_idx);
wset.ws_x = data["new-workspace"]["x"].asUInt();
wset.ws_y = data["new-workspace"]["y"].asUInt();
// correct existing views geometry
auto& out = wset.output.value().get();
auto dx = (int)out.w * ((int)wset.ws_x - data["previous-workspace"]["x"].asInt());
auto dy = (int)out.h * ((int)wset.ws_y - data["previous-workspace"]["y"].asInt());
for (auto& [_, view] : state.views) {
if (view["wset-index"].asUInt() == wset_idx &&
view["id"].asUInt() != state.vswitch_sticky_view_id) {
view["geometry"]["x"] = view["geometry"]["x"].asInt() - dx;
view["geometry"]["y"] = view["geometry"]["y"].asInt() - dy;
}
}
return;
}
// IPC responses
// https://github.com/WayfireWM/wayfire/blob/053b222/plugins/ipc-rules/ipc-rules.cpp#L27-L37
if (event == "window-rules/list-views") {
// data: [ view ]
state.views.clear();
for (auto& [_, wset] : state.wsets) std::ranges::fill(wset.wss, State::Workspace{});
for (const auto& view : data | std::views::filter(is_mapped_toplevel_view)) {
state.update_view(view);
}
return;
}
if (event == "window-rules/list-outputs") {
// data: [ output ]
state.outputs.clear();
for (const auto& output_data : data) {
state.outputs.emplace(output_data["name"].asString(),
State::Output{
.id = output_data["id"].asUInt(),
.w = output_data["geometry"]["width"].asUInt(),
.h = output_data["geometry"]["height"].asUInt(),
.wset_idx = output_data["wset-index"].asUInt(),
});
}
return;
}
if (event == "window-rules/list-wsets") {
// data: [ wset ]
std::unordered_map<size_t, State::Wset> wsets;
for (const auto& wset_data : data) {
auto wset_idx = wset_data["index"].asUInt();
auto output_name = wset_data["output-name"].asString();
auto output = state.outputs.contains(output_name)
? std::optional{std::ref(state.outputs.at(output_name))}
: std::nullopt;
const auto& ws_data = wset_data["workspace"];
auto ws_w = ws_data["grid_width"].asUInt();
auto ws_h = ws_data["grid_height"].asUInt();
wsets.emplace(wset_idx, State::Wset{
.output = output,
.wss = std::vector<State::Workspace>(ws_w * ws_h),
.ws_w = ws_w,
.ws_h = ws_h,
.ws_x = ws_data["x"].asUInt(),
.ws_y = ws_data["y"].asUInt(),
});
if (state.wsets.contains(wset_idx)) {
auto& old_wset = state.wsets.at(wset_idx);
auto& new_wset = wsets.at(wset_idx);
new_wset.wss = std::move(old_wset.wss);
new_wset.focused_view_id = old_wset.focused_view_id;
}
}
state.wsets = std::move(wsets);
return;
}
if (event == "window-rules/get-focused-view") {
// data: { ok, info: view? }
if (const auto& view = data["info"]) {
auto& wset = state.wsets.at(view["wset-index"].asUInt());
wset.focused_view_id = view["id"].asUInt();
state.update_view(view);
}
return;
}
if (event == "window-rules/get-focused-output") {
// data: { ok, info: output }
state.focused_output_name = data["info"]["name"].asString();
return;
}
}
} // namespace waybar::modules::wayfire
+77
View File
@@ -0,0 +1,77 @@
#include "modules/wayfire/window.hpp"
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include <spdlog/spdlog.h>
#include "util/rewrite_string.hpp"
#include "util/sanitize_str.hpp"
namespace waybar::modules::wayfire {
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: AAppIconLabel(config, "window", id, "{title}", 0, true),
ipc{IPC::get_instance()},
handler{[this](const auto&) { dp.emit(); }},
bar_{bar} {
ipc->register_handler("view-unmapped", handler);
ipc->register_handler("view-focused", handler);
ipc->register_handler("view-title-changed", handler);
ipc->register_handler("view-app-id-changed", handler);
ipc->register_handler("window-rules/get-focused-view", handler);
dp.emit();
}
Window::~Window() { ipc->unregister_handler(handler); }
auto Window::update() -> void {
update_icon_label();
AAppIconLabel::update();
}
auto Window::update_icon_label() -> void {
auto _ = ipc->lock_state();
const auto& output = ipc->get_outputs().at(bar_.output->name);
const auto& wset = ipc->get_wsets().at(output.wset_idx);
const auto& views = ipc->get_views();
auto ctx = bar_.window.get_style_context();
if (views.contains(wset.focused_view_id)) {
const auto& view = views.at(wset.focused_view_id);
auto title = view["title"].asString();
auto app_id = view["app-id"].asString();
// update label
label_.set_markup(waybar::util::rewriteString(
fmt::format(fmt::runtime(format_), fmt::arg("title", waybar::util::sanitize_string(title)),
fmt::arg("app_id", waybar::util::sanitize_string(app_id))),
config_["rewrite"]));
// update window#waybar.solo
if (wset.locate_ws(view["geometry"]).num_views > 1)
ctx->remove_class("solo");
else
ctx->add_class("solo");
// update window#waybar.<app_id>
ctx->remove_class(old_app_id_);
ctx->add_class(old_app_id_ = app_id);
// update window#waybar.empty
ctx->remove_class("empty");
//
updateAppIconName(app_id, "");
label_.show();
} else {
ctx->add_class("empty");
updateAppIconName("", "");
label_.hide();
}
}
} // namespace waybar::modules::wayfire
+183
View File
@@ -0,0 +1,183 @@
#include "modules/wayfire/workspaces.hpp"
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include <spdlog/spdlog.h>
#include <string>
#include <utility>
#include "modules/wayfire/backend.hpp"
namespace waybar::modules::wayfire {
Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config)
: AModule{config, "workspaces", id, false, !config["disable-scroll"].asBool()},
ipc{IPC::get_instance()},
handler{[this](const auto&) { dp.emit(); }},
bar_{bar} {
// init box_
box_.set_name("workspaces");
if (!id.empty()) box_.get_style_context()->add_class(id);
box_.get_style_context()->add_class(MODULE_CLASS);
event_box_.add(box_);
// scroll events
if (!config_["disable-scroll"].asBool()) {
auto& target = config_["enable-bar-scroll"].asBool() ? const_cast<Bar&>(bar_).window
: dynamic_cast<Gtk::Widget&>(box_);
target.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
target.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
}
// listen events
ipc->register_handler("view-mapped", handler);
ipc->register_handler("view-unmapped", handler);
ipc->register_handler("view-wset-changed", handler);
ipc->register_handler("output-gain-focus", handler);
ipc->register_handler("view-sticky", handler);
ipc->register_handler("view-workspace-changed", handler);
ipc->register_handler("output-wset-changed", handler);
ipc->register_handler("wset-workspace-changed", handler);
ipc->register_handler("window-rules/list-views", handler);
ipc->register_handler("window-rules/list-outputs", handler);
ipc->register_handler("window-rules/list-wsets", handler);
ipc->register_handler("window-rules/get-focused-output", handler);
// initial render
dp.emit();
}
Workspaces::~Workspaces() { ipc->unregister_handler(handler); }
auto Workspaces::handleScroll(GdkEventScroll* e) -> bool {
// Ignore emulated scroll events on window
if (gdk_event_get_pointer_emulated((GdkEvent*)e) != 0) return false;
auto dir = AModule::getScrollDir(e);
if (dir == SCROLL_DIR::NONE) return true;
int delta;
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT)
delta = 1;
else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT)
delta = -1;
else
return true;
// cycle workspace
Json::Value data;
{
auto _ = ipc->lock_state();
const auto& output = ipc->get_outputs().at(bar_.output->name);
const auto& wset = ipc->get_wsets().at(output.wset_idx);
auto n = wset.ws_w * wset.ws_h;
auto i = (wset.ws_idx() + delta + n) % n;
data["x"] = i % wset.ws_w;
data["y"] = i / wset.ws_h;
data["output-id"] = output.id;
}
ipc->send("vswitch/set-workspace", std::move(data));
return true;
}
auto Workspaces::update() -> void {
update_box();
AModule::update();
}
auto Workspaces::update_box() -> void {
auto _ = ipc->lock_state();
const auto& output_name = bar_.output->name;
const auto& output = ipc->get_outputs().at(output_name);
const auto& wset = ipc->get_wsets().at(output.wset_idx);
auto output_focused = ipc->get_focused_output_name() == output_name;
auto ws_w = wset.ws_w;
auto ws_h = wset.ws_h;
auto num_wss = ws_w * ws_h;
// add buttons for new workspaces
for (auto i = buttons_.size(); i < num_wss; i++) {
auto& btn = buttons_.emplace_back("");
box_.pack_start(btn, false, false, 0);
btn.set_relief(Gtk::RELIEF_NONE);
if (!config_["disable-click"].asBool()) {
btn.signal_pressed().connect([=, this] {
Json::Value data;
data["x"] = i % ws_w;
data["y"] = i / ws_h;
data["output-id"] = output.id;
ipc->send("vswitch/set-workspace", std::move(data));
});
}
}
// remove buttons for removed workspaces
buttons_.resize(num_wss);
// update buttons
for (size_t i = 0; i < num_wss; i++) {
const auto& ws = wset.wss[i];
auto& btn = buttons_[i];
auto ctx = btn.get_style_context();
auto ws_focused = i == wset.ws_idx();
auto ws_empty = ws.num_views == 0;
// update #workspaces button.focused
if (ws_focused)
ctx->add_class("focused");
else
ctx->remove_class("focused");
// update #workspaces button.empty
if (ws_empty)
ctx->add_class("empty");
else
ctx->remove_class("empty");
// update #workspaces button.current_output
if (output_focused)
ctx->add_class("current_output");
else
ctx->remove_class("current_output");
// update label
auto label = std::to_string(i + 1);
if (config_["format"].isString()) {
auto format = config_["format"].asString();
auto ws_idx = std::to_string(i + 1);
const auto& icons = config_["format-icons"];
std::string icon;
if (!icons)
icon = ws_idx;
else if (ws_focused && icons["focused"])
icon = icons["focused"].asString();
else if (icons[ws_idx])
icon = icons[ws_idx].asString();
else if (icons["default"])
icon = icons["default"].asString();
else
icon = ws_idx;
label = fmt::format(fmt::runtime(format), fmt::arg("icon", icon), fmt::arg("index", ws_idx),
fmt::arg("output", output_name));
}
if (!config_["disable-markup"].asBool())
static_cast<Gtk::Label*>(btn.get_children()[0])->set_markup(label);
else
btn.set_label(label);
//
if (config_["current-only"].asBool() && i != wset.ws_idx())
btn.hide();
else
btn.show();
}
}
} // namespace waybar::modules::wayfire
+62 -38
View File
@@ -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,22 +18,28 @@ 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(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");
spdlog::debug("[{}]: connecting to pipewire...", name_);
prepare(this);
spdlog::debug("[{}]: connecting to pipewire: '{}'...", name_, type_);
if (wp_core_connect(wp_core_) == 0) {
spdlog::error("[{}]: Could not connect to PipeWire", name_);
spdlog::error("[{}]: Could not connect to PipeWire: '{}'", name_, type_);
throw std::runtime_error("Could not connect to PipeWire\n");
}
spdlog::debug("[{}]: connected!", name_);
spdlog::debug("[{}]: {} connected!", name_, type_);
g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this);
@@ -39,6 +47,7 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
}
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_);
@@ -46,13 +55,15 @@ waybar::modules::Wireplumber::~Wireplumber() {
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;
}
@@ -80,7 +91,7 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber*
self->node_name_ = nick != nullptr ? nick
: description != nullptr ? description
: "Unknown node name";
spdlog::debug("[{}]: Updating node name to: {}", self->name_, self->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) {
@@ -88,7 +99,8 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se
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;
}
@@ -109,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, nullptr));
if (node == nullptr) {
spdlog::warn("[{}]: (onMixerChanged) - Object with id {} not found", self->name_, id);
// 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;
}
@@ -123,26 +144,27 @@ 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 defaultNodeId;
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &defaultNodeId);
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &defaultNodeId);
if (!isValidNodeId(defaultNodeId)) {
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node change.", self->name_,
defaultNodeId);
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring '{}' node change.", self->name_,
defaultNodeId, self->type_);
return;
}
@@ -151,8 +173,8 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir
"=u", defaultNodeId, nullptr));
if (node == nullptr) {
spdlog::warn("[{}]: (onDefaultNodesApiChanged) - Object with id {} not found", self->name_,
defaultNodeId);
spdlog::warn("[{}]: (onDefaultNodesApiChanged: {}) - Object with id {} not found", self->name_,
self->type_, defaultNodeId);
return;
}
@@ -160,21 +182,22 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir
wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name");
spdlog::debug(
"[{}]: (onDefaultNodesApiChanged) - got the following default node: Node(name: {}, id: {})",
self->name_, defaultNodeName, defaultNodeId);
"[{}]: (onDefaultNodesApiChanged: {}) - got the following default node: Node(name: {}, id: "
"{})",
self->name_, self->type_, defaultNodeName, defaultNodeId);
if (g_strcmp0(self->default_node_name_, defaultNodeName) == 0 &&
self->node_id_ == defaultNodeId) {
spdlog::debug(
"[{}]: (onDefaultNodesApiChanged) - Default node has not changed. Node(name: {}, id: {}). "
"Ignoring.",
self->name_, self->default_node_name_, defaultNodeId);
"[{}]: (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_, defaultNodeName, defaultNodeId);
"[{}]: (onDefaultNodesApiChanged: {}) - Default node changed to -> Node(name: {}, id: {})",
self->name_, self->type_, defaultNodeName, defaultNodeId);
g_free(self->default_node_name_);
self->default_node_name_ = g_strdup(defaultNodeName);
@@ -200,13 +223,14 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir
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_ != nullptr) {
spdlog::debug("[{}]: (onObjectManagerInstalled) - default configured node name: {} and id: {}",
self->name_, self->default_node_name_, self->node_id_);
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_);
@@ -243,10 +267,10 @@ void waybar::modules::Wireplumber::activatePlugins() {
}
}
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", nullptr);
"=s", self->type_, nullptr);
}
void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res,
@@ -275,7 +299,7 @@ void waybar::modules::Wireplumber::onMixerApiLoaded(WpObject* p, GAsyncResult* r
gboolean success = FALSE;
g_autoptr(GError) error = nullptr;
success = wp_core_load_component_finish(self->wp_core_, res, nullptr);
success = wp_core_load_component_finish(self->wp_core_, res, &error);
if (success == FALSE) {
spdlog::error("[{}]: mixer API load failed", self->name_);
+33
View File
@@ -383,8 +383,38 @@ 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() {
@@ -682,6 +712,9 @@ void Task::update() {
fmt::format(fmt::runtime(format_tooltip_), fmt::arg("title", title), fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)));
txt = waybar::util::rewriteString(txt, config_["rewrite"]);
if (markup)
button.set_tooltip_markup(txt);
else
+2 -2
View File
@@ -118,8 +118,8 @@ auto WorkspaceManager::sort_workspaces() -> void {
}
}
auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name,
uint32_t version) -> void {
auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version)
-> void {
if (workspace_manager_) {
spdlog::warn("Register workspace manager again although already registered!");
return;