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:
@@ -18,6 +18,7 @@
|
||||
#include "AModule.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "client.hpp"
|
||||
#include "ext-workspace-v1-client-protocol.h"
|
||||
#include "giomm/desktopappinfo.h"
|
||||
#include "util/icon_loader.hpp"
|
||||
#include "util/json.hpp"
|
||||
@@ -80,6 +81,7 @@ class Task {
|
||||
std::string title_;
|
||||
std::string app_id_;
|
||||
uint32_t state_ = 0;
|
||||
struct ext_workspace_handle_v1* workspace_ = nullptr;
|
||||
|
||||
int32_t drag_start_x;
|
||||
int32_t drag_start_y;
|
||||
@@ -102,6 +104,9 @@ class Task {
|
||||
bool minimized() const { return state_ & MINIMIZED; }
|
||||
bool active() const { return state_ & ACTIVE; }
|
||||
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:
|
||||
/* Callbacks for the wlr protocol */
|
||||
@@ -142,6 +147,12 @@ using TaskPtr = std::unique_ptr<Task>;
|
||||
|
||||
class Taskbar : public waybar::AModule {
|
||||
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();
|
||||
void update();
|
||||
@@ -156,22 +167,35 @@ class Taskbar : public waybar::AModule {
|
||||
std::map<std::string, std::string> app_ids_replace_map_;
|
||||
|
||||
struct zwlr_foreign_toplevel_manager_v1* manager_;
|
||||
struct ext_workspace_manager_v1* workspace_manager_;
|
||||
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:
|
||||
/* Callbacks for global registration */
|
||||
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);
|
||||
|
||||
/* Callbacks for the wlr protocol */
|
||||
void handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1*);
|
||||
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:
|
||||
void add_button(Gtk::Button&);
|
||||
void move_button(Gtk::Button&, int);
|
||||
void remove_button(Gtk::Button&);
|
||||
void remove_task(uint32_t);
|
||||
void assign_current_workspace(Task&);
|
||||
void update_bar_css_classes();
|
||||
|
||||
bool show_output(struct wl_output*) const;
|
||||
bool all_outputs() const;
|
||||
@@ -179,6 +203,9 @@ class Taskbar : public waybar::AModule {
|
||||
const IconLoader& icon_loader() const;
|
||||
const std::unordered_set<std::string>& ignore_list() 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 */
|
||||
|
||||
@@ -18,6 +18,13 @@ Addressed by *wlr/taskbar*
|
||||
default: false ++
|
||||
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*: ++
|
||||
typeof: string ++
|
||||
default: {icon} ++
|
||||
@@ -52,6 +59,12 @@ Addressed by *wlr/taskbar*
|
||||
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.
|
||||
|
||||
*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*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
@@ -161,3 +174,35 @@ Invalid expressions (e.g., mismatched parentheses) are skipped.
|
||||
- *#taskbar button.minimized*
|
||||
- *#taskbar button.active*
|
||||
- *#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
@@ -319,9 +319,12 @@ void Task::handle_output_enter(struct wl_output* output) {
|
||||
button.signal_size_allocate().connect_notify(
|
||||
sigc::mem_fun(this, &Task::on_button_size_allocated));
|
||||
tbar_->add_button(button);
|
||||
button.show();
|
||||
if (!config_["active-only"].asBool() || active()) {
|
||||
button.show();
|
||||
}
|
||||
button_visible_ = true;
|
||||
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_visible_ = false;
|
||||
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");
|
||||
}
|
||||
|
||||
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())
|
||||
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) {
|
||||
if (std::strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) {
|
||||
static_cast<Taskbar*>(data)->register_manager(registry, name, version);
|
||||
} else if (std::strcmp(interface, 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) {
|
||||
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),
|
||||
box_{bar.orientation, 0},
|
||||
manager_{nullptr},
|
||||
workspace_manager_{nullptr},
|
||||
seat_{nullptr} {
|
||||
box_.set_name("taskbar");
|
||||
if (!id.empty()) {
|
||||
@@ -649,6 +670,27 @@ Taskbar::Taskbar(const std::string& id, const waybar::Bar& bar, const Json::Valu
|
||||
}
|
||||
|
||||
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_) {
|
||||
struct wl_display* display = Client::inst()->wl_display;
|
||||
/*
|
||||
@@ -665,6 +707,13 @@ Taskbar::~Taskbar() {
|
||||
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() {
|
||||
@@ -700,6 +749,80 @@ static const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_i
|
||||
.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) {
|
||||
if (manager_) {
|
||||
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");
|
||||
}
|
||||
|
||||
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) {
|
||||
if (seat_) {
|
||||
spdlog::warn("Register seat again although already existing!");
|
||||
@@ -743,6 +878,64 @@ void Taskbar::handle_finished() {
|
||||
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) {
|
||||
/* Only let the buttons expand to fill the taskbar when "expand" is enabled
|
||||
* 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);
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user