Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
#include "AIconLabel.hpp"
|
||||
|
||||
#include <gdkmm/pixbuf.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace waybar {
|
||||
|
||||
@ -17,14 +18,36 @@ AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
|
||||
box_.set_orientation(Gtk::Orientation::ORIENTATION_HORIZONTAL);
|
||||
int rot = 0;
|
||||
|
||||
if (config_["rotate"].isUInt()) {
|
||||
rot = config["rotate"].asUInt() % 360;
|
||||
if ((rot % 90) != 00) rot = 0;
|
||||
rot /= 90;
|
||||
}
|
||||
|
||||
if ((rot % 2) == 0)
|
||||
box_.set_orientation(Gtk::Orientation::ORIENTATION_HORIZONTAL);
|
||||
else
|
||||
box_.set_orientation(Gtk::Orientation::ORIENTATION_VERTICAL);
|
||||
box_.set_name(name);
|
||||
|
||||
int spacing = config_["icon-spacing"].isInt() ? config_["icon-spacing"].asInt() : 8;
|
||||
box_.set_spacing(spacing);
|
||||
|
||||
box_.add(image_);
|
||||
box_.add(label_);
|
||||
bool swap_icon_label = false;
|
||||
if (not config_["swap-icon-label"].isBool())
|
||||
spdlog::warn("'swap-icon-label' must be a bool.");
|
||||
else
|
||||
swap_icon_label = config_["swap-icon-label"].asBool();
|
||||
|
||||
if ((rot == 0 || rot == 3) ^ swap_icon_label) {
|
||||
box_.add(image_);
|
||||
box_.add(label_);
|
||||
} else {
|
||||
box_.add(label_);
|
||||
box_.add(image_);
|
||||
}
|
||||
|
||||
event_box_.add(box_);
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
|
||||
}
|
||||
|
||||
AModule::~AModule() {
|
||||
for (const auto& pid : pid_) {
|
||||
for (const auto& pid : pid_children_) {
|
||||
if (pid != -1) {
|
||||
killpg(pid, SIGTERM);
|
||||
}
|
||||
@ -93,15 +93,15 @@ AModule::~AModule() {
|
||||
auto AModule::update() -> void {
|
||||
// Run user-provided update handler if configured
|
||||
if (config_["on-update"].isString()) {
|
||||
pid_.push_back(util::command::forkExec(config_["on-update"].asString()));
|
||||
pid_children_.push_back(util::command::forkExec(config_["on-update"].asString()));
|
||||
}
|
||||
}
|
||||
// Get mapping between event name and module action name
|
||||
// Then call overrided doAction in order to call appropriate module action
|
||||
// Then call overridden doAction in order to call appropriate module action
|
||||
auto AModule::doAction(const std::string& name) -> void {
|
||||
if (!name.empty()) {
|
||||
const std::map<std::string, std::string>::const_iterator& recA{eventActionMap_.find(name)};
|
||||
// Call overrided action if derrived class has implemented it
|
||||
// Call overridden action if derived class has implemented it
|
||||
if (recA != eventActionMap_.cend() && name != recA->second) this->doAction(recA->second);
|
||||
}
|
||||
}
|
||||
@ -182,7 +182,7 @@ bool AModule::handleUserEvent(GdkEventButton* const& e) {
|
||||
format.clear();
|
||||
}
|
||||
if (!format.empty()) {
|
||||
pid_.push_back(util::command::forkExec(format));
|
||||
pid_children_.push_back(util::command::forkExec(format));
|
||||
}
|
||||
dp.emit();
|
||||
return true;
|
||||
@ -267,7 +267,7 @@ bool AModule::handleScroll(GdkEventScroll* e) {
|
||||
this->AModule::doAction(eventName);
|
||||
// Second call user scripts
|
||||
if (config_[eventName].isString())
|
||||
pid_.push_back(util::command::forkExec(config_[eventName].asString()));
|
||||
pid_children_.push_back(util::command::forkExec(config_[eventName].asString()));
|
||||
|
||||
dp.emit();
|
||||
return true;
|
||||
|
@ -545,7 +545,6 @@ auto waybar::Bar::setupWidgets() -> void {
|
||||
if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) {
|
||||
box_.set_center_widget(center_);
|
||||
} else {
|
||||
spdlog::error("No fixed center_");
|
||||
box_.pack_start(center_, true, expand_center);
|
||||
}
|
||||
}
|
||||
@ -569,13 +568,13 @@ auto waybar::Bar::setupWidgets() -> void {
|
||||
|
||||
if (!no_center) {
|
||||
for (auto const& module : modules_center_) {
|
||||
center_.pack_start(*module, false, false);
|
||||
center_.pack_start(*module, module->expandEnabled(), module->expandEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
std::reverse(modules_right_.begin(), modules_right_.end());
|
||||
for (auto const& module : modules_right_) {
|
||||
right_.pack_end(*module, false, false);
|
||||
right_.pack_end(*module, module->expandEnabled(), module->expandEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << e.what() << '\n';
|
||||
spdlog::warn("caught exception in zxdg_output_v1_listener::done: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,7 +97,7 @@ void waybar::Client::handleOutputName(void *data, struct zxdg_output_v1 * /*xdg_
|
||||
auto &output = client->getOutput(data);
|
||||
output.name = name;
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << e.what() << '\n';
|
||||
spdlog::warn("caught exception in zxdg_output_v1_listener::name: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,13 +106,13 @@ void waybar::Client::handleOutputDescription(void *data, struct zxdg_output_v1 *
|
||||
auto *client = waybar::Client::inst();
|
||||
try {
|
||||
auto &output = client->getOutput(data);
|
||||
const char *open_paren = strrchr(description, '(');
|
||||
|
||||
// Description format: "identifier (name)"
|
||||
size_t identifier_length = open_paren - description;
|
||||
output.identifier = std::string(description, identifier_length - 1);
|
||||
auto s = std::string(description);
|
||||
auto pos = s.find(" (");
|
||||
output.identifier = pos != std::string::npos ? s.substr(0, pos) : s;
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << e.what() << '\n';
|
||||
spdlog::warn("caught exception in zxdg_output_v1_listener::description: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "modules/hyprland/language.hpp"
|
||||
#include "modules/hyprland/submap.hpp"
|
||||
#include "modules/hyprland/window.hpp"
|
||||
#include "modules/hyprland/windowcount.hpp"
|
||||
#include "modules/hyprland/workspaces.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_NIRI
|
||||
@ -41,6 +42,10 @@
|
||||
#include "modules/niri/window.hpp"
|
||||
#include "modules/niri/workspaces.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_WAYFIRE
|
||||
#include "modules/wayfire/window.hpp"
|
||||
#include "modules/wayfire/workspaces.hpp"
|
||||
#endif
|
||||
#if defined(__FreeBSD__) || defined(__linux__)
|
||||
#include "modules/battery.hpp"
|
||||
#endif
|
||||
@ -109,6 +114,9 @@
|
||||
#ifdef HAVE_SYSTEMD_MONITOR
|
||||
#include "modules/systemd_failed_units.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_LIBGPS
|
||||
#include "modules/gps.hpp"
|
||||
#endif
|
||||
#include "modules/cffi.hpp"
|
||||
#include "modules/custom.hpp"
|
||||
#include "modules/image.hpp"
|
||||
@ -201,6 +209,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
|
||||
if (ref == "hyprland/window") {
|
||||
return new waybar::modules::hyprland::Window(id, bar_, config_[name]);
|
||||
}
|
||||
if (ref == "hyprland/windowcount") {
|
||||
return new waybar::modules::hyprland::WindowCount(id, bar_, config_[name]);
|
||||
}
|
||||
if (ref == "hyprland/language") {
|
||||
return new waybar::modules::hyprland::Language(id, bar_, config_[name]);
|
||||
}
|
||||
@ -221,6 +232,14 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
|
||||
if (ref == "niri/workspaces") {
|
||||
return new waybar::modules::niri::Workspaces(id, bar_, config_[name]);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_WAYFIRE
|
||||
if (ref == "wayfire/window") {
|
||||
return new waybar::modules::wayfire::Window(id, bar_, config_[name]);
|
||||
}
|
||||
if (ref == "wayfire/workspaces") {
|
||||
return new waybar::modules::wayfire::Workspaces(id, bar_, config_[name]);
|
||||
}
|
||||
#endif
|
||||
if (ref == "idle_inhibitor") {
|
||||
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
|
||||
@ -331,6 +350,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
|
||||
if (ref == "systemd-failed-units") {
|
||||
return new waybar::modules::SystemdFailedUnits(id, config_[name]);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_LIBGPS
|
||||
if (ref == "gps") {
|
||||
return new waybar::modules::Gps(id, config_[name]);
|
||||
}
|
||||
#endif
|
||||
if (ref == "temperature") {
|
||||
return new waybar::modules::Temperature(id, config_[name]);
|
||||
|
170
src/main.cpp
170
src/main.cpp
@ -1,3 +1,4 @@
|
||||
#include <fcntl.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
@ -7,66 +8,115 @@
|
||||
#include <mutex>
|
||||
|
||||
#include "client.hpp"
|
||||
#include "util/SafeSignal.hpp"
|
||||
|
||||
std::mutex reap_mtx;
|
||||
std::list<pid_t> reap;
|
||||
volatile bool reload;
|
||||
|
||||
void* signalThread(void* args) {
|
||||
int err;
|
||||
int signum;
|
||||
sigset_t mask;
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGCHLD);
|
||||
static int signal_pipe_write_fd;
|
||||
|
||||
// Write a single signal to `signal_pipe_write_fd`.
|
||||
// This function is set as a signal handler, so it must be async-signal-safe.
|
||||
static void writeSignalToPipe(int signum) {
|
||||
ssize_t amt = write(signal_pipe_write_fd, &signum, sizeof(int));
|
||||
|
||||
// There's not much we can safely do inside of a signal handler.
|
||||
// Let's just ignore any errors.
|
||||
(void)amt;
|
||||
}
|
||||
|
||||
// This initializes `signal_pipe_write_fd`, and sets up signal handlers.
|
||||
//
|
||||
// This function will run forever, emitting every `SIGUSR1`, `SIGUSR2`,
|
||||
// `SIGINT`, `SIGCHLD`, and `SIGRTMIN + 1`...`SIGRTMAX` signal received
|
||||
// to `signal_handler`.
|
||||
static void catchSignals(waybar::SafeSignal<int>& signal_handler) {
|
||||
int fd[2];
|
||||
pipe(fd);
|
||||
|
||||
int signal_pipe_read_fd = fd[0];
|
||||
signal_pipe_write_fd = fd[1];
|
||||
|
||||
// This pipe should be able to buffer ~thousands of signals. If it fills up,
|
||||
// we'll drop signals instead of blocking.
|
||||
|
||||
// We can't allow the write end to block because we'll be writing to it in a
|
||||
// signal handler, which could interrupt the loop that's reading from it and
|
||||
// deadlock.
|
||||
|
||||
fcntl(signal_pipe_write_fd, F_SETFL, O_NONBLOCK);
|
||||
|
||||
std::signal(SIGUSR1, writeSignalToPipe);
|
||||
std::signal(SIGUSR2, writeSignalToPipe);
|
||||
std::signal(SIGINT, writeSignalToPipe);
|
||||
std::signal(SIGCHLD, writeSignalToPipe);
|
||||
|
||||
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
|
||||
std::signal(sig, writeSignalToPipe);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
err = sigwait(&mask, &signum);
|
||||
if (err != 0) {
|
||||
spdlog::error("sigwait failed: {}", strerror(errno));
|
||||
int signum;
|
||||
ssize_t amt = read(signal_pipe_read_fd, &signum, sizeof(int));
|
||||
if (amt < 0) {
|
||||
spdlog::error("read from signal pipe failed with error {}, closing thread", strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
if (amt != sizeof(int)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (signum) {
|
||||
case SIGCHLD:
|
||||
spdlog::debug("Received SIGCHLD in signalThread");
|
||||
if (!reap.empty()) {
|
||||
reap_mtx.lock();
|
||||
for (auto it = reap.begin(); it != reap.end(); ++it) {
|
||||
if (waitpid(*it, nullptr, WNOHANG) == *it) {
|
||||
spdlog::debug("Reaped child with PID: {}", *it);
|
||||
it = reap.erase(it);
|
||||
}
|
||||
}
|
||||
reap_mtx.unlock();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
spdlog::debug("Received signal with number {}, but not handling", signum);
|
||||
break;
|
||||
}
|
||||
signal_handler.emit(signum);
|
||||
}
|
||||
}
|
||||
|
||||
void startSignalThread() {
|
||||
int err;
|
||||
sigset_t mask;
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGCHLD);
|
||||
// Must be called on the main thread.
|
||||
//
|
||||
// If this signal should restart or close the bar, this function will write
|
||||
// `true` or `false`, respectively, into `reload`.
|
||||
static void handleSignalMainThread(int signum, bool& reload) {
|
||||
if (signum >= SIGRTMIN + 1 && signum <= SIGRTMAX) {
|
||||
for (auto& bar : waybar::Client::inst()->bars) {
|
||||
bar->handleSignal(signum);
|
||||
}
|
||||
|
||||
// Block SIGCHLD so it can be handled by the signal thread
|
||||
// Any threads created by this one (the main thread) should not
|
||||
// modify their signal mask to unblock SIGCHLD
|
||||
err = pthread_sigmask(SIG_BLOCK, &mask, nullptr);
|
||||
if (err != 0) {
|
||||
spdlog::error("pthread_sigmask failed in startSignalThread: {}", strerror(err));
|
||||
exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_t thread_id;
|
||||
err = pthread_create(&thread_id, nullptr, signalThread, nullptr);
|
||||
if (err != 0) {
|
||||
spdlog::error("pthread_create failed in startSignalThread: {}", strerror(err));
|
||||
exit(1);
|
||||
switch (signum) {
|
||||
case SIGUSR1:
|
||||
spdlog::debug("Visibility toggled");
|
||||
for (auto& bar : waybar::Client::inst()->bars) {
|
||||
bar->toggle();
|
||||
}
|
||||
break;
|
||||
case SIGUSR2:
|
||||
spdlog::info("Reloading...");
|
||||
reload = true;
|
||||
waybar::Client::inst()->reset();
|
||||
break;
|
||||
case SIGINT:
|
||||
spdlog::info("Quitting.");
|
||||
reload = false;
|
||||
waybar::Client::inst()->reset();
|
||||
break;
|
||||
case SIGCHLD:
|
||||
spdlog::debug("Received SIGCHLD in signalThread");
|
||||
if (!reap.empty()) {
|
||||
reap_mtx.lock();
|
||||
for (auto it = reap.begin(); it != reap.end(); ++it) {
|
||||
if (waitpid(*it, nullptr, WNOHANG) == *it) {
|
||||
spdlog::debug("Reaped child with PID: {}", *it);
|
||||
it = reap.erase(it);
|
||||
}
|
||||
}
|
||||
reap_mtx.unlock();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
spdlog::debug("Received signal with number {}, but not handling", signum);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,32 +124,16 @@ int main(int argc, char* argv[]) {
|
||||
try {
|
||||
auto* client = waybar::Client::inst();
|
||||
|
||||
std::signal(SIGUSR1, [](int /*signal*/) {
|
||||
for (auto& bar : waybar::Client::inst()->bars) {
|
||||
bar->toggle();
|
||||
}
|
||||
});
|
||||
bool reload;
|
||||
|
||||
std::signal(SIGUSR2, [](int /*signal*/) {
|
||||
spdlog::info("Reloading...");
|
||||
reload = true;
|
||||
waybar::Client::inst()->reset();
|
||||
});
|
||||
waybar::SafeSignal<int> posix_signal_received;
|
||||
posix_signal_received.connect([&](int signum) { handleSignalMainThread(signum, reload); });
|
||||
|
||||
std::signal(SIGINT, [](int /*signal*/) {
|
||||
spdlog::info("Quitting.");
|
||||
reload = false;
|
||||
waybar::Client::inst()->reset();
|
||||
});
|
||||
std::thread signal_thread([&]() { catchSignals(posix_signal_received); });
|
||||
|
||||
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
|
||||
std::signal(sig, [](int sig) {
|
||||
for (auto& bar : waybar::Client::inst()->bars) {
|
||||
bar->handleSignal(sig);
|
||||
}
|
||||
});
|
||||
}
|
||||
startSignalThread();
|
||||
// Every `std::thread` must be joined or detached.
|
||||
// This thread should run forever, so detach it.
|
||||
signal_thread.detach();
|
||||
|
||||
auto ret = 0;
|
||||
do {
|
||||
|
@ -687,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;
|
||||
}
|
||||
|
@ -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} {
|
||||
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
216
src/modules/gps.cpp
Normal file
216
src/modules/gps.cpp
Normal 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_);
|
||||
}
|
@ -66,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
|
||||
|
@ -16,7 +16,7 @@ Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
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_);
|
||||
@ -68,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_);
|
||||
|
142
src/modules/hyprland/windowcount.cpp
Normal file
142
src/modules/hyprland/windowcount.cpp
Normal 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
|
@ -89,7 +89,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;
|
||||
}
|
||||
|
||||
|
@ -109,12 +109,12 @@ void Workspace::setActiveWindow(WindowAddress const &addr) {
|
||||
}
|
||||
}
|
||||
|
||||
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_workspaceManager.enableTaskbar()) {
|
||||
auto addr = create_window_paylod.getAddress();
|
||||
auto addr = create_window_payload.getAddress();
|
||||
auto it = std::ranges::find_if(
|
||||
m_windowMap, [&addr](const auto &window) { return window.address == addr; });
|
||||
// If the vector contains the address, update the window representation, otherwise insert it
|
||||
@ -127,9 +127,9 @@ void Workspace::insertWindow(WindowCreationPayload create_window_paylod) {
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
@ -193,6 +193,10 @@ std::string &Workspace::selectIcon(std::map<std::string, std::string> &icons_map
|
||||
}
|
||||
|
||||
void Workspace::update(const std::string &workspace_icon) {
|
||||
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
|
||||
m_button.hide();
|
||||
return;
|
||||
}
|
||||
// clang-format off
|
||||
if (this->m_workspaceManager.activeOnly() && \
|
||||
!this->isActive() && \
|
||||
|
@ -518,7 +518,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;
|
||||
}
|
||||
}
|
||||
@ -623,6 +623,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);
|
||||
|
||||
@ -866,6 +867,40 @@ 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::ranges::sort( //
|
||||
m_workspaces, [&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
|
||||
@ -924,6 +959,9 @@ void Workspaces::sortWorkspaces() {
|
||||
// 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);
|
||||
|
@ -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)));
|
||||
|
@ -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);
|
||||
|
@ -223,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_);
|
||||
};
|
||||
@ -271,12 +271,10 @@ 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() && ipaddr6_.empty()) return "linked";
|
||||
if (essid_.empty()) return "ethernet";
|
||||
|
@ -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"];
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
||||
|
@ -74,6 +74,24 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientat
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
@ -88,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) {
|
||||
|
@ -1,5 +1,7 @@
|
||||
#include "modules/privacy/privacy_item.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "glibmm/main.h"
|
||||
@ -96,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);
|
||||
|
@ -388,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() {
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -57,9 +57,9 @@ 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"];
|
||||
if (windowRewrite.isObject()) {
|
||||
@ -271,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"]) {
|
||||
@ -340,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()) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,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_);
|
||||
|
445
src/modules/wayfire/backend.cpp
Normal file
445
src/modules/wayfire/backend.cpp
Normal 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
src/modules/wayfire/window.cpp
Normal file
77
src/modules/wayfire/window.cpp
Normal 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
src/modules/wayfire/workspaces.cpp
Normal file
183
src/modules/wayfire/workspaces.cpp
Normal 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
|
@ -503,6 +503,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
|
||||
|
@ -15,11 +15,24 @@ bool DefaultGtkIconThemeWrapper::has_icon(const std::string& value) {
|
||||
return Gtk::IconTheme::get_default()->has_icon(value);
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> DefaultGtkIconThemeWrapper::load_icon(const char* name, int tmp_size,
|
||||
Gtk::IconLookupFlags flags) {
|
||||
Glib::RefPtr<Gdk::Pixbuf> DefaultGtkIconThemeWrapper::load_icon(
|
||||
const char* name, int tmp_size, Gtk::IconLookupFlags flags,
|
||||
Glib::RefPtr<Gtk::StyleContext> style) {
|
||||
const std::lock_guard<std::mutex> lock(default_theme_mutex);
|
||||
|
||||
auto default_theme = Gtk::IconTheme::get_default();
|
||||
default_theme->rescan_if_needed();
|
||||
return default_theme->load_icon(name, tmp_size, flags);
|
||||
|
||||
auto icon_info = default_theme->lookup_icon(name, tmp_size, flags);
|
||||
|
||||
if (icon_info == nullptr) {
|
||||
return default_theme->load_icon(name, tmp_size, flags);
|
||||
}
|
||||
|
||||
if (style.get() == nullptr) {
|
||||
return icon_info.load_icon();
|
||||
}
|
||||
|
||||
bool is_sym = false;
|
||||
return icon_info.load_symbolic(style, is_sym);
|
||||
}
|
||||
|
@ -49,6 +49,8 @@ void PrivacyNodeInfo::handleNodeEventInfo(const struct pw_node_info *info) {
|
||||
pipewire_access_portal_app_id = item->value;
|
||||
} else if (strcmp(item->key, PW_KEY_APP_ICON_NAME) == 0) {
|
||||
application_icon_name = item->value;
|
||||
} else if (strcmp(item->key, "stream.monitor") == 0) {
|
||||
is_monitor = strcmp(item->value, "true") == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,6 @@ static constexpr const char* PORTAL_NAMESPACE = "org.freedesktop.appearance";
|
||||
static constexpr const char* PORTAL_KEY = "color-scheme";
|
||||
} // namespace waybar
|
||||
|
||||
using namespace Gio;
|
||||
|
||||
auto fmt::formatter<waybar::Appearance>::format(waybar::Appearance c, format_context& ctx) const {
|
||||
string_view name;
|
||||
switch (c) {
|
||||
@ -36,8 +34,8 @@ auto fmt::formatter<waybar::Appearance>::format(waybar::Appearance c, format_con
|
||||
}
|
||||
|
||||
waybar::Portal::Portal()
|
||||
: DBus::Proxy(DBus::Connection::get_sync(DBus::BusType::BUS_TYPE_SESSION), PORTAL_BUS_NAME,
|
||||
PORTAL_OBJ_PATH, PORTAL_INTERFACE),
|
||||
: Gio::DBus::Proxy(Gio::DBus::Connection::get_sync(Gio::DBus::BusType::BUS_TYPE_SESSION),
|
||||
PORTAL_BUS_NAME, PORTAL_OBJ_PATH, PORTAL_INTERFACE),
|
||||
currentMode(Appearance::UNKNOWN) {
|
||||
refreshAppearance();
|
||||
};
|
||||
|
Reference in New Issue
Block a user