Merge branch 'master' of github.com:Alexays/Waybar

This commit is contained in:
mazunki
2022-01-08 01:02:57 +01:00
50 changed files with 2822 additions and 357 deletions

View File

@ -9,8 +9,13 @@
#include "bar.hpp"
#include "client.hpp"
#include "factory.hpp"
#include "group.hpp"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
#ifdef HAVE_SWAY
#include "modules/sway/bar.hpp"
#endif
namespace waybar {
static constexpr const char* MIN_HEIGHT_MSG =
"Requested height: {} is less than the minimum height: {} required by the modules";
@ -23,6 +28,84 @@ static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height:
static constexpr const char* SIZE_DEFINED =
"{} size is defined in the config file so it will stay like that";
const Bar::bar_mode_map Bar::PRESET_MODES = { //
{"default",
{// Special mode to hold the global bar configuration
.layer = bar_layer::BOTTOM,
.exclusive = true,
.passthrough = false,
.visible = true}},
{"dock",
{// Modes supported by the sway config; see man sway-bar(5)
.layer = bar_layer::BOTTOM,
.exclusive = true,
.passthrough = false,
.visible = true}},
{"hide",
{//
.layer = bar_layer::TOP,
.exclusive = false,
.passthrough = false,
.visible = true}},
{"invisible",
{//
.layer = bar_layer::BOTTOM,
.exclusive = false,
.passthrough = true,
.visible = false}},
{"overlay",
{//
.layer = bar_layer::TOP,
.exclusive = false,
.passthrough = true,
.visible = true}}};
const std::string_view Bar::MODE_DEFAULT = "default";
const std::string_view Bar::MODE_INVISIBLE = "invisible";
const std::string_view DEFAULT_BAR_ID = "bar-0";
/* Deserializer for enum bar_layer */
void from_json(const Json::Value& j, bar_layer& l) {
if (j == "bottom") {
l = bar_layer::BOTTOM;
} else if (j == "top") {
l = bar_layer::TOP;
} else if (j == "overlay") {
l = bar_layer::OVERLAY;
}
}
/* Deserializer for struct bar_mode */
void from_json(const Json::Value& j, bar_mode& m) {
if (j.isObject()) {
if (auto v = j["layer"]; v.isString()) {
from_json(v, m.layer);
}
if (auto v = j["exclusive"]; v.isBool()) {
m.exclusive = v.asBool();
}
if (auto v = j["passthrough"]; v.isBool()) {
m.passthrough = v.asBool();
}
if (auto v = j["visible"]; v.isBool()) {
m.visible = v.asBool();
}
}
}
/* Deserializer for JSON Object -> map<string compatible type, Value>
* Assumes that all the values in the object are deserializable to the same type.
*/
template <typename Key, typename Value,
typename = std::enable_if_t<std::is_convertible<std::string_view, Key>::value>>
void from_json(const Json::Value& j, std::map<Key, Value>& m) {
if (j.isObject()) {
for (auto it = j.begin(); it != j.end(); ++it) {
from_json(*it, m[it.key().asString()]);
}
}
}
#ifdef HAVE_GTK_LAYER_SHELL
struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
@ -391,7 +474,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
: output(w_output),
config(w_config),
window{Gtk::WindowType::WINDOW_TOPLEVEL},
layer_{bar_layer::BOTTOM},
left_(Gtk::ORIENTATION_HORIZONTAL, 0),
center_(Gtk::ORIENTATION_HORIZONTAL, 0),
right_(Gtk::ORIENTATION_HORIZONTAL, 0),
@ -403,27 +485,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
window.get_style_context()->add_class(config["name"].asString());
window.get_style_context()->add_class(config["position"].asString());
if (config["layer"] == "top") {
layer_ = bar_layer::TOP;
} else if (config["layer"] == "overlay") {
layer_ = bar_layer::OVERLAY;
}
if (config["exclusive"].isBool()) {
exclusive = config["exclusive"].asBool();
} else if (layer_ == bar_layer::OVERLAY) {
// swaybar defaults: overlay mode does not reserve an exclusive zone
exclusive = false;
}
bool passthrough = false;
if (config["passthrough"].isBool()) {
passthrough = config["passthrough"].asBool();
} else if (layer_ == bar_layer::OVERLAY) {
// swaybar defaults: overlay mode does not accept pointer events.
passthrough = true;
}
auto position = config["position"].asString();
if (position == "right" || position == "left") {
@ -505,15 +566,43 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
surface_impl_ = std::make_unique<RawSurfaceImpl>(window, *output);
}
surface_impl_->setLayer(layer_);
surface_impl_->setExclusiveZone(exclusive);
surface_impl_->setMargins(margins_);
surface_impl_->setPassThrough(passthrough);
surface_impl_->setPosition(position);
surface_impl_->setSize(width, height);
/* Read custom modes if available */
if (auto modes = config.get("modes", {}); modes.isObject()) {
from_json(modes, configured_modes);
}
/* Update "default" mode with the global bar options */
from_json(config, configured_modes[MODE_DEFAULT]);
if (auto mode = config.get("mode", {}); mode.isString()) {
setMode(config["mode"].asString());
} else {
setMode(MODE_DEFAULT);
}
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
#if HAVE_SWAY
if (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) {
bar_id = Client::inst()->bar_id;
if (auto id = config["id"]; id.isString()) {
bar_id = id.asString();
}
if (bar_id.empty()) {
bar_id = DEFAULT_BAR_ID;
}
try {
_ipc_client = std::make_unique<BarIpcClient>(*this);
} catch (const std::exception& exc) {
spdlog::warn("Failed to open bar ipc connection: {}", exc.what());
}
}
#endif
setupWidgets();
window.show_all();
@ -528,6 +617,44 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
}
}
/* Need to define it here because of forward declared members */
waybar::Bar::~Bar() = default;
void waybar::Bar::setMode(const std::string_view& mode) {
using namespace std::literals::string_literals;
auto style = window.get_style_context();
/* remove styles added by previous setMode calls */
style->remove_class("mode-"s + last_mode_);
auto it = configured_modes.find(mode);
if (it != configured_modes.end()) {
last_mode_ = mode;
style->add_class("mode-"s + last_mode_);
setMode(it->second);
} else {
spdlog::warn("Unknown mode \"{}\" requested", mode);
last_mode_ = MODE_DEFAULT;
style->add_class("mode-"s + last_mode_);
setMode(configured_modes.at(MODE_DEFAULT));
}
}
void waybar::Bar::setMode(const struct bar_mode& mode) {
surface_impl_->setLayer(mode.layer);
surface_impl_->setExclusiveZone(mode.exclusive);
surface_impl_->setPassThrough(mode.passthrough);
if (mode.visible) {
window.get_style_context()->remove_class("hidden");
window.set_opacity(1);
} else {
window.get_style_context()->add_class("hidden");
window.set_opacity(0);
}
surface_impl_->commit();
}
void waybar::Bar::onMap(GdkEventAny*) {
/*
* Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).
@ -538,17 +665,7 @@ void waybar::Bar::onMap(GdkEventAny*) {
void waybar::Bar::setVisible(bool value) {
visible = value;
if (!visible) {
window.get_style_context()->add_class("hidden");
window.set_opacity(0);
surface_impl_->setLayer(bar_layer::BOTTOM);
} else {
window.get_style_context()->remove_class("hidden");
window.set_opacity(1);
surface_impl_->setLayer(layer_);
}
surface_impl_->setExclusiveZone(exclusive && visible);
surface_impl_->commit();
setMode(visible ? MODE_DEFAULT : MODE_INVISIBLE);
}
void waybar::Bar::toggle() { setVisible(!visible); }
@ -594,19 +711,7 @@ void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) {
}
void waybar::Bar::handleSignal(int signal) {
for (auto& module : modules_left_) {
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
if (custom != nullptr) {
custom->refresh(signal);
}
}
for (auto& module : modules_center_) {
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
if (custom != nullptr) {
custom->refresh(signal);
}
}
for (auto& module : modules_right_) {
for (auto& module : modules_all_) {
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
if (custom != nullptr) {
custom->refresh(signal);
@ -614,19 +719,36 @@ void waybar::Bar::handleSignal(int signal) {
}
}
void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
if (config[pos].isArray()) {
for (const auto& name : config[pos]) {
void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk::Box* group = nullptr) {
auto module_list = group ? config[pos]["modules"] : config[pos];
if (module_list.isArray()) {
for (const auto& name : module_list) {
try {
auto module = factory.makeModule(name.asString());
if (pos == "modules-left") {
modules_left_.emplace_back(module);
auto ref = name.asString();
AModule* module;
if (ref.compare(0, 6, "group/") == 0 && ref.size() > 6) {
auto group_module = new waybar::Group(ref, *this, config[ref]);
getModules(factory, ref, &group_module->box);
module = group_module;
} else {
module = factory.makeModule(ref);
}
if (pos == "modules-center") {
modules_center_.emplace_back(module);
}
if (pos == "modules-right") {
modules_right_.emplace_back(module);
std::shared_ptr<AModule> module_sp(module);
modules_all_.emplace_back(module_sp);
if (group) {
group->pack_start(*module, false, false);
} else {
if (pos == "modules-left") {
modules_left_.emplace_back(module_sp);
}
if (pos == "modules-center") {
modules_center_.emplace_back(module_sp);
}
if (pos == "modules-right") {
modules_right_.emplace_back(module_sp);
}
}
module->dp.connect([module, &name] {
try {
@ -645,7 +767,11 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
auto waybar::Bar::setupWidgets() -> void {
window.add(box_);
box_.pack_start(left_, false, false);
box_.set_center_widget(center_);
if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) {
box_.set_center_widget(center_);
} else {
box_.pack_start(center_, true, false);
}
box_.pack_end(right_, false, false);
// Convert to button code for every module that is used.

View File

@ -199,7 +199,6 @@ int waybar::Client::main(int argc, char *argv[]) {
bool show_version = false;
std::string config_opt;
std::string style_opt;
std::string bar_id;
std::string log_level;
auto cli = clara::detail::Help(show_help) |
clara::detail::Opt(show_version)["-v"]["--version"]("Show version") |

View File

@ -30,6 +30,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "wlr/taskbar") {
return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]);
}
#ifdef USE_EXPERIMENTAL
if (ref == "wlr/workspaces") {
return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]);
}
#endif
#endif
#ifdef HAVE_RIVER
if (ref == "river/tags") {
@ -89,6 +94,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "sndio") {
return new waybar::modules::Sndio(id, config_[name]);
}
#endif
#ifdef HAVE_GIO_UNIX
if (ref == "inhibitor") {
return new waybar::modules::Inhibitor(id, bar_, config_[name]);
}
#endif
if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]);

19
src/group.cpp Normal file
View File

@ -0,0 +1,19 @@
#include "group.hpp"
#include <fmt/format.h>
#include <util/command.hpp>
namespace waybar {
Group::Group(const std::string& name, const Bar& bar, const Json::Value& config)
: AModule(config, name, "", false, false),
box{bar.vertical ? Gtk::ORIENTATION_HORIZONTAL : Gtk::ORIENTATION_VERTICAL, 0}
{
}
auto Group::update() -> void {
// noop
}
Group::operator Gtk::Widget&() { return box; }
} // namespace waybar

View File

@ -14,17 +14,51 @@
using waybar::modules::waybar_time;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), fixed_time_zone_(false) {
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true),
current_time_zone_idx_(0),
is_calendar_in_tooltip_(false)
{
if (config_["timezones"].isArray() && !config_["timezones"].empty()) {
time_zone_idx_ = 0;
setTimeZone(config_["timezones"][time_zone_idx_]);
} else {
setTimeZone(config_["timezone"]);
for (const auto& zone_name: config_["timezones"]) {
if (!zone_name.isString() || zone_name.asString().empty()) {
time_zones_.push_back(nullptr);
continue;
}
time_zones_.push_back(
date::locate_zone(
zone_name.asString()
)
);
}
} else if (config_["timezone"].isString() && !config_["timezone"].asString().empty()) {
time_zones_.push_back(
date::locate_zone(
config_["timezone"].asString()
)
);
}
if (fixed_time_zone_) {
// If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark that local time should be shown.
if (!time_zones_.size()) {
time_zones_.push_back(nullptr);
}
if (!is_timezone_fixed()) {
spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018.");
}
// Check if a particular placeholder is present in the tooltip format, to know what to calculate on update.
if (config_["tooltip-format"].isString()) {
std::string trimmed_format = config_["tooltip-format"].asString();
trimmed_format.erase(std::remove_if(trimmed_format.begin(),
trimmed_format.end(),
[](unsigned char x){return std::isspace(x);}),
trimmed_format.end());
if (trimmed_format.find("{" + kCalendarPlaceholder + "}") != std::string::npos) {
is_calendar_in_tooltip_ = true;
}
}
if (config_["locale"].isString()) {
locale_ = std::locale(config_["locale"].asString());
} else {
@ -40,53 +74,46 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
};
}
const date::time_zone* waybar::modules::Clock::current_timezone() {
return time_zones_[current_time_zone_idx_] ? time_zones_[current_time_zone_idx_] : date::current_zone();
}
bool waybar::modules::Clock::is_timezone_fixed() {
return time_zones_[current_time_zone_idx_] != nullptr;
}
auto waybar::modules::Clock::update() -> void {
if (!fixed_time_zone_) {
// Time zone can change. Be sure to pick that.
time_zone_ = date::current_zone();
}
auto now = std::chrono::system_clock::now();
auto time_zone = current_timezone();
auto now = std::chrono::system_clock::now();
waybar_time wtime = {locale_,
date::make_zoned(time_zone_, date::floor<std::chrono::seconds>(now))};
std::string text;
if (!fixed_time_zone_) {
date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now))};
std::string text = "";
if (!is_timezone_fixed()) {
// As date dep is not fully compatible, prefer fmt
tzset();
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
text = fmt::format(format_, localtime);
label_.set_markup(text);
} else {
text = fmt::format(format_, wtime);
label_.set_markup(text);
}
label_.set_markup(text);
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
const auto calendar = calendar_text(wtime);
auto tooltip_format = config_["tooltip-format"].asString();
auto tooltip_text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar));
label_.set_tooltip_markup(tooltip_text);
} else {
label_.set_tooltip_markup(text);
std::string calendar_lines = "";
if (is_calendar_in_tooltip_) {
calendar_lines = calendar_text(wtime);
}
auto tooltip_format = config_["tooltip-format"].asString();
text = fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines));
}
}
label_.set_tooltip_markup(text);
// Call parent update
ALabel::update();
}
bool waybar::modules::Clock::setTimeZone(Json::Value zone_name) {
if (!zone_name.isString() || zone_name.asString().empty()) {
fixed_time_zone_ = false;
return false;
}
time_zone_ = date::locate_zone(zone_name.asString());
fixed_time_zone_ = true;
return true;
}
bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) {
// defer to user commands if set
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
@ -97,17 +124,18 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) {
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
return true;
}
if (!config_["timezones"].isArray() || config_["timezones"].empty()) {
if (time_zones_.size() == 1) {
return true;
}
auto nr_zones = config_["timezones"].size();
auto nr_zones = time_zones_.size();
if (dir == SCROLL_DIR::UP) {
size_t new_idx = time_zone_idx_ + 1;
time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
size_t new_idx = current_time_zone_idx_ + 1;
current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
} else {
time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1;
current_time_zone_idx_ = current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1;
}
setTimeZone(config_["timezones"][time_zone_idx_]);
update();
return true;
}

View File

@ -45,9 +45,9 @@ auto waybar::modules::Disk::update() -> void {
}
auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true);
auto used = pow_format((stats.f_blocks - stats.f_bavail) * stats.f_frsize, "B", true);
auto used = pow_format((stats.f_blocks - stats.f_bfree) * stats.f_frsize, "B", true);
auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true);
auto percentage_used = (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks;
auto percentage_used = (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks;
auto format = format_;
auto state = getState(percentage_used);

175
src/modules/inhibitor.cpp Normal file
View File

@ -0,0 +1,175 @@
#include "modules/inhibitor.hpp"
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include <spdlog/spdlog.h>
namespace {
using DBus = std::unique_ptr<GDBusConnection, void(*)(GDBusConnection*)>;
auto dbus() -> DBus {
GError *error = nullptr;
GDBusConnection* connection =
g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
if (error) {
spdlog::error("g_bus_get_sync() failed: {}", error->message);
g_error_free(error);
connection = nullptr;
}
auto destructor = [](GDBusConnection* connection) {
GError *error = nullptr;
g_dbus_connection_close_sync(connection, nullptr, &error);
if (error) {
spdlog::error(
"g_bus_connection_close_sync failed(): {}",
error->message);
g_error_free(error);
}
};
return DBus{connection, destructor};
}
auto getLocks(const DBus& bus, const std::string& inhibitors) -> int {
GError *error = nullptr;
GUnixFDList* fd_list;
int handle;
auto reply = g_dbus_connection_call_with_unix_fd_list_sync(bus.get(),
"org.freedesktop.login1",
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
"Inhibit",
g_variant_new(
"(ssss)",
inhibitors.c_str(),
"waybar",
"Asked by user",
"block"),
G_VARIANT_TYPE("(h)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
nullptr,
&fd_list,
nullptr,
&error);
if (error) {
spdlog::error(
"g_dbus_connection_call_with_unix_fd_list_sync() failed: {}",
error->message);
g_error_free(error);
handle = -1;
} else {
gint index;
g_variant_get(reply, "(h)", &index);
g_variant_unref(reply);
handle = g_unix_fd_list_get(fd_list, index, nullptr);
g_object_unref(fd_list);
}
return handle;
}
auto checkInhibitor(const std::string& inhibitor) -> const std::string& {
static const auto inhibitors = std::array{
"idle",
"shutdown",
"sleep",
"handle-power-key",
"handle-suspend-key",
"handle-hibernate-key",
"handle-lid-switch"
};
if (std::find(inhibitors.begin(), inhibitors.end(), inhibitor)
== inhibitors.end()) {
throw std::runtime_error("invalid logind inhibitor " + inhibitor);
}
return inhibitor;
}
auto getInhibitors(const Json::Value& config) -> std::string {
std::string inhibitors = "idle";
if (config["what"].empty()) {
return inhibitors;
}
if (config["what"].isString()) {
return checkInhibitor(config["what"].asString());
}
if (config["what"].isArray()) {
inhibitors = checkInhibitor(config["what"][0].asString());
for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) {
inhibitors += ":" + checkInhibitor(config["what"][i].asString());
}
return inhibitors;
}
return inhibitors;
}
}
namespace waybar::modules {
Inhibitor::Inhibitor(const std::string& id, const Bar& bar,
const Json::Value& config)
: ALabel(config, "inhibitor", id, "{status}", true),
dbus_(::dbus()),
inhibitors_(::getInhibitors(config)) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &Inhibitor::handleToggle));
dp.emit();
}
Inhibitor::~Inhibitor() {
if (handle_ != -1) {
::close(handle_);
}
}
auto Inhibitor::activated() -> bool {
return handle_ != -1;
}
auto Inhibitor::update() -> void {
std::string status_text = activated() ? "activated" : "deactivated";
label_.get_style_context()->remove_class(
activated() ? "deactivated" : "activated");
label_.set_markup(
fmt::format(format_, fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text);
if (tooltipEnabled()) {
label_.set_tooltip_text(status_text);
}
return ALabel::update();
}
auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool {
if (e->button == 1) {
if (activated()) {
::close(handle_);
handle_ = -1;
} else {
handle_ = ::getLocks(dbus_, inhibitors_);
if (handle_ == -1) {
spdlog::error("cannot get inhibitor locks");
}
}
}
return ALabel::handleToggle(e);
}
} // waybar::modules

View File

@ -131,6 +131,9 @@ void waybar::modules::MPD::setLabel() {
date = getTag(MPD_TAG_DATE);
song_pos = mpd_status_get_song_pos(status_.get());
volume = mpd_status_get_volume(status_.get());
if (volume < 0) {
volume = 0;
}
queue_length = mpd_status_get_queue_length(status_.get());
elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get()));
totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get()));

View File

@ -1,87 +1,77 @@
#include "modules/network.hpp"
#include <linux/if.h>
#include <spdlog/spdlog.h>
#include <sys/eventfd.h>
#include <linux/if.h>
#include <fstream>
#include <cassert>
#include <fstream>
#include <sstream>
#include <optional>
#include "modules/network.hpp"
#include "util/format.hpp"
#ifdef WANT_RFKILL
#include "util/rfkill.hpp"
#endif
namespace {
using namespace waybar::util;
constexpr const char *NETSTAT_FILE =
"/proc/net/netstat"; // std::ifstream does not take std::string_view as param
constexpr std::string_view BANDWIDTH_CATEGORY = "IpExt";
constexpr std::string_view BANDWIDTH_DOWN_TOTAL_KEY = "InOctets";
constexpr std::string_view BANDWIDTH_UP_TOTAL_KEY = "OutOctets";
constexpr const char *DEFAULT_FORMAT = "{ifname}";
std::ifstream netstat(NETSTAT_FILE);
std::optional<unsigned long long> read_netstat(std::string_view category, std::string_view key) {
if (!netstat) {
spdlog::warn("Failed to open netstat file {}", NETSTAT_FILE);
return {};
}
netstat.seekg(std::ios_base::beg);
// finding corresponding line (category)
// looks into the file for the first line starting by the 'category' string
auto starts_with = [](const std::string &str, std::string_view start) {
return start == std::string_view{str.data(), std::min(str.size(), start.size())};
};
std::string read;
while (std::getline(netstat, read) && !starts_with(read, category))
;
if (!starts_with(read, category)) {
spdlog::warn("Category '{}' not found in netstat file {}", category, NETSTAT_FILE);
return {};
}
// finding corresponding column (key)
// looks into the fetched line for the first word (space separated) equal to 'key'
int index = 0;
auto r_it = read.begin();
auto k_it = key.begin();
while (k_it != key.end() && r_it != read.end()) {
if (*r_it != *k_it) {
r_it = std::find(r_it, read.end(), ' ');
if (r_it != read.end()) {
++r_it;
}
k_it = key.begin();
++index;
} else {
++r_it;
++k_it;
}
}
if (r_it == read.end() && k_it != key.end()) {
spdlog::warn(
"Key '{}' not found in category '{}' of netstat file {}", key, category, NETSTAT_FILE);
return {};
}
// finally accessing value
// accesses the line right under the fetched one
std::getline(netstat, read);
assert(starts_with(read, category));
std::istringstream iss(read);
while (index--) {
std::getline(iss, read, ' ');
}
unsigned long long value;
iss >> value;
return value;
}
} // namespace
constexpr const char *NETDEV_FILE =
"/proc/net/dev"; // std::ifstream does not take std::string_view as param
std::optional<std::pair<unsigned long long, unsigned long long>>
waybar::modules::Network::readBandwidthUsage() {
std::ifstream netdev(NETDEV_FILE);
if (!netdev) {
spdlog::warn("Failed to open netdev file {}", NETDEV_FILE);
return {};
}
std::string line;
// skip the headers (first two lines)
std::getline(netdev, line);
std::getline(netdev, line);
unsigned long long receivedBytes = 0ull;
unsigned long long transmittedBytes = 0ull;
while (std::getline(netdev, line)) {
std::istringstream iss(line);
std::string ifacename;
iss >> ifacename; // ifacename contains "eth0:"
ifacename.pop_back(); // remove trailing ':'
if (!checkInterface(ifacename)) {
continue;
}
// The rest of the line consists of whitespace separated counts divided
// into two groups (receive and transmit). Each group has the following
// columns: bytes, packets, errs, drop, fifo, frame, compressed, multicast
//
// We only care about the bytes count, so we'll just ignore the 7 other
// columns.
unsigned long long r = 0ull;
unsigned long long t = 0ull;
// Read received bytes
iss >> r;
// Skip all the other columns in the received group
for (int colsToSkip = 7; colsToSkip > 0; colsToSkip--) {
// skip whitespace between columns
while (iss.peek() == ' ') { iss.ignore(); }
// skip the irrelevant column
while (iss.peek() != ' ') { iss.ignore(); }
}
// Read transmit bytes
iss >> t;
receivedBytes += r;
transmittedBytes += t;
}
return {{receivedBytes, transmittedBytes}};
}
waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
: ALabel(config, "network", id, DEFAULT_FORMAT, 60),
ifid_(-1),
@ -98,7 +88,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
#ifdef WANT_RFKILL
rfkill_{RFKILL_TYPE_WLAN},
#endif
frequency_(0) {
frequency_(0.0) {
// Start with some "text" in the module's label_, update() will then
// update it. Since the text should be different, update() will be able
@ -106,17 +96,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
// the module start with no text, but the the event_box_ is shown.
label_.set_markup("<s></s>");
auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY);
auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY);
if (down_octets) {
bandwidth_down_total_ = *down_octets;
auto bandwidth = readBandwidthUsage();
if (bandwidth.has_value()) {
bandwidth_down_total_ = (*bandwidth).first;
bandwidth_up_total_ = (*bandwidth).second;
} else {
bandwidth_down_total_ = 0;
}
if (up_octets) {
bandwidth_up_total_ = *up_octets;
} else {
bandwidth_up_total_ = 0;
}
@ -303,20 +288,21 @@ const std::string waybar::modules::Network::getNetworkState() const {
auto waybar::modules::Network::update() -> void {
std::lock_guard<std::mutex> lock(mutex_);
std::string tooltip_format;
auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY);
auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY);
unsigned long long bandwidth_down = 0;
if (down_octets) {
bandwidth_down = *down_octets - bandwidth_down_total_;
bandwidth_down_total_ = *down_octets;
auto bandwidth = readBandwidthUsage();
auto bandwidth_down = 0ull;
auto bandwidth_up = 0ull;
if (bandwidth.has_value()) {
auto down_octets = (*bandwidth).first;
auto up_octets = (*bandwidth).second;
bandwidth_down = down_octets - bandwidth_down_total_;
bandwidth_down_total_ = down_octets;
bandwidth_up = up_octets - bandwidth_up_total_;
bandwidth_up_total_ = up_octets;
}
unsigned long long bandwidth_up = 0;
if (up_octets) {
bandwidth_up = *up_octets - bandwidth_up_total_;
bandwidth_up_total_ = *up_octets;
}
if (!alt_) {
auto state = getNetworkState();
if (!state_.empty() && label_.get_style_context()->has_class(state_)) {
@ -345,12 +331,13 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("essid", essid_),
fmt::arg("signaldBm", signal_strength_dbm_),
fmt::arg("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_),
fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_),
fmt::arg("ipaddr", ipaddr_),
fmt::arg("gwaddr", gwaddr_),
fmt::arg("cidr", cidr_),
fmt::arg("frequency", frequency_),
fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
@ -374,12 +361,13 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("essid", essid_),
fmt::arg("signaldBm", signal_strength_dbm_),
fmt::arg("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_),
fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_),
fmt::arg("ipaddr", ipaddr_),
fmt::arg("gwaddr", gwaddr_),
fmt::arg("cidr", cidr_),
fmt::arg("frequency", frequency_),
fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits",
pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
@ -417,7 +405,8 @@ void waybar::modules::Network::clearIface() {
cidr_ = 0;
signal_strength_dbm_ = 0;
signal_strength_ = 0;
frequency_ = 0;
signal_strength_app_.clear();
frequency_ = 0.0;
}
int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
@ -484,7 +473,8 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
net->essid_.clear();
net->signal_strength_dbm_ = 0;
net->signal_strength_ = 0;
net->frequency_ = 0;
net->signal_strength_app_.clear();
net->frequency_ = 0.0;
}
}
net->carrier_ = carrier.value();
@ -802,13 +792,30 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) {
if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) {
// signalstrength in dBm from mBm
signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100;
// WiFi-hardware usually operates in the range -90 to -30dBm.
// WiFi-hardware usually operates in the range -90 to -20dBm.
const int hardwareMax = -20;
// If a signal is too strong, it can overwhelm receiving circuity that is designed
// to pick up and process a certain signal level. The following percentage is scaled to
// punish signals that are too strong (>= -45dBm) or too weak (<= -45 dBm).
const int hardwareOptimum = -45;
const int hardwareMin = -90;
const int strength =
((signal_strength_dbm_ - hardwareMin) / double{hardwareMax - hardwareMin}) * 100;
signal_strength_ = std::clamp(strength, 0, 100);
100 - ((abs(signal_strength_dbm_ - hardwareOptimum) / double{hardwareOptimum - hardwareMin}) * 100);
signal_strength_ = std::clamp(strength, 0, 100);
if (signal_strength_dbm_ >= -50) {
signal_strength_app_ = "Great Connectivity";
} else if (signal_strength_dbm_ >= -60) {
signal_strength_app_ = "Good Connectivity";
} else if (signal_strength_dbm_ >= -67) {
signal_strength_app_ = "Streaming";
} else if (signal_strength_dbm_ >= -70) {
signal_strength_app_ = "Web Surfing";
} else if (signal_strength_dbm_ >= -80) {
signal_strength_app_ = "Basic Connectivity";
} else {
signal_strength_app_ = "Poor Connectivity";
}
}
if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) {
signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]);
@ -817,8 +824,8 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) {
void waybar::modules::Network::parseFreq(struct nlattr **bss) {
if (bss[NL80211_BSS_FREQUENCY] != nullptr) {
// in MHz
frequency_ = nla_get_u32(bss[NL80211_BSS_FREQUENCY]);
// in GHz
frequency_ = (double) nla_get_u32(bss[NL80211_BSS_FREQUENCY]) / 1000;
}
}

View File

@ -79,6 +79,13 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
if (dir == SCROLL_DIR::NONE) {
return true;
}
if (config_["reverse-scrolling"].asInt() == 1){
if (dir == SCROLL_DIR::UP) {
dir = SCROLL_DIR::DOWN;
} else if (dir == SCROLL_DIR::DOWN) {
dir = SCROLL_DIR::UP;
}
}
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_volume_t change = volume_tick;
pa_cvolume pa_volume = pa_volume_;
@ -211,7 +218,7 @@ static const std::array<std::string, 9> ports = {
};
const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {
std::vector<std::string> res = {default_source_name_};
std::vector<std::string> res = {current_sink_name_, default_source_name_};
std::string nameLC = port_name_ + form_factor_;
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
for (auto const &port : ports) {

View File

@ -7,7 +7,6 @@
#include "client.hpp"
#include "modules/river/tags.hpp"
#include "river-status-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h"
namespace waybar::modules::river {
@ -33,6 +32,23 @@ static const zriver_output_status_v1_listener output_status_listener_impl{
.urgent_tags = listen_urgent_tags,
};
static void listen_command_success(void *data,
struct zriver_command_callback_v1 *zriver_command_callback_v1,
const char *output) {
// Do nothing but keep listener to avoid crashing when command was successful
}
static void listen_command_failure(void *data,
struct zriver_command_callback_v1 *zriver_command_callback_v1,
const char *output) {
spdlog::error("failure when selecting/toggling tags {}", output);
}
static const zriver_command_callback_v1_listener command_callback_listener_impl {
.success = listen_command_success,
.failure = listen_command_failure,
};
static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) {
if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {
@ -43,18 +59,33 @@ static void handle_global(void *data, struct wl_registry *registry, uint32_t nam
static_cast<Tags *>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1 *>(
wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version));
}
if (std::strcmp(interface, zriver_control_v1_interface.name) == 0) {
version = std::min<uint32_t>(version, 1);
static_cast<Tags *>(data)->control_ = static_cast<struct zriver_control_v1 *>(
wl_registry_bind(registry, name, &zriver_control_v1_interface, version));
}
if (std::strcmp(interface, wl_seat_interface.name) == 0) {
version = std::min<uint32_t>(version, 1);
static_cast<Tags *>(data)->seat_ = static_cast<struct wl_seat *>(
wl_registry_bind(registry, name, &wl_seat_interface, version));
}
}
static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {
/* Ignore event */
}
static const wl_registry_listener registry_listener_impl = {.global = handle_global,
.global_remove = handle_global_remove};
Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
: waybar::AModule(config, "tags", id, false, false),
status_manager_{nullptr},
control_{nullptr},
seat_{nullptr},
bar_(bar),
box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
output_status_{nullptr} {
@ -68,6 +99,14 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
return;
}
if (!control_) {
spdlog::error("river_control_v1 not advertised");
}
if (!seat_) {
spdlog::error("wl_seat not advertised");
}
box_.set_name("tags");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
@ -89,11 +128,17 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
}
}
uint32_t i = 1;
for (const auto &tag_label : tag_labels) {
Gtk::Button &button = buttons_.emplace_back(tag_label);
button.set_relief(Gtk::RELIEF_NONE);
box_.pack_start(button, false, false, 0);
if (!config_["disable-click"].asBool()) {
button.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_primary_clicked), i));
button.signal_button_press_event().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), i));
}
button.show();
i <<= 1;
}
struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
@ -107,6 +152,31 @@ Tags::~Tags() {
if (output_status_) {
zriver_output_status_v1_destroy(output_status_);
}
if (control_) {
zriver_control_v1_destroy(control_);
}
}
void Tags::handle_primary_clicked(uint32_t tag) {
// Send river command to select tag on left mouse click
zriver_command_callback_v1 *callback;
zriver_control_v1_add_argument(control_, "set-focused-tags");
zriver_control_v1_add_argument(control_, std::to_string(tag).c_str());
callback = zriver_control_v1_run_command(control_, seat_);
zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr);
}
bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) {
if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) {
// Send river command to toggle tag on right mouse click
zriver_command_callback_v1 *callback;
zriver_control_v1_add_argument(control_, "toggle-focused-tags");
zriver_control_v1_add_argument(control_, std::to_string(tag).c_str());
callback = zriver_control_v1_run_command(control_, seat_);
zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr);
}
return true;
}
void Tags::handle_focused_tags(uint32_t tags) {

107
src/modules/sway/bar.cpp Normal file
View File

@ -0,0 +1,107 @@
#include "modules/sway/bar.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h>
#include <stdexcept>
#include "bar.hpp"
#include "modules/sway/ipc/ipc.hpp"
namespace waybar::modules::sway {
BarIpcClient::BarIpcClient(waybar::Bar& bar) : bar_{bar} {
{
sigc::connection handle =
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &BarIpcClient::onInitialConfig));
ipc_.sendCmd(IPC_GET_BAR_CONFIG, bar_.bar_id);
handle.disconnect();
}
signal_config_.connect(sigc::mem_fun(*this, &BarIpcClient::onConfigUpdate));
signal_visible_.connect(sigc::mem_fun(*this, &BarIpcClient::onVisibilityUpdate));
ipc_.subscribe(R"(["bar_state_update", "barconfig_update"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &BarIpcClient::onIpcEvent));
// Launch worker
ipc_.setWorker([this] {
try {
ipc_.handleEvent();
} catch (const std::exception& e) {
spdlog::error("BarIpcClient::handleEvent {}", e.what());
}
});
}
struct swaybar_config parseConfig(const Json::Value& payload) {
swaybar_config conf;
if (auto id = payload["id"]; id.isString()) {
conf.id = id.asString();
}
if (auto mode = payload["mode"]; mode.isString()) {
conf.mode = mode.asString();
}
if (auto hs = payload["hidden_state"]; hs.isString()) {
conf.hidden_state = hs.asString();
}
return conf;
}
void BarIpcClient::onInitialConfig(const struct Ipc::ipc_response& res) {
auto payload = parser_.parse(res.payload);
if (auto success = payload.get("success", true); !success.asBool()) {
auto err = payload.get("error", "Unknown error");
throw std::runtime_error(err.asString());
}
auto config = parseConfig(payload);
onConfigUpdate(config);
}
void BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) {
try {
auto payload = parser_.parse(res.payload);
if (auto id = payload["id"]; id.isString() && id.asString() != bar_.bar_id) {
spdlog::trace("swaybar ipc: ignore event for {}", id.asString());
return;
}
if (payload.isMember("visible_by_modifier")) {
// visibility change for hidden bar
signal_visible_(payload["visible_by_modifier"].asBool());
} else {
// configuration update
auto config = parseConfig(payload);
signal_config_(std::move(config));
}
} catch (const std::exception& e) {
spdlog::error("BarIpcClient::onEvent {}", e.what());
}
}
void BarIpcClient::onConfigUpdate(const swaybar_config& config) {
spdlog::info("config update for {}: id {}, mode {}, hidden_state {}",
bar_.bar_id,
config.id,
config.mode,
config.hidden_state);
bar_config_ = config;
update();
}
void BarIpcClient::onVisibilityUpdate(bool visible_by_modifier) {
spdlog::debug("visiblity update for {}: {}", bar_.bar_id, visible_by_modifier);
visible_by_modifier_ = visible_by_modifier;
update();
}
void BarIpcClient::update() {
bool visible = visible_by_modifier_;
if (bar_config_.mode == "invisible") {
visible = false;
} else if (bar_config_.mode != "hide" || bar_config_.hidden_state != "hide") {
visible = true;
}
bar_.setMode(visible ? bar_config_.mode : Bar::MODE_INVISIBLE);
}
} // namespace waybar::modules::sway

View File

@ -99,7 +99,8 @@ auto Language::update() -> void {
fmt::arg("short", layout_.short_name),
fmt::arg("shortDescription", layout_.short_description),
fmt::arg("long", layout_.full_name),
fmt::arg("variant", layout_.variant)));
fmt::arg("variant", layout_.variant),
fmt::arg("flag", layout_.country_flag())));
label_.set_markup(display_layout);
if (tooltipEnabled()) {
if (tooltip_format_ != "") {
@ -107,7 +108,8 @@ auto Language::update() -> void {
fmt::arg("short", layout_.short_name),
fmt::arg("shortDescription", layout_.short_description),
fmt::arg("long", layout_.full_name),
fmt::arg("variant", layout_.variant)));
fmt::arg("variant", layout_.variant),
fmt::arg("flag", layout_.country_flag())));
label_.set_tooltip_markup(tooltip_display_layout);
} else {
label_.set_tooltip_markup(display_layout);
@ -212,4 +214,15 @@ Language::XKBContext::~XKBContext() {
rxkb_context_unref(context_);
delete layout_;
}
std::string Language::Layout::country_flag() const {
if (short_name.size() != 2) return "";
unsigned char result[] = "\xf0\x9f\x87\x00\xf0\x9f\x87\x00";
result[3] = short_name[0] + 0x45;
result[7] = short_name[1] + 0x45;
// Check if both emojis are in A-Z symbol bounds
if (result[3] < 0xa6 || result[3] > 0xbf) return "";
if (result[7] < 0xa6 || result[7] > 0xbf) return "";
return std::string{reinterpret_cast<char*>(result)};
}
} // namespace waybar::modules::sway

View File

@ -2,6 +2,7 @@
#include <spdlog/spdlog.h>
#include <algorithm>
#include <cctype>
#include <string>
@ -98,6 +99,7 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
Json::Value v;
v["name"] = p_w_name;
v["target_output"] = bar_.output->name;
v["num"] = convertWorkspaceNameToNum(p_w_name);
workspaces_.emplace_back(std::move(v));
break;
}
@ -107,57 +109,59 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
Json::Value v;
v["name"] = p_w_name;
v["target_output"] = "";
v["num"] = convertWorkspaceNameToNum(p_w_name);
workspaces_.emplace_back(std::move(v));
}
}
}
// config option to sort numeric workspace names before others
bool config_numeric_first = config_["numeric-first"].asBool();
// sway has a defined ordering of workspaces that should be preserved in
// the representation displayed by waybar to ensure that commands such
// as "workspace prev" or "workspace next" make sense when looking at
// the workspace representation in the bar.
// Due to waybar's own feature of persistent workspaces unknown to sway,
// custom sorting logic is necessary to make these workspaces appear
// naturally in the list of workspaces without messing up sway's
// sorting. For this purpose, a custom numbering property is created
// that preserves the order provided by sway while inserting numbered
// persistent workspaces at their natural positions.
//
// All of this code assumes that sway provides numbered workspaces first
// and other workspaces are sorted by their creation time.
//
// In a first pass, the maximum "num" value is computed to enqueue
// unnumbered workspaces behind numbered ones when computing the sort
// attribute.
int max_num = -1;
for (auto & workspace : workspaces_) {
max_num = std::max(workspace["num"].asInt(), max_num);
}
for (auto & workspace : workspaces_) {
auto workspace_num = workspace["num"].asInt();
if (workspace_num > -1) {
workspace["sort"] = workspace_num;
} else {
workspace["sort"] = ++max_num;
}
}
std::sort(workspaces_.begin(),
workspaces_.end(),
[config_numeric_first](const Json::Value &lhs, const Json::Value &rhs) {
// the "num" property (integer type):
// The workspace number or -1 for workspaces that do
// not start with a number.
// We could rely on sway providing this property:
//
// auto l = lhs["num"].asInt();
// auto r = rhs["num"].asInt();
//
// We cannot rely on the "num" property as provided by sway
// via IPC, because persistent workspace might not exist in
// sway's view. However, we need this property also for
// not-yet created persistent workspace. As such, we simply
// duplicate sway's logic of assigning the "num" property
// into waybar (see convertWorkspaceNameToNum). This way the
// sorting should work out even when we include workspaces
// that do not currently exist.
[](const Json::Value &lhs, const Json::Value &rhs) {
auto lname = lhs["name"].asString();
auto rname = rhs["name"].asString();
int l = convertWorkspaceNameToNum(lname);
int r = convertWorkspaceNameToNum(rname);
int l = lhs["sort"].asInt();
int r = rhs["sort"].asInt();
if (l == r) {
// in case both integers are the same, lexicographical
// sort. This also covers the case when both don't have a
// number (i.e., l == r == -1).
// In case both integers are the same, lexicographical
// sort. The code above already ensure that this will only
// happend in case of explicitly numbered workspaces.
return lname < rname;
}
// one of the workspaces doesn't begin with a number, so
// num is -1.
if (l < 0 || r < 0) {
if (config_numeric_first) {
return r < 0;
}
return l < 0;
}
// both workspaces have a "num" so let's just compare those
return l < r;
});
}
dp.emit();
} catch (const std::exception &e) {

View File

@ -11,7 +11,9 @@
#include <cstring>
#include <memory>
#include <sstream>
#include <utility>
#include <fmt/core.h>
#include <gdkmm/monitor.h>
#include <gtkmm/icontheme.h>
@ -86,8 +88,7 @@ static Glib::RefPtr<Gdk::Pixbuf> load_icon_from_file(std::string icon_path, int
}
}
/* Method 1 - get the correct icon name from the desktop file */
static std::string get_from_desktop_app_info(const std::string &app_id)
static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_by_name(const std::string& app_id)
{
static std::vector<std::string> prefixes = search_prefix();
@ -103,33 +104,29 @@ static std::string get_from_desktop_app_info(const std::string &app_id)
".desktop"
};
Glib::RefPtr<Gio::DesktopAppInfo> app_info;
for (auto& prefix : prefixes) {
for (auto& folder : app_folders) {
for (auto& suffix : suffixes) {
auto app_info_ = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
if (!app_info_) {
continue;
}
for (auto& prefix : prefixes)
for (auto& folder : app_folders)
for (auto& suffix : suffixes)
if (!app_info)
app_info = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
return app_info_;
}
}
}
if (app_info && app_info->get_icon())
return app_info->get_icon()->to_string();
return "";
return {};
}
/* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */
static std::string get_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
const std::string &app_id)
Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id)
{
if (icon_theme->lookup_icon(app_id, 24))
return app_id;
auto app_info = get_app_info_by_name(app_id);
if (app_info) {
return app_info;
}
return "";
}
/* Method 3 - as last resort perform a search for most appropriate desktop info file */
static std::string get_from_desktop_app_info_search(const std::string &app_id)
{
std::string desktop_file = "";
gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str());
@ -151,65 +148,84 @@ static std::string get_from_desktop_app_info_search(const std::string &app_id)
}
g_free(desktop_list);
return get_from_desktop_app_info(desktop_file);
return get_app_info_by_name(desktop_file);
}
static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
const std::string &app_id_list, int size)
{
void Task::set_app_info_from_app_id_list(const std::string& app_id_list) {
std::string app_id;
std::istringstream stream(app_id_list);
bool found = false;
/* Wayfire sends a list of app-id's in space separated format, other compositors
* send a single app-id, but in any case this works fine */
while (stream >> app_id)
{
app_info_ = get_desktop_app_info(app_id);
if (app_info_) {
return;
}
auto lower_app_id = app_id;
std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(),
[](char c){ return std::tolower(c); });
app_info_ = get_desktop_app_info(lower_app_id);
if (app_info_) {
return;
}
size_t start = 0, end = app_id.size();
start = app_id.rfind(".", end);
std::string app_name = app_id.substr(start+1, app_id.size());
app_info_ = get_desktop_app_info(app_name);
if (app_info_) {
return;
}
auto lower_app_id = app_id;
std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(),
[](char c){ return std::tolower(c); });
start = app_id.find("-");
app_name = app_id.substr(0, start);
app_info_ = get_desktop_app_info(app_name);
}
}
std::string icon_name = get_from_icon_theme(icon_theme, app_id);
static std::string get_icon_name_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
const std::string &app_id)
{
if (icon_theme->lookup_icon(app_id, 24))
return app_id;
if (icon_name.empty())
icon_name = get_from_icon_theme(icon_theme, lower_app_id);
if (icon_name.empty())
icon_name = get_from_icon_theme(icon_theme, app_name);
if (icon_name.empty())
icon_name = get_from_desktop_app_info(app_id);
if (icon_name.empty())
icon_name = get_from_desktop_app_info(lower_app_id);
if (icon_name.empty())
icon_name = get_from_desktop_app_info(app_name);
if (icon_name.empty())
icon_name = get_from_desktop_app_info_search(app_id);
return "";
}
if (icon_name.empty())
icon_name = "unknown";
bool Task::image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size)
{
std::string ret_icon_name = "unknown";
if (app_info) {
std::string icon_name = get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class());
if (!icon_name.empty()) {
ret_icon_name = icon_name;
} else {
if (app_info->get_icon()) {
ret_icon_name = app_info->get_icon()->to_string();
}
}
}
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
try {
pixbuf = icon_theme->load_icon(icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE);
} catch(...) {
if (Glib::file_test(icon_name, Glib::FILE_TEST_EXISTS))
pixbuf = load_icon_from_file(icon_name, size);
else
pixbuf = {};
}
try {
pixbuf = icon_theme->load_icon(ret_icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE);
} catch(...) {
if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS))
pixbuf = load_icon_from_file(ret_icon_name, size);
else
pixbuf = {};
}
if (pixbuf) {
image.set(pixbuf);
found = true;
break;
}
}
if (pixbuf) {
image.set(pixbuf);
return true;
}
return found;
return false;
}
/* Task class implementation */
@ -276,8 +292,7 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat) :
bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat},
id_{global_id++},
content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
button_visible_{false}, ignored_{false}
content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}
{
zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this);
@ -290,13 +305,15 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
content_.show();
button_.add(content_);
with_icon_ = false;
format_before_.clear();
format_after_.clear();
if (config_["format"].isString()) {
/* The user defined a format string, use it */
auto format = config_["format"].asString();
if (format.find("{name}") != std::string::npos) {
with_name_ = true;
}
auto icon_pos = format.find("{icon}");
if (icon_pos == 0) {
@ -377,13 +394,12 @@ std::string Task::state_string(bool shortened) const
void Task::handle_title(const char *title)
{
title_ = title;
hide_if_ignored();
}
void Task::handle_app_id(const char *app_id)
void Task::hide_if_ignored()
{
app_id_ = app_id;
if (tbar_->ignore_list().count(app_id)) {
if (tbar_->ignore_list().count(app_id_) || tbar_->ignore_list().count(title_)) {
ignored_ = true;
if (button_visible_) {
auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
@ -397,14 +413,35 @@ void Task::handle_app_id(const char *app_id)
handle_output_enter(output);
}
}
}
if (!with_icon_)
void Task::handle_app_id(const char *app_id)
{
app_id_ = app_id;
hide_if_ignored();
auto ids_replace_map = tbar_->app_ids_replace_map();
if (ids_replace_map.count(app_id_)) {
auto replaced_id = ids_replace_map[app_id_];
spdlog::debug(fmt::format("Task ({}) [{}] app_id was replaced with {}", id_, app_id_, replaced_id));
app_id_ = replaced_id;
}
if (!with_icon_ && !with_name_) {
return;
}
set_app_info_from_app_id_list(app_id_);
name_ = app_info_ ? app_info_->get_display_name() : app_id;
if (!with_icon_) {
return;
}
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
bool found = false;
for (auto& icon_theme : tbar_->icon_themes()) {
if (image_load_icon(icon_, icon_theme, app_id_, icon_size)) {
if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) {
found = true;
break;
}
@ -418,13 +455,13 @@ void Task::handle_app_id(const char *app_id)
void Task::handle_output_enter(struct wl_output *output)
{
spdlog::debug("{} entered output {}", repr(), (void*)output);
if (ignored_) {
spdlog::debug("{} is ignored", repr());
return;
}
spdlog::debug("{} entered output {}", repr(), (void*)output);
if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) {
/* The task entered the output of the current bar make the button visible */
tbar_->add_button(button_);
@ -560,14 +597,17 @@ void Task::update()
{
bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false;
std::string title = title_;
std::string name = name_;
std::string app_id = app_id_;
if (markup) {
title = Glib::Markup::escape_text(title);
name = Glib::Markup::escape_text(name);
app_id = Glib::Markup::escape_text(app_id);
}
if (!format_before_.empty()) {
auto txt = fmt::format(format_before_,
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))
@ -581,6 +621,7 @@ void Task::update()
if (!format_after_.empty()) {
auto txt = fmt::format(format_after_,
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))
@ -595,6 +636,7 @@ void Task::update()
if (!format_tooltip_.empty()) {
auto txt = fmt::format(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))
@ -722,6 +764,15 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu
}
}
// Load app_id remappings
if (config_["app_ids-mapping"].isObject()) {
const Json::Value& mapping = config_["app_ids-mapping"];
const std::vector<std::string> app_ids = config_["app_ids-mapping"].getMemberNames();
for (auto& app_id : app_ids) {
app_ids_replace_map_.emplace(app_id, mapping[app_id].asString());
}
}
icon_themes_.push_back(Gtk::IconTheme::get_default());
}
@ -853,10 +904,10 @@ bool Taskbar::all_outputs() const
return config_["all-outputs"].isBool() && config_["all-outputs"].asBool();
}
std::vector<Glib::RefPtr<Gtk::IconTheme>> Taskbar::icon_themes() const
{
return icon_themes_;
}
const std::unordered_set<std::string> &Taskbar::ignore_list() const { return ignore_list_; }
const std::vector<Glib::RefPtr<Gtk::IconTheme>>& Taskbar::icon_themes() const { return icon_themes_; }
const std::unordered_set<std::string>& Taskbar::ignore_list() const { return ignore_list_; }
const std::map<std::string, std::string>& Taskbar::app_ids_replace_map() const { return app_ids_replace_map_; }
} /* namespace waybar::modules::wlr */

View File

@ -0,0 +1,472 @@
#include "modules/wlr/workspace_manager.hpp"
#include <gdk/gdkwayland.h>
#include <gtkmm.h>
#include <spdlog/spdlog.h>
#include <algorithm>
#include <iterator>
#include <vector>
#include "gtkmm/widget.h"
#include "modules/wlr/workspace_manager_binding.hpp"
namespace waybar::modules::wlr {
uint32_t WorkspaceGroup::workspace_global_id = 0;
uint32_t WorkspaceManager::group_global_id = 0;
std::map<std::string, std::string> Workspace::icons_map_;
WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar,
const Json::Value &config)
: waybar::AModule(config, "workspaces", id, false, false),
bar_(bar),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) {
auto config_sort_by_name = config_["sort-by-name"];
if (config_sort_by_name.isBool()) {
sort_by_name_ = config_sort_by_name.asBool();
}
auto config_sort_by_coordinates = config_["sort-by-coordinates"];
if (config_sort_by_coordinates.isBool()) {
sort_by_coordinates_ = config_sort_by_coordinates.asBool();
}
auto config_all_outputs = config_["all-outputs"];
if (config_all_outputs.isBool()) {
all_outputs_ = config_all_outputs.asBool();
}
auto config_active_only = config_["active-only"];
if (config_active_only.isBool()) {
active_only_ = config_active_only.asBool();
creation_delayed_ = active_only_;
}
box_.set_name("workspaces");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
event_box_.add(box_);
add_registry_listener(this);
if (!workspace_manager_) {
return;
}
}
auto WorkspaceManager::workspace_comparator() const
-> std::function<bool(std::unique_ptr<Workspace> &, std::unique_ptr<Workspace> &)> {
return [=](std::unique_ptr<Workspace> &lhs, std::unique_ptr<Workspace> &rhs) {
auto is_name_less = lhs->get_name() < rhs->get_name();
auto is_name_eq = lhs->get_name() == rhs->get_name();
auto is_coords_less = lhs->get_coords() < rhs->get_coords();
if (sort_by_name_) {
if (sort_by_coordinates_) {
return is_name_eq ? is_coords_less : is_name_less;
} else {
return is_name_less;
}
}
if (sort_by_coordinates_) {
return is_coords_less;
}
return lhs->id() < rhs->id();
};
}
auto WorkspaceManager::sort_workspaces() -> void {
std::vector<std::reference_wrapper<std::unique_ptr<Workspace>>> all_workspaces;
for (auto &group : groups_) {
auto &group_workspaces = group->workspaces();
all_workspaces.reserve(all_workspaces.size() +
std::distance(group_workspaces.begin(), group_workspaces.end()));
if (!active_only()) {
all_workspaces.insert(all_workspaces.end(), group_workspaces.begin(), group_workspaces.end());
continue;
}
for (auto &workspace : group_workspaces) {
if (!workspace->is_active()) {
continue;
}
all_workspaces.push_back(workspace);
}
}
std::sort(all_workspaces.begin(), all_workspaces.end(), workspace_comparator());
for (size_t i = 0; i < all_workspaces.size(); ++i) {
box_.reorder_child(all_workspaces[i].get()->get_button_ref(), i);
}
}
auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version)
-> void {
if (workspace_manager_) {
spdlog::warn("Register workspace manager again although already registered!");
return;
}
if (version != 1) {
spdlog::warn("Using different workspace manager protocol version: {}", version);
}
workspace_manager_ = workspace_manager_bind(registry, name, version, this);
}
auto WorkspaceManager::handle_workspace_group_create(
zext_workspace_group_handle_v1 *workspace_group_handle) -> void {
auto new_id = ++group_global_id;
groups_.push_back(
std::make_unique<WorkspaceGroup>(bar_, box_, config_, *this, workspace_group_handle, new_id));
spdlog::debug("Workspace group {} created", new_id);
}
auto WorkspaceManager::handle_finished() -> void {
zext_workspace_manager_v1_destroy(workspace_manager_);
workspace_manager_ = nullptr;
}
auto WorkspaceManager::handle_done() -> void {
for (auto &group : groups_) {
group->handle_done();
}
dp.emit();
}
auto WorkspaceManager::update() -> void {
for (auto &group : groups_) {
group->update();
}
if (creation_delayed()) {
creation_delayed_ = false;
sort_workspaces();
}
AModule::update();
}
WorkspaceManager::~WorkspaceManager() {
if (!workspace_manager_) {
return;
}
zext_workspace_manager_v1_destroy(workspace_manager_);
workspace_manager_ = nullptr;
}
auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void {
auto it = std::find_if(groups_.begin(),
groups_.end(),
[id](const std::unique_ptr<WorkspaceGroup> &g) { return g->id() == id; });
if (it == groups_.end()) {
spdlog::warn("Can't find group with id {}", id);
return;
}
groups_.erase(it);
}
auto WorkspaceManager::commit() -> void { zext_workspace_manager_v1_commit(workspace_manager_); }
WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value &config,
WorkspaceManager &manager,
zext_workspace_group_handle_v1 *workspace_group_handle, uint32_t id)
: bar_(bar),
box_(box),
config_(config),
workspace_manager_(manager),
workspace_group_handle_(workspace_group_handle),
id_(id) {
add_workspace_group_listener(workspace_group_handle, this);
}
auto WorkspaceGroup::active_only() const -> bool { return workspace_manager_.active_only(); }
auto WorkspaceGroup::creation_delayed() const -> bool {
return workspace_manager_.creation_delayed();
}
auto WorkspaceGroup::add_button(Gtk::Button &button) -> void {
box_.pack_start(button, false, false);
}
WorkspaceGroup::~WorkspaceGroup() {
if (!workspace_group_handle_) {
return;
}
zext_workspace_group_handle_v1_destroy(workspace_group_handle_);
workspace_group_handle_ = nullptr;
}
auto WorkspaceGroup::handle_workspace_create(zext_workspace_handle_v1 *workspace) -> void {
auto new_id = ++workspace_global_id;
workspaces_.push_back(std::make_unique<Workspace>(bar_, config_, *this, workspace, new_id));
spdlog::debug("Workspace {} created", new_id);
}
auto WorkspaceGroup::handle_remove() -> void {
zext_workspace_group_handle_v1_destroy(workspace_group_handle_);
workspace_group_handle_ = nullptr;
workspace_manager_.remove_workspace_group(id_);
}
auto WorkspaceGroup::handle_output_enter(wl_output *output) -> void {
spdlog::debug("Output {} assigned to {} group", (void *)output, id_);
output_ = output;
if (!is_visible() || workspace_manager_.creation_delayed()) {
return;
}
for (auto &workspace : workspaces_) {
add_button(workspace->get_button_ref());
}
}
auto WorkspaceGroup::is_visible() const -> bool {
return output_ != nullptr &&
(workspace_manager_.all_outputs() ||
output_ == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()));
}
auto WorkspaceGroup::handle_output_leave() -> void {
spdlog::debug("Output {} remove from {} group", (void *)output_, id_);
output_ = nullptr;
if (output_ != gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())) {
return;
}
for (auto &workspace : workspaces_) {
remove_button(workspace->get_button_ref());
}
}
auto WorkspaceGroup::update() -> void {
for (auto &workspace : workspaces_) {
if (workspace_manager_.creation_delayed()) {
add_button(workspace->get_button_ref());
if (is_visible() && (workspace->is_active() || workspace->is_urgent())) {
workspace->show();
}
}
workspace->update();
}
}
auto WorkspaceGroup::remove_workspace(uint32_t id) -> void {
auto it = std::find_if(workspaces_.begin(),
workspaces_.end(),
[id](const std::unique_ptr<Workspace> &w) { return w->id() == id; });
if (it == workspaces_.end()) {
spdlog::warn("Can't find workspace with id {}", id);
return;
}
workspaces_.erase(it);
}
auto WorkspaceGroup::handle_done() -> void {
need_to_sort = false;
if (!is_visible()) {
return;
}
for (auto &workspace : workspaces_) {
workspace->handle_done();
}
if (creation_delayed()) {
return;
}
if (!workspace_manager_.all_outputs()) {
sort_workspaces();
} else {
workspace_manager_.sort_workspaces();
}
}
auto WorkspaceGroup::commit() -> void { workspace_manager_.commit(); }
auto WorkspaceGroup::sort_workspaces() -> void {
std::sort(workspaces_.begin(), workspaces_.end(), workspace_manager_.workspace_comparator());
for (size_t i = 0; i < workspaces_.size(); ++i) {
box_.reorder_child(workspaces_[i]->get_button_ref(), i);
}
}
auto WorkspaceGroup::remove_button(Gtk::Button &button) -> void { box_.remove(button); }
Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group,
zext_workspace_handle_v1 *workspace, uint32_t id)
: bar_(bar),
config_(config),
workspace_group_(workspace_group),
workspace_handle_(workspace),
id_(id) {
add_workspace_listener(workspace, this);
auto config_format = config["format"];
format_ = config_format.isString() ? config_format.asString() : "{name}";
with_icon_ = format_.find("{icon}") != std::string::npos;
if (with_icon_ && icons_map_.empty()) {
auto format_icons = config["format-icons"];
for (auto &name : format_icons.getMemberNames()) {
icons_map_.emplace(name, format_icons[name].asString());
}
}
/* Handle click events if configured */
if (config_["on-click"].isString() || config_["on-click-middle"].isString() ||
config_["on-click-right"].isString()) {
button_.add_events(Gdk::BUTTON_PRESS_MASK);
button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked),
false);
}
button_.set_relief(Gtk::RELIEF_NONE);
content_.set_center_widget(label_);
button_.add(content_);
if (!workspace_group.is_visible()) {
return;
}
workspace_group.add_button(button_);
button_.show_all();
}
Workspace::~Workspace() {
workspace_group_.remove_button(button_);
if (!workspace_handle_) {
return;
}
zext_workspace_handle_v1_destroy(workspace_handle_);
workspace_handle_ = nullptr;
}
auto Workspace::update() -> void {
label_.set_markup(fmt::format(
format_, fmt::arg("name", name_), fmt::arg("icon", with_icon_ ? get_icon() : "")));
}
auto Workspace::handle_state(const std::vector<uint32_t> &state) -> void {
state_ = 0;
for (auto state_entry : state) {
switch (state_entry) {
case ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE:
state_ |= (uint32_t)State::ACTIVE;
break;
case ZEXT_WORKSPACE_HANDLE_V1_STATE_URGENT:
state_ |= (uint32_t)State::URGENT;
break;
case ZEXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN:
state_ |= (uint32_t)State::HIDDEN;
break;
}
}
}
auto Workspace::handle_remove() -> void {
zext_workspace_handle_v1_destroy(workspace_handle_);
workspace_handle_ = nullptr;
workspace_group_.remove_workspace(id_);
}
auto add_or_remove_class(Glib::RefPtr<Gtk::StyleContext> context, bool condition,
const std::string &class_name) {
if (condition) {
context->add_class(class_name);
} else {
context->remove_class(class_name);
}
}
auto Workspace::handle_done() -> void {
spdlog::debug("Workspace {} changed to state {}", id_, state_);
auto style_context = button_.get_style_context();
add_or_remove_class(style_context, is_active(), "active");
add_or_remove_class(style_context, is_urgent(), "urgent");
add_or_remove_class(style_context, is_hidden(), "hidden");
if (workspace_group_.creation_delayed()) {
return;
}
if (workspace_group_.active_only() && (is_active() || is_urgent())) {
button_.show_all();
} else if (workspace_group_.active_only() && !(is_active() || is_urgent())) {
button_.hide();
}
}
auto Workspace::get_icon() -> std::string {
if (is_active()) {
auto active_icon_it = icons_map_.find("active");
if (active_icon_it != icons_map_.end()) {
return active_icon_it->second;
}
}
auto named_icon_it = icons_map_.find(name_);
if (named_icon_it != icons_map_.end()) {
return named_icon_it->second;
}
auto default_icon_it = icons_map_.find("default");
if (default_icon_it != icons_map_.end()) {
return default_icon_it->second;
}
return name_;
}
auto Workspace::handle_clicked(GdkEventButton *bt) -> bool {
std::string action;
if (config_["on-click"].isString() && bt->button == 1) {
action = config_["on-click"].asString();
} else if (config_["on-click-middle"].isString() && bt->button == 2) {
action = config_["on-click-middle"].asString();
} else if (config_["on-click-right"].isString() && bt->button == 3) {
action = config_["on-click-right"].asString();
}
if (action.empty())
return true;
else if (action == "activate") {
zext_workspace_handle_v1_activate(workspace_handle_);
} else if (action == "close") {
zext_workspace_handle_v1_remove(workspace_handle_);
} else {
spdlog::warn("Unknown action {}", action);
}
workspace_group_.commit();
return true;
}
auto Workspace::show() -> void { button_.show_all(); }
auto Workspace::hide() -> void { button_.hide(); }
auto Workspace::handle_name(const std::string &name) -> void {
if (name_ != name) {
workspace_group_.set_need_to_sort();
}
name_ = name;
}
auto Workspace::handle_coordinates(const std::vector<uint32_t> &coordinates) -> void {
if (coordinates_ != coordinates) {
workspace_group_.set_need_to_sort();
}
coordinates_ = coordinates;
}
} // namespace waybar::modules::wlr

View File

@ -0,0 +1,135 @@
#include "modules/wlr/workspace_manager_binding.hpp"
#include <spdlog/spdlog.h>
#include <cstdint>
#include "client.hpp"
#include "modules/wlr/workspace_manager.hpp"
namespace waybar::modules::wlr {
static void handle_global(void *data, wl_registry *registry, uint32_t name, const char *interface,
uint32_t version) {
if (std::strcmp(interface, zext_workspace_manager_v1_interface.name) == 0) {
static_cast<WorkspaceManager *>(data)->register_manager(registry, name, version);
}
}
static void handle_global_remove(void *data, wl_registry *registry, uint32_t name) {
/* Nothing to do here */
}
static const wl_registry_listener registry_listener_impl = {.global = handle_global,
.global_remove = handle_global_remove};
void add_registry_listener(void *data) {
wl_display * display = Client::inst()->wl_display;
wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener_impl, data);
wl_display_roundtrip(display);
wl_display_roundtrip(display);
}
static void workspace_manager_handle_workspace_group(
void *data, zext_workspace_manager_v1 *_, zext_workspace_group_handle_v1 *workspace_group) {
static_cast<WorkspaceManager *>(data)->handle_workspace_group_create(workspace_group);
}
static void workspace_manager_handle_done(void *data, zext_workspace_manager_v1 *_) {
static_cast<WorkspaceManager *>(data)->handle_done();
}
static void workspace_manager_handle_finished(void *data, zext_workspace_manager_v1 *_) {
static_cast<WorkspaceManager *>(data)->handle_finished();
}
static const zext_workspace_manager_v1_listener workspace_manager_impl = {
.workspace_group = workspace_manager_handle_workspace_group,
.done = workspace_manager_handle_done,
.finished = workspace_manager_handle_finished,
};
zext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name,
uint32_t version, void *data) {
auto *workspace_manager = static_cast<zext_workspace_manager_v1 *>(
wl_registry_bind(registry, name, &zext_workspace_manager_v1_interface, version));
if (workspace_manager)
zext_workspace_manager_v1_add_listener(workspace_manager, &workspace_manager_impl, data);
else
spdlog::error("Failed to register manager");
return workspace_manager;
}
static void workspace_group_handle_output_enter(void *data, zext_workspace_group_handle_v1 *_,
wl_output *output) {
static_cast<WorkspaceGroup *>(data)->handle_output_enter(output);
}
static void workspace_group_handle_output_leave(void *data, zext_workspace_group_handle_v1 *_,
wl_output *output) {
static_cast<WorkspaceGroup *>(data)->handle_output_leave();
}
static void workspace_group_handle_workspace(void *data, zext_workspace_group_handle_v1 *_,
zext_workspace_handle_v1 *workspace) {
static_cast<WorkspaceGroup *>(data)->handle_workspace_create(workspace);
}
static void workspace_group_handle_remove(void *data, zext_workspace_group_handle_v1 *_) {
static_cast<WorkspaceGroup *>(data)->handle_remove();
}
static const zext_workspace_group_handle_v1_listener workspace_group_impl = {
.output_enter = workspace_group_handle_output_enter,
.output_leave = workspace_group_handle_output_leave,
.workspace = workspace_group_handle_workspace,
.remove = workspace_group_handle_remove};
void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle,
void * data) {
zext_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data);
}
void workspace_handle_name(void *data, struct zext_workspace_handle_v1 *_, const char *name) {
static_cast<Workspace *>(data)->handle_name(name);
}
void workspace_handle_coordinates(void *data, struct zext_workspace_handle_v1 *_,
struct wl_array *coordinates) {
std::vector<uint32_t> coords_vec;
auto coords = static_cast<uint32_t *>(coordinates->data);
for (size_t i = 0; i < coordinates->size / sizeof(uint32_t); ++i) {
coords_vec.push_back(coords[i]);
}
static_cast<Workspace *>(data)->handle_coordinates(coords_vec);
}
void workspace_handle_state(void *data, struct zext_workspace_handle_v1 *workspace_handle,
struct wl_array *state) {
std::vector<uint32_t> state_vec;
auto states = static_cast<uint32_t *>(state->data);
for (size_t i = 0; i < state->size / sizeof(uint32_t); ++i) {
state_vec.push_back(states[i]);
}
static_cast<Workspace *>(data)->handle_state(state_vec);
}
void workspace_handle_remove(void *data, struct zext_workspace_handle_v1 *_) {
static_cast<Workspace *>(data)->handle_remove();
}
static const zext_workspace_handle_v1_listener workspace_impl = {
.name = workspace_handle_name,
.coordinates = workspace_handle_coordinates,
.state = workspace_handle_state,
.remove = workspace_handle_remove};
void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data) {
zext_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data);
}
} // namespace waybar::modules::wlr