Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/bar.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class Bar : public sigc::trackable {
void handleSignal(int);
util::KillSignalAction getOnSigusr1Action();
util::KillSignalAction getOnSigusr2Action();
int getAvailableWidthForLeftModule(const AModule* module) const;

void toggleSuspend(bool suspend);

Expand Down
9 changes: 9 additions & 0 deletions include/modules/sway/workspaces.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
#include <fmt/format.h>
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include <gtkmm/menu.h>

#include <optional>
#include <string_view>
#include <unordered_map>
#include <unordered_set>

#include "AModule.hpp"
#include "bar.hpp"
Expand Down Expand Up @@ -41,6 +43,10 @@ class Workspaces : public AModule, public sigc::trackable {
void updateWindows(const Json::Value&, std::string&);
Gtk::Button& addButton(const Json::Value&);
void onButtonReady(const Json::Value&, Gtk::Button&);
void onBarSizeAllocate(Gtk::Allocation& /*allocation*/);
void updateOverflow();
void rebuildOverflowMenu();
void switchToWorkspace(const Json::Value&);
std::string getIcon(const std::string&, const Json::Value&);
std::string getCycleWorkspace(std::vector<Json::Value>::iterator, bool prev) const;
uint16_t getWorkspaceIndex(const std::string& name) const;
Expand All @@ -53,12 +59,15 @@ class Workspaces : public AModule, public sigc::trackable {
std::vector<std::string> high_priority_named_;
std::vector<std::string> workspaces_order_;
Gtk::Box box_;
Gtk::Button overflow_button_{"…"};
Gtk::Menu overflow_menu_;
std::string m_formatWindowSeparator;
std::vector<std::regex> m_ignoreWorkspaces;
util::RegexCollection m_windowRewriteRules;
util::JsonParser parser_;
std::unordered_map<std::string, Gtk::Button> buttons_;
std::unordered_map<std::string, uint16_t> custom_sort_priorities_;
std::unordered_set<std::string> overflowed_;
std::mutex mutex_;
Ipc ipc_;
};
Expand Down
6 changes: 6 additions & 0 deletions man/waybar-sway-workspaces.5.scd
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ warp-on-scroll: ++
Keys are output names and values are class names like *${output}: {output-class}*.
Assignment in config is used to keep the stylesheet independent of the available outputs.

*overflow*: ++
typeof: bool ++
default: true ++
Hides workspace buttons that would displace modules on the right side of the
bar. Hidden workspaces are available from an ellipsis button. The focused
workspace is kept visible.

# FORMAT REPLACEMENTS

Expand Down
22 changes: 22 additions & 0 deletions src/bar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,28 @@ void waybar::Bar::handleSignal(int signal) {
waybar::util::KillSignalAction waybar::Bar::getOnSigusr1Action() { return this->onSigusr1; }
waybar::util::KillSignalAction waybar::Bar::getOnSigusr2Action() { return this->onSigusr2; }

int waybar::Bar::getAvailableWidthForLeftModule(const AModule* target) const {
Gdk::Rectangle geometry;
output->monitor->get_geometry(geometry);

int minimum = 0;
int right_natural = 0;
right_.get_preferred_width(minimum, right_natural);

int sibling_width = 0;
for (const auto& module : modules_left_) {
if (module.get() == target) {
continue;
}
int natural = 0;
static_cast<Gtk::Widget&>(*module).get_preferred_width(minimum, natural);
sibling_width += natural;
}

const int gaps = std::max(0, static_cast<int>(modules_left_.size()) - 1) * left_.get_spacing();
return std::max(1, geometry.get_width() - right_natural - sibling_width - gaps);
}

void waybar::Bar::getModules(const Factory& factory, const std::string& pos,
waybar::Group* group = nullptr) {
auto module_list = group != nullptr ? config[pos]["modules"] : config[pos];
Expand Down
168 changes: 141 additions & 27 deletions src/modules/sway/workspaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,25 @@ Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value&
}
box_.get_style_context()->add_class(MODULE_CLASS);
event_box_.add(box_);
overflow_button_.set_name("sway-workspaces-overflow");
overflow_button_.set_relief(Gtk::RELIEF_NONE);
overflow_button_.get_style_context()->add_class("overflow");
overflow_button_.signal_clicked().connect([this] {
{
std::lock_guard<std::mutex> lock(mutex_);
rebuildOverflowMenu();
}
const auto widget_anchor =
bar_.position == Gtk::POS_BOTTOM ? GDK_GRAVITY_NORTH_EAST : GDK_GRAVITY_SOUTH_EAST;
const auto menu_anchor =
bar_.position == Gtk::POS_BOTTOM ? GDK_GRAVITY_SOUTH_EAST : GDK_GRAVITY_NORTH_EAST;
gtk_menu_popup_at_widget(overflow_menu_.gobj(), GTK_WIDGET(overflow_button_.gobj()),
widget_anchor, menu_anchor, nullptr);
});
box_.pack_end(overflow_button_, false, false, 0);
overflow_button_.hide();
const_cast<Bar&>(bar_).window.signal_size_allocate().connect(
sigc::mem_fun(*this, &Workspaces::onBarSizeAllocate));
if (config_["format-window-separator"].isString()) {
m_formatWindowSeparator = config_["format-window-separator"].asString();
} else {
Expand Down Expand Up @@ -447,6 +466,7 @@ auto Workspaces::update() -> void {
}
onButtonReady(*it, button);
}
updateOverflow();
// Call parent update
AModule::update();
}
Expand All @@ -458,37 +478,131 @@ Gtk::Button& Workspaces::addButton(const Json::Value& node) {
button.set_name("sway-workspace-" + node["name"].asString());
button.set_relief(Gtk::RELIEF_NONE);
if (!config_["disable-click"].asBool()) {
button.signal_pressed().connect([this, node] {
try {
if (node["target_output"].isString()) {
ipc_.sendCmd(IPC_COMMAND,
fmt::format(persistent_workspace_switch_cmd_, "--no-auto-back-and-forth",
node["name"].asString(), node["target_output"].asString(),
"--no-auto-back-and-forth", node["name"].asString()));
} else {
std::string flag =
config_["disable-auto-back-and-forth"].asBool() ? "--no-auto-back-and-forth" : "";
if (config_["no-switch-output"].asBool()) {
ipc_.sendCmd(IPC_COMMAND,
fmt::format("[workspace=\"^{}$\"] move workspace to output current; "
"workspace number {} \"{}\"",
node["name"].asString(), flag, node["name"].asString()));
} else if (node["num"].asInt() >= 0) {
ipc_.sendCmd(IPC_COMMAND,
fmt::format(workspace_switch_number_cmd_, flag, node["num"].asInt()));
} else {
ipc_.sendCmd(IPC_COMMAND,
fmt::format(workspace_switch_cmd_, flag, node["name"].asString()));
}
}
} catch (const std::exception& e) {
spdlog::error("Workspaces: {}", e.what());
}
});
button.signal_pressed().connect([this, node] { switchToWorkspace(node); });
}
return button;
}

void Workspaces::switchToWorkspace(const Json::Value& node) {
try {
if (node["target_output"].isString()) {
ipc_.sendCmd(IPC_COMMAND,
fmt::format(persistent_workspace_switch_cmd_, "--no-auto-back-and-forth",
node["name"].asString(), node["target_output"].asString(),
"--no-auto-back-and-forth", node["name"].asString()));
} else {
std::string flag =
config_["disable-auto-back-and-forth"].asBool() ? "--no-auto-back-and-forth" : "";
if (config_["no-switch-output"].asBool()) {
ipc_.sendCmd(IPC_COMMAND,
fmt::format("[workspace=\"^{}$\"] move workspace to output current; "
"workspace number {} \"{}\"",
node["name"].asString(), flag, node["name"].asString()));
} else if (node["num"].asInt() >= 0) {
ipc_.sendCmd(IPC_COMMAND,
fmt::format(workspace_switch_number_cmd_, flag, node["num"].asInt()));
} else {
ipc_.sendCmd(IPC_COMMAND,
fmt::format(workspace_switch_cmd_, flag, node["name"].asString()));
}
}
} catch (const std::exception& e) {
spdlog::error("Workspaces: {}", e.what());
}
}

void Workspaces::rebuildOverflowMenu() {
for (auto* child : overflow_menu_.get_children()) {
overflow_menu_.remove(*child);
}

for (const auto& workspace : workspaces_) {
if (overflowed_.count(workspace["name"].asString()) == 0) {
continue;
}
auto* item = Gtk::manage(new Gtk::MenuItem(workspace["name"].asString()));
item->signal_activate().connect([this, workspace] { switchToWorkspace(workspace); });
overflow_menu_.append(*item);
}
overflow_menu_.show_all();
}

void Workspaces::onBarSizeAllocate(Gtk::Allocation& /*allocation*/) {
std::lock_guard<std::mutex> lock(mutex_);
updateOverflow();
}

void Workspaces::updateOverflow() {
for (const auto& name : overflowed_) {
auto button = buttons_.find(name);
if (button != buttons_.end()) {
button->second.show();
}
}
overflowed_.clear();

if (config_["overflow"].isBool() && !config_["overflow"].asBool()) {
overflow_button_.hide();
return;
}

const int max_width = bar_.getAvailableWidthForLeftModule(this);

int overflow_min = 0;
int overflow_natural = 0;
overflow_button_.get_preferred_width(overflow_min, overflow_natural);

int used = 0;
int total = 0;
std::vector<std::pair<std::string, int>> shown;
for (const auto& workspace : workspaces_) {
auto it = buttons_.find(workspace["name"].asString());
if (it == buttons_.end()) {
continue;
}
auto& button = it->second;
if (!button.get_visible()) {
continue;
}
int minimum = 0;
int natural = 0;
button.get_preferred_width(minimum, natural);
total += natural;
}

if (total <= max_width) {
overflow_button_.hide();
return;
}

for (const auto& workspace : workspaces_) {
auto it = buttons_.find(workspace["name"].asString());
if (it == buttons_.end() || !it->second.get_visible()) {
continue;
}
auto& button = it->second;
int minimum = 0;
int natural = 0;
button.get_preferred_width(minimum, natural);
const bool focused = hasFlag(workspace, "focused");
while (focused && used + natural + overflow_natural > max_width && !shown.empty()) {
const auto& [name, width] = shown.back();
buttons_.at(name).hide();
overflowed_.insert(name);
used -= width;
shown.pop_back();
}
if (!focused && used + natural + overflow_natural > max_width) {
button.hide();
overflowed_.insert(workspace["name"].asString());
} else {
used += natural;
shown.emplace_back(workspace["name"].asString(), natural);
}
}
overflow_button_.show();
}

std::string Workspaces::getIcon(const std::string& name, const Json::Value& node) {
std::vector<std::string> keys = {"high-priority-named", "urgent", "focused", name, "default"};
for (auto const& key : keys) {
Expand Down