Merge pull request #2264 from Anakael/pr/anakael/hyprland-workspaces
feat: hyprland/workspacespull/2268/head
commit
15fe85d18d
|
@ -31,6 +31,7 @@
|
||||||
#include "modules/hyprland/language.hpp"
|
#include "modules/hyprland/language.hpp"
|
||||||
#include "modules/hyprland/submap.hpp"
|
#include "modules/hyprland/submap.hpp"
|
||||||
#include "modules/hyprland/window.hpp"
|
#include "modules/hyprland/window.hpp"
|
||||||
|
#include "modules/hyprland/workspaces.hpp"
|
||||||
#endif
|
#endif
|
||||||
#if defined(__FreeBSD__) || (defined(__linux__) && !defined(NO_FILESYSTEM))
|
#if defined(__FreeBSD__) || (defined(__linux__) && !defined(NO_FILESYSTEM))
|
||||||
#include "modules/battery.hpp"
|
#include "modules/battery.hpp"
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include "util/json.hpp"
|
||||||
|
|
||||||
namespace waybar::modules::hyprland {
|
namespace waybar::modules::hyprland {
|
||||||
|
|
||||||
|
@ -22,12 +23,14 @@ class IPC {
|
||||||
void unregisterForIPC(EventHandler*);
|
void unregisterForIPC(EventHandler*);
|
||||||
|
|
||||||
std::string getSocket1Reply(const std::string& rq);
|
std::string getSocket1Reply(const std::string& rq);
|
||||||
|
Json::Value getSocket1JsonReply(const std::string& rq);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void startIPC();
|
void startIPC();
|
||||||
void parseIPC(const std::string&);
|
void parseIPC(const std::string&);
|
||||||
|
|
||||||
std::mutex callbackMutex;
|
std::mutex callbackMutex;
|
||||||
|
util::JsonParser parser_;
|
||||||
std::list<std::pair<std::string, EventHandler*>> callbacks;
|
std::list<std::pair<std::string, EventHandler*>> callbacks;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
#include <gtkmm/button.h>
|
||||||
|
#include <gtkmm/label.h>
|
||||||
|
|
||||||
|
#include "AModule.hpp"
|
||||||
|
#include "bar.hpp"
|
||||||
|
#include "modules/hyprland/backend.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules::hyprland {
|
||||||
|
|
||||||
|
struct WorkspaceDto {
|
||||||
|
int id;
|
||||||
|
|
||||||
|
static WorkspaceDto parse(const Json::Value& value);
|
||||||
|
};
|
||||||
|
|
||||||
|
class Workspace {
|
||||||
|
public:
|
||||||
|
Workspace(int id);
|
||||||
|
Workspace(WorkspaceDto dto);
|
||||||
|
int id() const { return id_; };
|
||||||
|
int active() const { return active_; };
|
||||||
|
std::string& select_icon(std::map<std::string, std::string>& icons_map);
|
||||||
|
void set_active(bool value = true) { active_ = value; };
|
||||||
|
Gtk::Button& button() { return button_; };
|
||||||
|
|
||||||
|
void update(const std::string& format, const std::string& icon);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int id_;
|
||||||
|
bool active_;
|
||||||
|
|
||||||
|
Gtk::Button button_;
|
||||||
|
Gtk::Box content_;
|
||||||
|
Gtk::Label label_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Workspaces : public AModule, public EventHandler {
|
||||||
|
public:
|
||||||
|
Workspaces(const std::string&, const waybar::Bar&, const Json::Value&);
|
||||||
|
virtual ~Workspaces();
|
||||||
|
void update() override;
|
||||||
|
void init();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onEvent(const std::string&) override;
|
||||||
|
void sort_workspaces();
|
||||||
|
|
||||||
|
std::string format_;
|
||||||
|
std::map<std::string, std::string> icons_map_;
|
||||||
|
bool with_icon_;
|
||||||
|
int active_workspace_id;
|
||||||
|
std::vector<Workspace> workspaces_;
|
||||||
|
std::mutex mutex_;
|
||||||
|
const Bar& bar_;
|
||||||
|
Gtk::Box box_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::modules::hyprland
|
|
@ -0,0 +1,59 @@
|
||||||
|
waybar-wlr-workspaces(5)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
waybar - hyprland workspaces module
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
The *workspaces* module displays the currently used workspaces in hyprland compositor.
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
Addressed by *hyprland/workspaces*
|
||||||
|
|
||||||
|
*format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: {id} ++
|
||||||
|
The format, how information should be displayed.
|
||||||
|
|
||||||
|
*format-icons*: ++
|
||||||
|
typeof: array ++
|
||||||
|
Based on the workspace id and state, the corresponding icon gets selected. See *icons*.
|
||||||
|
|
||||||
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
|
*{id}*: id of workspace assigned by compositor
|
||||||
|
|
||||||
|
*{icon}*: Icon, as defined in *format-icons*.
|
||||||
|
|
||||||
|
# ICONS
|
||||||
|
|
||||||
|
Additional to workspace name matching, the following *format-icons* can be set.
|
||||||
|
|
||||||
|
- *default*: Will be shown, when no string match is found.
|
||||||
|
- *active*: Will be shown, when workspace is active
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
"wlr/workspaces": {
|
||||||
|
"format": "{name}: {icon}",
|
||||||
|
"format-icons": {
|
||||||
|
"1": "",
|
||||||
|
"2": "",
|
||||||
|
"3": "",
|
||||||
|
"4": "",
|
||||||
|
"5": "",
|
||||||
|
"active": "",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"sort-by-number": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Style
|
||||||
|
|
||||||
|
- *#workspaces*
|
||||||
|
- *#workspaces button*
|
||||||
|
- *#workspaces button.active*
|
|
@ -65,7 +65,7 @@ Addressed by *wlr/workspaces*
|
||||||
Additional to workspace name matching, the following *format-icons* can be set.
|
Additional to workspace name matching, the following *format-icons* can be set.
|
||||||
|
|
||||||
- *default*: Will be shown, when no string match is found.
|
- *default*: Will be shown, when no string match is found.
|
||||||
- *focused*: Will be shown, when workspace is focused
|
- *active*: Will be shown, when workspace is active
|
||||||
|
|
||||||
# EXAMPLES
|
# EXAMPLES
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ Additional to workspace name matching, the following *format-icons* can be set.
|
||||||
"3": "",
|
"3": "",
|
||||||
"4": "",
|
"4": "",
|
||||||
"5": "",
|
"5": "",
|
||||||
"focused": "",
|
"active": "",
|
||||||
"default": ""
|
"default": ""
|
||||||
},
|
},
|
||||||
"sort-by-number": true
|
"sort-by-number": true
|
||||||
|
|
|
@ -240,6 +240,7 @@ if true
|
||||||
src_files += 'src/modules/hyprland/window.cpp'
|
src_files += 'src/modules/hyprland/window.cpp'
|
||||||
src_files += 'src/modules/hyprland/language.cpp'
|
src_files += 'src/modules/hyprland/language.cpp'
|
||||||
src_files += 'src/modules/hyprland/submap.cpp'
|
src_files += 'src/modules/hyprland/submap.cpp'
|
||||||
|
src_files += 'src/modules/hyprland/workspaces.cpp'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if libnl.found() and libnlgen.found()
|
if libnl.found() and libnlgen.found()
|
||||||
|
|
|
@ -83,6 +83,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
||||||
if (ref == "hyprland/submap") {
|
if (ref == "hyprland/submap") {
|
||||||
return new waybar::modules::hyprland::Submap(id, bar_, config_[name]);
|
return new waybar::modules::hyprland::Submap(id, bar_, config_[name]);
|
||||||
}
|
}
|
||||||
|
if (ref == "hyprland/workspaces") {
|
||||||
|
return new waybar::modules::hyprland::Workspaces(id, bar_, config_[name]);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
if (ref == "idle_inhibitor") {
|
if (ref == "idle_inhibitor") {
|
||||||
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
|
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
|
||||||
|
|
|
@ -198,4 +198,8 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Json::Value IPC::getSocket1JsonReply(const std::string& rq) {
|
||||||
|
return parser_.parse(getSocket1Reply("j/" + rq));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace waybar::modules::hyprland
|
} // namespace waybar::modules::hyprland
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
#include "modules/hyprland/workspaces.hpp"
|
||||||
|
|
||||||
|
#include <json/value.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <charconv>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace waybar::modules::hyprland {
|
||||||
|
|
||||||
|
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
|
||||||
|
: AModule(config, "workspaces", id, false, false),
|
||||||
|
bar_(bar),
|
||||||
|
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) {
|
||||||
|
Json::Value config_format = config["format"];
|
||||||
|
|
||||||
|
format_ = config_format.isString() ? config_format.asString() : "{id}";
|
||||||
|
with_icon_ = format_.find("{icon}") != std::string::npos;
|
||||||
|
|
||||||
|
if (with_icon_ && icons_map_.empty()) {
|
||||||
|
Json::Value format_icons = config["format-icons"];
|
||||||
|
for (std::string &name : format_icons.getMemberNames()) {
|
||||||
|
icons_map_.emplace(name, format_icons[name].asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
icons_map_.emplace("", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
box_.set_name("workspaces");
|
||||||
|
if (!id.empty()) {
|
||||||
|
box_.get_style_context()->add_class(id);
|
||||||
|
}
|
||||||
|
event_box_.add(box_);
|
||||||
|
modulesReady = true;
|
||||||
|
if (!gIPC.get()) {
|
||||||
|
gIPC = std::make_unique<IPC>();
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
gIPC->registerForIPC("workspace", this);
|
||||||
|
gIPC->registerForIPC("createworkspace", this);
|
||||||
|
gIPC->registerForIPC("destroyworkspace", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Workspaces::update() -> void {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
for (Workspace &workspace : workspaces_) {
|
||||||
|
workspace.set_active(workspace.id() == active_workspace_id);
|
||||||
|
|
||||||
|
std::string &workspace_icon = icons_map_[""];
|
||||||
|
if (with_icon_) {
|
||||||
|
workspace_icon = workspace.select_icon(icons_map_);
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace.update(format_, workspace_icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
AModule::update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspaces::onEvent(const std::string &ev) {
|
||||||
|
std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>'));
|
||||||
|
std::string payload = ev.substr(eventName.size() + 2);
|
||||||
|
if (eventName == "workspace") {
|
||||||
|
std::from_chars(payload.data(), payload.data() + payload.size(), active_workspace_id);
|
||||||
|
} else if (eventName == "destroyworkspace") {
|
||||||
|
int deleted_workspace_id;
|
||||||
|
std::from_chars(payload.data(), payload.data() + payload.size(), deleted_workspace_id);
|
||||||
|
workspaces_.erase(std::remove_if(workspaces_.begin(), workspaces_.end(),
|
||||||
|
[&](Workspace &x) { return x.id() == deleted_workspace_id; }));
|
||||||
|
} else if (eventName == "createworkspace") {
|
||||||
|
int new_workspace_id;
|
||||||
|
std::from_chars(payload.data(), payload.data() + payload.size(), new_workspace_id);
|
||||||
|
workspaces_.push_back(new_workspace_id);
|
||||||
|
Gtk::Button &new_workspace_button = workspaces_.back().button();
|
||||||
|
box_.pack_end(new_workspace_button, false, false);
|
||||||
|
sort_workspaces();
|
||||||
|
new_workspace_button.show_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspaces::init() {
|
||||||
|
const auto activeWorkspace = WorkspaceDto::parse(gIPC->getSocket1JsonReply("activeworkspace"));
|
||||||
|
active_workspace_id = activeWorkspace.id;
|
||||||
|
const Json::Value workspaces_json = gIPC->getSocket1JsonReply("workspaces");
|
||||||
|
for (const Json::Value &workspace_json : workspaces_json) {
|
||||||
|
workspaces_.push_back(Workspace(WorkspaceDto::parse(workspace_json)));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &workspace : workspaces_) {
|
||||||
|
box_.pack_start(workspace.button(), false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
sort_workspaces();
|
||||||
|
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Workspaces::~Workspaces() {
|
||||||
|
gIPC->unregisterForIPC(this);
|
||||||
|
// wait for possible event handler to finish
|
||||||
|
std::lock_guard<std::mutex> lg(mutex_);
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkspaceDto WorkspaceDto::parse(const Json::Value &value) {
|
||||||
|
return WorkspaceDto{value["id"].asInt()};
|
||||||
|
}
|
||||||
|
|
||||||
|
Workspace::Workspace(WorkspaceDto dto) : Workspace(dto.id){};
|
||||||
|
|
||||||
|
Workspace::Workspace(int id) : id_(id) {
|
||||||
|
button_.set_relief(Gtk::RELIEF_NONE);
|
||||||
|
content_.set_center_widget(label_);
|
||||||
|
button_.add(content_);
|
||||||
|
};
|
||||||
|
|
||||||
|
void 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspace::update(const std::string &format, const std::string &icon) {
|
||||||
|
Glib::RefPtr<Gtk::StyleContext> style_context = button_.get_style_context();
|
||||||
|
add_or_remove_class(style_context, active(), "active");
|
||||||
|
|
||||||
|
label_.set_markup(
|
||||||
|
fmt::format(fmt::runtime(format), fmt::arg("id", id()), fmt::arg("icon", icon)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspaces::sort_workspaces() {
|
||||||
|
std::sort(workspaces_.begin(), workspaces_.end(),
|
||||||
|
[](Workspace &lhs, Workspace &rhs) { return lhs.id() < rhs.id(); });
|
||||||
|
|
||||||
|
for (size_t i = 0; i < workspaces_.size(); ++i) {
|
||||||
|
box_.reorder_child(workspaces_[i].button(), i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string &Workspace::select_icon(std::map<std::string, std::string> &icons_map) {
|
||||||
|
if (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(std::to_string(id()));
|
||||||
|
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 icons_map[""];
|
||||||
|
}
|
||||||
|
} // namespace waybar::modules::hyprland
|
Loading…
Reference in New Issue