mirror of
				https://github.com/rad4day/Waybar.git
				synced 2025-11-04 09:42:42 +01:00 
			
		
		
		
	Merge branch 'master' of github.com:Alexays/Waybar
This commit is contained in:
		@@ -14,17 +14,51 @@
 | 
			
		||||
using waybar::modules::waybar_time;
 | 
			
		||||
 | 
			
		||||
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
 | 
			
		||||
    : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), fixed_time_zone_(false) {
 | 
			
		||||
    : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true),
 | 
			
		||||
      current_time_zone_idx_(0),
 | 
			
		||||
      is_calendar_in_tooltip_(false)
 | 
			
		||||
{
 | 
			
		||||
  if (config_["timezones"].isArray() && !config_["timezones"].empty()) {
 | 
			
		||||
    time_zone_idx_ = 0;
 | 
			
		||||
    setTimeZone(config_["timezones"][time_zone_idx_]);
 | 
			
		||||
  } else {
 | 
			
		||||
    setTimeZone(config_["timezone"]);
 | 
			
		||||
    for (const auto& zone_name: config_["timezones"]) {
 | 
			
		||||
      if (!zone_name.isString() || zone_name.asString().empty()) {
 | 
			
		||||
        time_zones_.push_back(nullptr);
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      time_zones_.push_back(
 | 
			
		||||
        date::locate_zone(
 | 
			
		||||
          zone_name.asString()
 | 
			
		||||
        )
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  } else if (config_["timezone"].isString() && !config_["timezone"].asString().empty()) {
 | 
			
		||||
    time_zones_.push_back(
 | 
			
		||||
        date::locate_zone(
 | 
			
		||||
          config_["timezone"].asString()
 | 
			
		||||
        )
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
  if (fixed_time_zone_) {
 | 
			
		||||
 | 
			
		||||
  // If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark that local time should be shown.
 | 
			
		||||
  if (!time_zones_.size()) {
 | 
			
		||||
    time_zones_.push_back(nullptr);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!is_timezone_fixed()) {
 | 
			
		||||
    spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018.");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Check if a particular placeholder is present in the tooltip format, to know what to calculate on update.
 | 
			
		||||
  if (config_["tooltip-format"].isString()) {
 | 
			
		||||
    std::string trimmed_format = config_["tooltip-format"].asString();
 | 
			
		||||
    trimmed_format.erase(std::remove_if(trimmed_format.begin(),
 | 
			
		||||
                              trimmed_format.end(),
 | 
			
		||||
                              [](unsigned char x){return std::isspace(x);}),
 | 
			
		||||
               trimmed_format.end());
 | 
			
		||||
    if (trimmed_format.find("{" + kCalendarPlaceholder + "}") != std::string::npos) {
 | 
			
		||||
      is_calendar_in_tooltip_ = true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (config_["locale"].isString()) {
 | 
			
		||||
    locale_ = std::locale(config_["locale"].asString());
 | 
			
		||||
  } else {
 | 
			
		||||
@@ -40,53 +74,46 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const date::time_zone* waybar::modules::Clock::current_timezone() {
 | 
			
		||||
  return time_zones_[current_time_zone_idx_] ? time_zones_[current_time_zone_idx_] : date::current_zone();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool waybar::modules::Clock::is_timezone_fixed() {
 | 
			
		||||
  return time_zones_[current_time_zone_idx_] != nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
auto waybar::modules::Clock::update() -> void {
 | 
			
		||||
  if (!fixed_time_zone_) {
 | 
			
		||||
    // Time zone can change. Be sure to pick that.
 | 
			
		||||
    time_zone_ = date::current_zone();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto        now = std::chrono::system_clock::now();
 | 
			
		||||
  auto time_zone = current_timezone();
 | 
			
		||||
  auto now = std::chrono::system_clock::now();
 | 
			
		||||
  waybar_time wtime = {locale_,
 | 
			
		||||
                       date::make_zoned(time_zone_, date::floor<std::chrono::seconds>(now))};
 | 
			
		||||
 | 
			
		||||
  std::string text;
 | 
			
		||||
  if (!fixed_time_zone_) {
 | 
			
		||||
                       date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now))};
 | 
			
		||||
  std::string text = "";
 | 
			
		||||
  if (!is_timezone_fixed()) {
 | 
			
		||||
    // As date dep is not fully compatible, prefer fmt
 | 
			
		||||
    tzset();
 | 
			
		||||
    auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
 | 
			
		||||
    text = fmt::format(format_, localtime);
 | 
			
		||||
    label_.set_markup(text);
 | 
			
		||||
  } else {
 | 
			
		||||
    text = fmt::format(format_, wtime);
 | 
			
		||||
    label_.set_markup(text);
 | 
			
		||||
  }
 | 
			
		||||
  label_.set_markup(text);
 | 
			
		||||
 | 
			
		||||
  if (tooltipEnabled()) {
 | 
			
		||||
    if (config_["tooltip-format"].isString()) {
 | 
			
		||||
      const auto calendar = calendar_text(wtime);
 | 
			
		||||
      auto       tooltip_format = config_["tooltip-format"].asString();
 | 
			
		||||
      auto       tooltip_text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar));
 | 
			
		||||
      label_.set_tooltip_markup(tooltip_text);
 | 
			
		||||
    } else {
 | 
			
		||||
      label_.set_tooltip_markup(text);
 | 
			
		||||
      std::string calendar_lines = "";
 | 
			
		||||
      if (is_calendar_in_tooltip_) {
 | 
			
		||||
        calendar_lines = calendar_text(wtime);
 | 
			
		||||
      }
 | 
			
		||||
      auto tooltip_format = config_["tooltip-format"].asString();
 | 
			
		||||
      text = fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  label_.set_tooltip_markup(text);
 | 
			
		||||
  // Call parent update
 | 
			
		||||
  ALabel::update();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool waybar::modules::Clock::setTimeZone(Json::Value zone_name) {
 | 
			
		||||
  if (!zone_name.isString() || zone_name.asString().empty()) {
 | 
			
		||||
      fixed_time_zone_ = false;
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  time_zone_ = date::locate_zone(zone_name.asString());
 | 
			
		||||
  fixed_time_zone_ = true;
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) {
 | 
			
		||||
  // defer to user commands if set
 | 
			
		||||
  if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
 | 
			
		||||
@@ -97,17 +124,18 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) {
 | 
			
		||||
  if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  if (!config_["timezones"].isArray() || config_["timezones"].empty()) {
 | 
			
		||||
  if (time_zones_.size() == 1) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  auto nr_zones = config_["timezones"].size();
 | 
			
		||||
 | 
			
		||||
  auto nr_zones = time_zones_.size();
 | 
			
		||||
  if (dir == SCROLL_DIR::UP) {
 | 
			
		||||
    size_t new_idx = time_zone_idx_ + 1;
 | 
			
		||||
    time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
 | 
			
		||||
    size_t new_idx = current_time_zone_idx_ + 1;
 | 
			
		||||
    current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
 | 
			
		||||
  } else {
 | 
			
		||||
    time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1;
 | 
			
		||||
    current_time_zone_idx_ = current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1;
 | 
			
		||||
  }
 | 
			
		||||
  setTimeZone(config_["timezones"][time_zone_idx_]);
 | 
			
		||||
 | 
			
		||||
  update();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -45,9 +45,9 @@ auto waybar::modules::Disk::update() -> void {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true);
 | 
			
		||||
  auto used = pow_format((stats.f_blocks - stats.f_bavail) * stats.f_frsize, "B", true);
 | 
			
		||||
  auto used = pow_format((stats.f_blocks - stats.f_bfree) * stats.f_frsize, "B", true);
 | 
			
		||||
  auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true);
 | 
			
		||||
  auto percentage_used = (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks;
 | 
			
		||||
  auto percentage_used = (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks;
 | 
			
		||||
 | 
			
		||||
  auto format = format_;
 | 
			
		||||
  auto state = getState(percentage_used);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										175
									
								
								src/modules/inhibitor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								src/modules/inhibitor.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,175 @@
 | 
			
		||||
#include "modules/inhibitor.hpp"
 | 
			
		||||
 | 
			
		||||
#include <gio/gio.h>
 | 
			
		||||
#include <gio/gunixfdlist.h>
 | 
			
		||||
#include <spdlog/spdlog.h>
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
using DBus = std::unique_ptr<GDBusConnection, void(*)(GDBusConnection*)>;
 | 
			
		||||
 | 
			
		||||
auto dbus() -> DBus {
 | 
			
		||||
  GError *error = nullptr;
 | 
			
		||||
  GDBusConnection* connection =
 | 
			
		||||
      g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
 | 
			
		||||
 | 
			
		||||
  if (error) {
 | 
			
		||||
    spdlog::error("g_bus_get_sync() failed: {}", error->message);
 | 
			
		||||
    g_error_free(error);
 | 
			
		||||
    connection = nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto destructor = [](GDBusConnection* connection) {
 | 
			
		||||
      GError *error = nullptr;
 | 
			
		||||
      g_dbus_connection_close_sync(connection, nullptr, &error);
 | 
			
		||||
      if (error) {
 | 
			
		||||
        spdlog::error(
 | 
			
		||||
            "g_bus_connection_close_sync failed(): {}",
 | 
			
		||||
            error->message);
 | 
			
		||||
        g_error_free(error);
 | 
			
		||||
      }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return DBus{connection, destructor};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
auto getLocks(const DBus& bus, const std::string& inhibitors) -> int {
 | 
			
		||||
  GError *error = nullptr;
 | 
			
		||||
  GUnixFDList* fd_list;
 | 
			
		||||
  int handle;
 | 
			
		||||
 | 
			
		||||
  auto reply = g_dbus_connection_call_with_unix_fd_list_sync(bus.get(),
 | 
			
		||||
      "org.freedesktop.login1",
 | 
			
		||||
      "/org/freedesktop/login1",
 | 
			
		||||
      "org.freedesktop.login1.Manager",
 | 
			
		||||
      "Inhibit",
 | 
			
		||||
      g_variant_new(
 | 
			
		||||
          "(ssss)",
 | 
			
		||||
          inhibitors.c_str(),
 | 
			
		||||
          "waybar",
 | 
			
		||||
          "Asked by user",
 | 
			
		||||
          "block"),
 | 
			
		||||
      G_VARIANT_TYPE("(h)"),
 | 
			
		||||
      G_DBUS_CALL_FLAGS_NONE,
 | 
			
		||||
      -1,
 | 
			
		||||
      nullptr,
 | 
			
		||||
      &fd_list,
 | 
			
		||||
      nullptr,
 | 
			
		||||
      &error);
 | 
			
		||||
  if (error) {
 | 
			
		||||
    spdlog::error(
 | 
			
		||||
        "g_dbus_connection_call_with_unix_fd_list_sync() failed: {}",
 | 
			
		||||
        error->message);
 | 
			
		||||
    g_error_free(error);
 | 
			
		||||
    handle = -1;
 | 
			
		||||
  } else {
 | 
			
		||||
    gint index;
 | 
			
		||||
    g_variant_get(reply, "(h)", &index);
 | 
			
		||||
    g_variant_unref(reply);
 | 
			
		||||
    handle = g_unix_fd_list_get(fd_list, index, nullptr);
 | 
			
		||||
    g_object_unref(fd_list);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return handle;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
auto checkInhibitor(const std::string& inhibitor) -> const std::string& {
 | 
			
		||||
  static const auto inhibitors = std::array{
 | 
			
		||||
      "idle",
 | 
			
		||||
      "shutdown",
 | 
			
		||||
      "sleep",
 | 
			
		||||
      "handle-power-key",
 | 
			
		||||
      "handle-suspend-key",
 | 
			
		||||
      "handle-hibernate-key",
 | 
			
		||||
      "handle-lid-switch"
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  if (std::find(inhibitors.begin(), inhibitors.end(), inhibitor)
 | 
			
		||||
          == inhibitors.end()) {
 | 
			
		||||
    throw std::runtime_error("invalid logind inhibitor " + inhibitor);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return inhibitor;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
auto getInhibitors(const Json::Value& config) -> std::string {
 | 
			
		||||
  std::string inhibitors = "idle";
 | 
			
		||||
 | 
			
		||||
  if (config["what"].empty()) {
 | 
			
		||||
    return inhibitors;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (config["what"].isString()) {
 | 
			
		||||
    return checkInhibitor(config["what"].asString());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (config["what"].isArray()) {
 | 
			
		||||
    inhibitors = checkInhibitor(config["what"][0].asString());
 | 
			
		||||
    for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) {
 | 
			
		||||
      inhibitors += ":" + checkInhibitor(config["what"][i].asString());
 | 
			
		||||
    }
 | 
			
		||||
    return inhibitors;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return inhibitors;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace waybar::modules {
 | 
			
		||||
 | 
			
		||||
Inhibitor::Inhibitor(const std::string& id, const Bar& bar,
 | 
			
		||||
                     const Json::Value& config)
 | 
			
		||||
    : ALabel(config, "inhibitor", id, "{status}", true),
 | 
			
		||||
      dbus_(::dbus()),
 | 
			
		||||
      inhibitors_(::getInhibitors(config)) {
 | 
			
		||||
  event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
 | 
			
		||||
  event_box_.signal_button_press_event().connect(
 | 
			
		||||
      sigc::mem_fun(*this, &Inhibitor::handleToggle));
 | 
			
		||||
  dp.emit();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Inhibitor::~Inhibitor() {
 | 
			
		||||
  if (handle_ != -1) {
 | 
			
		||||
    ::close(handle_);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
auto Inhibitor::activated() -> bool {
 | 
			
		||||
  return handle_ != -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
auto Inhibitor::update() -> void {
 | 
			
		||||
  std::string status_text = activated() ? "activated" : "deactivated";
 | 
			
		||||
 | 
			
		||||
  label_.get_style_context()->remove_class(
 | 
			
		||||
      activated() ? "deactivated" : "activated");
 | 
			
		||||
  label_.set_markup(
 | 
			
		||||
      fmt::format(format_, fmt::arg("status", status_text),
 | 
			
		||||
      fmt::arg("icon", getIcon(0, status_text))));
 | 
			
		||||
  label_.get_style_context()->add_class(status_text);
 | 
			
		||||
 | 
			
		||||
  if (tooltipEnabled()) {
 | 
			
		||||
    label_.set_tooltip_text(status_text);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return ALabel::update();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool {
 | 
			
		||||
  if (e->button == 1) {
 | 
			
		||||
    if (activated()) {
 | 
			
		||||
      ::close(handle_);
 | 
			
		||||
      handle_ = -1;
 | 
			
		||||
    } else {
 | 
			
		||||
      handle_ = ::getLocks(dbus_, inhibitors_);
 | 
			
		||||
      if (handle_ == -1) {
 | 
			
		||||
        spdlog::error("cannot get inhibitor locks");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return ALabel::handleToggle(e);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // waybar::modules
 | 
			
		||||
@@ -131,6 +131,9 @@ void waybar::modules::MPD::setLabel() {
 | 
			
		||||
    date = getTag(MPD_TAG_DATE);
 | 
			
		||||
    song_pos = mpd_status_get_song_pos(status_.get());
 | 
			
		||||
    volume = mpd_status_get_volume(status_.get());
 | 
			
		||||
    if (volume < 0) {
 | 
			
		||||
      volume = 0;
 | 
			
		||||
    }
 | 
			
		||||
    queue_length = mpd_status_get_queue_length(status_.get());
 | 
			
		||||
    elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get()));
 | 
			
		||||
    totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get()));
 | 
			
		||||
 
 | 
			
		||||
@@ -1,87 +1,77 @@
 | 
			
		||||
#include "modules/network.hpp"
 | 
			
		||||
#include <linux/if.h>
 | 
			
		||||
#include <spdlog/spdlog.h>
 | 
			
		||||
#include <sys/eventfd.h>
 | 
			
		||||
#include <linux/if.h>
 | 
			
		||||
#include <fstream>
 | 
			
		||||
 | 
			
		||||
#include <cassert>
 | 
			
		||||
#include <fstream>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <optional>
 | 
			
		||||
 | 
			
		||||
#include "modules/network.hpp"
 | 
			
		||||
#include "util/format.hpp"
 | 
			
		||||
#ifdef WANT_RFKILL
 | 
			
		||||
#include "util/rfkill.hpp"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
using namespace waybar::util;
 | 
			
		||||
 | 
			
		||||
constexpr const char *NETSTAT_FILE =
 | 
			
		||||
    "/proc/net/netstat";  // std::ifstream does not take std::string_view as param
 | 
			
		||||
constexpr std::string_view BANDWIDTH_CATEGORY = "IpExt";
 | 
			
		||||
constexpr std::string_view BANDWIDTH_DOWN_TOTAL_KEY = "InOctets";
 | 
			
		||||
constexpr std::string_view BANDWIDTH_UP_TOTAL_KEY = "OutOctets";
 | 
			
		||||
constexpr const char *DEFAULT_FORMAT = "{ifname}";
 | 
			
		||||
 | 
			
		||||
std::ifstream                     netstat(NETSTAT_FILE);
 | 
			
		||||
std::optional<unsigned long long> read_netstat(std::string_view category, std::string_view key) {
 | 
			
		||||
  if (!netstat) {
 | 
			
		||||
    spdlog::warn("Failed to open netstat file {}", NETSTAT_FILE);
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
  netstat.seekg(std::ios_base::beg);
 | 
			
		||||
 | 
			
		||||
  // finding corresponding line (category)
 | 
			
		||||
  // looks into the file for the first line starting by the 'category' string
 | 
			
		||||
  auto starts_with = [](const std::string &str, std::string_view start) {
 | 
			
		||||
    return start == std::string_view{str.data(), std::min(str.size(), start.size())};
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  std::string read;
 | 
			
		||||
  while (std::getline(netstat, read) && !starts_with(read, category))
 | 
			
		||||
    ;
 | 
			
		||||
  if (!starts_with(read, category)) {
 | 
			
		||||
    spdlog::warn("Category '{}' not found in netstat file {}", category, NETSTAT_FILE);
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // finding corresponding column (key)
 | 
			
		||||
  // looks into the fetched line for the first word (space separated) equal to 'key'
 | 
			
		||||
  int  index = 0;
 | 
			
		||||
  auto r_it = read.begin();
 | 
			
		||||
  auto k_it = key.begin();
 | 
			
		||||
  while (k_it != key.end() && r_it != read.end()) {
 | 
			
		||||
    if (*r_it != *k_it) {
 | 
			
		||||
      r_it = std::find(r_it, read.end(), ' ');
 | 
			
		||||
      if (r_it != read.end()) {
 | 
			
		||||
        ++r_it;
 | 
			
		||||
      }
 | 
			
		||||
      k_it = key.begin();
 | 
			
		||||
      ++index;
 | 
			
		||||
    } else {
 | 
			
		||||
      ++r_it;
 | 
			
		||||
      ++k_it;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (r_it == read.end() && k_it != key.end()) {
 | 
			
		||||
    spdlog::warn(
 | 
			
		||||
        "Key '{}' not found in category '{}' of netstat file {}", key, category, NETSTAT_FILE);
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // finally accessing value
 | 
			
		||||
  // accesses the line right under the fetched one
 | 
			
		||||
  std::getline(netstat, read);
 | 
			
		||||
  assert(starts_with(read, category));
 | 
			
		||||
  std::istringstream iss(read);
 | 
			
		||||
  while (index--) {
 | 
			
		||||
    std::getline(iss, read, ' ');
 | 
			
		||||
  }
 | 
			
		||||
  unsigned long long value;
 | 
			
		||||
  iss >> value;
 | 
			
		||||
  return value;
 | 
			
		||||
}
 | 
			
		||||
}  // namespace
 | 
			
		||||
 | 
			
		||||
constexpr const char *NETDEV_FILE =
 | 
			
		||||
    "/proc/net/dev";  // std::ifstream does not take std::string_view as param
 | 
			
		||||
std::optional<std::pair<unsigned long long, unsigned long long>>
 | 
			
		||||
waybar::modules::Network::readBandwidthUsage() {
 | 
			
		||||
  std::ifstream netdev(NETDEV_FILE);
 | 
			
		||||
  if (!netdev) {
 | 
			
		||||
    spdlog::warn("Failed to open netdev file {}", NETDEV_FILE);
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::string line;
 | 
			
		||||
  // skip the headers (first two lines)
 | 
			
		||||
  std::getline(netdev, line);
 | 
			
		||||
  std::getline(netdev, line);
 | 
			
		||||
 | 
			
		||||
  unsigned long long receivedBytes = 0ull;
 | 
			
		||||
  unsigned long long transmittedBytes = 0ull;
 | 
			
		||||
  while (std::getline(netdev, line)) {
 | 
			
		||||
    std::istringstream iss(line);
 | 
			
		||||
 | 
			
		||||
    std::string ifacename;
 | 
			
		||||
    iss >> ifacename;  // ifacename contains "eth0:"
 | 
			
		||||
    ifacename.pop_back();  // remove trailing ':'
 | 
			
		||||
    if (!checkInterface(ifacename)) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The rest of the line consists of whitespace separated counts divided
 | 
			
		||||
    // into two groups (receive and transmit). Each group has the following
 | 
			
		||||
    // columns: bytes, packets, errs, drop, fifo, frame, compressed, multicast
 | 
			
		||||
    //
 | 
			
		||||
    // We only care about the bytes count, so we'll just ignore the 7 other
 | 
			
		||||
    // columns.
 | 
			
		||||
    unsigned long long r = 0ull;
 | 
			
		||||
    unsigned long long t = 0ull;
 | 
			
		||||
    // Read received bytes
 | 
			
		||||
    iss >> r;
 | 
			
		||||
    // Skip all the other columns in the received group
 | 
			
		||||
    for (int colsToSkip = 7; colsToSkip > 0; colsToSkip--) {
 | 
			
		||||
      // skip whitespace between columns
 | 
			
		||||
      while (iss.peek() == ' ') { iss.ignore(); }
 | 
			
		||||
      // skip the irrelevant column
 | 
			
		||||
      while (iss.peek() != ' ') { iss.ignore(); }
 | 
			
		||||
    }
 | 
			
		||||
    // Read transmit bytes
 | 
			
		||||
    iss >> t;
 | 
			
		||||
 | 
			
		||||
    receivedBytes += r;
 | 
			
		||||
    transmittedBytes += t;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {{receivedBytes, transmittedBytes}};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
 | 
			
		||||
    : ALabel(config, "network", id, DEFAULT_FORMAT, 60),
 | 
			
		||||
      ifid_(-1),
 | 
			
		||||
@@ -98,7 +88,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
 | 
			
		||||
#ifdef WANT_RFKILL
 | 
			
		||||
      rfkill_{RFKILL_TYPE_WLAN},
 | 
			
		||||
#endif
 | 
			
		||||
      frequency_(0) {
 | 
			
		||||
      frequency_(0.0) {
 | 
			
		||||
 | 
			
		||||
  // Start with some "text" in the module's label_, update() will then
 | 
			
		||||
  // update it. Since the text should be different, update() will be able
 | 
			
		||||
@@ -106,17 +96,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
 | 
			
		||||
  // the module start with no text, but the the event_box_ is shown.
 | 
			
		||||
  label_.set_markup("<s></s>");
 | 
			
		||||
 | 
			
		||||
  auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY);
 | 
			
		||||
  auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY);
 | 
			
		||||
  if (down_octets) {
 | 
			
		||||
    bandwidth_down_total_ = *down_octets;
 | 
			
		||||
  auto bandwidth = readBandwidthUsage();
 | 
			
		||||
  if (bandwidth.has_value()) {
 | 
			
		||||
    bandwidth_down_total_ = (*bandwidth).first;
 | 
			
		||||
    bandwidth_up_total_ = (*bandwidth).second;
 | 
			
		||||
  } else {
 | 
			
		||||
    bandwidth_down_total_ = 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (up_octets) {
 | 
			
		||||
    bandwidth_up_total_ = *up_octets;
 | 
			
		||||
  } else {
 | 
			
		||||
    bandwidth_up_total_ = 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -303,20 +288,21 @@ const std::string waybar::modules::Network::getNetworkState() const {
 | 
			
		||||
auto waybar::modules::Network::update() -> void {
 | 
			
		||||
  std::lock_guard<std::mutex> lock(mutex_);
 | 
			
		||||
  std::string                 tooltip_format;
 | 
			
		||||
  auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY);
 | 
			
		||||
  auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY);
 | 
			
		||||
 | 
			
		||||
  unsigned long long bandwidth_down = 0;
 | 
			
		||||
  if (down_octets) {
 | 
			
		||||
    bandwidth_down = *down_octets - bandwidth_down_total_;
 | 
			
		||||
    bandwidth_down_total_ = *down_octets;
 | 
			
		||||
  auto bandwidth = readBandwidthUsage();
 | 
			
		||||
  auto bandwidth_down = 0ull;
 | 
			
		||||
  auto bandwidth_up = 0ull;
 | 
			
		||||
  if (bandwidth.has_value()) {
 | 
			
		||||
    auto down_octets = (*bandwidth).first;
 | 
			
		||||
    auto up_octets = (*bandwidth).second;
 | 
			
		||||
 | 
			
		||||
    bandwidth_down = down_octets - bandwidth_down_total_;
 | 
			
		||||
    bandwidth_down_total_ = down_octets;
 | 
			
		||||
 | 
			
		||||
    bandwidth_up = up_octets - bandwidth_up_total_;
 | 
			
		||||
    bandwidth_up_total_ = up_octets;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  unsigned long long bandwidth_up = 0;
 | 
			
		||||
  if (up_octets) {
 | 
			
		||||
    bandwidth_up = *up_octets - bandwidth_up_total_;
 | 
			
		||||
    bandwidth_up_total_ = *up_octets;
 | 
			
		||||
  }
 | 
			
		||||
  if (!alt_) {
 | 
			
		||||
    auto state = getNetworkState();
 | 
			
		||||
    if (!state_.empty() && label_.get_style_context()->has_class(state_)) {
 | 
			
		||||
@@ -345,12 +331,13 @@ auto waybar::modules::Network::update() -> void {
 | 
			
		||||
      fmt::arg("essid", essid_),
 | 
			
		||||
      fmt::arg("signaldBm", signal_strength_dbm_),
 | 
			
		||||
      fmt::arg("signalStrength", signal_strength_),
 | 
			
		||||
      fmt::arg("signalStrengthApp", signal_strength_app_),
 | 
			
		||||
      fmt::arg("ifname", ifname_),
 | 
			
		||||
      fmt::arg("netmask", netmask_),
 | 
			
		||||
      fmt::arg("ipaddr", ipaddr_),
 | 
			
		||||
      fmt::arg("gwaddr", gwaddr_),
 | 
			
		||||
      fmt::arg("cidr", cidr_),
 | 
			
		||||
      fmt::arg("frequency", frequency_),
 | 
			
		||||
      fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
 | 
			
		||||
      fmt::arg("icon", getIcon(signal_strength_, state_)),
 | 
			
		||||
      fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
 | 
			
		||||
      fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
 | 
			
		||||
@@ -374,12 +361,13 @@ auto waybar::modules::Network::update() -> void {
 | 
			
		||||
          fmt::arg("essid", essid_),
 | 
			
		||||
          fmt::arg("signaldBm", signal_strength_dbm_),
 | 
			
		||||
          fmt::arg("signalStrength", signal_strength_),
 | 
			
		||||
          fmt::arg("signalStrengthApp", signal_strength_app_),
 | 
			
		||||
          fmt::arg("ifname", ifname_),
 | 
			
		||||
          fmt::arg("netmask", netmask_),
 | 
			
		||||
          fmt::arg("ipaddr", ipaddr_),
 | 
			
		||||
          fmt::arg("gwaddr", gwaddr_),
 | 
			
		||||
          fmt::arg("cidr", cidr_),
 | 
			
		||||
          fmt::arg("frequency", frequency_),
 | 
			
		||||
          fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
 | 
			
		||||
          fmt::arg("icon", getIcon(signal_strength_, state_)),
 | 
			
		||||
          fmt::arg("bandwidthDownBits",
 | 
			
		||||
                   pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
 | 
			
		||||
@@ -417,7 +405,8 @@ void waybar::modules::Network::clearIface() {
 | 
			
		||||
  cidr_ = 0;
 | 
			
		||||
  signal_strength_dbm_ = 0;
 | 
			
		||||
  signal_strength_ = 0;
 | 
			
		||||
  frequency_ = 0;
 | 
			
		||||
  signal_strength_app_.clear();
 | 
			
		||||
  frequency_ = 0.0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
 | 
			
		||||
@@ -484,7 +473,8 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
 | 
			
		||||
            net->essid_.clear();
 | 
			
		||||
            net->signal_strength_dbm_ = 0;
 | 
			
		||||
            net->signal_strength_ = 0;
 | 
			
		||||
            net->frequency_ = 0;
 | 
			
		||||
            net->signal_strength_app_.clear();
 | 
			
		||||
            net->frequency_ = 0.0;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        net->carrier_ = carrier.value();
 | 
			
		||||
@@ -802,13 +792,30 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) {
 | 
			
		||||
  if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) {
 | 
			
		||||
    // signalstrength in dBm from mBm
 | 
			
		||||
    signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100;
 | 
			
		||||
    // WiFi-hardware usually operates in the range -90 to -30dBm.
 | 
			
		||||
 | 
			
		||||
    // WiFi-hardware usually operates in the range -90 to -20dBm.
 | 
			
		||||
    const int hardwareMax = -20;
 | 
			
		||||
    // If a signal is too strong, it can overwhelm receiving circuity that is designed
 | 
			
		||||
    // to pick up and process a certain signal level. The following percentage is scaled to
 | 
			
		||||
    // punish signals that are too strong (>= -45dBm) or too weak (<= -45 dBm).
 | 
			
		||||
    const int hardwareOptimum = -45;
 | 
			
		||||
    const int hardwareMin = -90;
 | 
			
		||||
    const int strength =
 | 
			
		||||
      ((signal_strength_dbm_ - hardwareMin) / double{hardwareMax - hardwareMin}) * 100;
 | 
			
		||||
    signal_strength_ = std::clamp(strength, 0, 100);
 | 
			
		||||
        100 - ((abs(signal_strength_dbm_ - hardwareOptimum) / double{hardwareOptimum - hardwareMin}) * 100);
 | 
			
		||||
    signal_strength_ = std::clamp(strength, 0, 100);  
 | 
			
		||||
 | 
			
		||||
    if (signal_strength_dbm_ >= -50) {
 | 
			
		||||
      signal_strength_app_ = "Great Connectivity";
 | 
			
		||||
    } else if (signal_strength_dbm_ >= -60) {
 | 
			
		||||
      signal_strength_app_ = "Good Connectivity";
 | 
			
		||||
    } else if (signal_strength_dbm_ >= -67) {
 | 
			
		||||
      signal_strength_app_ = "Streaming";
 | 
			
		||||
    } else if (signal_strength_dbm_ >= -70) {
 | 
			
		||||
      signal_strength_app_ = "Web Surfing";
 | 
			
		||||
    } else if (signal_strength_dbm_ >= -80) {
 | 
			
		||||
      signal_strength_app_ = "Basic Connectivity";
 | 
			
		||||
    } else {
 | 
			
		||||
      signal_strength_app_ = "Poor Connectivity";
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) {
 | 
			
		||||
    signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]);
 | 
			
		||||
@@ -817,8 +824,8 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) {
 | 
			
		||||
 | 
			
		||||
void waybar::modules::Network::parseFreq(struct nlattr **bss) {
 | 
			
		||||
  if (bss[NL80211_BSS_FREQUENCY] != nullptr) {
 | 
			
		||||
    // in MHz
 | 
			
		||||
    frequency_ = nla_get_u32(bss[NL80211_BSS_FREQUENCY]);
 | 
			
		||||
    // in GHz
 | 
			
		||||
    frequency_ = (double) nla_get_u32(bss[NL80211_BSS_FREQUENCY]) / 1000;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -79,6 +79,13 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
 | 
			
		||||
  if (dir == SCROLL_DIR::NONE) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  if (config_["reverse-scrolling"].asInt() == 1){
 | 
			
		||||
    if (dir == SCROLL_DIR::UP) {
 | 
			
		||||
      dir = SCROLL_DIR::DOWN;
 | 
			
		||||
    } else if (dir == SCROLL_DIR::DOWN) {
 | 
			
		||||
      dir = SCROLL_DIR::UP;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  double      volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
 | 
			
		||||
  pa_volume_t change = volume_tick;
 | 
			
		||||
  pa_cvolume  pa_volume = pa_volume_;
 | 
			
		||||
@@ -211,7 +218,7 @@ static const std::array<std::string, 9> ports = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {
 | 
			
		||||
  std::vector<std::string> res = {default_source_name_};
 | 
			
		||||
  std::vector<std::string> res = {current_sink_name_, default_source_name_};
 | 
			
		||||
  std::string nameLC = port_name_ + form_factor_;
 | 
			
		||||
  std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
 | 
			
		||||
  for (auto const &port : ports) {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,6 @@
 | 
			
		||||
 | 
			
		||||
#include "client.hpp"
 | 
			
		||||
#include "modules/river/tags.hpp"
 | 
			
		||||
#include "river-status-unstable-v1-client-protocol.h"
 | 
			
		||||
#include "xdg-output-unstable-v1-client-protocol.h"
 | 
			
		||||
 | 
			
		||||
namespace waybar::modules::river {
 | 
			
		||||
@@ -33,6 +32,23 @@ static const zriver_output_status_v1_listener output_status_listener_impl{
 | 
			
		||||
    .urgent_tags = listen_urgent_tags,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void listen_command_success(void *data,
 | 
			
		||||
                                   struct zriver_command_callback_v1 *zriver_command_callback_v1,
 | 
			
		||||
                                   const char *output) {
 | 
			
		||||
  // Do nothing but keep listener to avoid crashing when command was successful
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void listen_command_failure(void *data,
 | 
			
		||||
                                   struct zriver_command_callback_v1 *zriver_command_callback_v1,
 | 
			
		||||
                                   const char *output) {
 | 
			
		||||
  spdlog::error("failure when selecting/toggling tags {}", output);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const zriver_command_callback_v1_listener command_callback_listener_impl {
 | 
			
		||||
    .success = listen_command_success,
 | 
			
		||||
    .failure = listen_command_failure,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
 | 
			
		||||
                          const char *interface, uint32_t version) {
 | 
			
		||||
  if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {
 | 
			
		||||
@@ -43,18 +59,33 @@ static void handle_global(void *data, struct wl_registry *registry, uint32_t nam
 | 
			
		||||
    static_cast<Tags *>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1 *>(
 | 
			
		||||
        wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (std::strcmp(interface, zriver_control_v1_interface.name) == 0) {
 | 
			
		||||
    version = std::min<uint32_t>(version, 1);
 | 
			
		||||
    static_cast<Tags *>(data)->control_ = static_cast<struct zriver_control_v1 *>(
 | 
			
		||||
        wl_registry_bind(registry, name, &zriver_control_v1_interface, version));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (std::strcmp(interface, wl_seat_interface.name) == 0) {
 | 
			
		||||
    version = std::min<uint32_t>(version, 1);
 | 
			
		||||
    static_cast<Tags *>(data)->seat_ = static_cast<struct wl_seat *>(
 | 
			
		||||
        wl_registry_bind(registry, name, &wl_seat_interface, version));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {
 | 
			
		||||
  /* Ignore event */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static const wl_registry_listener registry_listener_impl = {.global = handle_global,
 | 
			
		||||
                                                            .global_remove = handle_global_remove};
 | 
			
		||||
 | 
			
		||||
Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
 | 
			
		||||
    : waybar::AModule(config, "tags", id, false, false),
 | 
			
		||||
      status_manager_{nullptr},
 | 
			
		||||
      control_{nullptr},
 | 
			
		||||
      seat_{nullptr},
 | 
			
		||||
      bar_(bar),
 | 
			
		||||
      box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
 | 
			
		||||
      output_status_{nullptr} {
 | 
			
		||||
@@ -68,6 +99,14 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!control_) {
 | 
			
		||||
    spdlog::error("river_control_v1 not advertised");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!seat_) {
 | 
			
		||||
    spdlog::error("wl_seat not advertised");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  box_.set_name("tags");
 | 
			
		||||
  if (!id.empty()) {
 | 
			
		||||
    box_.get_style_context()->add_class(id);
 | 
			
		||||
@@ -89,11 +128,17 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint32_t i = 1;
 | 
			
		||||
  for (const auto &tag_label : tag_labels) {
 | 
			
		||||
    Gtk::Button &button = buttons_.emplace_back(tag_label);
 | 
			
		||||
    button.set_relief(Gtk::RELIEF_NONE);
 | 
			
		||||
    box_.pack_start(button, false, false, 0);
 | 
			
		||||
    if (!config_["disable-click"].asBool()) {
 | 
			
		||||
      button.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_primary_clicked), i));
 | 
			
		||||
      button.signal_button_press_event().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), i));
 | 
			
		||||
    }
 | 
			
		||||
    button.show();
 | 
			
		||||
    i <<= 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
 | 
			
		||||
@@ -107,6 +152,31 @@ Tags::~Tags() {
 | 
			
		||||
  if (output_status_) {
 | 
			
		||||
    zriver_output_status_v1_destroy(output_status_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (control_) {
 | 
			
		||||
    zriver_control_v1_destroy(control_);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Tags::handle_primary_clicked(uint32_t tag) {
 | 
			
		||||
  // Send river command to select tag on left mouse click
 | 
			
		||||
  zriver_command_callback_v1 *callback;
 | 
			
		||||
  zriver_control_v1_add_argument(control_, "set-focused-tags");
 | 
			
		||||
  zriver_control_v1_add_argument(control_, std::to_string(tag).c_str());
 | 
			
		||||
  callback = zriver_control_v1_run_command(control_, seat_);
 | 
			
		||||
  zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) {
 | 
			
		||||
  if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) {
 | 
			
		||||
    // Send river command to toggle tag on right mouse click
 | 
			
		||||
    zriver_command_callback_v1 *callback;
 | 
			
		||||
    zriver_control_v1_add_argument(control_, "toggle-focused-tags");
 | 
			
		||||
    zriver_control_v1_add_argument(control_, std::to_string(tag).c_str());
 | 
			
		||||
    callback = zriver_control_v1_run_command(control_, seat_);
 | 
			
		||||
    zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr);
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Tags::handle_focused_tags(uint32_t tags) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										107
									
								
								src/modules/sway/bar.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/modules/sway/bar.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
#include "modules/sway/bar.hpp"
 | 
			
		||||
 | 
			
		||||
#include <fmt/ostream.h>
 | 
			
		||||
#include <spdlog/spdlog.h>
 | 
			
		||||
 | 
			
		||||
#include <stdexcept>
 | 
			
		||||
 | 
			
		||||
#include "bar.hpp"
 | 
			
		||||
#include "modules/sway/ipc/ipc.hpp"
 | 
			
		||||
 | 
			
		||||
namespace waybar::modules::sway {
 | 
			
		||||
 | 
			
		||||
BarIpcClient::BarIpcClient(waybar::Bar& bar) : bar_{bar} {
 | 
			
		||||
  {
 | 
			
		||||
    sigc::connection handle =
 | 
			
		||||
        ipc_.signal_cmd.connect(sigc::mem_fun(*this, &BarIpcClient::onInitialConfig));
 | 
			
		||||
    ipc_.sendCmd(IPC_GET_BAR_CONFIG, bar_.bar_id);
 | 
			
		||||
 | 
			
		||||
    handle.disconnect();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  signal_config_.connect(sigc::mem_fun(*this, &BarIpcClient::onConfigUpdate));
 | 
			
		||||
  signal_visible_.connect(sigc::mem_fun(*this, &BarIpcClient::onVisibilityUpdate));
 | 
			
		||||
 | 
			
		||||
  ipc_.subscribe(R"(["bar_state_update", "barconfig_update"])");
 | 
			
		||||
  ipc_.signal_event.connect(sigc::mem_fun(*this, &BarIpcClient::onIpcEvent));
 | 
			
		||||
  // Launch worker
 | 
			
		||||
  ipc_.setWorker([this] {
 | 
			
		||||
    try {
 | 
			
		||||
      ipc_.handleEvent();
 | 
			
		||||
    } catch (const std::exception& e) {
 | 
			
		||||
      spdlog::error("BarIpcClient::handleEvent {}", e.what());
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct swaybar_config parseConfig(const Json::Value& payload) {
 | 
			
		||||
  swaybar_config conf;
 | 
			
		||||
  if (auto id = payload["id"]; id.isString()) {
 | 
			
		||||
    conf.id = id.asString();
 | 
			
		||||
  }
 | 
			
		||||
  if (auto mode = payload["mode"]; mode.isString()) {
 | 
			
		||||
    conf.mode = mode.asString();
 | 
			
		||||
  }
 | 
			
		||||
  if (auto hs = payload["hidden_state"]; hs.isString()) {
 | 
			
		||||
    conf.hidden_state = hs.asString();
 | 
			
		||||
  }
 | 
			
		||||
  return conf;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BarIpcClient::onInitialConfig(const struct Ipc::ipc_response& res) {
 | 
			
		||||
  auto payload = parser_.parse(res.payload);
 | 
			
		||||
  if (auto success = payload.get("success", true); !success.asBool()) {
 | 
			
		||||
    auto err = payload.get("error", "Unknown error");
 | 
			
		||||
    throw std::runtime_error(err.asString());
 | 
			
		||||
  }
 | 
			
		||||
  auto config = parseConfig(payload);
 | 
			
		||||
  onConfigUpdate(config);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) {
 | 
			
		||||
  try {
 | 
			
		||||
    auto payload = parser_.parse(res.payload);
 | 
			
		||||
    if (auto id = payload["id"]; id.isString() && id.asString() != bar_.bar_id) {
 | 
			
		||||
      spdlog::trace("swaybar ipc: ignore event for {}", id.asString());
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (payload.isMember("visible_by_modifier")) {
 | 
			
		||||
      // visibility change for hidden bar
 | 
			
		||||
      signal_visible_(payload["visible_by_modifier"].asBool());
 | 
			
		||||
    } else {
 | 
			
		||||
      // configuration update
 | 
			
		||||
      auto config = parseConfig(payload);
 | 
			
		||||
      signal_config_(std::move(config));
 | 
			
		||||
    }
 | 
			
		||||
  } catch (const std::exception& e) {
 | 
			
		||||
    spdlog::error("BarIpcClient::onEvent {}", e.what());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BarIpcClient::onConfigUpdate(const swaybar_config& config) {
 | 
			
		||||
  spdlog::info("config update for {}: id {}, mode {}, hidden_state {}",
 | 
			
		||||
               bar_.bar_id,
 | 
			
		||||
               config.id,
 | 
			
		||||
               config.mode,
 | 
			
		||||
               config.hidden_state);
 | 
			
		||||
  bar_config_ = config;
 | 
			
		||||
  update();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BarIpcClient::onVisibilityUpdate(bool visible_by_modifier) {
 | 
			
		||||
  spdlog::debug("visiblity update for {}: {}", bar_.bar_id, visible_by_modifier);
 | 
			
		||||
  visible_by_modifier_ = visible_by_modifier;
 | 
			
		||||
  update();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BarIpcClient::update() {
 | 
			
		||||
  bool visible = visible_by_modifier_;
 | 
			
		||||
  if (bar_config_.mode == "invisible") {
 | 
			
		||||
    visible = false;
 | 
			
		||||
  } else if (bar_config_.mode != "hide" || bar_config_.hidden_state != "hide") {
 | 
			
		||||
    visible = true;
 | 
			
		||||
  }
 | 
			
		||||
  bar_.setMode(visible ? bar_config_.mode : Bar::MODE_INVISIBLE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace waybar::modules::sway
 | 
			
		||||
@@ -99,7 +99,8 @@ auto Language::update() -> void {
 | 
			
		||||
                                         fmt::arg("short", layout_.short_name),
 | 
			
		||||
                                         fmt::arg("shortDescription", layout_.short_description),
 | 
			
		||||
                                         fmt::arg("long", layout_.full_name),
 | 
			
		||||
                                         fmt::arg("variant", layout_.variant)));
 | 
			
		||||
                                         fmt::arg("variant", layout_.variant),
 | 
			
		||||
                                         fmt::arg("flag", layout_.country_flag())));
 | 
			
		||||
  label_.set_markup(display_layout);
 | 
			
		||||
  if (tooltipEnabled()) {
 | 
			
		||||
    if (tooltip_format_ != "") {
 | 
			
		||||
@@ -107,7 +108,8 @@ auto Language::update() -> void {
 | 
			
		||||
                                                     fmt::arg("short", layout_.short_name),
 | 
			
		||||
                                                     fmt::arg("shortDescription", layout_.short_description),
 | 
			
		||||
                                                     fmt::arg("long", layout_.full_name),
 | 
			
		||||
                                                     fmt::arg("variant", layout_.variant)));
 | 
			
		||||
                                                     fmt::arg("variant", layout_.variant),
 | 
			
		||||
                                                     fmt::arg("flag", layout_.country_flag())));
 | 
			
		||||
      label_.set_tooltip_markup(tooltip_display_layout);
 | 
			
		||||
    } else {
 | 
			
		||||
      label_.set_tooltip_markup(display_layout);
 | 
			
		||||
@@ -212,4 +214,15 @@ Language::XKBContext::~XKBContext() {
 | 
			
		||||
  rxkb_context_unref(context_);
 | 
			
		||||
  delete layout_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string Language::Layout::country_flag() const {
 | 
			
		||||
  if (short_name.size() != 2) return "";
 | 
			
		||||
  unsigned char result[] = "\xf0\x9f\x87\x00\xf0\x9f\x87\x00";
 | 
			
		||||
  result[3] = short_name[0] + 0x45;
 | 
			
		||||
  result[7] = short_name[1] + 0x45;
 | 
			
		||||
  // Check if both emojis are in A-Z symbol bounds
 | 
			
		||||
  if (result[3] < 0xa6 || result[3] > 0xbf) return "";
 | 
			
		||||
  if (result[7] < 0xa6 || result[7] > 0xbf) return "";
 | 
			
		||||
  return std::string{reinterpret_cast<char*>(result)};
 | 
			
		||||
}
 | 
			
		||||
}  // namespace waybar::modules::sway
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
#include <spdlog/spdlog.h>
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cctype>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
@@ -98,6 +99,7 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
 | 
			
		||||
                  Json::Value v;
 | 
			
		||||
                  v["name"] = p_w_name;
 | 
			
		||||
                  v["target_output"] = bar_.output->name;
 | 
			
		||||
                  v["num"] = convertWorkspaceNameToNum(p_w_name);
 | 
			
		||||
                  workspaces_.emplace_back(std::move(v));
 | 
			
		||||
                  break;
 | 
			
		||||
                }
 | 
			
		||||
@@ -107,57 +109,59 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
 | 
			
		||||
              Json::Value v;
 | 
			
		||||
              v["name"] = p_w_name;
 | 
			
		||||
              v["target_output"] = "";
 | 
			
		||||
              v["num"] = convertWorkspaceNameToNum(p_w_name);
 | 
			
		||||
              workspaces_.emplace_back(std::move(v));
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // config option to sort numeric workspace names before others
 | 
			
		||||
        bool config_numeric_first = config_["numeric-first"].asBool();
 | 
			
		||||
 | 
			
		||||
        // sway has a defined ordering of workspaces that should be preserved in
 | 
			
		||||
        // the representation displayed by waybar to ensure that commands such
 | 
			
		||||
        // as "workspace prev" or "workspace next" make sense when looking at
 | 
			
		||||
        // the workspace representation in the bar.
 | 
			
		||||
        // Due to waybar's own feature of persistent workspaces unknown to sway,
 | 
			
		||||
        // custom sorting logic is necessary to make these workspaces appear
 | 
			
		||||
        // naturally in the list of workspaces without messing up sway's
 | 
			
		||||
        // sorting. For this purpose, a custom numbering property is created
 | 
			
		||||
        // that preserves the order provided by sway while inserting numbered
 | 
			
		||||
        // persistent workspaces at their natural positions.
 | 
			
		||||
        //
 | 
			
		||||
        // All of this code assumes that sway provides numbered workspaces first
 | 
			
		||||
        // and other workspaces are sorted by their creation time.
 | 
			
		||||
        //
 | 
			
		||||
        // In a first pass, the maximum "num" value is computed to enqueue
 | 
			
		||||
        // unnumbered workspaces behind numbered ones when computing the sort
 | 
			
		||||
        // attribute.
 | 
			
		||||
        int max_num = -1;
 | 
			
		||||
        for (auto & workspace : workspaces_) {
 | 
			
		||||
          max_num = std::max(workspace["num"].asInt(), max_num);
 | 
			
		||||
        }
 | 
			
		||||
        for (auto & workspace : workspaces_) {
 | 
			
		||||
          auto workspace_num = workspace["num"].asInt();
 | 
			
		||||
          if (workspace_num > -1) {
 | 
			
		||||
            workspace["sort"] = workspace_num;
 | 
			
		||||
          } else {
 | 
			
		||||
            workspace["sort"] = ++max_num;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        std::sort(workspaces_.begin(),
 | 
			
		||||
                  workspaces_.end(),
 | 
			
		||||
                  [config_numeric_first](const Json::Value &lhs, const Json::Value &rhs) {
 | 
			
		||||
                    // the "num" property (integer type):
 | 
			
		||||
                    // The workspace number or -1 for workspaces that do
 | 
			
		||||
                    // not start with a number.
 | 
			
		||||
                    // We could rely on sway providing this property:
 | 
			
		||||
                    //
 | 
			
		||||
                    //     auto l = lhs["num"].asInt();
 | 
			
		||||
                    //     auto r = rhs["num"].asInt();
 | 
			
		||||
                    //
 | 
			
		||||
                    // We cannot rely on the "num" property as provided by sway
 | 
			
		||||
                    // via IPC, because persistent workspace might not exist in
 | 
			
		||||
                    // sway's view. However, we need this property also for
 | 
			
		||||
                    // not-yet created persistent workspace. As such, we simply
 | 
			
		||||
                    // duplicate sway's logic of assigning the "num" property
 | 
			
		||||
                    // into waybar (see convertWorkspaceNameToNum). This way the
 | 
			
		||||
                    // sorting should work out even when we include workspaces
 | 
			
		||||
                    // that do not currently exist.
 | 
			
		||||
                  [](const Json::Value &lhs, const Json::Value &rhs) {
 | 
			
		||||
                    auto lname = lhs["name"].asString();
 | 
			
		||||
                    auto rname = rhs["name"].asString();
 | 
			
		||||
                    int  l = convertWorkspaceNameToNum(lname);
 | 
			
		||||
                    int  r = convertWorkspaceNameToNum(rname);
 | 
			
		||||
                    int  l = lhs["sort"].asInt();
 | 
			
		||||
                    int  r = rhs["sort"].asInt();
 | 
			
		||||
 | 
			
		||||
                    if (l == r) {
 | 
			
		||||
                      // in case both integers are the same, lexicographical
 | 
			
		||||
                      // sort. This also covers the case when both don't have a
 | 
			
		||||
                      // number (i.e., l == r == -1).
 | 
			
		||||
                      // In case both integers are the same, lexicographical
 | 
			
		||||
                      // sort. The code above already ensure that this will only
 | 
			
		||||
                      // happend in case of explicitly numbered workspaces.
 | 
			
		||||
                      return lname < rname;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // one of the workspaces doesn't begin with a number, so
 | 
			
		||||
                    // num is -1.
 | 
			
		||||
                    if (l < 0 || r < 0) {
 | 
			
		||||
                      if (config_numeric_first) {
 | 
			
		||||
                        return r < 0;
 | 
			
		||||
                      }
 | 
			
		||||
                      return l < 0;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // both workspaces have a "num" so let's just compare those
 | 
			
		||||
                    return l < r;
 | 
			
		||||
                  });
 | 
			
		||||
 | 
			
		||||
      }
 | 
			
		||||
      dp.emit();
 | 
			
		||||
    } catch (const std::exception &e) {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,9 @@
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
#include <gdkmm/monitor.h>
 | 
			
		||||
 | 
			
		||||
#include <gtkmm/icontheme.h>
 | 
			
		||||
@@ -86,8 +88,7 @@ static Glib::RefPtr<Gdk::Pixbuf> load_icon_from_file(std::string icon_path, int
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Method 1 - get the correct icon name from the desktop file */
 | 
			
		||||
static std::string get_from_desktop_app_info(const std::string &app_id)
 | 
			
		||||
static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_by_name(const std::string& app_id)
 | 
			
		||||
{
 | 
			
		||||
    static std::vector<std::string> prefixes = search_prefix();
 | 
			
		||||
 | 
			
		||||
@@ -103,33 +104,29 @@ static std::string get_from_desktop_app_info(const std::string &app_id)
 | 
			
		||||
        ".desktop"
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Glib::RefPtr<Gio::DesktopAppInfo> app_info;
 | 
			
		||||
	for (auto& prefix : prefixes) {
 | 
			
		||||
		for (auto& folder : app_folders) {
 | 
			
		||||
			for (auto& suffix : suffixes) {
 | 
			
		||||
                    auto app_info_ = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
 | 
			
		||||
					if (!app_info_) {
 | 
			
		||||
						continue;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
    for (auto& prefix : prefixes)
 | 
			
		||||
        for (auto& folder : app_folders)
 | 
			
		||||
            for (auto& suffix : suffixes)
 | 
			
		||||
                if (!app_info)
 | 
			
		||||
                    app_info = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
 | 
			
		||||
					return app_info_;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    if (app_info && app_info->get_icon())
 | 
			
		||||
        return app_info->get_icon()->to_string();
 | 
			
		||||
 | 
			
		||||
    return "";
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */
 | 
			
		||||
static std::string get_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
 | 
			
		||||
        const std::string &app_id)
 | 
			
		||||
Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id)
 | 
			
		||||
{
 | 
			
		||||
    if (icon_theme->lookup_icon(app_id, 24))
 | 
			
		||||
        return app_id;
 | 
			
		||||
	auto app_info = get_app_info_by_name(app_id);
 | 
			
		||||
	if (app_info) {
 | 
			
		||||
		return app_info;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Method 3 - as last resort perform a search for most appropriate desktop info file */
 | 
			
		||||
static std::string get_from_desktop_app_info_search(const std::string &app_id)
 | 
			
		||||
{
 | 
			
		||||
    std::string desktop_file = "";
 | 
			
		||||
 | 
			
		||||
    gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str());
 | 
			
		||||
@@ -151,65 +148,84 @@ static std::string get_from_desktop_app_info_search(const std::string &app_id)
 | 
			
		||||
    }
 | 
			
		||||
    g_free(desktop_list);
 | 
			
		||||
 | 
			
		||||
    return get_from_desktop_app_info(desktop_file);
 | 
			
		||||
    return get_app_info_by_name(desktop_file);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
 | 
			
		||||
        const std::string &app_id_list, int size)
 | 
			
		||||
{
 | 
			
		||||
void Task::set_app_info_from_app_id_list(const std::string& app_id_list) {
 | 
			
		||||
    std::string app_id;
 | 
			
		||||
    std::istringstream stream(app_id_list);
 | 
			
		||||
    bool found = false;
 | 
			
		||||
 | 
			
		||||
    /* Wayfire sends a list of app-id's in space separated format, other compositors
 | 
			
		||||
     * send a single app-id, but in any case this works fine */
 | 
			
		||||
    while (stream >> app_id)
 | 
			
		||||
    {
 | 
			
		||||
        app_info_ = get_desktop_app_info(app_id);
 | 
			
		||||
		if (app_info_) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		auto lower_app_id = app_id;
 | 
			
		||||
		std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(),
 | 
			
		||||
				[](char c){ return std::tolower(c); });
 | 
			
		||||
		app_info_ = get_desktop_app_info(lower_app_id);
 | 
			
		||||
		if (app_info_) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
				
 | 
			
		||||
        size_t start = 0, end = app_id.size();
 | 
			
		||||
        start = app_id.rfind(".", end);
 | 
			
		||||
        std::string app_name = app_id.substr(start+1, app_id.size());
 | 
			
		||||
        app_info_ = get_desktop_app_info(app_name);
 | 
			
		||||
		if (app_info_) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        auto lower_app_id = app_id;
 | 
			
		||||
        std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(),
 | 
			
		||||
                [](char c){ return std::tolower(c); });
 | 
			
		||||
        start = app_id.find("-");
 | 
			
		||||
        app_name = app_id.substr(0, start);
 | 
			
		||||
        app_info_ = get_desktop_app_info(app_name);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
        std::string icon_name = get_from_icon_theme(icon_theme, app_id);
 | 
			
		||||
static std::string get_icon_name_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
 | 
			
		||||
        const std::string &app_id)
 | 
			
		||||
{
 | 
			
		||||
    if (icon_theme->lookup_icon(app_id, 24))
 | 
			
		||||
        return app_id;
 | 
			
		||||
 | 
			
		||||
        if (icon_name.empty())
 | 
			
		||||
            icon_name = get_from_icon_theme(icon_theme, lower_app_id);
 | 
			
		||||
        if (icon_name.empty())
 | 
			
		||||
            icon_name = get_from_icon_theme(icon_theme, app_name);
 | 
			
		||||
        if (icon_name.empty())
 | 
			
		||||
            icon_name = get_from_desktop_app_info(app_id);
 | 
			
		||||
        if (icon_name.empty())
 | 
			
		||||
            icon_name = get_from_desktop_app_info(lower_app_id);
 | 
			
		||||
        if (icon_name.empty())
 | 
			
		||||
            icon_name = get_from_desktop_app_info(app_name);
 | 
			
		||||
        if (icon_name.empty())
 | 
			
		||||
            icon_name = get_from_desktop_app_info_search(app_id);
 | 
			
		||||
    return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
        if (icon_name.empty())
 | 
			
		||||
            icon_name = "unknown";
 | 
			
		||||
bool Task::image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size)
 | 
			
		||||
{
 | 
			
		||||
	std::string ret_icon_name = "unknown";
 | 
			
		||||
	if (app_info) {
 | 
			
		||||
		std::string icon_name = get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class());
 | 
			
		||||
		if (!icon_name.empty()) {
 | 
			
		||||
			ret_icon_name = icon_name;
 | 
			
		||||
		} else {
 | 
			
		||||
			if (app_info->get_icon()) {
 | 
			
		||||
				ret_icon_name = app_info->get_icon()->to_string();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
        Glib::RefPtr<Gdk::Pixbuf> pixbuf;
 | 
			
		||||
	Glib::RefPtr<Gdk::Pixbuf> pixbuf;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            pixbuf = icon_theme->load_icon(icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE);
 | 
			
		||||
        } catch(...) {
 | 
			
		||||
            if (Glib::file_test(icon_name, Glib::FILE_TEST_EXISTS))
 | 
			
		||||
                pixbuf = load_icon_from_file(icon_name, size);
 | 
			
		||||
            else
 | 
			
		||||
                pixbuf = {};
 | 
			
		||||
        }
 | 
			
		||||
	try {
 | 
			
		||||
		pixbuf = icon_theme->load_icon(ret_icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE);
 | 
			
		||||
	} catch(...) {
 | 
			
		||||
		if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS))
 | 
			
		||||
			pixbuf = load_icon_from_file(ret_icon_name, size);
 | 
			
		||||
		else
 | 
			
		||||
			pixbuf = {};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
        if (pixbuf) {
 | 
			
		||||
            image.set(pixbuf);
 | 
			
		||||
            found = true;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
	if (pixbuf) {
 | 
			
		||||
		image.set(pixbuf);
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    return found;
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Task class implementation */
 | 
			
		||||
@@ -276,8 +292,7 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
 | 
			
		||||
        struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat) :
 | 
			
		||||
    bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat},
 | 
			
		||||
    id_{global_id++},
 | 
			
		||||
    content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
 | 
			
		||||
    button_visible_{false}, ignored_{false}
 | 
			
		||||
    content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}
 | 
			
		||||
{
 | 
			
		||||
    zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this);
 | 
			
		||||
 | 
			
		||||
@@ -290,13 +305,15 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
 | 
			
		||||
    content_.show();
 | 
			
		||||
    button_.add(content_);
 | 
			
		||||
 | 
			
		||||
    with_icon_ = false;
 | 
			
		||||
    format_before_.clear();
 | 
			
		||||
    format_after_.clear();
 | 
			
		||||
 | 
			
		||||
    if (config_["format"].isString()) {
 | 
			
		||||
        /* The user defined a format string, use it */
 | 
			
		||||
        auto format = config_["format"].asString();
 | 
			
		||||
		if (format.find("{name}") != std::string::npos) {
 | 
			
		||||
			with_name_ = true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        auto icon_pos = format.find("{icon}");
 | 
			
		||||
        if (icon_pos == 0) {
 | 
			
		||||
@@ -377,13 +394,12 @@ std::string Task::state_string(bool shortened) const
 | 
			
		||||
void Task::handle_title(const char *title)
 | 
			
		||||
{
 | 
			
		||||
    title_ = title;
 | 
			
		||||
    hide_if_ignored();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Task::handle_app_id(const char *app_id)
 | 
			
		||||
void Task::hide_if_ignored()
 | 
			
		||||
{
 | 
			
		||||
    app_id_ = app_id;
 | 
			
		||||
 | 
			
		||||
    if (tbar_->ignore_list().count(app_id)) {
 | 
			
		||||
    if (tbar_->ignore_list().count(app_id_) || tbar_->ignore_list().count(title_)) {
 | 
			
		||||
        ignored_ = true;
 | 
			
		||||
        if (button_visible_) {
 | 
			
		||||
          auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
 | 
			
		||||
@@ -397,14 +413,35 @@ void Task::handle_app_id(const char *app_id)
 | 
			
		||||
          handle_output_enter(output);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    if (!with_icon_)
 | 
			
		||||
void Task::handle_app_id(const char *app_id)
 | 
			
		||||
{
 | 
			
		||||
    app_id_ = app_id;
 | 
			
		||||
    hide_if_ignored();
 | 
			
		||||
 | 
			
		||||
	auto ids_replace_map = tbar_->app_ids_replace_map();
 | 
			
		||||
	if (ids_replace_map.count(app_id_)) {
 | 
			
		||||
		auto replaced_id = ids_replace_map[app_id_];
 | 
			
		||||
		spdlog::debug(fmt::format("Task ({}) [{}] app_id was replaced with {}", id_, app_id_, replaced_id));
 | 
			
		||||
		app_id_ = replaced_id;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!with_icon_ && !with_name_) {
 | 
			
		||||
        return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	set_app_info_from_app_id_list(app_id_);
 | 
			
		||||
	name_ = app_info_ ? app_info_->get_display_name() : app_id;
 | 
			
		||||
 | 
			
		||||
	if (!with_icon_) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
 | 
			
		||||
    bool found = false;
 | 
			
		||||
    for (auto& icon_theme : tbar_->icon_themes()) {
 | 
			
		||||
        if (image_load_icon(icon_, icon_theme, app_id_, icon_size)) {
 | 
			
		||||
        if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) {
 | 
			
		||||
            found = true;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
@@ -418,13 +455,13 @@ void Task::handle_app_id(const char *app_id)
 | 
			
		||||
 | 
			
		||||
void Task::handle_output_enter(struct wl_output *output)
 | 
			
		||||
{
 | 
			
		||||
    spdlog::debug("{} entered output {}", repr(), (void*)output);
 | 
			
		||||
 | 
			
		||||
    if (ignored_) {
 | 
			
		||||
      spdlog::debug("{} is ignored", repr());
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    spdlog::debug("{} entered output {}", repr(), (void*)output);
 | 
			
		||||
 | 
			
		||||
    if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) {
 | 
			
		||||
        /* The task entered the output of the current bar make the button visible */
 | 
			
		||||
        tbar_->add_button(button_);
 | 
			
		||||
@@ -560,14 +597,17 @@ void Task::update()
 | 
			
		||||
{
 | 
			
		||||
    bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false;
 | 
			
		||||
    std::string title = title_;
 | 
			
		||||
    std::string name = name_;
 | 
			
		||||
    std::string app_id = app_id_;
 | 
			
		||||
    if (markup) {
 | 
			
		||||
        title = Glib::Markup::escape_text(title);
 | 
			
		||||
        name = Glib::Markup::escape_text(name);
 | 
			
		||||
        app_id = Glib::Markup::escape_text(app_id);
 | 
			
		||||
    }
 | 
			
		||||
    if (!format_before_.empty()) {
 | 
			
		||||
        auto txt = fmt::format(format_before_,
 | 
			
		||||
                    fmt::arg("title", title),
 | 
			
		||||
                    fmt::arg("name", name),
 | 
			
		||||
                    fmt::arg("app_id", app_id),
 | 
			
		||||
                    fmt::arg("state", state_string()),
 | 
			
		||||
                    fmt::arg("short_state", state_string(true))
 | 
			
		||||
@@ -581,6 +621,7 @@ void Task::update()
 | 
			
		||||
    if (!format_after_.empty()) {
 | 
			
		||||
        auto txt = fmt::format(format_after_,
 | 
			
		||||
                    fmt::arg("title", title),
 | 
			
		||||
                    fmt::arg("name", name),
 | 
			
		||||
                    fmt::arg("app_id", app_id),
 | 
			
		||||
                    fmt::arg("state", state_string()),
 | 
			
		||||
                    fmt::arg("short_state", state_string(true))
 | 
			
		||||
@@ -595,6 +636,7 @@ void Task::update()
 | 
			
		||||
    if (!format_tooltip_.empty()) {
 | 
			
		||||
        auto txt = fmt::format(format_tooltip_,
 | 
			
		||||
                    fmt::arg("title", title),
 | 
			
		||||
                    fmt::arg("name", name),
 | 
			
		||||
                    fmt::arg("app_id", app_id),
 | 
			
		||||
                    fmt::arg("state", state_string()),
 | 
			
		||||
                    fmt::arg("short_state", state_string(true))
 | 
			
		||||
@@ -722,6 +764,15 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Load app_id remappings
 | 
			
		||||
    if (config_["app_ids-mapping"].isObject()) {
 | 
			
		||||
		const Json::Value& mapping = config_["app_ids-mapping"];
 | 
			
		||||
		const std::vector<std::string> app_ids = config_["app_ids-mapping"].getMemberNames();
 | 
			
		||||
        for (auto& app_id : app_ids) {
 | 
			
		||||
          app_ids_replace_map_.emplace(app_id, mapping[app_id].asString());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    icon_themes_.push_back(Gtk::IconTheme::get_default());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -853,10 +904,10 @@ bool Taskbar::all_outputs() const
 | 
			
		||||
    return config_["all-outputs"].isBool() && config_["all-outputs"].asBool();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<Glib::RefPtr<Gtk::IconTheme>> Taskbar::icon_themes() const
 | 
			
		||||
{
 | 
			
		||||
    return icon_themes_;
 | 
			
		||||
}
 | 
			
		||||
const std::unordered_set<std::string> &Taskbar::ignore_list() const { return ignore_list_; }
 | 
			
		||||
const std::vector<Glib::RefPtr<Gtk::IconTheme>>& Taskbar::icon_themes() const { return icon_themes_; }
 | 
			
		||||
 | 
			
		||||
const std::unordered_set<std::string>& Taskbar::ignore_list() const { return ignore_list_; }
 | 
			
		||||
 | 
			
		||||
const std::map<std::string, std::string>& Taskbar::app_ids_replace_map() const { return app_ids_replace_map_; }
 | 
			
		||||
 | 
			
		||||
} /* namespace waybar::modules::wlr */
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										472
									
								
								src/modules/wlr/workspace_manager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										472
									
								
								src/modules/wlr/workspace_manager.cpp
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										135
									
								
								src/modules/wlr/workspace_manager_binding.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								src/modules/wlr/workspace_manager_binding.cpp
									
									
									
									
									
										Normal 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, ®istry_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
 | 
			
		||||
		Reference in New Issue
	
	Block a user