added workspaces
parent
72f478c195
commit
c1f92d2a3c
|
@ -24,6 +24,7 @@
|
||||||
#ifdef HAVE_HYPRLAND
|
#ifdef HAVE_HYPRLAND
|
||||||
#include "modules/hyprland/backend.hpp"
|
#include "modules/hyprland/backend.hpp"
|
||||||
#include "modules/hyprland/window.hpp"
|
#include "modules/hyprland/window.hpp"
|
||||||
|
#include "modules/hyprland/workspaces.hpp"
|
||||||
#endif
|
#endif
|
||||||
#if defined(__linux__) && !defined(NO_FILESYSTEM)
|
#if defined(__linux__) && !defined(NO_FILESYSTEM)
|
||||||
#include "modules/battery.hpp"
|
#include "modules/battery.hpp"
|
||||||
|
|
|
@ -18,10 +18,6 @@ private:
|
||||||
void onEvent(const std::string&);
|
void onEvent(const std::string&);
|
||||||
|
|
||||||
const Bar& bar_;
|
const Bar& bar_;
|
||||||
IPC ipc;
|
|
||||||
unsigned app_icon_size_{24};
|
|
||||||
bool update_app_icon_{true};
|
|
||||||
std::string app_icon_name_;
|
|
||||||
util::JsonParser parser_;
|
util::JsonParser parser_;
|
||||||
std::string lastView;
|
std::string lastView;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <gtkmm/button.h>
|
||||||
|
#include <gtkmm/label.h>
|
||||||
|
|
||||||
|
#include "AModule.hpp"
|
||||||
|
#include "bar.hpp"
|
||||||
|
#include "modules/hyprland/backend.hpp"
|
||||||
|
#include "util/json.hpp"
|
||||||
|
#include <deque>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
namespace waybar::modules::hyprland {
|
||||||
|
|
||||||
|
class Workspaces : public AModule, public sigc::trackable {
|
||||||
|
public:
|
||||||
|
Workspaces(const std::string&, const waybar::Bar&, const Json::Value&);
|
||||||
|
~Workspaces() = default;
|
||||||
|
auto update() -> void;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onEvent(const std::string&);
|
||||||
|
bool handleScroll(GdkEventScroll*);
|
||||||
|
void configOnLaunch(const Json::Value&);
|
||||||
|
void updateButtons();
|
||||||
|
void parseInitHyprlandWorkspaces();
|
||||||
|
Gtk::Button& addButton(const std::string&);
|
||||||
|
std::string getIcon(const std::string&);
|
||||||
|
std::deque<std::string> getAllSortedWS();
|
||||||
|
|
||||||
|
bool isNumber(const std::string&);
|
||||||
|
|
||||||
|
Gtk::Box box_;
|
||||||
|
const Bar& bar_;
|
||||||
|
std::deque<std::string> workspaces;
|
||||||
|
std::deque<std::string> persistentWorkspaces;
|
||||||
|
std::unordered_map<std::string, Gtk::Button> buttons_;
|
||||||
|
std::string focusedWorkspace;
|
||||||
|
|
||||||
|
std::mutex mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::modules::sway
|
|
@ -205,6 +205,7 @@ if true
|
||||||
add_project_arguments('-DHAVE_HYPRLAND', language: 'cpp')
|
add_project_arguments('-DHAVE_HYPRLAND', language: 'cpp')
|
||||||
src_files += 'src/modules/hyprland/backend.cpp'
|
src_files += 'src/modules/hyprland/backend.cpp'
|
||||||
src_files += 'src/modules/hyprland/window.cpp'
|
src_files += 'src/modules/hyprland/window.cpp'
|
||||||
|
src_files += 'src/modules/hyprland/workspaces.cpp'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if libnl.found() and libnlgen.found()
|
if libnl.found() and libnlgen.found()
|
||||||
|
|
|
@ -61,6 +61,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
||||||
if (ref == "hyprland/window") {
|
if (ref == "hyprland/window") {
|
||||||
return new waybar::modules::hyprland::Window(id, bar_, config_[name]);
|
return new waybar::modules::hyprland::Window(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]);
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
#include "modules/hyprland/backend.hpp"
|
#include "modules/hyprland/backend.hpp"
|
||||||
|
|
||||||
#include <errno.h>
|
#include <ctype.h>
|
||||||
#include <fcntl.h>
|
#include <netdb.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
@ -12,9 +13,13 @@
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
void waybar::modules::hyprland::IPC::startIPC() {
|
namespace waybar::modules::hyprland {
|
||||||
|
|
||||||
|
void IPC::startIPC() {
|
||||||
// will start IPC and relay events to parseIPC
|
// will start IPC and relay events to parseIPC
|
||||||
|
|
||||||
std::thread([&]() {
|
std::thread([&]() {
|
||||||
|
@ -84,7 +89,7 @@ void waybar::modules::hyprland::IPC::startIPC() {
|
||||||
}).detach();
|
}).detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
void waybar::modules::hyprland::IPC::parseIPC(const std::string& ev) {
|
void IPC::parseIPC(const std::string& ev) {
|
||||||
// todo
|
// todo
|
||||||
std::string request = ev.substr(0, ev.find_first_of('>'));
|
std::string request = ev.substr(0, ev.find_first_of('>'));
|
||||||
|
|
||||||
|
@ -95,7 +100,7 @@ void waybar::modules::hyprland::IPC::parseIPC(const std::string& ev) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void waybar::modules::hyprland::IPC::registerForIPC(const std::string& ev,
|
void IPC::registerForIPC(const std::string& ev,
|
||||||
std::function<void(const std::string&)> fn) {
|
std::function<void(const std::string&)> fn) {
|
||||||
callbackMutex.lock();
|
callbackMutex.lock();
|
||||||
|
|
||||||
|
@ -103,3 +108,65 @@ void waybar::modules::hyprland::IPC::registerForIPC(const std::string& ev,
|
||||||
|
|
||||||
callbackMutex.unlock();
|
callbackMutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string IPC::getSocket1Reply(const std::string& rq) {
|
||||||
|
// basically hyprctl
|
||||||
|
|
||||||
|
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
|
||||||
|
if (SERVERSOCKET < 0) {
|
||||||
|
spdlog::error("Hyprland IPC: Couldn't open a socket (1)");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto SERVER = gethostbyname("localhost");
|
||||||
|
|
||||||
|
if (!SERVER) {
|
||||||
|
spdlog::error("Hyprland IPC: Couldn't get host (2)");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the instance signature
|
||||||
|
auto instanceSig = getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||||
|
|
||||||
|
if (!instanceSig) {
|
||||||
|
spdlog::error("Hyprland IPC: HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?)");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string instanceSigStr = std::string(instanceSig);
|
||||||
|
|
||||||
|
sockaddr_un serverAddress = {0};
|
||||||
|
serverAddress.sun_family = AF_UNIX;
|
||||||
|
|
||||||
|
std::string socketPath = "/tmp/hypr/" + instanceSigStr + "/.socket.sock";
|
||||||
|
|
||||||
|
strcpy(serverAddress.sun_path, socketPath.c_str());
|
||||||
|
|
||||||
|
if (connect(SERVERSOCKET, (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) {
|
||||||
|
spdlog::error("Hyprland IPC: Couldn't connect to " + socketPath + ". (3)");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sizeWritten = write(SERVERSOCKET, rq.c_str(), rq.length());
|
||||||
|
|
||||||
|
if (sizeWritten < 0) {
|
||||||
|
spdlog::error("Hyprland IPC: Couldn't write (4)");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
char buffer[8192] = {0};
|
||||||
|
|
||||||
|
sizeWritten = read(SERVERSOCKET, buffer, 8192);
|
||||||
|
|
||||||
|
if (sizeWritten < 0) {
|
||||||
|
spdlog::error("Hyprland IPC: Couldn't read (5)");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
close(SERVERSOCKET);
|
||||||
|
|
||||||
|
return std::string(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,228 @@
|
||||||
|
#include "modules/hyprland/workspaces.hpp"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <ranges>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace waybar::modules::hyprland {
|
||||||
|
|
||||||
|
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
|
||||||
|
: AModule(config, "workspaces", id, false, !config["disable-scroll"].asBool()),
|
||||||
|
bar_(bar),
|
||||||
|
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) {
|
||||||
|
box_.set_name("workspaces");
|
||||||
|
if (!id.empty()) {
|
||||||
|
box_.get_style_context()->add_class(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
event_box_.add(box_);
|
||||||
|
|
||||||
|
if (config["enable-bar-scroll"].asBool()) {
|
||||||
|
auto &window = const_cast<Bar &>(bar_).window;
|
||||||
|
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
||||||
|
window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
|
||||||
|
}
|
||||||
|
|
||||||
|
modulesReady = true;
|
||||||
|
|
||||||
|
if (!gIPC.get()) {
|
||||||
|
gIPC = std::make_unique<IPC>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// register for hyprland ipc
|
||||||
|
gIPC->registerForIPC("createworkspace", [&](const std::string &ev) { this->onEvent(ev); });
|
||||||
|
gIPC->registerForIPC("destroyworkspace", [&](const std::string &ev) { this->onEvent(ev); });
|
||||||
|
gIPC->registerForIPC("activemon", [&](const std::string &ev) { this->onEvent(ev); });
|
||||||
|
gIPC->registerForIPC("workspace", [&](const std::string &ev) { this->onEvent(ev); });
|
||||||
|
|
||||||
|
// parse cfg stuff
|
||||||
|
configOnLaunch(config);
|
||||||
|
|
||||||
|
// parse workspaces already existing
|
||||||
|
parseInitHyprlandWorkspaces();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspaces::parseInitHyprlandWorkspaces() {
|
||||||
|
std::istringstream WORKSPACES;
|
||||||
|
WORKSPACES.str(gIPC->getSocket1Reply("workspaces"));
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(WORKSPACES, line)) {
|
||||||
|
if (line.find("workspace ID") == 0) {
|
||||||
|
auto workspaceName = line.substr(line.find_first_of('(') + 1).substr(0, line.find_first_of(')') - line.find_first_of('(') - 1);
|
||||||
|
workspaces.emplace_back(workspaceName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspaces::configOnLaunch(const Json::Value& cfg) {
|
||||||
|
if (cfg["persistent_workspaces"].isObject()) {
|
||||||
|
spdlog::info("persistent");
|
||||||
|
const Json::Value &persistentWorkspacesJSON = cfg["persistent_workspaces"];
|
||||||
|
|
||||||
|
for (auto &wn : persistentWorkspacesJSON.getMemberNames()) {
|
||||||
|
persistentWorkspaces.emplace_back(wn);
|
||||||
|
spdlog::info("persistent ws {}", wn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::deque<std::string> Workspaces::getAllSortedWS() {
|
||||||
|
std::deque<std::string> result;
|
||||||
|
for (auto& ws : workspaces)
|
||||||
|
result.emplace_back(ws);
|
||||||
|
for (auto& ws : persistentWorkspaces)
|
||||||
|
result.emplace_back(ws);
|
||||||
|
|
||||||
|
std::sort(result.begin(), result.end(), [&](const std::string& ws1, const std::string& ws2) {
|
||||||
|
if (isNumber(ws1) && isNumber(ws2)) {
|
||||||
|
return std::stoi(ws1) < std::stoi(ws2);
|
||||||
|
} else if (isNumber(ws1)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Workspaces::getIcon(const std::string &name) {
|
||||||
|
return config_["format-icons"][name].asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Workspaces::update() -> void {
|
||||||
|
updateButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspaces::updateButtons() {
|
||||||
|
mutex_.lock();
|
||||||
|
|
||||||
|
auto ws = getAllSortedWS();
|
||||||
|
|
||||||
|
for (auto it = ws.begin(); it != ws.end(); ++it) {
|
||||||
|
auto bit = buttons_.find(*it);
|
||||||
|
|
||||||
|
auto &button = bit == buttons_.end() ? addButton(*it) : bit->second;
|
||||||
|
|
||||||
|
if (focusedWorkspace == *it) {
|
||||||
|
button.get_style_context()->add_class("focused");
|
||||||
|
} else {
|
||||||
|
button.get_style_context()->remove_class("focused");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string label = *it;
|
||||||
|
if (config_["format"].isString()) {
|
||||||
|
auto format = config_["format"].asString();
|
||||||
|
label = fmt::format(format, fmt::arg("icon", getIcon(*it)), fmt::arg("name", *it));
|
||||||
|
}
|
||||||
|
|
||||||
|
button.set_label(label);
|
||||||
|
|
||||||
|
button.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
AModule::update();
|
||||||
|
|
||||||
|
mutex_.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Workspaces::isNumber(const std::string& str) {
|
||||||
|
for (auto &c : str) {
|
||||||
|
if (!(isdigit(c) != 0 || c == '-')) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk::Button &Workspaces::addButton(const std::string& name) {
|
||||||
|
auto pair = buttons_.emplace(name, name);
|
||||||
|
auto &&button = pair.first->second;
|
||||||
|
box_.pack_start(button, false, false, 0);
|
||||||
|
button.set_name(name);
|
||||||
|
button.set_relief(Gtk::RELIEF_NONE);
|
||||||
|
if (!config_["disable-click"].asBool()) {
|
||||||
|
button.signal_pressed().connect([&, name]{
|
||||||
|
if (isNumber(name)) {
|
||||||
|
gIPC->getSocket1Reply("dispatch workspace " + name);
|
||||||
|
spdlog::info("executing {}", "dispatch workspace " + name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gIPC->getSocket1Reply("dispatch workspace name:" + name);
|
||||||
|
spdlog::info("executing {}", "dispatch workspace name:" + name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspaces::onEvent(const std::string& ev) {
|
||||||
|
const auto EVENT = ev.substr(0, ev.find_first_of('>'));
|
||||||
|
const auto WORKSPACE = ev.substr(ev.find_last_of('>') + 1);
|
||||||
|
|
||||||
|
mutex_.lock();
|
||||||
|
|
||||||
|
if (EVENT == "activemon") {
|
||||||
|
focusedWorkspace = WORKSPACE.substr(WORKSPACE.find_first_of(',') + 1);
|
||||||
|
} else if (EVENT == "workspace") {
|
||||||
|
focusedWorkspace = WORKSPACE;
|
||||||
|
} else if (EVENT == "createworkspace") {
|
||||||
|
workspaces.emplace_back(WORKSPACE);
|
||||||
|
|
||||||
|
// remove the buttons for reorder
|
||||||
|
buttons_.clear();
|
||||||
|
} else {
|
||||||
|
const auto it = std::remove(workspaces.begin(), workspaces.end(), WORKSPACE);
|
||||||
|
|
||||||
|
if (it != workspaces.end())
|
||||||
|
workspaces.erase(it);
|
||||||
|
|
||||||
|
// also remove the buttons
|
||||||
|
buttons_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
dp.emit();
|
||||||
|
|
||||||
|
mutex_.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Workspaces::handleScroll(GdkEventScroll *e) {
|
||||||
|
if (gdk_event_get_pointer_emulated((GdkEvent *)e)) {
|
||||||
|
/**
|
||||||
|
* Ignore emulated scroll events on window
|
||||||
|
*/
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto dir = AModule::getScrollDir(e);
|
||||||
|
if (dir == SCROLL_DIR::NONE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_.lock();
|
||||||
|
|
||||||
|
if (dir == SCROLL_DIR::UP) {
|
||||||
|
gIPC->getSocket1Reply("dispatch workspace +1");
|
||||||
|
} else if (dir == SCROLL_DIR::DOWN) {
|
||||||
|
gIPC->getSocket1Reply("dispatch workspace -1");
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_.unlock();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in New Issue