Merge pull request #5126 from iamcheyan/feat/wlr-taskbar-bar-css-states

feat(wlr/taskbar): expose active toplevel state on the bar window
This commit is contained in:
Alexis Rouillard
2026-07-03 22:30:26 +02:00
committed by GitHub
3 changed files with 316 additions and 1 deletions
+27
View File
@@ -18,6 +18,7 @@
#include "AModule.hpp" #include "AModule.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "client.hpp" #include "client.hpp"
#include "ext-workspace-v1-client-protocol.h"
#include "giomm/desktopappinfo.h" #include "giomm/desktopappinfo.h"
#include "util/icon_loader.hpp" #include "util/icon_loader.hpp"
#include "util/json.hpp" #include "util/json.hpp"
@@ -80,6 +81,7 @@ class Task {
std::string title_; std::string title_;
std::string app_id_; std::string app_id_;
uint32_t state_ = 0; uint32_t state_ = 0;
struct ext_workspace_handle_v1* workspace_ = nullptr;
int32_t drag_start_x; int32_t drag_start_x;
int32_t drag_start_y; int32_t drag_start_y;
@@ -102,6 +104,9 @@ class Task {
bool minimized() const { return state_ & MINIMIZED; } bool minimized() const { return state_ & MINIMIZED; }
bool active() const { return state_ & ACTIVE; } bool active() const { return state_ & ACTIVE; }
bool fullscreen() const { return state_ & FULLSCREEN; } bool fullscreen() const { return state_ & FULLSCREEN; }
bool visible() const { return button_visible_; }
struct ext_workspace_handle_v1* workspace() const { return workspace_; }
void set_workspace(struct ext_workspace_handle_v1* workspace) { workspace_ = workspace; }
public: public:
/* Callbacks for the wlr protocol */ /* Callbacks for the wlr protocol */
@@ -142,6 +147,12 @@ using TaskPtr = std::unique_ptr<Task>;
class Taskbar : public waybar::AModule { class Taskbar : public waybar::AModule {
public: public:
struct WorkspaceState {
Taskbar* taskbar;
struct ext_workspace_handle_v1* handle;
uint32_t state = 0;
};
Taskbar(const std::string&, const waybar::Bar&, const Json::Value&); Taskbar(const std::string&, const waybar::Bar&, const Json::Value&);
~Taskbar(); ~Taskbar();
void update(); void update();
@@ -156,22 +167,35 @@ class Taskbar : public waybar::AModule {
std::map<std::string, std::string> app_ids_replace_map_; std::map<std::string, std::string> app_ids_replace_map_;
struct zwlr_foreign_toplevel_manager_v1* manager_; struct zwlr_foreign_toplevel_manager_v1* manager_;
struct ext_workspace_manager_v1* workspace_manager_;
struct wl_seat* seat_; struct wl_seat* seat_;
std::vector<struct ext_workspace_group_handle_v1*> workspace_groups_;
std::vector<std::unique_ptr<WorkspaceState>> workspaces_;
struct ext_workspace_handle_v1* current_workspace_ = nullptr;
public: public:
/* Callbacks for global registration */ /* Callbacks for global registration */
void register_manager(struct wl_registry*, uint32_t name, uint32_t version); void register_manager(struct wl_registry*, uint32_t name, uint32_t version);
void register_workspace_manager(struct wl_registry*, uint32_t name, uint32_t version);
void register_seat(struct wl_registry*, uint32_t name, uint32_t version); void register_seat(struct wl_registry*, uint32_t name, uint32_t version);
/* Callbacks for the wlr protocol */ /* Callbacks for the wlr protocol */
void handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1*); void handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1*);
void handle_finished(); void handle_finished();
void handle_workspace_group_create(struct ext_workspace_group_handle_v1*);
void handle_workspace_group_removed(struct ext_workspace_group_handle_v1*);
void handle_workspace_create(struct ext_workspace_handle_v1*);
void handle_workspace_done();
void handle_workspace_finished();
void handle_workspace_removed(struct ext_workspace_handle_v1*);
public: public:
void add_button(Gtk::Button&); void add_button(Gtk::Button&);
void move_button(Gtk::Button&, int); void move_button(Gtk::Button&, int);
void remove_button(Gtk::Button&); void remove_button(Gtk::Button&);
void remove_task(uint32_t); void remove_task(uint32_t);
void assign_current_workspace(Task&);
void update_bar_css_classes();
bool show_output(struct wl_output*) const; bool show_output(struct wl_output*) const;
bool all_outputs() const; bool all_outputs() const;
@@ -179,6 +203,9 @@ class Taskbar : public waybar::AModule {
const IconLoader& icon_loader() const; const IconLoader& icon_loader() const;
const std::unordered_set<std::string>& ignore_list() const; const std::unordered_set<std::string>& ignore_list() const;
const std::map<std::string, std::string>& app_ids_replace_map() const; const std::map<std::string, std::string>& app_ids_replace_map() const;
private:
void set_bar_css_class(const std::string&, bool);
}; };
} /* namespace waybar::modules::wlr */ } /* namespace waybar::modules::wlr */
+45
View File
@@ -18,6 +18,13 @@ Addressed by *wlr/taskbar*
default: false ++ default: false ++
If set to false applications on the waybar's current output will be shown. Otherwise, all applications are shown. If set to false applications on the waybar's current output will be shown. Otherwise, all applications are shown.
*bar-css-states*: ++
typeof: bool ++
default: false ++
If set to true, application state is exposed as CSS classes on the Waybar
window. Maximized and fullscreen state is aggregated across applications
known to belong to the active workspace. See *Bar state style* below.
*format*: ++ *format*: ++
typeof: string ++ typeof: string ++
default: {icon} ++ default: {icon} ++
@@ -52,6 +59,12 @@ Addressed by *wlr/taskbar*
default: false ++ default: false ++
If set to true, always reorder the tasks in the taskbar so that the currently active one is first. Otherwise don't reorder. If set to true, always reorder the tasks in the taskbar so that the currently active one is first. Otherwise don't reorder.
*active-only*: ++
typeof: bool ++
default: false ++
If set to true, only the currently active application button is shown.
Other applications remain tracked and reappear when activated.
*sort-by-app-id*: ++ *sort-by-app-id*: ++
typeof: bool ++ typeof: bool ++
default: false ++ default: false ++
@@ -161,3 +174,35 @@ Invalid expressions (e.g., mismatched parentheses) are skipped.
- *#taskbar button.minimized* - *#taskbar button.minimized*
- *#taskbar button.active* - *#taskbar button.active*
- *#taskbar button.fullscreen* - *#taskbar button.fullscreen*
# Bar state style
When *bar-css-states* is enabled, the following classes are added to
*window#waybar*:
- *window#waybar.toplevel-active*
- *window#waybar.toplevel-maximized*
- *window#waybar.toplevel-minimized*
- *window#waybar.toplevel-fullscreen*
The active, minimized classes describe the active application. The maximized
and fullscreen classes are set if any non-minimized application known to belong
to the active workspace has that state.
Workspace membership is learned when an application is activated and requires
the compositor to support *ext-workspace-v1*. Before an application has been
activated during the current Waybar session, its workspace may be unknown. On
compositors without *ext-workspace-v1*, these classes fall back to the active
application's state.
For example:
```
window#waybar {
background-color: rgba(0, 0, 0, 0.5);
}
window#waybar.toplevel-maximized {
background-color: rgba(0, 0, 0, 1);
}
```
+244 -1
View File
@@ -319,9 +319,12 @@ void Task::handle_output_enter(struct wl_output* output) {
button.signal_size_allocate().connect_notify( button.signal_size_allocate().connect_notify(
sigc::mem_fun(this, &Task::on_button_size_allocated)); sigc::mem_fun(this, &Task::on_button_size_allocated));
tbar_->add_button(button); tbar_->add_button(button);
button.show(); if (!config_["active-only"].asBool() || active()) {
button.show();
}
button_visible_ = true; button_visible_ = true;
spdlog::debug("{} now visible on {}", repr(), bar_.output->name); spdlog::debug("{} now visible on {}", repr(), bar_.output->name);
tbar_->update_bar_css_classes();
} }
} }
@@ -334,6 +337,7 @@ void Task::handle_output_leave(struct wl_output* output) {
button.hide(); button.hide();
button_visible_ = false; button_visible_ = false;
spdlog::debug("{} now invisible on {}", repr(), bar_.output->name); spdlog::debug("{} now invisible on {}", repr(), bar_.output->name);
tbar_->update_bar_css_classes();
} }
} }
@@ -376,6 +380,20 @@ void Task::handle_done() {
button.get_style_context()->remove_class("fullscreen"); button.get_style_context()->remove_class("fullscreen");
} }
if (button_visible_ && config_["active-only"].asBool()) {
if (active()) {
button.show();
} else {
button.hide();
}
}
if (active()) {
tbar_->assign_current_workspace(*this);
}
tbar_->update_bar_css_classes();
if (config_["active-first"].isBool() && config_["active-first"].asBool() && active()) if (config_["active-first"].isBool() && config_["active-first"].asBool() && active())
tbar_->move_button(button, 0); tbar_->move_button(button, 0);
@@ -577,6 +595,8 @@ static void handle_global(void* data, struct wl_registry* registry, uint32_t nam
const char* interface, uint32_t version) { const char* interface, uint32_t version) {
if (std::strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) { if (std::strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) {
static_cast<Taskbar*>(data)->register_manager(registry, name, version); static_cast<Taskbar*>(data)->register_manager(registry, name, version);
} else if (std::strcmp(interface, ext_workspace_manager_v1_interface.name) == 0) {
static_cast<Taskbar*>(data)->register_workspace_manager(registry, name, version);
} else if (std::strcmp(interface, wl_seat_interface.name) == 0) { } else if (std::strcmp(interface, wl_seat_interface.name) == 0) {
static_cast<Taskbar*>(data)->register_seat(registry, name, version); static_cast<Taskbar*>(data)->register_seat(registry, name, version);
} }
@@ -594,6 +614,7 @@ Taskbar::Taskbar(const std::string& id, const waybar::Bar& bar, const Json::Valu
bar_(bar), bar_(bar),
box_{bar.orientation, 0}, box_{bar.orientation, 0},
manager_{nullptr}, manager_{nullptr},
workspace_manager_{nullptr},
seat_{nullptr} { seat_{nullptr} {
box_.set_name("taskbar"); box_.set_name("taskbar");
if (!id.empty()) { if (!id.empty()) {
@@ -649,6 +670,27 @@ Taskbar::Taskbar(const std::string& id, const waybar::Bar& bar, const Json::Valu
} }
Taskbar::~Taskbar() { Taskbar::~Taskbar() {
for (auto& workspace : workspaces_) {
ext_workspace_handle_v1_destroy(workspace->handle);
}
workspaces_.clear();
for (auto* group : workspace_groups_) {
ext_workspace_group_handle_v1_destroy(group);
}
workspace_groups_.clear();
if (workspace_manager_) {
struct wl_display* display = Client::inst()->wl_display;
ext_workspace_manager_v1_stop(workspace_manager_);
wl_display_roundtrip(display);
if (workspace_manager_) {
spdlog::warn("Workspace manager destroyed before .finished event");
ext_workspace_manager_v1_destroy(workspace_manager_);
workspace_manager_ = nullptr;
}
}
if (manager_) { if (manager_) {
struct wl_display* display = Client::inst()->wl_display; struct wl_display* display = Client::inst()->wl_display;
/* /*
@@ -665,6 +707,13 @@ Taskbar::~Taskbar() {
manager_ = nullptr; manager_ = nullptr;
} }
} }
if (config_["bar-css-states"].asBool()) {
set_bar_css_class("toplevel-active", false);
set_bar_css_class("toplevel-maximized", false);
set_bar_css_class("toplevel-minimized", false);
set_bar_css_class("toplevel-fullscreen", false);
}
} }
void Taskbar::update() { void Taskbar::update() {
@@ -700,6 +749,80 @@ static const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_i
.finished = tm_handle_finished, .finished = tm_handle_finished,
}; };
static void workspace_handle_id(void*, struct ext_workspace_handle_v1*, const char*) {}
static void workspace_handle_name(void*, struct ext_workspace_handle_v1*, const char*) {}
static void workspace_handle_coordinates(void*, struct ext_workspace_handle_v1*, struct wl_array*) {
}
static void workspace_handle_state(void* data, struct ext_workspace_handle_v1*, uint32_t state) {
static_cast<Taskbar::WorkspaceState*>(data)->state = state;
}
static void workspace_handle_capabilities(void*, struct ext_workspace_handle_v1*, uint32_t) {}
static void workspace_handle_removed(void* data, struct ext_workspace_handle_v1* handle) {
static_cast<Taskbar::WorkspaceState*>(data)->taskbar->handle_workspace_removed(handle);
}
static const struct ext_workspace_handle_v1_listener workspace_handle_impl = {
.id = workspace_handle_id,
.name = workspace_handle_name,
.coordinates = workspace_handle_coordinates,
.state = workspace_handle_state,
.capabilities = workspace_handle_capabilities,
.removed = workspace_handle_removed,
};
static void workspace_group_handle_capabilities(void*, struct ext_workspace_group_handle_v1*,
uint32_t) {}
static void workspace_group_handle_output_enter(void*, struct ext_workspace_group_handle_v1*,
struct wl_output*) {}
static void workspace_group_handle_output_leave(void*, struct ext_workspace_group_handle_v1*,
struct wl_output*) {}
static void workspace_group_handle_workspace_enter(void*, struct ext_workspace_group_handle_v1*,
struct ext_workspace_handle_v1*) {}
static void workspace_group_handle_workspace_leave(void*, struct ext_workspace_group_handle_v1*,
struct ext_workspace_handle_v1*) {}
static void workspace_group_handle_removed(void* data,
struct ext_workspace_group_handle_v1* group) {
static_cast<Taskbar*>(data)->handle_workspace_group_removed(group);
}
static const struct ext_workspace_group_handle_v1_listener workspace_group_impl = {
.capabilities = workspace_group_handle_capabilities,
.output_enter = workspace_group_handle_output_enter,
.output_leave = workspace_group_handle_output_leave,
.workspace_enter = workspace_group_handle_workspace_enter,
.workspace_leave = workspace_group_handle_workspace_leave,
.removed = workspace_group_handle_removed,
};
static void workspace_manager_handle_group(void* data, struct ext_workspace_manager_v1*,
struct ext_workspace_group_handle_v1* group) {
static_cast<Taskbar*>(data)->handle_workspace_group_create(group);
}
static void workspace_manager_handle_workspace(void* data, struct ext_workspace_manager_v1*,
struct ext_workspace_handle_v1* workspace) {
static_cast<Taskbar*>(data)->handle_workspace_create(workspace);
}
static void workspace_manager_handle_done(void* data, struct ext_workspace_manager_v1*) {
static_cast<Taskbar*>(data)->handle_workspace_done();
}
static void workspace_manager_handle_finished(void* data, struct ext_workspace_manager_v1*) {
static_cast<Taskbar*>(data)->handle_workspace_finished();
}
static const struct ext_workspace_manager_v1_listener workspace_manager_impl = {
.workspace_group = workspace_manager_handle_group,
.workspace = workspace_manager_handle_workspace,
.done = workspace_manager_handle_done,
.finished = workspace_manager_handle_finished,
};
void Taskbar::register_manager(struct wl_registry* registry, uint32_t name, uint32_t version) { void Taskbar::register_manager(struct wl_registry* registry, uint32_t name, uint32_t version) {
if (manager_) { if (manager_) {
spdlog::warn("Register foreign toplevel manager again although already existing!"); spdlog::warn("Register foreign toplevel manager again although already existing!");
@@ -724,6 +847,18 @@ void Taskbar::register_manager(struct wl_registry* registry, uint32_t name, uint
spdlog::debug("Failed to register manager"); spdlog::debug("Failed to register manager");
} }
void Taskbar::register_workspace_manager(struct wl_registry* registry, uint32_t name,
uint32_t version) {
if (workspace_manager_) {
return;
}
version = std::min<uint32_t>(version, ext_workspace_manager_v1_interface.version);
workspace_manager_ = static_cast<struct ext_workspace_manager_v1*>(
wl_registry_bind(registry, name, &ext_workspace_manager_v1_interface, version));
ext_workspace_manager_v1_add_listener(workspace_manager_, &workspace_manager_impl, this);
}
void Taskbar::register_seat(struct wl_registry* registry, uint32_t name, uint32_t version) { void Taskbar::register_seat(struct wl_registry* registry, uint32_t name, uint32_t version) {
if (seat_) { if (seat_) {
spdlog::warn("Register seat again although already existing!"); spdlog::warn("Register seat again although already existing!");
@@ -743,6 +878,64 @@ void Taskbar::handle_finished() {
manager_ = nullptr; manager_ = nullptr;
} }
void Taskbar::handle_workspace_group_create(struct ext_workspace_group_handle_v1* handle) {
ext_workspace_group_handle_v1_add_listener(handle, &workspace_group_impl, this);
workspace_groups_.push_back(handle);
}
void Taskbar::handle_workspace_group_removed(struct ext_workspace_group_handle_v1* handle) {
const auto group = std::find(workspace_groups_.begin(), workspace_groups_.end(), handle);
if (group != workspace_groups_.end()) {
ext_workspace_group_handle_v1_destroy(*group);
workspace_groups_.erase(group);
}
}
void Taskbar::handle_workspace_create(struct ext_workspace_handle_v1* handle) {
auto workspace = std::make_unique<WorkspaceState>(WorkspaceState{this, handle});
ext_workspace_handle_v1_add_listener(handle, &workspace_handle_impl, workspace.get());
workspaces_.push_back(std::move(workspace));
}
void Taskbar::handle_workspace_done() {
const auto active_workspace =
std::find_if(workspaces_.begin(), workspaces_.end(), [](const auto& workspace) {
return workspace->state & EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE;
});
current_workspace_ =
active_workspace == workspaces_.end() ? nullptr : (*active_workspace)->handle;
if (current_workspace_) {
const auto active_task =
std::find_if(tasks_.begin(), tasks_.end(), [](const auto& task) { return task->active(); });
if (active_task != tasks_.end()) {
(*active_task)->set_workspace(current_workspace_);
}
}
update_bar_css_classes();
}
void Taskbar::handle_workspace_finished() { workspace_manager_ = nullptr; }
void Taskbar::handle_workspace_removed(struct ext_workspace_handle_v1* handle) {
if (current_workspace_ == handle) {
current_workspace_ = nullptr;
}
for (auto& task : tasks_) {
if (task->workspace() == handle) {
task->set_workspace(nullptr);
}
}
const auto workspace =
std::find_if(workspaces_.begin(), workspaces_.end(),
[handle](const auto& workspace) { return workspace->handle == handle; });
if (workspace != workspaces_.end()) {
ext_workspace_handle_v1_destroy((*workspace)->handle);
workspaces_.erase(workspace);
}
update_bar_css_classes();
}
void Taskbar::add_button(Gtk::Button& bt) { void Taskbar::add_button(Gtk::Button& bt) {
/* Only let the buttons expand to fill the taskbar when "expand" is enabled /* Only let the buttons expand to fill the taskbar when "expand" is enabled
* and the bar is horizontal (see the Task constructor for details). */ * and the bar is horizontal (see the Task constructor for details). */
@@ -775,6 +968,56 @@ void Taskbar::remove_task(uint32_t id) {
} }
tasks_.erase(it); tasks_.erase(it);
update_bar_css_classes();
}
void Taskbar::assign_current_workspace(Task& task) {
if (current_workspace_) {
task.set_workspace(current_workspace_);
}
}
void Taskbar::update_bar_css_classes() {
if (!config_["bar-css-states"].asBool()) {
return;
}
const auto active_task = std::find_if(tasks_.begin(), tasks_.end(), [](const TaskPtr& task) {
return task->visible() && task->active();
});
const bool has_active_task = active_task != tasks_.end();
const auto on_current_workspace = [this](const TaskPtr& task) {
if (!current_workspace_) {
return task->active();
}
return task->workspace() == current_workspace_;
};
const bool has_maximized_task =
std::any_of(tasks_.begin(), tasks_.end(), [&on_current_workspace](const TaskPtr& task) {
return task->visible() && !task->minimized() && on_current_workspace(task) &&
task->maximized();
});
const bool has_fullscreen_task =
std::any_of(tasks_.begin(), tasks_.end(), [&on_current_workspace](const TaskPtr& task) {
return task->visible() && !task->minimized() && on_current_workspace(task) &&
task->fullscreen();
});
set_bar_css_class("toplevel-active", has_active_task);
set_bar_css_class("toplevel-maximized", has_maximized_task);
set_bar_css_class("toplevel-minimized", has_active_task && (*active_task)->minimized());
set_bar_css_class("toplevel-fullscreen", has_fullscreen_task);
}
void Taskbar::set_bar_css_class(const std::string& class_name, bool enabled) {
const auto style = bar_.window.get_style_context();
if (enabled && !style->has_class(class_name)) {
spdlog::trace("Adding bar class: {}", class_name);
style->add_class(class_name);
} else if (!enabled && style->has_class(class_name)) {
spdlog::trace("Removing bar class: {}", class_name);
style->remove_class(class_name);
}
} }
bool Taskbar::show_output(struct wl_output* output) const { bool Taskbar::show_output(struct wl_output* output) const {