Merge branch 'master' into disk

This commit is contained in:
Alex
2021-11-23 13:54:17 +01:00
committed by GitHub
142 changed files with 8421 additions and 1543 deletions

View File

@ -5,8 +5,9 @@
namespace waybar {
ALabel::ALabel(const Json::Value& config, const std::string& name, const std::string& id,
const std::string& format, uint16_t interval, bool ellipsize)
: AModule(config, name, id, config["format-alt"].isString()),
const std::string& format, uint16_t interval, bool ellipsize, bool enable_click,
bool enable_scroll)
: AModule(config, name, id, config["format-alt"].isString() || enable_click, enable_scroll),
format_(config_["format"].isString() ? config_["format"].asString() : format),
interval_(config_["interval"] == "once"
? std::chrono::seconds(100000000)
@ -19,15 +20,36 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
}
event_box_.add(label_);
if (config_["max-length"].isUInt()) {
label_.set_max_width_chars(config_["max-length"].asUInt());
label_.set_max_width_chars(config_["max-length"].asInt());
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
label_.set_single_line_mode(true);
} else if (ellipsize && label_.get_max_width_chars() == -1) {
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
label_.set_single_line_mode(true);
}
if (config_["rotate"].isUInt()) {
label_.set_angle(config["rotate"].asUInt());
if (config_["min-length"].isUInt()) {
label_.set_width_chars(config_["min-length"].asUInt());
}
uint rotate = 0;
if (config_["rotate"].isUInt()) {
rotate = config["rotate"].asUInt();
label_.set_angle(rotate);
}
if (config_["align"].isDouble()) {
auto align = config_["align"].asFloat();
if (rotate == 90 || rotate == 270) {
label_.set_yalign(align);
} else {
label_.set_xalign(align);
}
}
}
auto ALabel::update() -> void {
@ -45,8 +67,10 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
}
if (format_icons.isArray()) {
auto size = format_icons.size();
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx];
if (size) {
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx];
}
}
if (format_icons.isString()) {
return format_icons.asString();
@ -54,22 +78,24 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
return "";
}
std::string ALabel::getIcon(uint16_t percentage, std::vector<std::string>& alts, uint16_t max) {
std::string ALabel::getIcon(uint16_t percentage, const std::vector<std::string>& alts, uint16_t max) {
auto format_icons = config_["format-icons"];
if (format_icons.isObject()) {
std::string _alt = "default";
for (const auto& alt : alts) {
if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) {
format_icons = format_icons[alt];
_alt = alt;
break;
} else {
format_icons = format_icons["default"];
}
}
format_icons = format_icons[_alt];
}
if (format_icons.isArray()) {
auto size = format_icons.size();
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx];
if (size) {
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx];
}
}
if (format_icons.isString()) {
return format_icons.asString();

View File

@ -6,7 +6,7 @@ namespace waybar {
AModule::AModule(const Json::Value& config, const std::string& name, const std::string& id,
bool enable_click, bool enable_scroll)
: config_(std::move(config)) {
: name_(std::move(name)), config_(std::move(config)) {
// configure events' user commands
if (config_["on-click"].isString() || config_["on-click-middle"].isString() ||
config_["on-click-backward"].isString() || config_["on-click-forward"].isString() ||
@ -23,11 +23,12 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
AModule::~AModule() {
for (const auto& pid : pid_) {
if (pid != -1) {
kill(-pid, 9);
killpg(pid, SIGTERM);
}
}
}
auto AModule::update() -> void {
// Run user-provided update handler if configured
if (config_["on-update"].isString()) {
@ -43,9 +44,9 @@ bool AModule::handleToggle(GdkEventButton* const& e) {
format = config_["on-click-middle"].asString();
} else if (config_["on-click-right"].isString() && e->button == 3) {
format = config_["on-click-right"].asString();
} else if (config_["on-click-forward"].isString() && e->button == 8) {
} else if (config_["on-click-backward"].isString() && e->button == 8) {
format = config_["on-click-backward"].asString();
} else if (config_["on-click-backward"].isString() && e->button == 9) {
} else if (config_["on-click-forward"].isString() && e->button == 9) {
format = config_["on-click-forward"].asString();
}
if (!format.empty()) {

View File

@ -2,18 +2,396 @@
#include <gtk-layer-shell.h>
#endif
#include <spdlog/spdlog.h>
#include <type_traits>
#include "bar.hpp"
#include "client.hpp"
#include "factory.hpp"
#include <spdlog/spdlog.h>
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
namespace waybar {
static constexpr const char* MIN_HEIGHT_MSG =
"Requested height: {} is less than the minimum height: {} required by the modules";
static constexpr const char* MIN_WIDTH_MSG =
"Requested width: {} is less than the minimum width: {} required by the modules";
static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: {}) for output: {}";
static constexpr const char* SIZE_DEFINED =
"{} size is defined in the config file so it will stay like that";
#ifdef HAVE_GTK_LAYER_SHELL
struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
output_name_ = output.name;
// this has to be executed before GtkWindow.realize
gtk_layer_init_for_window(window_.gobj());
gtk_layer_set_keyboard_interactivity(window.gobj(), FALSE);
gtk_layer_set_monitor(window_.gobj(), output.monitor->gobj());
gtk_layer_set_namespace(window_.gobj(), "waybar");
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &GLSSurfaceImpl::onMap));
window.signal_configure_event().connect_notify(
sigc::mem_fun(*this, &GLSSurfaceImpl::onConfigure));
}
void setExclusiveZone(bool enable) override {
if (enable) {
gtk_layer_auto_exclusive_zone_enable(window_.gobj());
} else {
gtk_layer_set_exclusive_zone(window_.gobj(), 0);
}
}
void setMargins(const struct bar_margins& margins) override {
gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_LEFT, margins.left);
gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, margins.right);
gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_TOP, margins.top);
gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, margins.bottom);
}
void setLayer(bar_layer value) override {
auto layer = GTK_LAYER_SHELL_LAYER_BOTTOM;
if (value == bar_layer::TOP) {
layer = GTK_LAYER_SHELL_LAYER_TOP;
} else if (value == bar_layer::OVERLAY) {
layer = GTK_LAYER_SHELL_LAYER_OVERLAY;
}
gtk_layer_set_layer(window_.gobj(), layer);
}
void setPassThrough(bool enable) override {
passthrough_ = enable;
auto gdk_window = window_.get_window();
if (gdk_window) {
Cairo::RefPtr<Cairo::Region> region;
if (enable) {
region = Cairo::Region::create();
}
gdk_window->input_shape_combine_region(region, 0, 0);
}
}
void setPosition(const std::string_view& position) override {
auto unanchored = GTK_LAYER_SHELL_EDGE_BOTTOM;
vertical_ = false;
if (position == "bottom") {
unanchored = GTK_LAYER_SHELL_EDGE_TOP;
} else if (position == "left") {
unanchored = GTK_LAYER_SHELL_EDGE_RIGHT;
vertical_ = true;
} else if (position == "right") {
vertical_ = true;
unanchored = GTK_LAYER_SHELL_EDGE_LEFT;
}
for (auto edge : {GTK_LAYER_SHELL_EDGE_LEFT,
GTK_LAYER_SHELL_EDGE_RIGHT,
GTK_LAYER_SHELL_EDGE_TOP,
GTK_LAYER_SHELL_EDGE_BOTTOM}) {
gtk_layer_set_anchor(window_.gobj(), edge, unanchored != edge);
}
}
void setSize(uint32_t width, uint32_t height) override {
width_ = width;
height_ = height;
window_.set_size_request(width_, height_);
};
private:
Gtk::Window& window_;
std::string output_name_;
uint32_t width_;
uint32_t height_;
bool passthrough_ = false;
bool vertical_ = false;
void onMap(GdkEventAny* ev) { setPassThrough(passthrough_); }
void onConfigure(GdkEventConfigure* ev) {
/*
* GTK wants new size for the window.
* Actual resizing and management of the exclusve zone is handled within the gtk-layer-shell
* code. This event handler only updates stored size of the window and prints some warnings.
*
* Note: forced resizing to a window smaller than required by GTK would not work with
* gtk-layer-shell.
*/
if (vertical_) {
if (width_ > 1 && ev->width > static_cast<int>(width_)) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
} else {
if (height_ > 1 && ev->height > static_cast<int>(height_)) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
}
width_ = ev->width;
height_ = ev->height;
spdlog::info(BAR_SIZE_MSG, width_, height_, output_name_);
}
};
#endif
struct RawSurfaceImpl : public BarSurface, public sigc::trackable {
RawSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
output_ = gdk_wayland_monitor_get_wl_output(output.monitor->gobj());
output_name_ = output.name;
window.signal_realize().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::onRealize));
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::onMap));
window.signal_configure_event().connect_notify(
sigc::mem_fun(*this, &RawSurfaceImpl::onConfigure));
if (window.get_realized()) {
onRealize();
}
}
void setExclusiveZone(bool enable) override {
exclusive_zone_ = enable;
if (layer_surface_) {
auto zone = 0;
if (enable) {
// exclusive zone already includes margin for anchored edge,
// only opposite margin should be added
if ((anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR) {
zone += width_;
zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? margins_.right : margins_.left;
} else {
zone += height_;
zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? margins_.bottom : margins_.top;
}
}
spdlog::debug("Set exclusive zone {} for output {}", zone, output_name_);
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_.get(), zone);
}
}
void setLayer(bar_layer layer) override {
layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
if (layer == bar_layer::TOP) {
layer_ = ZWLR_LAYER_SHELL_V1_LAYER_TOP;
} else if (layer == bar_layer::OVERLAY) {
layer_ = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
}
// updating already mapped window
if (layer_surface_) {
if (zwlr_layer_surface_v1_get_version(layer_surface_.get()) >=
ZWLR_LAYER_SURFACE_V1_SET_LAYER_SINCE_VERSION) {
zwlr_layer_surface_v1_set_layer(layer_surface_.get(), layer_);
} else {
spdlog::warn("Unable to change layer: layer-shell implementation is too old");
}
}
}
void setMargins(const struct bar_margins& margins) override {
margins_ = margins;
// updating already mapped window
if (layer_surface_) {
zwlr_layer_surface_v1_set_margin(
layer_surface_.get(), margins_.top, margins_.right, margins_.bottom, margins_.left);
}
}
void setPassThrough(bool enable) override {
passthrough_ = enable;
/* GTK overwrites any region changes applied directly to the wl_surface,
* thus the same GTK region API as in the GLS impl has to be used. */
auto gdk_window = window_.get_window();
if (gdk_window) {
Cairo::RefPtr<Cairo::Region> region;
if (enable) {
region = Cairo::Region::create();
}
gdk_window->input_shape_combine_region(region, 0, 0);
}
}
void setPosition(const std::string_view& position) override {
anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
if (position == "bottom") {
anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
} else if (position == "left") {
anchor_ = VERTICAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
} else if (position == "right") {
anchor_ = VERTICAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
}
// updating already mapped window
if (layer_surface_) {
zwlr_layer_surface_v1_set_anchor(layer_surface_.get(), anchor_);
}
}
void setSize(uint32_t width, uint32_t height) override {
configured_width_ = width_ = width;
configured_height_ = height_ = height;
// layer_shell.configure handler should update exclusive zone if size changes
window_.set_size_request(width, height);
};
void commit() override {
if (surface_) {
wl_surface_commit(surface_);
}
}
private:
constexpr static uint8_t VERTICAL_ANCHOR =
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
constexpr static uint8_t HORIZONTAL_ANCHOR =
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
template <auto fn>
using deleter_fn = std::integral_constant<decltype(fn), fn>;
using layer_surface_ptr =
std::unique_ptr<zwlr_layer_surface_v1, deleter_fn<zwlr_layer_surface_v1_destroy>>;
Gtk::Window& window_;
std::string output_name_;
uint32_t configured_width_ = 0;
uint32_t configured_height_ = 0;
uint32_t width_ = 0;
uint32_t height_ = 0;
uint8_t anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
bool exclusive_zone_ = true;
bool passthrough_ = false;
struct bar_margins margins_;
zwlr_layer_shell_v1_layer layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
struct wl_output* output_ = nullptr; // owned by GTK
struct wl_surface* surface_ = nullptr; // owned by GTK
layer_surface_ptr layer_surface_;
void onRealize() {
auto gdk_window = window_.get_window()->gobj();
gdk_wayland_window_set_use_custom_surface(gdk_window);
}
void onMap(GdkEventAny* ev) {
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = onSurfaceConfigure,
.closed = onSurfaceClosed,
};
auto client = Client::inst();
auto gdk_window = window_.get_window()->gobj();
surface_ = gdk_wayland_window_get_wl_surface(gdk_window);
layer_surface_.reset(zwlr_layer_shell_v1_get_layer_surface(
client->layer_shell, surface_, output_, layer_, "waybar"));
zwlr_layer_surface_v1_add_listener(layer_surface_.get(), &layer_surface_listener, this);
zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_.get(), false);
zwlr_layer_surface_v1_set_anchor(layer_surface_.get(), anchor_);
zwlr_layer_surface_v1_set_margin(
layer_surface_.get(), margins_.top, margins_.right, margins_.bottom, margins_.left);
setSurfaceSize(width_, height_);
setExclusiveZone(exclusive_zone_);
setPassThrough(passthrough_);
commit();
wl_display_roundtrip(client->wl_display);
}
void onConfigure(GdkEventConfigure* ev) {
/*
* GTK wants new size for the window.
*
* Prefer configured size if it's non-default.
* If the size is not set and the window is smaller than requested by GTK, request resize from
* layer surface.
*/
auto tmp_height = height_;
auto tmp_width = width_;
if (ev->height > static_cast<int>(height_)) {
// Default minimal value
if (height_ > 1) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
if (configured_height_ > 1) {
spdlog::info(SIZE_DEFINED, "Height");
} else {
tmp_height = ev->height;
}
}
if (ev->width > static_cast<int>(width_)) {
// Default minimal value
if (width_ > 1) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
if (configured_width_ > 1) {
spdlog::info(SIZE_DEFINED, "Width");
} else {
tmp_width = ev->width;
}
}
if (tmp_width != width_ || tmp_height != height_) {
setSurfaceSize(tmp_width, tmp_height);
commit();
}
}
void setSurfaceSize(uint32_t width, uint32_t height) {
/* If the client is anchored to two opposite edges, layer_surface.configure will return
* size without margins for the axis.
* layer_surface.set_size, however, expects size with margins for the anchored axis.
* This is not specified by wlr-layer-shell and based on actual behavior of sway.
*
* If the size for unanchored axis is not set (0), change request to 1 to avoid automatic
* assignment by the compositor.
*/
if ((anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR) {
width = width > 0 ? width : 1;
if (height > 1) {
height += margins_.top + margins_.bottom;
}
} else {
height = height > 0 ? height : 1;
if (width > 1) {
width += margins_.right + margins_.left;
}
}
spdlog::debug("Set surface size {}x{} for output {}", width, height, output_name_);
zwlr_layer_surface_v1_set_size(layer_surface_.get(), width, height);
}
static void onSurfaceConfigure(void* data, struct zwlr_layer_surface_v1* surface, uint32_t serial,
uint32_t width, uint32_t height) {
auto o = static_cast<RawSurfaceImpl*>(data);
if (width != o->width_ || height != o->height_) {
o->width_ = width;
o->height_ = height;
o->window_.set_size_request(o->width_, o->height_);
o->window_.resize(o->width_, o->height_);
o->setExclusiveZone(o->exclusive_zone_);
spdlog::info(BAR_SIZE_MSG,
o->width_ == 1 ? "auto" : std::to_string(o->width_),
o->height_ == 1 ? "auto" : std::to_string(o->height_),
o->output_name_);
o->commit();
}
zwlr_layer_surface_v1_ack_configure(surface, serial);
}
static void onSurfaceClosed(void* data, struct zwlr_layer_surface_v1* /* surface */) {
auto o = static_cast<RawSurfaceImpl*>(data);
o->layer_surface_.reset();
}
};
}; // namespace waybar
waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
: output(w_output),
config(w_config),
window{Gtk::WindowType::WINDOW_TOPLEVEL},
surface(nullptr),
layer_surface_(nullptr),
anchor_(ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP),
layer_{bar_layer::BOTTOM},
left_(Gtk::ORIENTATION_HORIZONTAL, 0),
center_(Gtk::ORIENTATION_HORIZONTAL, 0),
right_(Gtk::ORIENTATION_HORIZONTAL, 0),
@ -25,26 +403,30 @@ 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["position"] == "right" || config["position"] == "left") {
height_ = 0;
width_ = 1;
if (config["layer"] == "top") {
layer_ = bar_layer::TOP;
} else if (config["layer"] == "overlay") {
layer_ = bar_layer::OVERLAY;
}
height_ = config["height"].isUInt() ? config["height"].asUInt() : height_;
width_ = config["width"].isUInt() ? config["width"].asUInt() : width_;
if (config["position"] == "bottom") {
anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
} else if (config["position"] == "left") {
anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
} else if (config["position"] == "right") {
anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
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;
}
if (anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM ||
anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) {
anchor_ |= ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
} else if (anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT ||
anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) {
anchor_ |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
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") {
left_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
center_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
right_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
@ -52,6 +434,22 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
vertical = true;
}
left_.get_style_context()->add_class("modules-left");
center_.get_style_context()->add_class("modules-center");
right_.get_style_context()->add_class("modules-right");
if (config["spacing"].isInt()) {
int spacing = config["spacing"].asInt();
left_.set_spacing(spacing);
center_.set_spacing(spacing);
right_.set_spacing(spacing);
}
uint32_t height = config["height"].isUInt() ? config["height"].asUInt() : 0;
uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0;
struct bar_margins margins_;
if (config["margin-top"].isInt() || config["margin-right"].isInt() ||
config["margin-bottom"].isInt() || config["margin-left"].isInt()) {
margins_ = {
@ -98,170 +496,63 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
}
#ifdef HAVE_GTK_LAYER_SHELL
use_gls_ = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true;
if (use_gls_) {
initGtkLayerShell();
}
#endif
window.signal_realize().connect_notify(sigc::mem_fun(*this, &Bar::onRealize));
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigure));
window.set_size_request(width_, height_);
setupWidgets();
if (window.get_realized()) {
onRealize();
}
window.show_all();
}
void waybar::Bar::onConfigure(GdkEventConfigure* ev) {
auto tmp_height = height_;
auto tmp_width = width_;
if (ev->height > static_cast<int>(height_)) {
// Default minimal value
if (height_ != 1) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
if (config["height"].isUInt()) {
spdlog::info(SIZE_DEFINED, "Height");
} else {
tmp_height = ev->height;
}
}
if (ev->width > static_cast<int>(width_)) {
// Default minimal value
if (width_ != 1) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
if (config["width"].isUInt()) {
spdlog::info(SIZE_DEFINED, "Width");
} else {
tmp_width = ev->width;
}
}
if (use_gls_) {
width_ = tmp_width;
height_ = tmp_height;
spdlog::debug("Set surface size {}x{} for output {}", width_, height_, output->name);
setExclusiveZone(tmp_width, tmp_height);
} else if (tmp_width != width_ || tmp_height != height_) {
setSurfaceSize(tmp_width, tmp_height);
}
}
#ifdef HAVE_GTK_LAYER_SHELL
void waybar::Bar::initGtkLayerShell() {
auto gtk_window = window.gobj();
// this has to be executed before GtkWindow.realize
gtk_layer_init_for_window(gtk_window);
gtk_layer_set_keyboard_interactivity(gtk_window, FALSE);
auto layer = config["layer"] == "top" ? GTK_LAYER_SHELL_LAYER_TOP : GTK_LAYER_SHELL_LAYER_BOTTOM;
gtk_layer_set_layer(gtk_window, layer);
gtk_layer_set_monitor(gtk_window, output->monitor->gobj());
gtk_layer_set_namespace(gtk_window, "waybar");
gtk_layer_set_anchor(
gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
gtk_layer_set_anchor(
gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
gtk_layer_set_anchor(
gtk_window, GTK_LAYER_SHELL_EDGE_TOP, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP);
gtk_layer_set_anchor(
gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, margins_.left);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, margins_.right);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_TOP, margins_.top);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, margins_.bottom);
if (width_ > 1 && height_ > 1) {
/* configure events are not emitted if the bar is using initial size */
setExclusiveZone(width_, height_);
}
}
#endif
void waybar::Bar::onRealize() {
auto gdk_window = window.get_window()->gobj();
gdk_wayland_window_set_use_custom_surface(gdk_window);
}
void waybar::Bar::onMap(GdkEventAny* ev) {
auto gdk_window = window.get_window()->gobj();
surface = gdk_wayland_window_get_wl_surface(gdk_window);
if (use_gls_) {
return;
}
auto client = waybar::Client::inst();
// owned by output->monitor; no need to destroy
auto wl_output = gdk_wayland_monitor_get_wl_output(output->monitor->gobj());
auto layer =
config["layer"] == "top" ? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
layer_surface_ = zwlr_layer_shell_v1_get_layer_surface(
client->layer_shell, surface, wl_output, layer, "waybar");
zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_, false);
zwlr_layer_surface_v1_set_anchor(layer_surface_, anchor_);
zwlr_layer_surface_v1_set_margin(
layer_surface_, margins_.top, margins_.right, margins_.bottom, margins_.left);
setSurfaceSize(width_, height_);
setExclusiveZone(width_, height_);
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = layerSurfaceHandleConfigure,
.closed = layerSurfaceHandleClosed,
};
zwlr_layer_surface_v1_add_listener(layer_surface_, &layer_surface_listener, this);
wl_surface_commit(surface);
wl_display_roundtrip(client->wl_display);
}
void waybar::Bar::setExclusiveZone(uint32_t width, uint32_t height) {
auto zone = 0;
if (visible) {
// exclusive zone already includes margin for anchored edge,
// only opposite margin should be added
if (vertical) {
zone += width;
zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? margins_.right : margins_.left;
} else {
zone += height;
zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? margins_.bottom : margins_.top;
}
}
spdlog::debug("Set exclusive zone {} for output {}", zone, output->name);
#ifdef HAVE_GTK_LAYER_SHELL
if (use_gls_) {
gtk_layer_set_exclusive_zone(window.gobj(), zone);
bool use_gls = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true;
if (use_gls) {
surface_impl_ = std::make_unique<GLSSurfaceImpl>(window, *output);
} else
#endif
{
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_, zone);
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);
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
setupWidgets();
window.show_all();
if (spdlog::should_log(spdlog::level::debug)) {
// Unfortunately, this function isn't in the C++ bindings, so we have to call the C version.
char* gtk_tree = gtk_style_context_to_string(
window.get_style_context()->gobj(),
(GtkStyleContextPrintFlags)(GTK_STYLE_CONTEXT_PRINT_RECURSE |
GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE));
spdlog::debug("GTK widget tree:\n{}", gtk_tree);
g_free(gtk_tree);
}
}
void waybar::Bar::setSurfaceSize(uint32_t width, uint32_t height) {
/* If the client is anchored to two opposite edges, layer_surface.configure will return
* size without margins for the axis.
* layer_surface.set_size, however, expects size with margins for the anchored axis.
* This is not specified by wlr-layer-shell and based on actual behavior of sway.
void waybar::Bar::onMap(GdkEventAny*) {
/*
* Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).
*/
if (vertical && height > 1) {
height += margins_.top + margins_.bottom;
}
if (!vertical && width > 1) {
width += margins_.right + margins_.left;
}
spdlog::debug("Set surface size {}x{} for output {}", width, height, output->name);
zwlr_layer_surface_v1_set_size(layer_surface_, width, height);
auto gdk_window = window.get_window()->gobj();
surface = gdk_wayland_window_get_wl_surface(gdk_window);
}
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();
}
void waybar::Bar::toggle() { setVisible(!visible); }
// Converting string to button code rn as to avoid doing it later
void waybar::Bar::setupAltFormatKeyForModule(const std::string& module_name) {
if (config.isMember(module_name)) {
@ -323,48 +614,6 @@ void waybar::Bar::handleSignal(int signal) {
}
}
void waybar::Bar::layerSurfaceHandleConfigure(void* data, struct zwlr_layer_surface_v1* surface,
uint32_t serial, uint32_t width, uint32_t height) {
auto o = static_cast<waybar::Bar*>(data);
if (width != o->width_ || height != o->height_) {
o->width_ = width;
o->height_ = height;
o->window.set_size_request(o->width_, o->height_);
o->window.resize(o->width_, o->height_);
o->setExclusiveZone(width, height);
spdlog::info(BAR_SIZE_MSG,
o->width_ == 1 ? "auto" : std::to_string(o->width_),
o->height_ == 1 ? "auto" : std::to_string(o->height_),
o->output->name);
wl_surface_commit(o->surface);
}
zwlr_layer_surface_v1_ack_configure(surface, serial);
}
void waybar::Bar::layerSurfaceHandleClosed(void* data, struct zwlr_layer_surface_v1* /*surface*/) {
auto o = static_cast<waybar::Bar*>(data);
if (o->layer_surface_) {
zwlr_layer_surface_v1_destroy(o->layer_surface_);
o->layer_surface_ = nullptr;
}
o->modules_left_.clear();
o->modules_center_.clear();
o->modules_right_.clear();
}
auto waybar::Bar::toggle() -> void {
visible = !visible;
if (!visible) {
window.get_style_context()->add_class("hidden");
window.set_opacity(0);
} else {
window.get_style_context()->remove_class("hidden");
window.set_opacity(1);
}
setExclusiveZone(width_, height_);
wl_surface_commit(surface);
}
void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
if (config[pos].isArray()) {
for (const auto& name : config[pos]) {

View File

@ -1,37 +1,25 @@
#include "client.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h>
#include <fstream>
#include <iostream>
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "util/clara.hpp"
#include "util/json.hpp"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
waybar::Client *waybar::Client::inst() {
static auto c = new Client();
return c;
}
const std::string waybar::Client::getValidPath(const std::vector<std::string> &paths) const {
wordexp_t p;
for (const std::string &path : paths) {
if (wordexp(path.c_str(), &p, 0) == 0) {
if (access(*p.we_wordv, F_OK) == 0) {
std::string result = *p.we_wordv;
wordfree(&p);
return result;
}
wordfree(&p);
}
}
return std::string();
}
void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) {
auto client = static_cast<Client *>(data);
if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
// limit version to a highest supported by the client protocol file
version = std::min<uint32_t>(version, zwlr_layer_shell_v1_interface.version);
client->layer_shell = static_cast<struct zwlr_layer_shell_v1 *>(
wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version));
} else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 &&
@ -53,9 +41,9 @@ void waybar::Client::handleOutput(struct waybar_output &output) {
static const struct zxdg_output_v1_listener xdgOutputListener = {
.logical_position = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {},
.logical_size = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {},
.done = [](void *, struct zxdg_output_v1 *) {},
.done = &handleOutputDone,
.name = &handleOutputName,
.description = [](void *, struct zxdg_output_v1 *, const char *) {},
.description = &handleOutputDescription,
};
// owned by output->monitor; no need to destroy
auto wl_output = gdk_wayland_monitor_get_wl_output(output.monitor->gobj());
@ -63,27 +51,6 @@ void waybar::Client::handleOutput(struct waybar_output &output) {
zxdg_output_v1_add_listener(output.xdg_output.get(), &xdgOutputListener, &output);
}
bool waybar::Client::isValidOutput(const Json::Value &config, struct waybar_output &output) {
if (config["output"].isArray()) {
for (auto const &output_conf : config["output"]) {
if (output_conf.isString() && output_conf.asString() == output.name) {
return true;
}
}
return false;
} else if (config["output"].isString()) {
auto config_output_name = config["output"].asString();
if (!config_output_name.empty()) {
if (config_output_name.substr(0, 1) == "!") {
return config_output_name.substr(1) != output.name;
}
return config_output_name == output.name;
}
}
return true;
}
struct waybar::waybar_output &waybar::Client::getOutput(void *addr) {
auto it = std::find_if(
outputs_.begin(), outputs_.end(), [&addr](const auto &output) { return &output == addr; });
@ -94,17 +61,36 @@ struct waybar::waybar_output &waybar::Client::getOutput(void *addr) {
}
std::vector<Json::Value> waybar::Client::getOutputConfigs(struct waybar_output &output) {
std::vector<Json::Value> configs;
if (config_.isArray()) {
for (auto const &config : config_) {
if (config.isObject() && isValidOutput(config, output)) {
configs.push_back(config);
return config.getOutputConfigs(output.name, output.identifier);
}
void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_output*/) {
auto client = waybar::Client::inst();
try {
auto &output = client->getOutput(data);
/**
* Multiple .done events may arrive in batch. In this case libwayland would queue
* xdg_output.destroy and dispatch all pending events, triggering this callback several times
* for the same output. .done events can also arrive after that for a scale or position changes.
* We wouldn't want to draw a duplicate bar for each such event either.
*
* All the properties we care about are immutable so it's safe to delete the xdg_output object
* on the first event and use the ptr value to check that the callback was already invoked.
*/
if (output.xdg_output) {
output.xdg_output.reset();
spdlog::debug("Output detection done: {} ({})", output.name, output.identifier);
auto configs = client->getOutputConfigs(output);
if (!configs.empty()) {
for (const auto &config : configs) {
client->bars.emplace_back(std::make_unique<Bar>(&output, config));
}
}
}
} else if (isValidOutput(config_, output)) {
configs.push_back(config_);
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
return configs;
}
void waybar::Client::handleOutputName(void * data, struct zxdg_output_v1 * /*xdg_output*/,
@ -113,22 +99,21 @@ void waybar::Client::handleOutputName(void * data, struct zxdg_output_v1 *
try {
auto &output = client->getOutput(data);
output.name = name;
spdlog::debug("Output detected: {} ({} {})",
name,
output.monitor->get_manufacturer(),
output.monitor->get_model());
auto configs = client->getOutputConfigs(output);
if (configs.empty()) {
output.xdg_output.reset();
} else {
wl_display_roundtrip(client->wl_display);
for (const auto &config : configs) {
client->bars.emplace_back(std::make_unique<Bar>(&output, config));
Glib::RefPtr<Gdk::Screen> screen = client->bars.back()->window.get_screen();
client->style_context_->add_provider_for_screen(
screen, client->css_provider_, GTK_STYLE_PROVIDER_PRIORITY_USER);
}
}
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
}
void waybar::Client::handleOutputDescription(void *data, struct zxdg_output_v1 * /*xdg_output*/,
const char *description) {
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);
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
@ -142,10 +127,21 @@ void waybar::Client::handleMonitorAdded(Glib::RefPtr<Gdk::Monitor> monitor) {
void waybar::Client::handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor) {
spdlog::debug("Output removed: {} {}", monitor->get_manufacturer(), monitor->get_model());
/* This event can be triggered from wl_display_roundtrip called by GTK or our code.
* Defer destruction of bars for the output to the next iteration of the event loop to avoid
* deleting objects referenced by currently executed code.
*/
Glib::signal_idle().connect_once(
sigc::bind(sigc::mem_fun(*this, &Client::handleDeferredMonitorRemoval), monitor),
Glib::PRIORITY_HIGH_IDLE);
}
void waybar::Client::handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor) {
for (auto it = bars.begin(); it != bars.end();) {
if ((*it)->output->monitor == monitor) {
auto output_name = (*it)->output->name;
(*it)->window.close();
(*it)->window.hide();
gtk_app->remove_window((*it)->window);
it = bars.erase(it);
spdlog::info("Bar removed from output: {}", output_name);
} else {
@ -155,40 +151,14 @@ void waybar::Client::handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor) {
outputs_.remove_if([&monitor](const auto &output) { return output.monitor == monitor; });
}
std::tuple<const std::string, const std::string> waybar::Client::getConfigs(
const std::string &config, const std::string &style) const {
auto config_file = config.empty() ? getValidPath({
"$XDG_CONFIG_HOME/waybar/config",
"$HOME/.config/waybar/config",
"$HOME/waybar/config",
SYSCONFDIR "/xdg/waybar/config",
"./resources/config",
})
: config;
auto css_file = style.empty() ? getValidPath({
"$XDG_CONFIG_HOME/waybar/style.css",
"$HOME/.config/waybar/style.css",
"$HOME/waybar/style.css",
SYSCONFDIR "/xdg/waybar/style.css",
"./resources/style.css",
})
: style;
if (css_file.empty() || config_file.empty()) {
throw std::runtime_error("Missing required resources files");
const std::string waybar::Client::getStyle(const std::string &style) {
auto css_file = style.empty() ? Config::findConfigPath({"style.css"}) : style;
if (!css_file) {
throw std::runtime_error("Missing required resource files");
}
spdlog::info("Resources files: {}, {}", config_file, css_file);
return {config_file, css_file};
}
auto waybar::Client::setupConfig(const std::string &config_file) -> void {
std::ifstream file(config_file);
if (!file.is_open()) {
throw std::runtime_error("Can't open config file");
}
std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
util::JsonParser parser;
config_ = parser.parse(str);
}
spdlog::info("Using CSS file {}", css_file.value());
return css_file.value();
};
auto waybar::Client::setupCss(const std::string &css_file) -> void {
css_provider_ = Gtk::CssProvider::create();
@ -198,6 +168,9 @@ auto waybar::Client::setupCss(const std::string &css_file) -> void {
if (!css_provider_->load_from_path(css_file)) {
throw std::runtime_error("Can't open style file");
}
// there's always only one screen
style_context_->add_provider_for_screen(
Gdk::Screen::get_default(), css_provider_, GTK_STYLE_PROVIDER_PRIORITY_USER);
}
void waybar::Client::bindInterfaces() {
@ -224,14 +197,14 @@ void waybar::Client::bindInterfaces() {
int waybar::Client::main(int argc, char *argv[]) {
bool show_help = false;
bool show_version = false;
std::string config;
std::string style;
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") |
clara::detail::Opt(config, "config")["-c"]["--config"]("Config path") |
clara::detail::Opt(style, "style")["-s"]["--style"]("Style path") |
clara::detail::Opt(config_opt, "config")["-c"]["--config"]("Config path") |
clara::detail::Opt(style_opt, "style")["-s"]["--style"]("Style path") |
clara::detail::Opt(
log_level,
"trace|debug|info|warning|error|critical|off")["-l"]["--log-level"]("Log level") |
@ -252,7 +225,8 @@ int waybar::Client::main(int argc, char *argv[]) {
if (!log_level.empty()) {
spdlog::set_level(spdlog::level::from_str(log_level));
}
gtk_app = Gtk::Application::create(argc, argv, "fr.arouillard.waybar", Gio::APPLICATION_HANDLES_COMMAND_LINE);
gtk_app = Gtk::Application::create(
argc, argv, "fr.arouillard.waybar", Gio::APPLICATION_HANDLES_COMMAND_LINE);
gdk_display = Gdk::Display::get_default();
if (!gdk_display) {
throw std::runtime_error("Can't find display");
@ -261,17 +235,16 @@ int waybar::Client::main(int argc, char *argv[]) {
throw std::runtime_error("Bar need to run under Wayland");
}
wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj());
auto [config_file, css_file] = getConfigs(config, style);
setupConfig(config_file);
config.load(config_opt);
auto css_file = getStyle(style_opt);
setupCss(css_file);
bindInterfaces();
gtk_app->hold();
gtk_app->run();
bars.clear();
zxdg_output_manager_v1_destroy(xdg_output_manager);
zwlr_layer_shell_v1_destroy(layer_shell);
zwp_idle_inhibit_manager_v1_destroy(idle_inhibit_manager);
wl_registry_destroy(registry);
wl_display_disconnect(wl_display);
return 0;
}
void waybar::Client::reset() {
gtk_app->quit();
}

153
src/config.cpp Normal file
View File

@ -0,0 +1,153 @@
#include "config.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h>
#include <unistd.h>
#include <wordexp.h>
#include <fstream>
#include <stdexcept>
#include "util/json.hpp"
namespace waybar {
const std::vector<std::string> Config::CONFIG_DIRS = {
"$XDG_CONFIG_HOME/waybar/",
"$HOME/.config/waybar/",
"$HOME/waybar/",
"/etc/xdg/waybar/",
SYSCONFDIR "/xdg/waybar/",
"./resources/",
};
std::optional<std::string> tryExpandPath(const std::string &path) {
wordexp_t p;
if (wordexp(path.c_str(), &p, 0) == 0) {
if (access(*p.we_wordv, F_OK) == 0) {
std::string result = *p.we_wordv;
wordfree(&p);
return result;
}
wordfree(&p);
}
return std::nullopt;
}
std::optional<std::string> Config::findConfigPath(const std::vector<std::string> &names,
const std::vector<std::string> &dirs) {
std::vector<std::string> paths;
for (const auto &dir : dirs) {
for (const auto &name : names) {
if (auto res = tryExpandPath(dir + name); res) {
return res;
}
}
}
return std::nullopt;
}
void Config::setupConfig(Json::Value &dst, const std::string &config_file, int depth) {
if (depth > 100) {
throw std::runtime_error("Aborting due to likely recursive include in config files");
}
std::ifstream file(config_file);
if (!file.is_open()) {
throw std::runtime_error("Can't open config file");
}
std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
util::JsonParser parser;
Json::Value tmp_config = parser.parse(str);
if (tmp_config.isArray()) {
for (auto &config_part : tmp_config) {
resolveConfigIncludes(config_part, depth);
}
} else {
resolveConfigIncludes(tmp_config, depth);
}
mergeConfig(dst, tmp_config);
}
void Config::resolveConfigIncludes(Json::Value &config, int depth) {
Json::Value includes = config["include"];
if (includes.isArray()) {
for (const auto &include : includes) {
spdlog::info("Including resource file: {}", include.asString());
setupConfig(config, tryExpandPath(include.asString()).value_or(""), ++depth);
}
} else if (includes.isString()) {
spdlog::info("Including resource file: {}", includes.asString());
setupConfig(config, tryExpandPath(includes.asString()).value_or(""), ++depth);
}
}
void Config::mergeConfig(Json::Value &a_config_, Json::Value &b_config_) {
if (!a_config_) {
// For the first config
a_config_ = b_config_;
} else if (a_config_.isObject() && b_config_.isObject()) {
for (const auto &key : b_config_.getMemberNames()) {
// [] creates key with default value. Use `get` to avoid that.
if (a_config_.get(key, Json::Value::nullSingleton()).isObject() &&
b_config_[key].isObject()) {
mergeConfig(a_config_[key], b_config_[key]);
} else if (!a_config_.isMember(key)) {
// do not allow overriding value set by top or previously included config
a_config_[key] = b_config_[key];
} else {
spdlog::trace("Option {} is already set; ignoring value {}", key, b_config_[key]);
}
}
} else {
spdlog::error("Cannot merge config, conflicting or invalid JSON types");
}
}
bool isValidOutput(const Json::Value &config, const std::string &name,
const std::string &identifier) {
if (config["output"].isArray()) {
for (auto const &output_conf : config["output"]) {
if (output_conf.isString() &&
(output_conf.asString() == name || output_conf.asString() == identifier)) {
return true;
}
}
return false;
} else if (config["output"].isString()) {
auto config_output = config["output"].asString();
if (!config_output.empty()) {
if (config_output.substr(0, 1) == "!") {
return config_output.substr(1) != name && config_output.substr(1) != identifier;
}
return config_output == name || config_output == identifier;
}
}
return true;
}
void Config::load(const std::string &config) {
auto file = config.empty() ? findConfigPath({"config", "config.jsonc"}) : config;
if (!file) {
throw std::runtime_error("Missing required resource files");
}
config_file_ = file.value();
spdlog::info("Using configuration file {}", config_file_);
setupConfig(config_, config_file_, 0);
}
std::vector<Json::Value> Config::getOutputConfigs(const std::string &name,
const std::string &identifier) {
std::vector<Json::Value> configs;
if (config_.isArray()) {
for (auto const &config : config_) {
if (config.isObject() && isValidOutput(config, name, identifier)) {
configs.push_back(config);
}
}
} else if (isValidOutput(config_, name, identifier)) {
configs.push_back(config_);
}
return configs;
}
} // namespace waybar

View File

@ -22,6 +22,22 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "sway/window") {
return new waybar::modules::sway::Window(id, bar_, config_[name]);
}
if (ref == "sway/language") {
return new waybar::modules::sway::Language(id, config_[name]);
}
#endif
#ifdef HAVE_WLR
if (ref == "wlr/taskbar") {
return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]);
}
if (ref == "wlr/workspaces") {
return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]);
}
#endif
#ifdef HAVE_RIVER
if (ref == "river/tags") {
return new waybar::modules::river::Tags(id, bar_, config_[name]);
}
#endif
if (ref == "idle_inhibitor") {
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
@ -57,6 +73,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
return new waybar::modules::Backlight(id, config_[name]);
}
#endif
#ifdef HAVE_LIBEVDEV
if (ref == "keyboard-state") {
return new waybar::modules::KeyboardState(id, bar_, config_[name]);
}
#endif
#ifdef HAVE_LIBPULSE
if (ref == "pulseaudio") {
return new waybar::modules::Pulseaudio(id, config_[name]);
@ -66,14 +87,21 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "mpd") {
return new waybar::modules::MPD(id, config_[name]);
}
#endif
#ifdef HAVE_LIBSNDIO
if (ref == "sndio") {
return new waybar::modules::Sndio(id, config_[name]);
}
#endif
if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]);
}
#if defined(__linux__)
# ifdef WANT_RFKILL
if (ref == "bluetooth") {
return new waybar::modules::Bluetooth(id, config_[name]);
}
# endif
#endif
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
return new waybar::modules::Custom(ref.substr(7), id, config_[name]);

View File

@ -1,16 +1,89 @@
#include <csignal>
#include <list>
#include <mutex>
#include <sys/types.h>
#include <sys/wait.h>
#include <spdlog/spdlog.h>
#include "client.hpp"
std::mutex reap_mtx;
std::list<pid_t> reap;
volatile bool reload;
void* signalThread(void* args) {
int err, signum;
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
while (true) {
err = sigwait(&mask, &signum);
if (err != 0) {
spdlog::error("sigwait failed: {}", strerror(errno));
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;
}
}
}
void startSignalThread(void) {
int err;
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
// 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);
}
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);
}
}
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();
}
});
std::signal(SIGUSR2, [](int /*signal*/) {
spdlog::info("Reloading...");
reload = true;
waybar::Client::inst()->reset();
});
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
std::signal(sig, [](int sig) {
for (auto& bar : waybar::Client::inst()->bars) {
@ -18,8 +91,14 @@ int main(int argc, char* argv[]) {
}
});
}
startSignalThread();
auto ret = 0;
do {
reload = false;
ret = client->main(argc, argv);
} while (reload);
auto ret = client->main(argc, argv);
delete client;
return ret;
} catch (const std::exception& e) {

View File

@ -173,7 +173,7 @@ auto waybar::modules::Backlight::update() -> void {
return;
}
const auto percent = best->get_max() == 0 ? 100 : best->get_actual() * 100 / best->get_max();
const uint8_t percent = best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());
label_.set_markup(fmt::format(
format_, fmt::arg("percent", std::to_string(percent)), fmt::arg("icon", getIcon(percent))));
getState(percent);

View File

@ -4,46 +4,82 @@
waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config)
: ALabel(config, "battery", id, "{capacity}%", 60) {
getBatteries();
fd_ = inotify_init1(IN_CLOEXEC);
if (fd_ == -1) {
battery_watch_fd_ = inotify_init1(IN_CLOEXEC);
if (battery_watch_fd_ == -1) {
throw std::runtime_error("Unable to listen batteries.");
}
for (auto const& bat : batteries_) {
auto wd = inotify_add_watch(fd_, (bat / "uevent").c_str(), IN_ACCESS);
if (wd != -1) {
wds_.push_back(wd);
}
global_watch_fd_ = inotify_init1(IN_CLOEXEC);
if (global_watch_fd_ == -1) {
throw std::runtime_error("Unable to listen batteries.");
}
// Watch the directory for any added or removed batteries
global_watch = inotify_add_watch(global_watch_fd_, data_dir_.c_str(), IN_CREATE | IN_DELETE);
if (global_watch < 0) {
throw std::runtime_error("Could not watch for battery plug/unplug");
}
refreshBatteries();
worker();
}
waybar::modules::Battery::~Battery() {
for (auto wd : wds_) {
inotify_rm_watch(fd_, wd);
std::lock_guard<std::mutex> guard(battery_list_mutex_);
if (global_watch >= 0) {
inotify_rm_watch(global_watch_fd_, global_watch);
}
close(fd_);
close(global_watch_fd_);
for (auto it = batteries_.cbegin(); it != batteries_.cend(); it++) {
auto watch_id = (*it).second;
if (watch_id >= 0) {
inotify_rm_watch(battery_watch_fd_, watch_id);
}
batteries_.erase(it);
}
close(battery_watch_fd_);
}
void waybar::modules::Battery::worker() {
thread_timer_ = [this] {
// Make sure we eventually update the list of batteries even if we miss an
// inotify event for some reason
refreshBatteries();
dp.emit();
thread_timer_.sleep_for(interval_);
};
thread_ = [this] {
struct inotify_event event = {0};
int nbytes = read(fd_, &event, sizeof(event));
int nbytes = read(battery_watch_fd_, &event, sizeof(event));
if (nbytes != sizeof(event) || event.mask & IN_IGNORED) {
thread_.stop();
return;
}
// TODO: don't stop timer for now since there is some bugs :?
// thread_timer_.stop();
dp.emit();
};
thread_battery_update_ = [this] {
struct inotify_event event = {0};
int nbytes = read(global_watch_fd_, &event, sizeof(event));
if (nbytes != sizeof(event) || event.mask & IN_IGNORED) {
thread_.stop();
return;
}
refreshBatteries();
dp.emit();
};
}
void waybar::modules::Battery::getBatteries() {
void waybar::modules::Battery::refreshBatteries() {
std::lock_guard<std::mutex> guard(battery_list_mutex_);
// Mark existing list of batteries as not necessarily found
std::map<fs::path, bool> check_map;
for (auto const& bat : batteries_) {
check_map[bat.first] = false;
}
try {
for (auto& node : fs::directory_iterator(data_dir_)) {
if (!fs::is_directory(node)) {
@ -53,8 +89,23 @@ void waybar::modules::Battery::getBatteries() {
auto bat_defined = config_["bat"].isString();
if (((bat_defined && dir_name == config_["bat"].asString()) || !bat_defined) &&
fs::exists(node.path() / "capacity") && fs::exists(node.path() / "uevent") &&
fs::exists(node.path() / "status")) {
batteries_.push_back(node.path());
fs::exists(node.path() / "status") && fs::exists(node.path() / "type")) {
std::string type;
std::ifstream(node.path() / "type") >> type;
if (!type.compare("Battery")){
check_map[node.path()] = true;
auto search = batteries_.find(node.path());
if (search == batteries_.end()) {
// We've found a new battery save it and start listening for events
auto event_path = (node.path() / "uevent");
auto wd = inotify_add_watch(battery_watch_fd_, event_path.c_str(), IN_ACCESS);
if (wd < 0) {
throw std::runtime_error("Could not watch events for " + node.path().string());
}
batteries_[node.path()] = wd;
}
}
}
auto adap_defined = config_["adapter"].isString();
if (((adap_defined && dir_name == config_["adapter"].asString()) || !adap_defined) &&
@ -71,36 +122,86 @@ void waybar::modules::Battery::getBatteries() {
}
throw std::runtime_error("No batteries.");
}
// Remove any batteries that are no longer present and unwatch them
for (auto const& check : check_map) {
if (!check.second) {
auto watch_id = batteries_[check.first];
if (watch_id >= 0) {
inotify_rm_watch(battery_watch_fd_, watch_id);
}
batteries_.erase(check.first);
}
}
}
const std::tuple<uint8_t, float, std::string> waybar::modules::Battery::getInfos() const {
// Unknown > Full > Not charging > Discharging > Charging
static bool status_gt(const std::string& a, const std::string& b) {
if (a == b) return false;
else if (a == "Unknown") return true;
else if (a == "Full" && b != "Unknown") return true;
else if (a == "Not charging" && b != "Unknown" && b != "Full") return true;
else if (a == "Discharging" && b != "Unknown" && b != "Full" && b != "Not charging") return true;
return false;
}
const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::getInfos() {
std::lock_guard<std::mutex> guard(battery_list_mutex_);
try {
uint16_t total = 0;
uint32_t total_power = 0; // μW
uint32_t total_energy = 0; // μWh
uint32_t total_energy_full = 0;
uint32_t total_energy_full_design = 0;
std::string status = "Unknown";
for (auto const& bat : batteries_) {
uint16_t capacity;
for (auto const& item : batteries_) {
auto bat = item.first;
uint32_t power_now;
uint32_t energy_full;
uint32_t energy_now;
uint32_t energy_full_design;
std::string _status;
std::ifstream(bat / "capacity") >> capacity;
std::ifstream(bat / "status") >> _status;
auto rate_path = fs::exists(bat / "current_now") ? "current_now" : "power_now";
std::ifstream(bat / rate_path) >> power_now;
auto now_path = fs::exists(bat / "charge_now") ? "charge_now" : "energy_now";
std::ifstream(bat / now_path) >> energy_now;
auto full_path = fs::exists(bat / "charge_full") ? "charge_full" : "energy_full";
std::ifstream(bat / full_path) >> energy_full;
if (_status != "Unknown") {
// Some battery will report current and charge in μA/μAh.
// Scale these by the voltage to get μW/μWh.
if (fs::exists(bat / "current_now")) {
uint32_t voltage_now;
uint32_t current_now;
uint32_t charge_now;
uint32_t charge_full;
uint32_t charge_full_design;
std::ifstream(bat / "voltage_now") >> voltage_now;
std::ifstream(bat / "current_now") >> current_now;
std::ifstream(bat / "charge_full") >> charge_full;
std::ifstream(bat / "charge_full_design") >> charge_full_design;
if (fs::exists(bat / "charge_now"))
std::ifstream(bat / "charge_now") >> charge_now;
else {
// charge_now is missing on some systems, estimate using capacity.
uint32_t capacity;
std::ifstream(bat / "capacity") >> capacity;
charge_now = (capacity * charge_full) / 100;
}
power_now = ((uint64_t)current_now * (uint64_t)voltage_now) / 1000000;
energy_now = ((uint64_t)charge_now * (uint64_t)voltage_now) / 1000000;
energy_full = ((uint64_t)charge_full * (uint64_t)voltage_now) / 1000000;
energy_full_design = ((uint64_t)charge_full_design * (uint64_t)voltage_now) / 1000000;
} else {
std::ifstream(bat / "power_now") >> power_now;
std::ifstream(bat / "energy_now") >> energy_now;
std::ifstream(bat / "energy_full") >> energy_full;
std::ifstream(bat / "energy_full_design") >> energy_full_design;
}
// Show the "smallest" status among all batteries
if (status_gt(status, _status)) {
status = _status;
}
total += capacity;
total_power += power_now;
total_energy += energy_now;
total_energy_full += energy_full;
total_energy_full_design += energy_full_design;
}
if (!adapter_.empty() && status == "Discharging") {
bool online;
@ -114,22 +215,40 @@ const std::tuple<uint8_t, float, std::string> waybar::modules::Battery::getInfos
time_remaining = (float)total_energy / total_power;
} else if (status == "Charging" && total_power != 0) {
time_remaining = -(float)(total_energy_full - total_energy) / total_power;
if (time_remaining > 0.0f) {
// If we've turned positive it means the battery is past 100% and so
// just report that as no time remaining
time_remaining = 0.0f;
}
}
float capacity = ((float)total_energy * 100.0f / (float) total_energy_full);
// Handle design-capacity
if (config_["design-capacity"].isBool() ? config_["design-capacity"].asBool() : false) {
capacity = ((float)total_energy * 100.0f / (float) total_energy_full_design);
}
uint16_t capacity = total / batteries_.size();
// Handle full-at
if (config_["full-at"].isUInt()) {
auto full_at = config_["full-at"].asUInt();
if (full_at < 100) {
capacity = 100.f * capacity / full_at;
if (capacity > full_at) {
capacity = full_at;
}
}
}
return {capacity, time_remaining, status};
if (capacity > 100.f) {
// This can happen when the battery is calibrating and goes above 100%
// Handle it gracefully by clamping at 100%
capacity = 100.f;
}
uint8_t cap = round(capacity);
if (cap == 100 && status == "Charging") {
// If we've reached 100% just mark as full as some batteries can stay
// stuck reporting they're still charging but not yet done
status = "Full";
}
return {cap, time_remaining, status, total_power / 1e6};
} catch (const std::exception& e) {
spdlog::error("Battery: {}", e.what());
return {0, 0, "Unknown"};
return {0, 0, "Unknown", 0};
}
}
@ -153,6 +272,10 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai
uint16_t full_hours = static_cast<uint16_t>(hoursRemaining);
uint16_t minutes = static_cast<uint16_t>(60 * (hoursRemaining - full_hours));
auto format = std::string("{H} h {M} min");
if (full_hours == 0 && minutes == 0) {
// Migh as well not show "0h 0min"
return "";
}
if (config_["format-time"].isString()) {
format = config_["format-time"].asString();
}
@ -160,26 +283,41 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai
}
auto waybar::modules::Battery::update() -> void {
auto [capacity, time_remaining, status] = getInfos();
auto [capacity, time_remaining, status, power] = getInfos();
if (status == "Unknown") {
status = getAdapterStatus(capacity);
}
if (tooltipEnabled()) {
std::string tooltip_text;
if (time_remaining != 0) {
std::string time_to = std::string("Time to ") + ((time_remaining > 0) ? "empty" : "full");
tooltip_text = time_to + ": " + formatTimeRemaining(time_remaining);
} else {
tooltip_text = status;
}
label_.set_tooltip_text(tooltip_text);
}
auto status_pretty = status;
// Transform to lowercase and replace space with dash
std::transform(status.begin(), status.end(), status.begin(), [](char ch) {
return ch == ' ' ? '-' : std::tolower(ch);
});
auto format = format_;
auto state = getState(capacity, true);
auto time_remaining_formatted = formatTimeRemaining(time_remaining);
if (tooltipEnabled()) {
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;
} else {
tooltip_text_default = status_pretty;
}
if (!state.empty() && config_["tooltip-format-" + status + "-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + status + "-" + state].asString();
} else if (config_["tooltip-format-" + status].isString()) {
tooltip_format = config_["tooltip-format-" + status].asString();
} else if (!state.empty() && config_["tooltip-format-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + state].asString();
} else if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
label_.set_tooltip_text(fmt::format(tooltip_format,
fmt::arg("timeTo", tooltip_text_default),
fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted)));
}
if (!old_status_.empty()) {
label_.get_style_context()->remove_class(old_status_);
}
@ -199,8 +337,9 @@ auto waybar::modules::Battery::update() -> void {
auto icons = std::vector<std::string>{status + "-" + state, status, state};
label_.set_markup(fmt::format(format,
fmt::arg("capacity", capacity),
fmt::arg("power", power),
fmt::arg("icon", getIcon(capacity, icons)),
fmt::arg("time", formatTimeRemaining(time_remaining))));
fmt::arg("time", time_remaining_formatted)));
}
// Call parent update
ALabel::update();

View File

@ -1,45 +1,25 @@
#include "modules/bluetooth.hpp"
#include "util/rfkill.hpp"
#include <linux/rfkill.h>
#include <time.h>
#include <fmt/format.h>
waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config)
: ALabel(config, "bluetooth", id, "{icon}", 10),
status_("disabled"),
rfkill_{RFKILL_TYPE_BLUETOOTH} {
thread_ = [this] {
dp.emit();
rfkill_.waitForEvent();
};
intervall_thread_ = [this] {
auto now = std::chrono::system_clock::now();
auto timeout = std::chrono::floor<std::chrono::seconds>(now + interval_);
auto diff = std::chrono::seconds(timeout.time_since_epoch().count() % interval_.count());
thread_.sleep_until(timeout - diff);
dp.emit();
};
: ALabel(config, "bluetooth", id, "{icon}", 10), rfkill_{RFKILL_TYPE_BLUETOOTH} {
rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update)));
}
auto waybar::modules::Bluetooth::update() -> void {
if (rfkill_.getState()) {
status_ = "disabled";
} else {
status_ = "enabled";
}
std::string status = rfkill_.getState() ? "disabled" : "enabled";
label_.set_markup(
fmt::format(
format_,
fmt::arg("status", status_),
fmt::arg("icon", getIcon(0, status_))));
fmt::format(format_, fmt::arg("status", status), fmt::arg("icon", getIcon(0, status))));
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
auto tooltip_text = fmt::format(tooltip_format, status_);
auto tooltip_text = fmt::format(tooltip_format, status, fmt::arg("status", status));
label_.set_tooltip_text(tooltip_text);
} else {
label_.set_tooltip_text(status_);
label_.set_tooltip_text(status);
}
}
}

View File

@ -5,6 +5,7 @@
#include <sstream>
#include <type_traits>
#include "util/ustring_clen.hpp"
#ifdef HAVE_LANGINFO_1STDAY
#include <langinfo.h>
#include <locale.h>
@ -13,11 +14,15 @@
using waybar::modules::waybar_time;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60), fixed_time_zone_(false) {
if (config_["timezone"].isString()) {
spdlog::warn("As using a timezone, some format args may be missing as the date library havn't got a release since 2018.");
time_zone_ = date::locate_zone(config_["timezone"].asString());
fixed_time_zone_ = true;
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), fixed_time_zone_(false) {
if (config_["timezones"].isArray() && !config_["timezones"].empty()) {
time_zone_idx_ = 0;
setTimeZone(config_["timezones"][time_zone_idx_]);
} else {
setTimeZone(config_["timezone"]);
}
if (fixed_time_zone_) {
spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018.");
}
if (config_["locale"].isString()) {
@ -71,6 +76,42 @@ auto waybar::modules::Clock::update() -> void {
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()) {
return AModule::handleScroll(e);
}
auto dir = AModule::getScrollDir(e);
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
return true;
}
if (!config_["timezones"].isArray() || config_["timezones"].empty()) {
return true;
}
auto nr_zones = config_["timezones"].size();
if (dir == SCROLL_DIR::UP) {
size_t new_idx = time_zone_idx_ + 1;
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;
}
setTimeZone(config_["timezones"][time_zone_idx_]);
update();
return true;
}
auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string {
const auto daypoint = date::floor<date::days>(wtime.ztime.get_local_time());
const auto ymd = date::year_month_day(daypoint);
@ -99,7 +140,12 @@ auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::str
os << '\n';
}
if (d == curr_day) {
os << "<b><u>" << date::format("%e", d) << "</u></b>";
if (config_["today-format"].isString()) {
auto today_format = config_["today-format"].asString();
os << fmt::format(today_format, date::format("%e", d));
} else {
os << "<b><u>" << date::format("%e", d) << "</u></b>";
}
} else {
os << date::format("%e", d);
}
@ -117,12 +163,14 @@ auto waybar::modules::Clock::weekdays_header(const date::weekday& first_dow, std
do {
if (wd != first_dow) os << ' ';
Glib::ustring wd_ustring(date::format(locale_, "%a", wd));
auto wd_len = wd_ustring.length();
if (wd_len > 2) {
wd_ustring = wd_ustring.substr(0, 2);
wd_len = 2;
auto clen = ustring_clen(wd_ustring);
auto wd_len = wd_ustring.length();
while (clen > 2) {
wd_ustring = wd_ustring.substr(0, wd_len-1);
wd_len--;
clen = ustring_clen(wd_ustring);
}
const std::string pad(2 - wd_len, ' ');
const std::string pad(2 - clen, ' ');
os << pad << wd_ustring;
} while (++wd != first_dow);
os << "\n";
@ -156,6 +204,9 @@ template <>
struct fmt::formatter<waybar_time> : fmt::formatter<std::tm> {
template <typename FormatContext>
auto format(const waybar_time& t, FormatContext& ctx) {
#if FMT_VERSION >= 80000
auto& tm_format = specs;
#endif
return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(tm_format), t.ztime));
}
};

View File

@ -2,8 +2,10 @@
#include <sys/types.h>
#include <sys/sysctl.h>
#include <spdlog/spdlog.h>
#include <cstdlib> // malloc
#include <unistd.h> // sysconf
#include <cmath> // NAN
#if defined(__NetBSD__) || defined(__OpenBSD__)
# include <sys/sched.h>
@ -95,3 +97,12 @@ std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
free(cp_time);
return cpuinfo;
}
std::vector<float> waybar::modules::Cpu::parseCpuFrequencies() {
static std::vector<float> frequencies;
if (frequencies.empty()) {
spdlog::warn("cpu/bsd: parseCpuFrequencies is not implemented, expect garbage in {*_frequency}");
frequencies.push_back(NAN);
}
return frequencies;
}

View File

@ -1,5 +1,14 @@
#include "modules/cpu.hpp"
// In the 80000 version of fmt library authors decided to optimize imports
// and moved declarations required for fmt::dynamic_format_arg_store in new
// header fmt/args.h
#if (FMT_VERSION >= 80000)
#include <fmt/args.h>
#else
#include <fmt/core.h>
#endif
waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config)
: ALabel(config, "cpu", id, "{usage}%", 10) {
thread_ = [this] {
@ -12,31 +21,60 @@ auto waybar::modules::Cpu::update() -> void {
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
auto cpu_load = getCpuLoad();
auto [cpu_usage, tooltip] = getCpuUsage();
auto [max_frequency, min_frequency, avg_frequency] = getCpuFrequency();
if (tooltipEnabled()) {
label_.set_tooltip_text(tooltip);
}
label_.set_markup(fmt::format(format_, fmt::arg("load", cpu_load), fmt::arg("usage", cpu_usage)));
getState(cpu_usage);
auto format = format_;
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
auto state = getState(total_usage);
if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
}
if (format.empty()) {
event_box_.hide();
} else {
event_box_.show();
auto icons = std::vector<std::string>{state};
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(fmt::arg("load", cpu_load));
store.push_back(fmt::arg("load", cpu_load));
store.push_back(fmt::arg("usage", total_usage));
store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
store.push_back(fmt::arg("max_frequency", max_frequency));
store.push_back(fmt::arg("min_frequency", min_frequency));
store.push_back(fmt::arg("avg_frequency", avg_frequency));
for (size_t i = 1; i < cpu_usage.size(); ++i) {
auto core_i = i - 1;
auto core_format = fmt::format("usage{}", core_i);
store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i]));
auto icon_format = fmt::format("icon{}", core_i);
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons)));
}
label_.set_markup(fmt::vformat(format, store));
}
// Call parent update
ALabel::update();
}
uint16_t waybar::modules::Cpu::getCpuLoad() {
double waybar::modules::Cpu::getCpuLoad() {
double load[1];
if (getloadavg(load, 1) != -1) {
return load[0] * 100 / sysconf(_SC_NPROCESSORS_ONLN);
return load[0];
}
throw std::runtime_error("Can't get Cpu load");
}
std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() {
std::tuple<std::vector<uint16_t>, std::string> waybar::modules::Cpu::getCpuUsage() {
if (prev_times_.empty()) {
prev_times_ = parseCpuinfo();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::vector<std::tuple<size_t, size_t>> curr_times = parseCpuinfo();
std::string tooltip;
uint16_t usage = 0;
std::vector<uint16_t> usage;
for (size_t i = 0; i < curr_times.size(); ++i) {
auto [curr_idle, curr_total] = curr_times[i];
auto [prev_idle, prev_total] = prev_times_[i];
@ -44,12 +82,25 @@ std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() {
const float delta_total = curr_total - prev_total;
uint16_t tmp = 100 * (1 - delta_idle / delta_total);
if (i == 0) {
usage = tmp;
tooltip = fmt::format("Total: {}%", tmp);
} else {
tooltip = tooltip + fmt::format("\nCore{}: {}%", i - 1, tmp);
}
usage.push_back(tmp);
}
prev_times_ = curr_times;
return {usage, tooltip};
}
std::tuple<float, float, float> waybar::modules::Cpu::getCpuFrequency() {
std::vector<float> frequencies = parseCpuFrequencies();
auto [min, max] = std::minmax_element(std::begin(frequencies), std::end(frequencies));
float avg_frequency = std::accumulate(std::begin(frequencies), std::end(frequencies), 0.0) / frequencies.size();
// Round frequencies with double decimal precision to get GHz
float max_frequency = std::ceil(*max / 10.0) / 100.0;
float min_frequency = std::ceil(*min / 10.0) / 100.0;
avg_frequency = std::ceil(avg_frequency / 10.0) / 100.0;
return { max_frequency, min_frequency, avg_frequency };
}

View File

@ -1,3 +1,4 @@
#include <filesystem>
#include "modules/cpu.hpp"
std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
@ -27,3 +28,50 @@ std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
}
return cpuinfo;
}
std::vector<float> waybar::modules::Cpu::parseCpuFrequencies() {
const std::string file_path_ = "/proc/cpuinfo";
std::ifstream info(file_path_);
if (!info.is_open()) {
throw std::runtime_error("Can't open " + file_path_);
}
std::vector<float> frequencies;
std::string line;
while (getline(info, line)) {
if (line.substr(0, 7).compare("cpu MHz") != 0) {
continue;
}
std::string frequency_str = line.substr(line.find(":") + 2);
float frequency = std::strtol(frequency_str.c_str(), nullptr, 10);
frequencies.push_back(frequency);
}
info.close();
if (frequencies.size() <= 0) {
std::string cpufreq_dir = "/sys/devices/system/cpu/cpufreq";
if (std::filesystem::exists(cpufreq_dir)) {
std::vector<std::string> frequency_files = {
"/cpuinfo_min_freq",
"/cpuinfo_max_freq"
};
for (auto& p: std::filesystem::directory_iterator(cpufreq_dir)) {
for (auto freq_file: frequency_files) {
std::string freq_file_path = p.path().string() + freq_file;
if (std::filesystem::exists(freq_file_path)) {
std::string freq_value;
std::ifstream freq(freq_file_path);
if (freq.is_open()) {
getline(freq, freq_value);
float frequency = std::strtol(freq_value.c_str(), nullptr, 10);
frequencies.push_back(frequency / 1000);
freq.close();
}
}
}
}
}
}
return frequencies;
}

View File

@ -50,7 +50,6 @@ void waybar::modules::Custom::continuousWorker() {
thread_ = [this, cmd] {
char* buff = nullptr;
size_t len = 0;
bool restart = false;
if (getline(&buff, &len, fp_) == -1) {
int exit_code = 1;
if (fp_) {
@ -63,8 +62,8 @@ void waybar::modules::Custom::continuousWorker() {
spdlog::error("{} stopped unexpectedly, is it endless?", name_);
}
if (config_["restart-interval"].isUInt()) {
restart = true;
pid_ = -1;
thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt()));
fp_ = util::command::open(cmd, pid_);
if (!fp_) {
throw std::runtime_error("Unable to open " + cmd);
@ -83,9 +82,6 @@ void waybar::modules::Custom::continuousWorker() {
output_ = {0, output};
dp.emit();
}
if (restart) {
thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt()));
}
};
}
@ -95,15 +91,21 @@ void waybar::modules::Custom::refresh(int sig) {
}
}
void waybar::modules::Custom::handleEvent() {
if (!config_["exec-on-event"].isBool() || config_["exec-on-event"].asBool()) {
thread_.wake_up();
}
}
bool waybar::modules::Custom::handleScroll(GdkEventScroll* e) {
auto ret = ALabel::handleScroll(e);
thread_.wake_up();
handleEvent();
return ret;
}
bool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) {
auto ret = ALabel::handleToggle(e);
thread_.wake_up();
handleEvent();
return ret;
}
@ -129,9 +131,13 @@ auto waybar::modules::Custom::update() -> void {
label_.set_markup(str);
if (tooltipEnabled()) {
if (text_ == tooltip_) {
label_.set_tooltip_text(str);
if (label_.get_tooltip_markup() != str) {
label_.set_tooltip_markup(str);
}
} else {
label_.set_tooltip_text(tooltip_);
if (label_.get_tooltip_markup() != tooltip_) {
label_.set_tooltip_markup(tooltip_);
}
}
}
auto classes = label_.get_style_context()->list_classes();

View File

@ -47,16 +47,29 @@ 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_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_bfree) * 100 / stats.f_blocks;
auto format = format_;
auto state = getState(percentage_used);
if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
}
if (format.empty()) {
event_box_.hide();
} else {
event_box_.show();
label_.set_markup(fmt::format(format
, stats.f_bavail * 100 / stats.f_blocks
, fmt::arg("free", free)
, fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks)
, fmt::arg("used", used)
, fmt::arg("percentage_used", percentage_used)
, fmt::arg("total", total)
, fmt::arg("path", path_)
));
}
label_.set_markup(fmt::format(format_
, stats.f_bavail * 100 / stats.f_blocks
, fmt::arg("free", free)
, fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks)
, fmt::arg("used", used)
, fmt::arg("percentage_used", (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks)
, fmt::arg("total", total)
, fmt::arg("path", path_)
));
if (tooltipEnabled()) {
std::string tooltip_format = "{used} used out of {total} on {path} ({percentage_used}%)";
if (config_["tooltip-format"].isString()) {
@ -67,12 +80,11 @@ auto waybar::modules::Disk::update() -> void {
, fmt::arg("free", free)
, fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks)
, fmt::arg("used", used)
, fmt::arg("percentage_used", (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks)
, fmt::arg("percentage_used", percentage_used)
, fmt::arg("total", total)
, fmt::arg("path", path_)
));
}
event_box_.show();
// Call parent update
ALabel::update();
}

View File

@ -1,16 +1,28 @@
#include "modules/idle_inhibitor.hpp"
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "util/command.hpp"
std::list<waybar::AModule*> waybar::modules::IdleInhibitor::modules;
bool waybar::modules::IdleInhibitor::status = false;
waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar,
const Json::Value& config)
: ALabel(config, "idle_inhibitor", id, "{status}"),
bar_(bar),
status_("deactivated"),
idle_inhibitor_(nullptr),
pid_(-1) {
if (waybar::Client::inst()->idle_inhibit_manager == nullptr) {
throw std::runtime_error("idle-inhibit not available");
}
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &IdleInhibitor::handleToggle));
// Add this to the modules list
waybar::modules::IdleInhibitor::modules.push_back(this);
dp.emit();
}
@ -19,6 +31,10 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
}
// Remove this from the modules list
waybar::modules::IdleInhibitor::modules.remove(this);
if (pid_ != -1) {
kill(-pid_, 9);
pid_ = -1;
@ -26,11 +42,27 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() {
}
auto waybar::modules::IdleInhibitor::update() -> void {
// Check status
if (status) {
label_.get_style_context()->remove_class("deactivated");
if (idle_inhibitor_ == nullptr) {
idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor(
waybar::Client::inst()->idle_inhibit_manager, bar_.surface);
}
} else {
label_.get_style_context()->remove_class("activated");
if (idle_inhibitor_ != nullptr) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
}
}
std::string status_text = status ? "activated" : "deactivated";
label_.set_markup(
fmt::format(format_, fmt::arg("status", status_), fmt::arg("icon", getIcon(0, status_))));
label_.get_style_context()->add_class(status_);
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_);
label_.set_tooltip_text(status_text);
}
// Call parent update
ALabel::update();
@ -38,18 +70,16 @@ auto waybar::modules::IdleInhibitor::update() -> void {
bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
if (e->button == 1) {
label_.get_style_context()->remove_class(status_);
if (idle_inhibitor_ != nullptr) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
status_ = "deactivated";
} else {
idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor(
waybar::Client::inst()->idle_inhibit_manager, bar_.surface);
status_ = "activated";
status = !status;
// Make all other idle inhibitor modules update
for (auto const& module : waybar::modules::IdleInhibitor::modules) {
if (module != this) {
module->update();
}
}
click_param = status_;
}
ALabel::handleToggle(e);
return true;
}

View File

@ -0,0 +1,152 @@
#include "modules/keyboard_state.hpp"
#include <filesystem>
#include <spdlog/spdlog.h>
extern "C" {
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
}
waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar, const Json::Value& config)
: AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
numlock_label_(""),
capslock_label_(""),
numlock_format_(config_["format"].isString() ? config_["format"].asString()
: config_["format"]["numlock"].isString() ? config_["format"]["numlock"].asString()
: "{name} {icon}"),
capslock_format_(config_["format"].isString() ? config_["format"].asString()
: config_["format"]["capslock"].isString() ? config_["format"]["capslock"].asString()
: "{name} {icon}"),
scrolllock_format_(config_["format"].isString() ? config_["format"].asString()
: config_["format"]["scrolllock"].isString() ? config_["format"]["scrolllock"].asString()
: "{name} {icon}"),
interval_(std::chrono::seconds(config_["interval"].isUInt() ? config_["interval"].asUInt() : 1)),
icon_locked_(config_["format-icons"]["locked"].isString()
? config_["format-icons"]["locked"].asString()
: "locked"),
icon_unlocked_(config_["format-icons"]["unlocked"].isString()
? config_["format-icons"]["unlocked"].asString()
: "unlocked"),
fd_(0),
dev_(nullptr) {
box_.set_name("keyboard-state");
if (config_["numlock"].asBool()) {
box_.pack_end(numlock_label_, false, false, 0);
}
if (config_["capslock"].asBool()) {
box_.pack_end(capslock_label_, false, false, 0);
}
if (config_["scrolllock"].asBool()) {
box_.pack_end(scrolllock_label_, false, false, 0);
}
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
event_box_.add(box_);
if (config_["device-path"].isString()) {
std::string dev_path = config_["device-path"].asString();
std::tie(fd_, dev_) = openDevice(dev_path);
} else {
DIR* dev_dir = opendir("/dev/input");
if (dev_dir == nullptr) {
throw std::runtime_error("Failed to open /dev/input");
}
dirent *ep;
while ((ep = readdir(dev_dir))) {
if (ep->d_type != DT_CHR) continue;
std::string dev_path = std::string("/dev/input/") + ep->d_name;
try {
std::tie(fd_, dev_) = openDevice(dev_path);
spdlog::info("Found device {} at '{}'", libevdev_get_name(dev_), dev_path);
break;
} catch (const std::runtime_error& e) {
continue;
}
}
if (dev_ == nullptr) {
throw std::runtime_error("Failed to find keyboard device");
}
}
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
}
waybar::modules::KeyboardState::~KeyboardState() {
libevdev_free(dev_);
int err = close(fd_);
if (err < 0) {
// Not much we can do, so ignore it.
}
}
auto waybar::modules::KeyboardState::openDevice(const std::string& path) -> std::pair<int, libevdev*> {
int fd = open(path.c_str(), O_NONBLOCK | O_CLOEXEC | O_RDONLY);
if (fd < 0) {
throw std::runtime_error("Can't open " + path);
}
libevdev* dev;
int err = libevdev_new_from_fd(fd, &dev);
if (err < 0) {
throw std::runtime_error("Can't create libevdev device");
}
if (!libevdev_has_event_type(dev, EV_LED)) {
throw std::runtime_error("Device doesn't support LED events");
}
if (!libevdev_has_event_code(dev, EV_LED, LED_NUML)
|| !libevdev_has_event_code(dev, EV_LED, LED_CAPSL)
|| !libevdev_has_event_code(dev, EV_LED, LED_SCROLLL)) {
throw std::runtime_error("Device doesn't support num lock, caps lock, or scroll lock events");
}
return std::make_pair(fd, dev);
}
auto waybar::modules::KeyboardState::update() -> void {
int err = LIBEVDEV_READ_STATUS_SUCCESS;
while (err == LIBEVDEV_READ_STATUS_SUCCESS) {
input_event ev;
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_NORMAL, &ev);
while (err == LIBEVDEV_READ_STATUS_SYNC) {
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_SYNC, &ev);
}
}
if (err != -EAGAIN) {
throw std::runtime_error("Failed to sync evdev device");
}
int numl = libevdev_get_event_value(dev_, EV_LED, LED_NUML);
int capsl = libevdev_get_event_value(dev_, EV_LED, LED_CAPSL);
int scrolll = libevdev_get_event_value(dev_, EV_LED, LED_SCROLLL);
struct {
bool state;
Gtk::Label& label;
const std::string& format;
const char* name;
} label_states[] = {
{(bool) numl, numlock_label_, numlock_format_, "Num"},
{(bool) capsl, capslock_label_, capslock_format_, "Caps"},
{(bool) scrolll, scrolllock_label_, scrolllock_format_, "Scroll"},
};
for (auto& label_state : label_states) {
std::string text;
text = fmt::format(label_state.format,
fmt::arg("icon", label_state.state ? icon_locked_ : icon_unlocked_),
fmt::arg("name", label_state.name));
label_state.label.set_markup(text);
if (label_state.state) {
label_state.label.get_style_context()->add_class("locked");
} else {
label_state.label.get_style_context()->remove_class("locked");
}
}
AModule::update();
}

View File

@ -15,11 +15,11 @@ auto waybar::modules::Memory::update() -> void {
unsigned long memfree;
if (meminfo_.count("MemAvailable")) {
// New kernels (3.4+) have an accurate available memory field.
memfree = meminfo_["MemAvailable"];
memfree = meminfo_["MemAvailable"] + meminfo_["zfs_size"];
} else {
// Old kernel; give a best-effort approximation of available memory.
memfree = meminfo_["MemFree"] + meminfo_["Buffers"] + meminfo_["Cached"] +
meminfo_["SReclaimable"] - meminfo_["Shmem"];
meminfo_["SReclaimable"] - meminfo_["Shmem"] + meminfo_["zfs_size"];
}
if (memtotal > 0 && memfree >= 0) {
@ -28,17 +28,39 @@ auto waybar::modules::Memory::update() -> void {
auto used_ram_gigabytes = (memtotal - memfree) / std::pow(1024, 2);
auto available_ram_gigabytes = memfree / std::pow(1024, 2);
getState(used_ram_percentage);
label_.set_markup(fmt::format(format_,
used_ram_percentage,
fmt::arg("total", total_ram_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes)));
if (tooltipEnabled()) {
label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1));
auto format = format_;
auto state = getState(used_ram_percentage);
if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
}
if (format.empty()) {
event_box_.hide();
} else {
event_box_.show();
auto icons = std::vector<std::string>{state};
label_.set_markup(fmt::format(format,
used_ram_percentage,
fmt::arg("icon", getIcon(used_ram_percentage, icons)),
fmt::arg("total", total_ram_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes)));
}
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format(tooltip_format,
used_ram_percentage,
fmt::arg("total", total_ram_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes)));
} else {
label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
}
}
event_box_.show();
} else {
event_box_.hide();
}

View File

@ -1,8 +1,29 @@
#include "modules/memory.hpp"
static unsigned zfsArcSize() {
std::ifstream zfs_arc_stats{"/proc/spl/kstat/zfs/arcstats"};
if (zfs_arc_stats.is_open()) {
std::string name;
std::string type;
unsigned long data{0};
std::string line;
while (std::getline(zfs_arc_stats, line)) {
std::stringstream(line) >> name >> type >> data;
if (name == "size") {
return data / 1024; // convert to kB
}
}
}
return 0;
}
void waybar::modules::Memory::parseMeminfo() {
const std::string data_dir_ = "/proc/meminfo";
std::ifstream info(data_dir_);
std::ifstream info(data_dir_);
if (!info.is_open()) {
throw std::runtime_error("Can't open " + data_dir_);
}
@ -17,4 +38,6 @@ void waybar::modules::Memory::parseMeminfo() {
int64_t value = std::stol(line.substr(posDelim + 1));
meminfo_[name] = value;
}
meminfo_["zfs_size"] = zfsArcSize();
}

View File

@ -1,16 +1,23 @@
#include "modules/mpd.hpp"
#include "modules/mpd/mpd.hpp"
#include <fmt/chrono.h>
#include <spdlog/spdlog.h>
#include <glibmm/ustring.h>
#include "modules/mpd/state.hpp"
#if defined(MPD_NOINLINE)
namespace waybar::modules {
#include "modules/mpd/state.inl.hpp"
} // namespace waybar::modules
#endif
waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
: ALabel(config, "mpd", id, "{album} - {artist} - {title}", 5),
module_name_(id.empty() ? "mpd" : "mpd#" + id),
server_(nullptr),
port_(config_["port"].isUInt() ? config["port"].asUInt() : 0),
password_(config_["password"].empty() ? "" : config_["password"].asString()),
timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000),
connection_(nullptr, &mpd_connection_free),
alternate_connection_(nullptr, &mpd_connection_free),
status_(nullptr, &mpd_status_free),
song_(nullptr, &mpd_song_free) {
if (!config_["port"].isNull() && !config_["port"].isUInt()) {
@ -28,73 +35,33 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
server_ = config["server"].asCString();
}
event_listener().detach();
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &MPD::handlePlayPause));
}
auto waybar::modules::MPD::update() -> void {
std::lock_guard guard(connection_lock_);
tryConnect();
if (connection_ != nullptr) {
try {
bool wasPlaying = playing();
if(!wasPlaying) {
// Wait until the periodic_updater has stopped
std::lock_guard periodic_guard(periodic_lock_);
}
fetchState();
if (!wasPlaying && playing()) {
periodic_updater().detach();
}
} catch (const std::exception& e) {
spdlog::error("{}: {}", module_name_, e.what());
state_ = MPD_STATE_UNKNOWN;
}
}
setLabel();
context_.update();
// Call parent update
ALabel::update();
}
std::thread waybar::modules::MPD::event_listener() {
return std::thread([this] {
while (true) {
try {
if (connection_ == nullptr) {
// Retry periodically if no connection
dp.emit();
std::this_thread::sleep_for(interval_);
} else {
waitForEvent();
dp.emit();
}
} catch (const std::exception& e) {
if (strcmp(e.what(), "Connection to MPD closed") == 0) {
spdlog::debug("{}: {}", module_name_, e.what());
} else {
spdlog::warn("{}: {}", module_name_, e.what());
}
}
void waybar::modules::MPD::queryMPD() {
if (connection_ != nullptr) {
spdlog::debug("{}: fetching state information", module_name_);
try {
fetchState();
spdlog::debug("{}: fetch complete", module_name_);
} catch (std::exception const& e) {
spdlog::error("{}: {}", module_name_, e.what());
state_ = MPD_STATE_UNKNOWN;
}
});
dp.emit();
}
}
std::thread waybar::modules::MPD::periodic_updater() {
return std::thread([this] {
std::lock_guard guard(periodic_lock_);
while (connection_ != nullptr && playing()) {
dp.emit();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
}
std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) {
std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) const {
std::string result =
config_["unknown-tag"].isString() ? config_["unknown-tag"].asString() : "N/A";
const char* tag = mpd_song_get_tag(song_.get(), type, idx);
@ -131,8 +98,9 @@ void waybar::modules::MPD::setLabel() {
}
auto format = format_;
std::string artist, album_artist, album, title, date;
Glib::ustring artist, album_artist, album, title;
std::string date;
int song_pos = 0, queue_length = 0, volume = 0;
std::chrono::seconds elapsedTime, totalTime;
std::string stateIcon = "";
@ -148,8 +116,8 @@ void waybar::modules::MPD::setLabel() {
label_.get_style_context()->add_class("playing");
label_.get_style_context()->remove_class("paused");
} else if (paused()) {
format =
config_["format-paused"].isString() ? config_["format-paused"].asString() : config_["format"].asString();
format = config_["format-paused"].isString() ? config_["format-paused"].asString()
: config_["format"].asString();
label_.get_style_context()->add_class("paused");
label_.get_style_context()->remove_class("playing");
}
@ -161,6 +129,12 @@ void waybar::modules::MPD::setLabel() {
album = getTag(MPD_TAG_ALBUM);
title = getTag(MPD_TAG_TITLE);
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()));
}
@ -173,43 +147,62 @@ void waybar::modules::MPD::setLabel() {
std::string repeatIcon = getOptionIcon("repeat", repeatActivated);
bool singleActivated = mpd_status_get_single(status_.get());
std::string singleIcon = getOptionIcon("single", singleActivated);
if (config_["artist-len"].isInt()) artist = artist.substr(0, config_["artist-len"].asInt());
if (config_["album-artist-len"].isInt()) album_artist = album_artist.substr(0, config_["album-artist-len"].asInt());
if (config_["album-len"].isInt()) album = album.substr(0, config_["album-len"].asInt());
if (config_["title-len"].isInt()) title = title.substr(0,config_["title-len"].asInt());
// TODO: format can fail
label_.set_markup(
fmt::format(format,
fmt::arg("artist", Glib::Markup::escape_text(artist).raw()),
fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()),
fmt::arg("album", Glib::Markup::escape_text(album).raw()),
fmt::arg("title", Glib::Markup::escape_text(title).raw()),
fmt::arg("date", Glib::Markup::escape_text(date).raw()),
fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime),
fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon)));
try {
label_.set_markup(
fmt::format(format,
fmt::arg("artist", Glib::Markup::escape_text(artist).raw()),
fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()),
fmt::arg("album", Glib::Markup::escape_text(album).raw()),
fmt::arg("title", Glib::Markup::escape_text(title).raw()),
fmt::arg("date", Glib::Markup::escape_text(date).raw()),
fmt::arg("volume", volume),
fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos),
fmt::arg("queueLength", queue_length),
fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon)));
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error: {}", e.what());
}
if (tooltipEnabled()) {
std::string tooltip_format;
tooltip_format = config_["tooltip-format"].isString() ? config_["tooltip-format"].asString()
: "MPD (connected)";
auto tooltip_text = fmt::format(tooltip_format,
fmt::arg("artist", artist),
fmt::arg("albumArtist", album_artist),
fmt::arg("album", album),
fmt::arg("title", title),
fmt::arg("date", date),
fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon));
label_.set_tooltip_text(tooltip_text);
try {
auto tooltip_text = fmt::format(tooltip_format,
fmt::arg("artist", artist.raw()),
fmt::arg("albumArtist", album_artist.raw()),
fmt::arg("album", album.raw()),
fmt::arg("title", title.raw()),
fmt::arg("date", date),
fmt::arg("volume", volume),
fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos),
fmt::arg("queueLength", queue_length),
fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon));
label_.set_tooltip_text(tooltip_text);
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error (tooltip): {}", e.what());
}
}
}
std::string waybar::modules::MPD::getStateIcon() {
std::string waybar::modules::MPD::getStateIcon() const {
if (!config_["state-icons"].isObject()) {
return "";
}
@ -231,7 +224,7 @@ std::string waybar::modules::MPD::getStateIcon() {
}
}
std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) {
std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) const {
if (!config_[optionName + "-icons"].isObject()) {
return "";
}
@ -254,25 +247,30 @@ void waybar::modules::MPD::tryConnect() {
}
connection_ =
unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);
detail::unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);
alternate_connection_ =
unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);
if (connection_ == nullptr || alternate_connection_ == nullptr) {
if (connection_ == nullptr) {
spdlog::error("{}: Failed to connect to MPD", module_name_);
connection_.reset();
alternate_connection_.reset();
return;
}
try {
checkErrors(connection_.get());
spdlog::debug("{}: Connected to MPD", module_name_);
if (!password_.empty()) {
bool res = mpd_run_password(connection_.get(), password_.c_str());
if (!res) {
spdlog::error("{}: Wrong MPD password", module_name_);
connection_.reset();
return;
}
checkErrors(connection_.get());
}
} catch (std::runtime_error& e) {
spdlog::error("{}: Failed to connect to MPD: {}", module_name_, e.what());
connection_.reset();
alternate_connection_.reset();
}
}
@ -285,51 +283,34 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) {
case MPD_ERROR_CLOSED:
mpd_connection_clear_error(conn);
connection_.reset();
alternate_connection_.reset();
state_ = MPD_STATE_UNKNOWN;
throw std::runtime_error("Connection to MPD closed");
default:
if (conn) {
auto error_message = mpd_connection_get_error_message(conn);
std::string error(error_message);
mpd_connection_clear_error(conn);
throw std::runtime_error(std::string(error_message));
throw std::runtime_error(error);
}
throw std::runtime_error("Invalid connection");
}
}
void waybar::modules::MPD::fetchState() {
if (connection_ == nullptr) {
spdlog::error("{}: Not connected to MPD", module_name_);
return;
}
auto conn = connection_.get();
status_ = unique_status(mpd_run_status(conn), &mpd_status_free);
status_ = detail::unique_status(mpd_run_status(conn), &mpd_status_free);
checkErrors(conn);
state_ = mpd_status_get_state(status_.get());
checkErrors(conn);
song_ = unique_song(mpd_run_current_song(conn), &mpd_song_free);
checkErrors(conn);
}
void waybar::modules::MPD::waitForEvent() {
auto conn = alternate_connection_.get();
// Wait for a player (play/pause), option (random, shuffle, etc.), or playlist
// change
if (!mpd_send_idle_mask(
conn, static_cast<mpd_idle>(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) {
checkErrors(conn);
return;
}
// alternate_idle_ = true;
// See issue #277:
// https://github.com/Alexays/Waybar/issues/277
mpd_recv_idle(conn, /* disable_timeout = */ false);
// See issue #281:
// https://github.com/Alexays/Waybar/issues/281
std::lock_guard guard(connection_lock_);
checkErrors(conn);
mpd_response_finish(conn);
song_ = detail::unique_song(mpd_run_current_song(conn), &mpd_song_free);
checkErrors(conn);
}
@ -339,24 +320,13 @@ bool waybar::modules::MPD::handlePlayPause(GdkEventButton* const& e) {
}
if (e->button == 1) {
std::lock_guard guard(connection_lock_);
if (stopped()) {
mpd_run_play(connection_.get());
} else {
mpd_run_toggle_pause(connection_.get());
}
if (state_ == MPD_STATE_PLAY)
context_.pause();
else
context_.play();
} else if (e->button == 3) {
std::lock_guard guard(connection_lock_);
mpd_run_stop(connection_.get());
context_.stop();
}
return true;
}
bool waybar::modules::MPD::stopped() {
return connection_ == nullptr || state_ == MPD_STATE_UNKNOWN || state_ == MPD_STATE_STOP;
}
bool waybar::modules::MPD::playing() { return connection_ != nullptr && state_ == MPD_STATE_PLAY; }
bool waybar::modules::MPD::paused() { return connection_ != nullptr && state_ == MPD_STATE_PAUSE; }

383
src/modules/mpd/state.cpp Normal file
View File

@ -0,0 +1,383 @@
#include "modules/mpd/state.hpp"
#include <fmt/chrono.h>
#include <spdlog/spdlog.h>
#include "modules/mpd/mpd.hpp"
#if defined(MPD_NOINLINE)
namespace waybar::modules {
#include "modules/mpd/state.inl.hpp"
} // namespace waybar::modules
#endif
namespace waybar::modules::detail {
#define IDLE_RUN_NOIDLE_AND_CMD(...) \
if (idle_connection_.connected()) { \
idle_connection_.disconnect(); \
auto conn = ctx_->connection().get(); \
if (!mpd_run_noidle(conn)) { \
if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { \
spdlog::error("mpd: Idle: failed to unregister for IDLE events"); \
ctx_->checkErrors(conn); \
} \
} \
__VA_ARGS__; \
}
void Idle::play() {
IDLE_RUN_NOIDLE_AND_CMD(mpd_run_play(conn));
ctx_->setState(std::make_unique<Playing>(ctx_));
}
void Idle::pause() {
IDLE_RUN_NOIDLE_AND_CMD(mpd_run_pause(conn, true));
ctx_->setState(std::make_unique<Paused>(ctx_));
}
void Idle::stop() {
IDLE_RUN_NOIDLE_AND_CMD(mpd_run_stop(conn));
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
#undef IDLE_RUN_NOIDLE_AND_CMD
void Idle::update() noexcept {
// This is intentionally blank.
}
void Idle::entry() noexcept {
auto conn = ctx_->connection().get();
assert(conn != nullptr);
if (!mpd_send_idle_mask(
conn, static_cast<mpd_idle>(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) {
ctx_->checkErrors(conn);
spdlog::error("mpd: Idle: failed to register for IDLE events");
} else {
spdlog::debug("mpd: Idle: watching FD");
sigc::slot<bool, Glib::IOCondition const&> idle_slot = sigc::mem_fun(*this, &Idle::on_io);
idle_connection_ =
Glib::signal_io().connect(idle_slot,
mpd_connection_get_fd(conn),
Glib::IO_IN | Glib::IO_PRI | Glib::IO_ERR | Glib::IO_HUP);
}
}
void Idle::exit() noexcept {
if (idle_connection_.connected()) {
idle_connection_.disconnect();
spdlog::debug("mpd: Idle: unwatching FD");
}
}
bool Idle::on_io(Glib::IOCondition const&) {
auto conn = ctx_->connection().get();
// callback should do this:
enum mpd_idle events = mpd_recv_idle(conn, /* ignore_timeout?= */ false);
spdlog::debug("mpd: Idle: recv_idle events -> {}", events);
mpd_response_finish(conn);
try {
ctx_->checkErrors(conn);
} catch (std::exception const& e) {
spdlog::warn("mpd: Idle: error: {}", e.what());
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
ctx_->fetchState();
mpd_state state = ctx_->state();
if (state == MPD_STATE_STOP) {
ctx_->emit();
ctx_->setState(std::make_unique<Stopped>(ctx_));
} else if (state == MPD_STATE_PLAY) {
ctx_->emit();
ctx_->setState(std::make_unique<Playing>(ctx_));
} else if (state == MPD_STATE_PAUSE) {
ctx_->emit();
ctx_->setState(std::make_unique<Paused>(ctx_));
} else {
ctx_->emit();
// self transition
ctx_->setState(std::make_unique<Idle>(ctx_));
}
return false;
}
void Playing::entry() noexcept {
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Playing::on_timer);
timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 1'000);
spdlog::debug("mpd: Playing: enabled 1 second periodic timer.");
}
void Playing::exit() noexcept {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
spdlog::debug("mpd: Playing: disabled 1 second periodic timer.");
}
}
bool Playing::on_timer() {
// Attempt to connect with MPD.
try {
ctx_->tryConnect();
// Success?
if (!ctx_->is_connected()) {
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
ctx_->fetchState();
if (!ctx_->is_playing()) {
if (ctx_->is_paused()) {
ctx_->setState(std::make_unique<Paused>(ctx_));
} else {
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
return false;
}
ctx_->queryMPD();
ctx_->emit();
} catch (std::exception const& e) {
spdlog::warn("mpd: Playing: error: {}", e.what());
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
return true;
}
void Playing::stop() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_stop(ctx_->connection().get());
}
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
void Playing::pause() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_pause(ctx_->connection().get(), true);
}
ctx_->setState(std::make_unique<Paused>(ctx_));
}
void Playing::update() noexcept { ctx_->do_update(); }
void Paused::entry() noexcept {
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Paused::on_timer);
timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200);
spdlog::debug("mpd: Paused: enabled 200 ms periodic timer.");
}
void Paused::exit() noexcept {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
spdlog::debug("mpd: Paused: disabled 200 ms periodic timer.");
}
}
bool Paused::on_timer() {
bool rc = true;
// Attempt to connect with MPD.
try {
ctx_->tryConnect();
// Success?
if (!ctx_->is_connected()) {
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
ctx_->fetchState();
ctx_->emit();
if (ctx_->is_paused()) {
ctx_->setState(std::make_unique<Idle>(ctx_));
rc = false;
} else if (ctx_->is_playing()) {
ctx_->setState(std::make_unique<Playing>(ctx_));
rc = false;
} else if (ctx_->is_stopped()) {
ctx_->setState(std::make_unique<Stopped>(ctx_));
rc = false;
}
} catch (std::exception const& e) {
spdlog::warn("mpd: Paused: error: {}", e.what());
ctx_->setState(std::make_unique<Disconnected>(ctx_));
rc = false;
}
return rc;
}
void Paused::play() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_play(ctx_->connection().get());
}
ctx_->setState(std::make_unique<Playing>(ctx_));
}
void Paused::stop() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_stop(ctx_->connection().get());
}
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
void Paused::update() noexcept { ctx_->do_update(); }
void Stopped::entry() noexcept {
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Stopped::on_timer);
timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200);
spdlog::debug("mpd: Stopped: enabled 200 ms periodic timer.");
}
void Stopped::exit() noexcept {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
spdlog::debug("mpd: Stopped: disabled 200 ms periodic timer.");
}
}
bool Stopped::on_timer() {
bool rc = true;
// Attempt to connect with MPD.
try {
ctx_->tryConnect();
// Success?
if (!ctx_->is_connected()) {
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
ctx_->fetchState();
ctx_->emit();
if (ctx_->is_stopped()) {
ctx_->setState(std::make_unique<Idle>(ctx_));
rc = false;
} else if (ctx_->is_playing()) {
ctx_->setState(std::make_unique<Playing>(ctx_));
rc = false;
} else if (ctx_->is_paused()) {
ctx_->setState(std::make_unique<Paused>(ctx_));
rc = false;
}
} catch (std::exception const& e) {
spdlog::warn("mpd: Stopped: error: {}", e.what());
ctx_->setState(std::make_unique<Disconnected>(ctx_));
rc = false;
}
return rc;
}
void Stopped::play() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_play(ctx_->connection().get());
}
ctx_->setState(std::make_unique<Playing>(ctx_));
}
void Stopped::pause() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_pause(ctx_->connection().get(), true);
}
ctx_->setState(std::make_unique<Paused>(ctx_));
}
void Stopped::update() noexcept { ctx_->do_update(); }
void Disconnected::arm_timer(int interval) noexcept {
// unregister timer, if present
disarm_timer();
// register timer
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Disconnected::on_timer);
timer_connection_ =
Glib::signal_timeout().connect(timer_slot, interval);
spdlog::debug("mpd: Disconnected: enabled interval timer.");
}
void Disconnected::disarm_timer() noexcept {
// unregister timer, if present
if (timer_connection_.connected()) {
timer_connection_.disconnect();
spdlog::debug("mpd: Disconnected: disabled interval timer.");
}
}
void Disconnected::entry() noexcept {
ctx_->emit();
arm_timer(1'000);
}
void Disconnected::exit() noexcept {
disarm_timer();
}
bool Disconnected::on_timer() {
// Attempt to connect with MPD.
try {
ctx_->tryConnect();
// Success?
if (ctx_->is_connected()) {
ctx_->fetchState();
ctx_->emit();
if (ctx_->is_playing()) {
ctx_->setState(std::make_unique<Playing>(ctx_));
} else if (ctx_->is_paused()) {
ctx_->setState(std::make_unique<Paused>(ctx_));
} else {
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
return false; // do not rearm timer
}
} catch (std::exception const& e) {
spdlog::warn("mpd: Disconnected: error: {}", e.what());
}
arm_timer(ctx_->interval() * 1'000);
return false;
}
void Disconnected::update() noexcept { ctx_->do_update(); }
} // namespace waybar::modules::detail

File diff suppressed because it is too large Load Diff

View File

@ -151,8 +151,24 @@ void waybar::modules::Pulseaudio::sourceInfoCb(pa_context * /*context*/, const p
*/
void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i,
int /*eol*/, void *data) {
if (i == nullptr)
return;
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
if (i != nullptr && pa->default_sink_name_ == i->name) {
if (pa->current_sink_name_ == i->name) {
if (i->state != PA_SINK_RUNNING) {
pa->current_sink_running_ = false;
} else {
pa->current_sink_running_ = true;
}
}
if (!pa->current_sink_running_ && i->state == PA_SINK_RUNNING) {
pa->current_sink_name_ = i->name;
pa->current_sink_running_ = true;
}
if (pa->current_sink_name_ == i->name) {
pa->pa_volume_ = i->volume;
float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) / float{PA_VOLUME_NORM};
pa->sink_idx_ = i->index;
@ -175,11 +191,11 @@ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_
void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, const pa_server_info *i,
void *data) {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
pa->default_sink_name_ = i->default_sink_name;
pa->current_sink_name_ = i->default_sink_name;
pa->default_source_name_ = i->default_source_name;
pa_context_get_sink_info_by_name(context, i->default_sink_name, sinkInfoCb, data);
pa_context_get_source_info_by_name(context, i->default_source_name, sourceInfoCb, data);
pa_context_get_sink_info_list(context, sinkInfoCb, data);
pa_context_get_source_info_list(context, sourceInfoCb, data);
}
static const std::array<std::string, 9> ports = {
@ -194,22 +210,26 @@ static const std::array<std::string, 9> ports = {
"phone",
};
const std::string waybar::modules::Pulseaudio::getPortIcon() const {
const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {
std::vector<std::string> res = {default_source_name_};
std::string nameLC = port_name_ + form_factor_;
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
for (auto const &port : ports) {
if (nameLC.find(port) != std::string::npos) {
return port;
res.push_back(port);
return res;
}
}
return port_name_;
return res;
}
auto waybar::modules::Pulseaudio::update() -> void {
auto format = format_;
std::string tooltip_format;
if (!alt_) {
std::string format_name = "format";
if (monitor_.find("a2dp_sink") != std::string::npos) {
if (monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio
monitor_.find("a2dp-sink") != std::string::npos) { // PipeWire
format_name = format_name + "-bluetooth";
label_.get_style_context()->add_class("bluetooth");
} else {
@ -222,28 +242,53 @@ auto waybar::modules::Pulseaudio::update() -> void {
}
format_name = format_name + "-muted";
label_.get_style_context()->add_class("muted");
label_.get_style_context()->add_class("sink-muted");
} else {
label_.get_style_context()->remove_class("muted");
label_.get_style_context()->remove_class("sink-muted");
}
format =
config_[format_name].isString() ? config_[format_name].asString() : format;
}
// TODO: find a better way to split source/sink
std::string format_source = "{volume}%";
if (source_muted_ && config_["format-source-muted"].isString()) {
format_source = config_["format-source-muted"].asString();
} else if (!source_muted_ && config_["format-source"].isString()) {
format_source = config_["format-source"].asString();
if (source_muted_) {
label_.get_style_context()->add_class("source-muted");
if (config_["format-source-muted"].isString()) {
format_source = config_["format-source-muted"].asString();
}
} else {
label_.get_style_context()->remove_class("source-muted");
if (config_["format-source-muted"].isString()) {
format_source = config_["format-source"].asString();
}
}
format_source = fmt::format(format_source, fmt::arg("volume", source_volume_));
label_.set_markup(fmt::format(format,
fmt::arg("desc", desc_),
fmt::arg("volume", volume_),
fmt::arg("format_source", format_source),
fmt::arg("icon", getIcon(volume_, getPortIcon()))));
fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_),
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
getState(volume_);
if (tooltipEnabled()) {
label_.set_tooltip_text(desc_);
if (tooltip_format.empty() && config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(
tooltip_format,
fmt::arg("desc", desc_),
fmt::arg("volume", volume_),
fmt::arg("format_source", format_source),
fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_),
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
} else {
label_.set_tooltip_text(desc_);
}
}
// Call parent update

225
src/modules/river/tags.cpp Normal file
View File

@ -0,0 +1,225 @@
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include <spdlog/spdlog.h>
#include <wayland-client.h>
#include <algorithm>
#include "client.hpp"
#include "modules/river/tags.hpp"
#include "xdg-output-unstable-v1-client-protocol.h"
namespace waybar::modules::river {
static void listen_focused_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
uint32_t tags) {
static_cast<Tags *>(data)->handle_focused_tags(tags);
}
static void listen_view_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
struct wl_array *tags) {
static_cast<Tags *>(data)->handle_view_tags(tags);
}
static void listen_urgent_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
uint32_t tags) {
static_cast<Tags *>(data)->handle_urgent_tags(tags);
}
static const zriver_output_status_v1_listener output_status_listener_impl{
.focused_tags = listen_focused_tags,
.view_tags = listen_view_tags,
.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) {
version = std::min<uint32_t>(version, 2);
if (version < ZRIVER_OUTPUT_STATUS_V1_URGENT_TAGS_SINCE_VERSION) {
spdlog::warn("river server does not support urgent tags");
}
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} {
struct wl_display * display = Client::inst()->wl_display;
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener_impl, this);
wl_display_roundtrip(display);
if (!status_manager_) {
spdlog::error("river_status_manager_v1 not advertised");
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);
}
event_box_.add(box_);
// Default to 9 tags, cap at 32
const uint32_t num_tags =
config["num-tags"].isUInt() ? std::min<uint32_t>(32, config_["num-tags"].asUInt()) : 9;
std::vector<std::string> tag_labels(num_tags);
for (uint32_t tag = 0; tag < num_tags; ++tag) {
tag_labels[tag] = std::to_string(tag+1);
}
const Json::Value custom_labels = config["tag-labels"];
if (custom_labels.isArray() && !custom_labels.empty()) {
for (uint32_t tag = 0; tag < std::min(num_tags, custom_labels.size()); ++tag) {
tag_labels[tag] = custom_labels[tag].asString();
}
}
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());
output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output);
zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this);
zriver_status_manager_v1_destroy(status_manager_);
}
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) {
uint32_t i = 0;
for (auto &button : buttons_) {
if ((1 << i) & tags) {
button.get_style_context()->add_class("focused");
} else {
button.get_style_context()->remove_class("focused");
}
++i;
}
}
void Tags::handle_view_tags(struct wl_array *view_tags) {
// First clear all occupied state
for (auto &button : buttons_) {
button.get_style_context()->remove_class("occupied");
}
// Set tags with a view to occupied
uint32_t *start = static_cast<uint32_t *>(view_tags->data);
for (uint32_t *tags = start; tags < start + view_tags->size / sizeof(uint32_t); ++tags) {
uint32_t i = 0;
for (auto &button : buttons_) {
if (*tags & (1 << i)) {
button.get_style_context()->add_class("occupied");
}
++i;
}
}
}
void Tags::handle_urgent_tags(uint32_t tags) {
uint32_t i = 0;
for (auto &button : buttons_) {
if ((1 << i) & tags) {
button.get_style_context()->add_class("urgent");
} else {
button.get_style_context()->remove_class("urgent");
}
++i;
}
}
} /* namespace waybar::modules::river */

View File

@ -0,0 +1,33 @@
#include "modules/simpleclock.hpp"
#include <time.h>
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60) {
thread_ = [this] {
dp.emit();
auto now = std::chrono::system_clock::now();
auto timeout = std::chrono::floor<std::chrono::seconds>(now + interval_);
auto diff = std::chrono::seconds(timeout.time_since_epoch().count() % interval_.count());
thread_.sleep_until(timeout - diff);
};
}
auto waybar::modules::Clock::update() -> void {
tzset(); // Update timezone information
auto now = std::chrono::system_clock::now();
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
auto text = fmt::format(format_, localtime);
label_.set_markup(text);
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
auto tooltip_text = fmt::format(tooltip_format, localtime);
label_.set_tooltip_text(tooltip_text);
} else {
label_.set_tooltip_text(text);
}
}
// Call parent update
ALabel::update();
}

201
src/modules/sndio.cpp Normal file
View File

@ -0,0 +1,201 @@
#include "modules/sndio.hpp"
#include <algorithm>
#include <cstdlib>
#include <poll.h>
#include <fmt/format.h>
#include <spdlog/spdlog.h>
namespace waybar::modules {
void ondesc(void *arg, struct sioctl_desc *d, int curval) {
auto self = static_cast<Sndio*>(arg);
if (d == NULL) {
// d is NULL when the list is done
return;
}
self->set_desc(d, curval);
}
void onval(void *arg, unsigned int addr, unsigned int val) {
auto self = static_cast<Sndio*>(arg);
self->put_val(addr, val);
}
auto Sndio::connect_to_sndio() -> void {
hdl_ = sioctl_open(SIO_DEVANY, SIOCTL_READ | SIOCTL_WRITE, 0);
if (hdl_ == nullptr) {
throw std::runtime_error("sioctl_open() failed.");
}
if (sioctl_ondesc(hdl_, ondesc, this) == 0) {
throw std::runtime_error("sioctl_ondesc() failed.");
}
if (sioctl_onval(hdl_, onval, this) == 0) {
throw std::runtime_error("sioctl_onval() failed.");
}
pfds_.reserve(sioctl_nfds(hdl_));
}
Sndio::Sndio(const std::string &id, const Json::Value &config)
: ALabel(config, "sndio", id, "{volume}%", 1),
hdl_(nullptr),
pfds_(0),
addr_(0),
volume_(0),
old_volume_(0),
maxval_(0),
muted_(false) {
connect_to_sndio();
event_box_.show();
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK | Gdk::BUTTON_PRESS_MASK);
event_box_.signal_scroll_event().connect(
sigc::mem_fun(*this, &Sndio::handleScroll));
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &Sndio::handleToggle));
thread_ = [this] {
dp.emit();
int nfds = sioctl_pollfd(hdl_, pfds_.data(), POLLIN);
if (nfds == 0) {
throw std::runtime_error("sioctl_pollfd() failed.");
}
while (poll(pfds_.data(), nfds, -1) < 0) {
if (errno != EINTR) {
throw std::runtime_error("poll() failed.");
}
}
int revents = sioctl_revents(hdl_, pfds_.data());
if (revents & POLLHUP) {
spdlog::warn("sndio disconnected!");
sioctl_close(hdl_);
hdl_ = nullptr;
// reconnection loop
while (thread_.isRunning()) {
try {
connect_to_sndio();
} catch(std::runtime_error const& e) {
// avoid leaking hdl_
if (hdl_) {
sioctl_close(hdl_);
hdl_ = nullptr;
}
// rate limiting for the retries
thread_.sleep_for(interval_);
continue;
}
spdlog::warn("sndio reconnected!");
break;
}
}
};
}
Sndio::~Sndio() {
sioctl_close(hdl_);
}
auto Sndio::update() -> void {
auto format = format_;
unsigned int vol = 100. * static_cast<double>(volume_) / static_cast<double>(maxval_);
if (volume_ == 0) {
label_.get_style_context()->add_class("muted");
} else {
label_.get_style_context()->remove_class("muted");
}
label_.set_markup(fmt::format(format,
fmt::arg("volume", vol),
fmt::arg("raw_value", volume_)));
ALabel::update();
}
auto Sndio::set_desc(struct sioctl_desc *d, unsigned int val) -> void {
std::string name{d->func};
std::string node_name{d->node0.name};
if (name == "level" && node_name == "output" && d->type == SIOCTL_NUM) {
// store addr for output.level value, used in put_val
addr_ = d->addr;
maxval_ = d->maxval;
volume_ = val;
}
}
auto Sndio::put_val(unsigned int addr, unsigned int val) -> void {
if (addr == addr_) {
volume_ = val;
}
}
bool Sndio::handleScroll(GdkEventScroll *e) {
// change the volume only when no user provided
// events are configured
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
return AModule::handleScroll(e);
}
// only try to talk to sndio if connected
if (hdl_ == nullptr) return true;
auto dir = AModule::getScrollDir(e);
if (dir == SCROLL_DIR::NONE) {
return true;
}
int step = 5;
if (config_["scroll-step"].isInt()) {
step = config_["scroll-step"].asInt();
}
int new_volume = volume_;
if (muted_) {
new_volume = old_volume_;
}
if (dir == SCROLL_DIR::UP) {
new_volume += step;
} else if (dir == SCROLL_DIR::DOWN) {
new_volume -= step;
}
new_volume = std::clamp(new_volume, 0, static_cast<int>(maxval_));
// quits muted mode if volume changes
muted_ = false;
sioctl_setval(hdl_, addr_, new_volume);
return true;
}
bool Sndio::handleToggle(GdkEventButton* const& e) {
// toggle mute only when no user provided events are configured
if (config_["on-click"].isString()) {
return AModule::handleToggle(e);
}
// only try to talk to sndio if connected
if (hdl_ == nullptr) return true;
muted_ = !muted_;
if (muted_) {
// store old volume to be able to restore it later
old_volume_ = volume_;
sioctl_setval(hdl_, addr_, 0);
} else {
sioctl_setval(hdl_, addr_, old_volume_);
}
return true;
}
} /* namespace waybar::modules */

View File

@ -4,7 +4,7 @@
namespace waybar::modules::SNI {
Host::Host(const std::size_t id, const Json::Value& config,
Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar,
const std::function<void(std::unique_ptr<Item>&)>& on_add,
const std::function<void(std::unique_ptr<Item>&)>& on_remove)
: bus_name_("org.kde.StatusNotifierHost-" + std::to_string(getpid()) + "-" +
@ -13,6 +13,7 @@ Host::Host(const std::size_t id, const Json::Value& config,
bus_name_id_(Gio::DBus::own_name(Gio::DBus::BusType::BUS_TYPE_SESSION, bus_name_,
sigc::mem_fun(*this, &Host::busAcquired))),
config_(config),
bar_(bar),
on_add_(on_add),
on_remove_(on_remove) {}
@ -136,7 +137,7 @@ void Host::addRegisteredItem(std::string service) {
return bus_name == item->bus_name && object_path == item->object_path;
});
if (it == items_.end()) {
items_.emplace_back(new Item(bus_name, object_path, config_));
items_.emplace_back(new Item(bus_name, object_path, config_, bar_));
on_add_(items_.back());
}
}

View File

@ -1,7 +1,12 @@
#include "modules/sni/item.hpp"
#include <gdkmm/general.h>
#include <glibmm/main.h>
#include <gtkmm/tooltip.h>
#include <spdlog/spdlog.h>
#include <fstream>
#include <map>
template <>
struct fmt::formatter<Glib::ustring> : formatter<std::string> {
@ -34,19 +39,31 @@ namespace waybar::modules::SNI {
static const Glib::ustring SNI_INTERFACE_NAME = sn_item_interface_info()->name;
static const unsigned UPDATE_DEBOUNCE_TIME = 10;
Item::Item(const std::string& bn, const std::string& op, const Json::Value& config)
Item::Item(const std::string& bn, const std::string& op, const Json::Value& config, const Bar& bar)
: bus_name(bn),
object_path(op),
icon_size(16),
effective_icon_size(0),
icon_theme(Gtk::IconTheme::create()),
update_pending_(false) {
icon_theme(Gtk::IconTheme::create()) {
if (config["icon-size"].isUInt()) {
icon_size = config["icon-size"].asUInt();
}
if (config["smooth-scrolling-threshold"].isNumeric()) {
scroll_threshold_ = config["smooth-scrolling-threshold"].asDouble();
}
if (config["show-passive-items"].isBool()) {
show_passive_ = config["show-passive-items"].asBool();
}
auto &window = const_cast<Bar &>(bar).window;
window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Item::onConfigure));
event_box.add(image);
event_box.add_events(Gdk::BUTTON_PRESS_MASK);
event_box.add_events(Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick));
event_box.signal_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll));
// initial visibility
event_box.show_all();
event_box.set_visible(show_passive_);
cancellable_ = Gio::Cancellable::create();
@ -60,6 +77,10 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf
interface);
}
void Item::onConfigure(GdkEventConfigure* ev) {
this->updateImage();
}
void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
try {
this->proxy_ = Gio::DBus::Proxy::create_for_bus_finish(result);
@ -73,12 +94,11 @@ void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
this->proxy_->signal_signal().connect(sigc::mem_fun(*this, &Item::onSignal));
if (this->id.empty() || this->category.empty() || this->status.empty()) {
if (this->id.empty() || this->category.empty()) {
spdlog::error("Invalid Status Notifier Item: {}, {}", bus_name, object_path);
return;
}
this->updateImage();
// this->event_box.set_tooltip_text(this->title);
} catch (const Glib::Error& err) {
spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what());
@ -88,10 +108,24 @@ void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
}
template <typename T>
T get_variant(Glib::VariantBase& value) {
T get_variant(const Glib::VariantBase& value) {
return Glib::VariantBase::cast_dynamic<Glib::Variant<T>>(value).get();
}
template <>
ToolTip get_variant<ToolTip>(const Glib::VariantBase& value) {
ToolTip result;
// Unwrap (sa(iiay)ss)
auto container = value.cast_dynamic<Glib::VariantContainerBase>(value);
result.icon_name = get_variant<Glib::ustring>(container.get_child(0));
result.text = get_variant<Glib::ustring>(container.get_child(2));
auto description = get_variant<Glib::ustring>(container.get_child(3));
if (!description.empty()) {
result.text = fmt::format("<b>{}</b>\n{}", result.text, description);
}
return result;
}
void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
try {
spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, name, value);
@ -102,10 +136,11 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
id = get_variant<std::string>(value);
} else if (name == "Title") {
title = get_variant<std::string>(value);
if (tooltip.text.empty()) {
event_box.set_tooltip_markup(title);
}
} else if (name == "Status") {
status = get_variant<std::string>(value);
} else if (name == "WindowId") {
window_id = get_variant<int32_t>(value);
setStatus(get_variant<Glib::ustring>(value));
} else if (name == "IconName") {
icon_name = get_variant<std::string>(value);
} else if (name == "IconPixmap") {
@ -121,7 +156,10 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
} else if (name == "AttentionMovieName") {
attention_movie_name = get_variant<std::string>(value);
} else if (name == "ToolTip") {
// TODO: tooltip
tooltip = get_variant<ToolTip>(value);
if (!tooltip.text.empty()) {
event_box.set_tooltip_markup(tooltip.text);
}
} else if (name == "IconThemePath") {
icon_theme_path = get_variant<std::string>(value);
if (!icon_theme_path.empty()) {
@ -148,9 +186,22 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
}
}
void Item::getUpdatedProperties() {
update_pending_ = false;
void Item::setStatus(const Glib::ustring& value) {
Glib::ustring lower = value.lowercase();
event_box.set_visible(show_passive_ || lower.compare("passive") != 0);
auto style = event_box.get_style_context();
for (const auto& class_name : style->list_classes()) {
style->remove_class(class_name);
}
if (lower.compare("needsattention") == 0) {
// convert status to dash-case for CSS
lower = "needs-attention";
}
style->add_class(lower);
}
void Item::getUpdatedProperties() {
auto params = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)});
proxy_->call("org.freedesktop.DBus.Properties.GetAll",
@ -167,33 +218,48 @@ void Item::processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& _result) {
auto properties = properties_variant.get();
for (const auto& [name, value] : properties) {
Glib::VariantBase old_value;
proxy_->get_cached_property(old_value, name);
if (!old_value || !value.equal(old_value)) {
proxy_->set_cached_property(name, value);
if (update_pending_.count(name.raw())) {
setProperty(name, const_cast<Glib::VariantBase&>(value));
}
}
this->updateImage();
// this->event_box.set_tooltip_text(this->title);
} catch (const Glib::Error& err) {
spdlog::warn("Failed to update properties: {}", err.what());
} catch (const std::exception& err) {
spdlog::warn("Failed to update properties: {}", err.what());
}
update_pending_.clear();
}
/**
* Mapping from a signal name to a set of possibly changed properties.
* Commented signals are not handled by the tray module at the moment.
*/
static const std::map<std::string_view, std::set<std::string_view>> signal2props = {
{"NewTitle", {"Title"}},
{"NewIcon", {"IconName", "IconPixmap"}},
// {"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}},
// {"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}},
{"NewIconThemePath", {"IconThemePath"}},
{"NewToolTip", {"ToolTip"}},
{"NewStatus", {"Status"}},
// {"XAyatanaNewLabel", {"XAyatanaLabel"}},
};
void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
const Glib::VariantContainerBase& arguments) {
spdlog::trace("Tray item '{}' got signal {}", id, signal_name);
if (!update_pending_ && signal_name.compare(0, 3, "New") == 0) {
/* Debounce signals and schedule update of all properties.
* Based on behavior of Plasma dataengine for StatusNotifierItem.
*/
update_pending_ = true;
Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties),
UPDATE_DEBOUNCE_TIME);
auto changed = signal2props.find(signal_name.raw());
if (changed != signal2props.end()) {
if (update_pending_.empty()) {
/* Debounce signals and schedule update of all properties.
* Based on behavior of Plasma dataengine for StatusNotifierItem.
*/
Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties),
UPDATE_DEBOUNCE_TIME);
}
update_pending_.insert(changed->second.begin(), changed->second.end());
}
}
@ -222,7 +288,11 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
if (array != nullptr) {
g_free(array);
}
#if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 68
array = static_cast<guchar*>(g_memdup2(data, size));
#else
array = static_cast<guchar*>(g_memdup(data, size));
#endif
lwidth = width;
lheight = height;
}
@ -252,35 +322,41 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
}
void Item::updateImage() {
image.set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU);
image.set_pixel_size(icon_size);
if (!icon_name.empty()) {
try {
// Try to find icons specified by path and filename
auto pixbuf = getIconPixbuf();
auto scaled_icon_size = getScaledIconSize();
if (!pixbuf) {
pixbuf = getIconByName("image-missing", getScaledIconSize());
}
// If the loaded icon is not square, assume that the icon height should match the
// requested icon size, but the width is allowed to be different. As such, if the
// height of the image does not match the requested icon size, resize the icon such that
// the aspect ratio is maintained, but the height matches the requested icon size.
if (pixbuf->get_height() != scaled_icon_size) {
int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();
pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
}
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, 0, image.get_window());
image.set(surface);
}
Glib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() {
try {
if (!icon_name.empty()) {
std::ifstream temp(icon_name);
if (temp.is_open()) {
auto pixbuf = Gdk::Pixbuf::create_from_file(icon_name);
if (pixbuf->gobj() != nullptr) {
// An icon specified by path and filename may be the wrong size for
// the tray
// Keep the aspect ratio and scale to make the height equal to icon_size
// If people have non square icons, assume they want it to grow in width not height
int width = icon_size * pixbuf->get_width() / pixbuf->get_height();
pixbuf = pixbuf->scale_simple(width, icon_size, Gdk::InterpType::INTERP_BILINEAR);
image.set(pixbuf);
}
} else {
image.set(getIconByName(icon_name, icon_size));
return Gdk::Pixbuf::create_from_file(icon_name);
}
} catch (Glib::Error& e) {
spdlog::error("Item '{}': {}", id, static_cast<std::string>(e.what()));
return getIconByName(icon_name, getScaledIconSize());
} else if (icon_pixmap) {
return icon_pixmap;
}
} else if (icon_pixmap) {
// An icon extracted may be the wrong size for the tray
icon_pixmap = icon_pixmap->scale_simple(icon_size, icon_size, Gdk::InterpType::INTERP_BILINEAR);
image.set(icon_pixmap);
}
} catch (Glib::Error& e) {
spdlog::error("Item '{}': {}", id, static_cast<std::string>(e.what()));
}
return getIconByName("image-missing", getScaledIconSize());
}
Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) {
@ -315,6 +391,11 @@ Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int reque
name.c_str(), tmp_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
}
double Item::getScaledIconSize() {
// apply the scale factor from the Gtk window to the requested icon size
return icon_size * image.get_scale_factor();
}
void Item::onMenuDestroyed(Item* self, GObject* old_menu_pointer) {
if (old_menu_pointer == reinterpret_cast<GObject*>(self->dbus_menu)) {
self->gtk_menu = nullptr;
@ -360,4 +441,52 @@ bool Item::handleClick(GdkEventButton* const& ev) {
return false;
}
bool Item::handleScroll(GdkEventScroll* const& ev) {
int dx = 0, dy = 0;
switch (ev->direction) {
case GDK_SCROLL_UP:
dy = -1;
break;
case GDK_SCROLL_DOWN:
dy = 1;
break;
case GDK_SCROLL_LEFT:
dx = -1;
break;
case GDK_SCROLL_RIGHT:
dx = 1;
break;
case GDK_SCROLL_SMOOTH:
distance_scrolled_x_ += ev->delta_x;
distance_scrolled_y_ += ev->delta_y;
// check against the configured threshold and ensure that the absolute value >= 1
if (distance_scrolled_x_ > scroll_threshold_) {
dx = (int)lround(std::max(distance_scrolled_x_, 1.0));
distance_scrolled_x_ = 0;
} else if (distance_scrolled_x_ < -scroll_threshold_) {
dx = (int)lround(std::min(distance_scrolled_x_, -1.0));
distance_scrolled_x_ = 0;
}
if (distance_scrolled_y_ > scroll_threshold_) {
dy = (int)lround(std::max(distance_scrolled_y_, 1.0));
distance_scrolled_y_ = 0;
} else if (distance_scrolled_y_ < -scroll_threshold_) {
dy = (int)lround(std::min(distance_scrolled_y_, -1.0));
distance_scrolled_y_ = 0;
}
break;
}
if (dx != 0) {
auto parameters = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<int>::create(dx), Glib::Variant<Glib::ustring>::create("horizontal")});
proxy_->call("Scroll", parameters);
}
if (dy != 0) {
auto parameters = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<int>::create(dy), Glib::Variant<Glib::ustring>::create("vertical")});
proxy_->call("Scroll", parameters);
}
return true;
}
} // namespace waybar::modules::SNI

View File

@ -7,7 +7,7 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
: AModule(config, "tray", id),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
watcher_(SNI::Watcher::getInstance()),
host_(nb_hosts_, config, std::bind(&Tray::onAdd, this, std::placeholders::_1),
host_(nb_hosts_, config, bar, std::bind(&Tray::onAdd, this, std::placeholders::_1),
std::bind(&Tray::onRemove, this, std::placeholders::_1)) {
spdlog::warn(
"For a functional tray you must have libappindicator-* installed and export "
@ -35,11 +35,8 @@ void Tray::onRemove(std::unique_ptr<Item>& item) {
}
auto Tray::update() -> void {
if (box_.get_children().empty()) {
box_.hide();
} else {
box_.show_all();
}
// Show tray only when items are available
box_.set_visible(!box_.get_children().empty());
// Call parent update
AModule::update();
}

View File

@ -0,0 +1,215 @@
#include "modules/sway/language.hpp"
#include <fmt/core.h>
#include <json/json.h>
#include <spdlog/spdlog.h>
#include <xkbcommon/xkbregistry.h>
#include <cstring>
#include <string>
#include <vector>
#include "modules/sway/ipc/ipc.hpp"
#include "util/string.hpp"
namespace waybar::modules::sway {
const std::string Language::XKB_LAYOUT_NAMES_KEY = "xkb_layout_names";
const std::string Language::XKB_ACTIVE_LAYOUT_NAME_KEY = "xkb_active_layout_name";
Language::Language(const std::string& id, const Json::Value& config)
: ALabel(config, "language", id, "{}", 0, true) {
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);
}
if (format_.find("{shortDescription}") != std::string::npos) {
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortDescription);
}
if (config.isMember("tooltip-format")) {
tooltip_format_ = config["tooltip-format"].asString();
}
ipc_.subscribe(R"(["input"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent));
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd));
ipc_.sendCmd(IPC_GET_INPUTS);
// Launch worker
ipc_.setWorker([this] {
try {
ipc_.handleEvent();
} catch (const std::exception& e) {
spdlog::error("Language: {}", e.what());
}
});
dp.emit();
}
void Language::onCmd(const struct Ipc::ipc_response& res) {
if (res.type != static_cast<uint32_t>(IPC_GET_INPUTS)) {
return;
}
try {
std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload);
std::vector<std::string> used_layouts;
// Display current layout of a device with a maximum count of layouts, expecting that all will
// be OK
Json::ArrayIndex max_id = 0, max = 0;
for (Json::ArrayIndex i = 0; i < payload.size(); i++) {
auto size = payload[i][XKB_LAYOUT_NAMES_KEY].size();
if (size > max) {
max = size;
max_id = i;
}
}
for (const auto& layout : payload[max_id][XKB_LAYOUT_NAMES_KEY]) {
used_layouts.push_back(layout.asString());
}
init_layouts_map(used_layouts);
set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
dp.emit();
} catch (const std::exception& e) {
spdlog::error("Language: {}", e.what());
}
}
void Language::onEvent(const struct Ipc::ipc_response& res) {
if (res.type != static_cast<uint32_t>(IPC_EVENT_INPUT)) {
return;
}
try {
std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload)["input"];
if (payload["type"].asString() == "keyboard") {
set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
}
dp.emit();
} catch (const std::exception& e) {
spdlog::error("Language: {}", e.what());
}
}
auto Language::update() -> void {
std::lock_guard<std::mutex> lock(mutex_);
auto display_layout = trim(fmt::format(format_,
fmt::arg("short", layout_.short_name),
fmt::arg("shortDescription", layout_.short_description),
fmt::arg("long", layout_.full_name),
fmt::arg("variant", layout_.variant)));
label_.set_markup(display_layout);
if (tooltipEnabled()) {
if (tooltip_format_ != "") {
auto tooltip_display_layout = trim(fmt::format(tooltip_format_,
fmt::arg("short", layout_.short_name),
fmt::arg("shortDescription", layout_.short_description),
fmt::arg("long", layout_.full_name),
fmt::arg("variant", layout_.variant)));
label_.set_tooltip_markup(tooltip_display_layout);
} else {
label_.set_tooltip_markup(display_layout);
}
}
event_box_.show();
// Call parent update
ALabel::update();
}
auto Language::set_current_layout(std::string current_layout) -> void {
layout_ = layouts_map_[current_layout];
}
auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> void {
std::map<std::string, std::vector<Layout*>> found_by_short_names;
XKBContext xkb_context;
auto layout = xkb_context.next_layout();
for (; layout != nullptr; layout = xkb_context.next_layout()) {
if (std::find(used_layouts.begin(), used_layouts.end(), layout->full_name) ==
used_layouts.end()) {
continue;
}
if (!is_variant_displayed) {
auto short_name = layout->short_name;
if (found_by_short_names.count(short_name) > 0) {
found_by_short_names[short_name].push_back(layout);
} else {
found_by_short_names[short_name] = {layout};
}
}
layouts_map_.emplace(layout->full_name, *layout);
}
if (is_variant_displayed || found_by_short_names.size() == 0) {
return;
}
std::map<std::string, int> short_name_to_number_map;
for (const auto& used_layout_name : used_layouts) {
auto used_layout = &layouts_map_.find(used_layout_name)->second;
auto layouts_with_same_name_list = found_by_short_names[used_layout->short_name];
if (layouts_with_same_name_list.size() < 2) {
continue;
}
if (short_name_to_number_map.count(used_layout->short_name) == 0) {
short_name_to_number_map[used_layout->short_name] = 1;
}
if (displayed_short_flag != static_cast<std::byte>(0)) {
int& number = short_name_to_number_map[used_layout->short_name];
used_layout->short_name =
used_layout->short_name + std::to_string(number);
used_layout->short_description =
used_layout->short_description + std::to_string(number);
++number;
}
}
}
Language::XKBContext::XKBContext() {
context_ = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
rxkb_context_include_path_append_default(context_);
rxkb_context_parse_default_ruleset(context_);
}
auto Language::XKBContext::next_layout() -> Layout* {
if (xkb_layout_ == nullptr) {
xkb_layout_ = rxkb_layout_first(context_);
} else {
xkb_layout_ = rxkb_layout_next(xkb_layout_);
}
if (xkb_layout_ == nullptr) {
return nullptr;
}
auto description = std::string(rxkb_layout_get_description(xkb_layout_));
auto name = std::string(rxkb_layout_get_name(xkb_layout_));
auto variant_ = rxkb_layout_get_variant(xkb_layout_);
std::string variant = variant_ == nullptr ? "" : std::string(variant_);
auto short_description_ = rxkb_layout_get_brief(xkb_layout_);
std::string short_description;
if (short_description_ != nullptr) {
short_description = std::string(short_description_);
base_layouts_by_name_.emplace(name, xkb_layout_);
} else {
auto base_layout = base_layouts_by_name_[name];
short_description = base_layout == nullptr ? "" : std::string(rxkb_layout_get_brief(base_layout));
}
delete layout_;
layout_ = new Layout{description, name, variant, short_description};
return layout_;
}
Language::XKBContext::~XKBContext() {
rxkb_context_unref(context_);
delete layout_;
}
} // namespace waybar::modules::sway

View File

@ -1,5 +1,6 @@
#include "modules/sway/window.hpp"
#include <spdlog/spdlog.h>
#include <regex>
namespace waybar::modules::sway {
@ -56,7 +57,8 @@ auto Window::update() -> void {
bar_.window.get_style_context()->remove_class("solo");
bar_.window.get_style_context()->remove_class("empty");
}
label_.set_markup(fmt::format(format_, window_));
label_.set_markup(fmt::format(format_, fmt::arg("title", rewriteTitle(window_)),
fmt::arg("app_id", app_id_)));
if (tooltipEnabled()) {
label_.set_tooltip_text(window_);
}
@ -64,29 +66,58 @@ auto Window::update() -> void {
ALabel::update();
}
std::tuple<std::size_t, int, std::string, std::string> Window::getFocusedNode(
const Json::Value& nodes, std::string& output) {
for (auto const& node : nodes) {
int leafNodesInWorkspace(const Json::Value& node) {
auto const& nodes = node["nodes"];
auto const& floating_nodes = node["floating_nodes"];
if(nodes.empty() && floating_nodes.empty()) {
if(node["type"] == "workspace")
return 0;
else
return 1;
}
int sum = 0;
if (!nodes.empty()) {
for(auto const& node : nodes)
sum += leafNodesInWorkspace(node);
}
if (!floating_nodes.empty()) {
for(auto const& node : floating_nodes)
sum += leafNodesInWorkspace(node);
}
return sum;
}
std::tuple<std::size_t, int, std::string, std::string> gfnWithWorkspace(
const Json::Value& nodes, std::string& output, const Json::Value& config_,
const Bar& bar_, Json::Value& parentWorkspace) {
for(auto const& node : nodes) {
if (node["output"].isString()) {
output = node["output"].asString();
}
// found node
if (node["focused"].asBool() && (node["type"] == "con" || node["type"] == "floating_con")) {
if ((!config_["all-outputs"].asBool() && output == bar_.output->name) ||
config_["all-outputs"].asBool()) {
auto app_id = node["app_id"].isString() ? node["app_id"].asString()
: node["window_properties"]["instance"].asString();
return {nodes.size(),
node["id"].asInt(),
Glib::Markup::escape_text(node["name"].asString()),
app_id};
: node["window_properties"]["instance"].asString();
int nb = node.size();
if(parentWorkspace != 0)
nb = leafNodesInWorkspace(parentWorkspace);
return {nb,
node["id"].asInt(),
Glib::Markup::escape_text(node["name"].asString()),
app_id};
}
}
auto [nb, id, name, app_id] = getFocusedNode(node["nodes"], output);
// iterate
if(node["type"] == "workspace")
parentWorkspace = node;
auto [nb, id, name, app_id] = gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace);
if (id > -1 && !name.empty()) {
return {nb, id, name, app_id};
}
// Search for floating node
std::tie(nb, id, name, app_id) = getFocusedNode(node["floating_nodes"], output);
std::tie(nb, id, name, app_id) = gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace);
if (id > -1 && !name.empty()) {
return {nb, id, name, app_id};
}
@ -94,6 +125,12 @@ std::tuple<std::size_t, int, std::string, std::string> Window::getFocusedNode(
return {0, -1, "", ""};
}
std::tuple<std::size_t, int, std::string, std::string> Window::getFocusedNode(
const Json::Value& nodes, std::string& output) {
Json::Value placeholder = 0;
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder);
}
void Window::getTree() {
try {
ipc_.sendCmd(IPC_GET_TREE);
@ -102,4 +139,30 @@ void Window::getTree() {
}
}
std::string Window::rewriteTitle(const std::string& title) {
const auto& rules = config_["rewrite"];
if (!rules.isObject()) {
return title;
}
std::string res = title;
for (auto it = rules.begin(); it != rules.end(); ++it) {
if (it.key().isString() && it->isString()) {
try {
// malformated regexes will cause an exception.
// in this case, log error and try the next rule.
const std::regex rule{it.key().asString()};
if (std::regex_match(title, rule)) {
res = std::regex_replace(res, rule, it->asString());
}
} catch (const std::regex_error& e) {
spdlog::error("Invalid rule {}: {}", it.key().asString(), e.what());
}
}
}
return res;
}
} // namespace waybar::modules::sway

View File

@ -2,8 +2,27 @@
#include <spdlog/spdlog.h>
#include <cctype>
#include <string>
namespace waybar::modules::sway {
// Helper function to to assign a number to a workspace, just like sway. In fact
// this is taken quite verbatim from `sway/ipc-json.c`.
int Workspaces::convertWorkspaceNameToNum(std::string name) {
if (isdigit(name[0])) {
errno = 0;
char * endptr = NULL;
long long parsed_num = strtoll(name.c_str(), &endptr, 10);
if (errno != 0 || parsed_num > INT32_MAX || parsed_num < 0 || endptr == name.c_str()) {
return -1;
} else {
return (int)parsed_num;
}
}
return -1;
}
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
: AModule(config, "workspaces", id, false, !config["disable-scroll"].asBool()),
bar_(bar),
@ -102,13 +121,29 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
// the "num" property (integer type):
// The workspace number or -1 for workspaces that do
// not start with a number.
auto l = lhs["num"].asInt();
auto r = rhs["num"].asInt();
// 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.
auto lname = lhs["name"].asString();
auto rname = rhs["name"].asString();
int l = convertWorkspaceNameToNum(lname);
int r = convertWorkspaceNameToNum(rname);
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).
return lhs["name"].asString() < rhs["name"].asString();
return lname < rname;
}
// one of the workspaces doesn't begin with a number, so
@ -213,23 +248,34 @@ Gtk::Button &Workspaces::addButton(const Json::Value &node) {
auto pair = buttons_.emplace(node["name"].asString(), node["name"].asString());
auto &&button = pair.first->second;
box_.pack_start(button, false, false, 0);
button.set_name("sway-workspace-" + node["name"].asString());
button.set_relief(Gtk::RELIEF_NONE);
button.signal_clicked().connect([this, node] {
try {
if (node["target_output"].isString()) {
ipc_.sendCmd(
IPC_COMMAND,
fmt::format("workspace \"{}\"; move workspace to output \"{}\"; workspace \"{}\"",
node["name"].asString(),
node["target_output"].asString(),
node["name"].asString()));
} else {
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", node["name"].asString()));
if (!config_["disable-click"].asBool()) {
button.signal_pressed().connect([this, node] {
try {
if (node["target_output"].isString()) {
ipc_.sendCmd(
IPC_COMMAND,
fmt::format(workspace_switch_cmd_ + "; move workspace to output \"{}\"; " + workspace_switch_cmd_,
"--no-auto-back-and-forth",
node["name"].asString(),
node["target_output"].asString(),
"--no-auto-back-and-forth",
node["name"].asString()));
} else {
ipc_.sendCmd(
IPC_COMMAND,
fmt::format("workspace {} \"{}\"",
config_["disable-auto-back-and-forth"].asBool()
? "--no-auto-back-and-forth"
: "",
node["name"].asString()));
}
} catch (const std::exception &e) {
spdlog::error("Workspaces: {}", e.what());
}
} catch (const std::exception &e) {
spdlog::error("Workspaces: {}", e.what());
}
});
});
}
return button;
}
@ -245,12 +291,20 @@ std::string Workspaces::getIcon(const std::string &name, const Json::Value &node
return config_["format-icons"]["persistent"].asString();
} else if (config_["format-icons"][key].isString()) {
return config_["format-icons"][key].asString();
} else if (config_["format-icons"][trimWorkspaceName(key)].isString()) {
return config_["format-icons"][trimWorkspaceName(key)].asString();
}
}
return name;
}
bool Workspaces::handleScroll(GdkEventScroll *e) {
if (gdk_event_get_pointer_emulated((GdkEvent *)e)) {
/**
* Ignore emulated scroll events on window
*/
return false;
}
auto dir = AModule::getScrollDir(e);
if (dir == SCROLL_DIR::NONE) {
return true;
@ -276,7 +330,9 @@ bool Workspaces::handleScroll(GdkEventScroll *e) {
}
}
try {
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", name));
ipc_.sendCmd(
IPC_COMMAND,
fmt::format(workspace_switch_cmd_, "--no-auto-back-and-forth", name));
} catch (const std::exception &e) {
spdlog::error("Workspaces: {}", e.what());
}

View File

@ -6,7 +6,7 @@ waybar::modules::Temperature::Temperature(const std::string& id, const Json::Val
if (config_["hwmon-path"].isString()) {
file_path_ = config_["hwmon-path"].asString();
} else if (config_["hwmon-path-abs"].isString() && config_["input-filename"].isString()) {
file_path_ = (*std::filesystem::directory_iterator(config_["hwmon-path-abs"].asString())).path().u8string() + "/" + config_["input-filename"].asString();
file_path_ = (*std::filesystem::directory_iterator(config_["hwmon-path-abs"].asString())).path().string() + "/" + config_["input-filename"].asString();
} else {
auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0;
file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone);
@ -40,6 +40,16 @@ auto waybar::modules::Temperature::update() -> void {
fmt::arg("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k),
fmt::arg("icon", getIcon(temperature_c, "", max_temp))));
if (tooltipEnabled()) {
std::string tooltip_format = "{temperatureC}°C";
if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
label_.set_tooltip_text(fmt::format(tooltip_format,
fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k)));
}
// Call parent update
ALabel::update();
}

866
src/modules/wlr/taskbar.cpp Normal file
View File

@ -0,0 +1,866 @@
#include "modules/wlr/taskbar.hpp"
#include "glibmm/error.h"
#include "glibmm/fileutils.h"
#include "glibmm/refptr.h"
#include "util/format.hpp"
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <sstream>
#include <gdkmm/monitor.h>
#include <gtkmm/icontheme.h>
#include <giomm/desktopappinfo.h>
#include <gio/gdesktopappinfo.h>
#include <spdlog/spdlog.h>
namespace waybar::modules::wlr {
/* String manipulation methods */
const std::string WHITESPACE = " \n\r\t\f\v";
static std::string ltrim(const std::string& s)
{
size_t start = s.find_first_not_of(WHITESPACE);
return (start == std::string::npos) ? "" : s.substr(start);
}
static std::string rtrim(const std::string& s)
{
size_t end = s.find_last_not_of(WHITESPACE);
return (end == std::string::npos) ? "" : s.substr(0, end + 1);
}
static std::string trim(const std::string& s)
{
return rtrim(ltrim(s));
}
/* Icon loading functions */
static std::vector<std::string> search_prefix()
{
std::vector<std::string> prefixes = {""};
auto xdg_data_dirs = std::getenv("XDG_DATA_DIRS");
if (!xdg_data_dirs) {
prefixes.emplace_back("/usr/share/");
prefixes.emplace_back("/usr/local/share/");
} else {
std::string xdg_data_dirs_str(xdg_data_dirs);
size_t start = 0, end = 0;
do {
end = xdg_data_dirs_str.find(':', start);
auto p = xdg_data_dirs_str.substr(start, end-start);
prefixes.push_back(trim(p) + "/");
start = end == std::string::npos ? end : end + 1;
} while(end != std::string::npos);
}
std::string home_dir = std::getenv("HOME");
prefixes.push_back(home_dir + "/.local/share/");
for (auto& p : prefixes)
spdlog::debug("Using 'desktop' search path prefix: {}", p);
return prefixes;
}
static Glib::RefPtr<Gdk::Pixbuf> load_icon_from_file(std::string icon_path, int size)
{
try {
auto pb = Gdk::Pixbuf::create_from_file(icon_path, size, size);
return pb;
} catch(...) {
return {};
}
}
/* 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 std::vector<std::string> prefixes = search_prefix();
std::vector<std::string> app_folders = {
"",
"applications/",
"applications/kde/",
"applications/org.kde."
};
std::vector<std::string> suffixes = {
"",
".desktop"
};
Glib::RefPtr<Gio::DesktopAppInfo> app_info;
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);
if (app_info && app_info->get_icon())
return app_info->get_icon()->to_string();
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)
{
if (icon_theme->lookup_icon(app_id, 24))
return app_id;
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());
if (desktop_list != nullptr && desktop_list[0] != nullptr) {
for (size_t i=0; desktop_list[0][i]; i++) {
if (desktop_file == "") {
desktop_file = desktop_list[0][i];
} else {
auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]);
auto startup_class = tmp_info->get_startup_wm_class();
if (startup_class == app_id) {
desktop_file = desktop_list[0][i];
break;
}
}
}
g_strfreev(desktop_list[0]);
}
g_free(desktop_list);
return get_from_desktop_app_info(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)
{
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)
{
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());
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); });
std::string icon_name = get_from_icon_theme(icon_theme, 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);
if (icon_name.empty())
icon_name = "unknown";
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 = {};
}
if (pixbuf) {
image.set(pixbuf);
found = true;
break;
}
}
return found;
}
/* Task class implementation */
uint32_t Task::global_id = 0;
static void tl_handle_title(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle,
const char *title)
{
return static_cast<Task*>(data)->handle_title(title);
}
static void tl_handle_app_id(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle,
const char *app_id)
{
return static_cast<Task*>(data)->handle_app_id(app_id);
}
static void tl_handle_output_enter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle,
struct wl_output *output)
{
return static_cast<Task*>(data)->handle_output_enter(output);
}
static void tl_handle_output_leave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle,
struct wl_output *output)
{
return static_cast<Task*>(data)->handle_output_leave(output);
}
static void tl_handle_state(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle,
struct wl_array *state)
{
return static_cast<Task*>(data)->handle_state(state);
}
static void tl_handle_done(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle)
{
return static_cast<Task*>(data)->handle_done();
}
static void tl_handle_parent(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle,
struct zwlr_foreign_toplevel_handle_v1 *parent)
{
/* This is explicitly left blank */
}
static void tl_handle_closed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle)
{
return static_cast<Task*>(data)->handle_closed();
}
static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_handle_impl = {
.title = tl_handle_title,
.app_id = tl_handle_app_id,
.output_enter = tl_handle_output_enter,
.output_leave = tl_handle_output_leave,
.state = tl_handle_state,
.done = tl_handle_done,
.closed = tl_handle_closed,
.parent = tl_handle_parent,
};
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}
{
zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this);
button_.set_relief(Gtk::RELIEF_NONE);
content_.add(text_before_);
content_.add(icon_);
content_.add(text_after_);
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();
auto icon_pos = format.find("{icon}");
if (icon_pos == 0) {
with_icon_ = true;
format_after_ = format.substr(6);
} else if (icon_pos == std::string::npos) {
format_before_ = format;
} else {
with_icon_ = true;
format_before_ = format.substr(0, icon_pos);
format_after_ = format.substr(icon_pos + 6);
}
} else {
/* The default is to only show the icon */
with_icon_ = true;
}
/* Strip spaces at the beginning and end of the format strings */
format_tooltip_.clear();
if (!config_["tooltip"].isBool() || config_["tooltip"].asBool()) {
if (config_["tooltip-format"].isString())
format_tooltip_ = config_["tooltip-format"].asString();
else
format_tooltip_ = "{title}";
}
/* 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, &Task::handle_clicked), false);
}
}
Task::~Task()
{
if (handle_) {
zwlr_foreign_toplevel_handle_v1_destroy(handle_);
handle_ = nullptr;
}
if (button_visible_) {
tbar_->remove_button(button_);
button_visible_ = false;
}
}
std::string Task::repr() const
{
std::stringstream ss;
ss << "Task (" << id_ << ") " << title_ << " [" << app_id_ << "] <"
<< (active() ? "A" : "a")
<< (maximized() ? "M" : "m")
<< (minimized() ? "I" : "i")
<< (fullscreen() ? "F" : "f")
<< ">";
return ss.str();
}
std::string Task::state_string(bool shortened) const
{
std::stringstream ss;
if (shortened)
ss << (minimized() ? "m" : "") << (maximized() ? "M" : "")
<< (active() ? "A" : "") << (fullscreen() ? "F" : "");
else
ss << (minimized() ? "minimized " : "") << (maximized() ? "maximized " : "")
<< (active() ? "active " : "") << (fullscreen() ? "fullscreen " : "");
std::string res = ss.str();
if (shortened || res.empty())
return res;
else
return res.substr(0, res.size() - 1);
}
void Task::handle_title(const char *title)
{
title_ = title;
hide_if_ignored();
}
void Task::hide_if_ignored()
{
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());
handle_output_leave(output);
}
} else {
bool is_was_ignored = ignored_;
ignored_ = false;
if (is_was_ignored) {
auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
handle_output_enter(output);
}
}
}
void Task::handle_app_id(const char *app_id)
{
app_id_ = app_id;
hide_if_ignored();
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)) {
found = true;
break;
}
}
if (found)
icon_.show();
else
spdlog::debug("Couldn't find icon for {}", app_id_);
}
void Task::handle_output_enter(struct wl_output *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_);
button_.show();
button_visible_ = true;
spdlog::debug("{} now visible on {}", repr(), bar_.output->name);
}
}
void Task::handle_output_leave(struct wl_output *output)
{
spdlog::debug("{} left output {}", repr(), (void*)output);
if (button_visible_ && !tbar_->all_outputs() && tbar_->show_output(output)) {
/* The task left the output of the current bar, make the button invisible */
tbar_->remove_button(button_);
button_.hide();
button_visible_ = false;
spdlog::debug("{} now invisible on {}", repr(), bar_.output->name);
}
}
void Task::handle_state(struct wl_array *state)
{
state_ = 0;
size_t size = state->size / sizeof(uint32_t);
for (size_t i = 0; i < size; ++i) {
auto entry = static_cast<uint32_t*>(state->data)[i];
if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED)
state_ |= MAXIMIZED;
if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED)
state_ |= MINIMIZED;
if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED)
state_ |= ACTIVE;
if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN)
state_ |= FULLSCREEN;
}
}
void Task::handle_done()
{
spdlog::debug("{} changed", repr());
if (state_ & MAXIMIZED) {
button_.get_style_context()->add_class("maximized");
} else if (!(state_ & MAXIMIZED)) {
button_.get_style_context()->remove_class("maximized");
}
if (state_ & MINIMIZED) {
button_.get_style_context()->add_class("minimized");
} else if (!(state_ & MINIMIZED)) {
button_.get_style_context()->remove_class("minimized");
}
if (state_ & ACTIVE) {
button_.get_style_context()->add_class("active");
} else if (!(state_ & ACTIVE)) {
button_.get_style_context()->remove_class("active");
}
if (state_ & FULLSCREEN) {
button_.get_style_context()->add_class("fullscreen");
} else if (!(state_ & FULLSCREEN)) {
button_.get_style_context()->remove_class("fullscreen");
}
if (config_["active-first"].isBool() && config_["active-first"].asBool() && active())
tbar_->move_button(button_, 0);
tbar_->dp.emit();
}
void Task::handle_closed()
{
spdlog::debug("{} closed", repr());
zwlr_foreign_toplevel_handle_v1_destroy(handle_);
handle_ = nullptr;
if (button_visible_) {
tbar_->remove_button(button_);
button_visible_ = false;
}
tbar_->remove_task(id_);
}
bool Task::handle_clicked(GdkEventButton *bt)
{
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")
activate();
else if (action == "minimize")
minimize(!minimized());
else if (action == "minimize-raise"){
if (minimized())
minimize(false);
else if (active())
minimize(true);
else
activate();
}
else if (action == "maximize")
maximize(!maximized());
else if (action == "fullscreen")
fullscreen(!fullscreen());
else if (action == "close")
close();
else
spdlog::warn("Unknown action {}", action);
return true;
}
bool Task::operator==(const Task &o) const
{
return o.id_ == id_;
}
bool Task::operator!=(const Task &o) const
{
return o.id_ != id_;
}
void Task::update()
{
bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false;
std::string title = title_;
std::string app_id = app_id_;
if (markup) {
title = Glib::Markup::escape_text(title);
app_id = Glib::Markup::escape_text(app_id);
}
if (!format_before_.empty()) {
auto txt = fmt::format(format_before_,
fmt::arg("title", title),
fmt::arg("app_id", app_id),
fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true))
);
if (markup)
text_before_.set_markup(txt);
else
text_before_.set_label(txt);
text_before_.show();
}
if (!format_after_.empty()) {
auto txt = fmt::format(format_after_,
fmt::arg("title", title),
fmt::arg("app_id", app_id),
fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true))
);
if (markup)
text_after_.set_markup(txt);
else
text_after_.set_label(txt);
text_after_.show();
}
if (!format_tooltip_.empty()) {
auto txt = fmt::format(format_tooltip_,
fmt::arg("title", title),
fmt::arg("app_id", app_id),
fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true))
);
if (markup)
button_.set_tooltip_markup(txt);
else
button_.set_tooltip_text(txt);
}
}
void Task::maximize(bool set)
{
if (set)
zwlr_foreign_toplevel_handle_v1_set_maximized(handle_);
else
zwlr_foreign_toplevel_handle_v1_unset_maximized(handle_);
}
void Task::minimize(bool set)
{
if (set)
zwlr_foreign_toplevel_handle_v1_set_minimized(handle_);
else
zwlr_foreign_toplevel_handle_v1_unset_minimized(handle_);
}
void Task::activate()
{
zwlr_foreign_toplevel_handle_v1_activate(handle_, seat_);
}
void Task::fullscreen(bool set)
{
if (zwlr_foreign_toplevel_handle_v1_get_version(handle_) < ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_FULLSCREEN_SINCE_VERSION) {
spdlog::warn("Foreign toplevel manager server does not support for set/unset fullscreen.");
return;
}
if (set)
zwlr_foreign_toplevel_handle_v1_set_fullscreen(handle_, nullptr);
else
zwlr_foreign_toplevel_handle_v1_unset_fullscreen(handle_);
}
void Task::close()
{
zwlr_foreign_toplevel_handle_v1_close(handle_);
}
/* Taskbar class implementation */
static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version)
{
if (std::strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) {
static_cast<Taskbar*>(data)->register_manager(registry, name, version);
} else if (std::strcmp(interface, wl_seat_interface.name) == 0) {
static_cast<Taskbar*>(data)->register_seat(registry, name, version);
}
}
static void handle_global_remove(void *data, struct 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
};
Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
: waybar::AModule(config, "taskbar", id, false, false),
bar_(bar),
box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
manager_{nullptr}, seat_{nullptr}
{
box_.set_name("taskbar");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
event_box_.add(box_);
struct wl_display *display = Client::inst()->wl_display;
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener_impl, this);
wl_display_roundtrip(display);
if (!manager_) {
spdlog::error("Failed to register as toplevel manager");
return;
}
if (!seat_) {
spdlog::error("Failed to get wayland seat");
return;
}
/* Get the configured icon theme if specified */
if (config_["icon-theme"].isArray()) {
for (auto& c : config_["icon-theme"]) {
auto it_name = c.asString();
auto it = Gtk::IconTheme::create();
it->set_custom_theme(it_name);
spdlog::debug("Use custom icon theme: {}", it_name);
icon_themes_.push_back(it);
}
} else if (config_["icon-theme"].isString()) {
auto it_name = config_["icon-theme"].asString();
auto it = Gtk::IconTheme::create();
it->set_custom_theme(it_name);
spdlog::debug("Use custom icon theme: {}", it_name);
icon_themes_.push_back(it);
}
// Load ignore-list
if (config_["ignore-list"].isArray()) {
for (auto& app_name : config_["ignore-list"]) {
ignore_list_.emplace(app_name.asString());
}
}
icon_themes_.push_back(Gtk::IconTheme::get_default());
}
Taskbar::~Taskbar()
{
if (manager_) {
struct wl_display *display = Client::inst()->wl_display;
/*
* Send `stop` request and wait for one roundtrip.
* This is not quite correct as the protocol encourages us to wait for the .finished event,
* but it should work with wlroots foreign toplevel manager implementation.
*/
zwlr_foreign_toplevel_manager_v1_stop(manager_);
wl_display_roundtrip(display);
if (manager_) {
spdlog::warn("Foreign toplevel manager destroyed before .finished event");
zwlr_foreign_toplevel_manager_v1_destroy(manager_);
manager_ = nullptr;
}
}
}
void Taskbar::update()
{
for (auto& t : tasks_) {
t->update();
}
AModule::update();
}
static void tm_handle_toplevel(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager,
struct zwlr_foreign_toplevel_handle_v1 *tl_handle)
{
return static_cast<Taskbar*>(data)->handle_toplevel_create(tl_handle);
}
static void tm_handle_finished(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager)
{
return static_cast<Taskbar*>(data)->handle_finished();
}
static const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_impl = {
.toplevel = tm_handle_toplevel,
.finished = tm_handle_finished,
};
void Taskbar::register_manager(struct wl_registry *registry, uint32_t name, uint32_t version)
{
if (manager_) {
spdlog::warn("Register foreign toplevel manager again although already existing!");
return;
}
if (version < ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_FULLSCREEN_SINCE_VERSION) {
spdlog::warn("Foreign toplevel manager server does not have the appropriate version."
" To be able to use all features, you need at least version 2, but server is version {}", version);
}
// limit version to a highest supported by the client protocol file
version = std::min<uint32_t>(version, zwlr_foreign_toplevel_manager_v1_interface.version);
manager_ = static_cast<struct zwlr_foreign_toplevel_manager_v1 *>(wl_registry_bind(registry, name,
&zwlr_foreign_toplevel_manager_v1_interface, version));
if (manager_)
zwlr_foreign_toplevel_manager_v1_add_listener(manager_, &toplevel_manager_impl, this);
else
spdlog::debug("Failed to register manager");
}
void Taskbar::register_seat(struct wl_registry *registry, uint32_t name, uint32_t version)
{
if (seat_) {
spdlog::warn("Register seat again although already existing!");
return;
}
version = std::min<uint32_t>(version, wl_seat_interface.version);
seat_ = static_cast<wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));
}
void Taskbar::handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1 *tl_handle)
{
tasks_.push_back(std::make_unique<Task>(bar_, config_, this, tl_handle, seat_));
}
void Taskbar::handle_finished()
{
zwlr_foreign_toplevel_manager_v1_destroy(manager_);
manager_ = nullptr;
}
void Taskbar::add_button(Gtk::Button &bt)
{
box_.pack_start(bt, false, false);
}
void Taskbar::move_button(Gtk::Button &bt, int pos)
{
box_.reorder_child(bt, pos);
}
void Taskbar::remove_button(Gtk::Button &bt)
{
box_.remove(bt);
}
void Taskbar::remove_task(uint32_t id)
{
auto it = std::find_if(std::begin(tasks_), std::end(tasks_),
[id](const TaskPtr &p) { return p->id() == id; });
if (it == std::end(tasks_)) {
spdlog::warn("Can't find task with id {}", id);
return;
}
tasks_.erase(it);
}
bool Taskbar::show_output(struct wl_output *output) const
{
return output == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
}
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_; }
} /* 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

View File

@ -3,11 +3,11 @@
* Copyright 2009 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2009 Marcel Holtmann <marcel@holtmann.org>
* Copyright 2009 Tim Gardner <tim.gardner@canonical.com>
*
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
@ -17,67 +17,66 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
#include "util/rfkill.hpp"
#include <linux/rfkill.h>
#include <unistd.h>
#include <stdlib.h>
#include <cstring>
#include <fcntl.h>
#include <sys/poll.h>
#include <glibmm/main.h>
#include <linux/rfkill.h>
#include <spdlog/spdlog.h>
#include <unistd.h>
#include <cerrno>
#include <stdexcept>
waybar::util::Rfkill::Rfkill(const enum rfkill_type rfkill_type)
: rfkill_type_(rfkill_type) {
}
void waybar::util::Rfkill::waitForEvent() {
struct rfkill_event event;
struct pollfd p;
ssize_t len;
int fd, n;
fd = open("/dev/rfkill", O_RDONLY);
if (fd < 0) {
throw std::runtime_error("Can't open RFKILL control device");
waybar::util::Rfkill::Rfkill(const enum rfkill_type rfkill_type) : rfkill_type_(rfkill_type) {
fd_ = open("/dev/rfkill", O_RDONLY);
if (fd_ < 0) {
spdlog::error("Can't open RFKILL control device");
return;
}
memset(&p, 0, sizeof(p));
p.fd = fd;
p.events = POLLIN | POLLHUP;
while (1) {
n = poll(&p, 1, -1);
if (n < 0) {
throw std::runtime_error("Failed to poll RFKILL control device");
break;
}
if (n == 0)
continue;
len = read(fd, &event, sizeof(event));
if (len < 0) {
throw std::runtime_error("Reading of RFKILL events failed");
break;
}
if (len != RFKILL_EVENT_SIZE_V1) {
throw std::runtime_error("Wrong size of RFKILL event");
continue;
}
if(event.type == rfkill_type_ && event.op == RFKILL_OP_CHANGE) {
state_ = event.soft || event.hard;
break;
}
int rc = fcntl(fd_, F_SETFL, O_NONBLOCK);
if (rc < 0) {
spdlog::error("Can't set RFKILL control device to non-blocking: {}", errno);
close(fd_);
fd_ = -1;
return;
}
close(fd);
return;
Glib::signal_io().connect(
sigc::mem_fun(*this, &Rfkill::on_event), fd_, Glib::IO_IN | Glib::IO_ERR | Glib::IO_HUP);
}
bool waybar::util::Rfkill::getState() const {
return state_;
waybar::util::Rfkill::~Rfkill() {
if (fd_ >= 0) {
close(fd_);
}
}
bool waybar::util::Rfkill::on_event(Glib::IOCondition cond) {
if (cond & Glib::IO_IN) {
struct rfkill_event event;
ssize_t len;
len = read(fd_, &event, sizeof(event));
if (len < 0) {
if (errno == EAGAIN) {
return true;
}
spdlog::error("Reading of RFKILL events failed: {}", errno);
return false;
}
if (len < RFKILL_EVENT_SIZE_V1) {
spdlog::error("Wrong size of RFKILL event: {} < {}", len, RFKILL_EVENT_SIZE_V1);
return true;
}
if (event.type == rfkill_type_ && (event.op == RFKILL_OP_ADD || event.op == RFKILL_OP_CHANGE)) {
state_ = event.soft || event.hard;
on_update.emit(event);
}
return true;
} else {
spdlog::error("Failed to poll RFKILL control device");
return false;
}
}
bool waybar::util::Rfkill::getState() const { return state_; }

View File

@ -0,0 +1,9 @@
#include "util/ustring_clen.hpp"
int ustring_clen(const Glib::ustring &str){
int total = 0;
for (auto i = str.begin(); i != str.end(); ++i) {
total += g_unichar_iswide(*i) + 1;
}
return total;
}