diff --git a/include/factory.hpp b/include/factory.hpp index 1e79bd79..47ef530b 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -21,6 +21,10 @@ #include "modules/river/tags.hpp" #include "modules/river/window.hpp" #endif +#ifdef HAVE_HYPRLAND +#include "modules/hyprland/backend.hpp" +#include "modules/hyprland/window.hpp" +#endif #if defined(__linux__) && !defined(NO_FILESYSTEM) #include "modules/battery.hpp" #endif diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp new file mode 100644 index 00000000..b9d1c99e --- /dev/null +++ b/include/modules/hyprland/backend.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace waybar::modules::hyprland { +class IPC { +public: + IPC() { startIPC(); } + + void registerForIPC(const std::string&, std::function); + + std::string getSocket1Reply(const std::string& rq); + +private: + + void startIPC(); + void parseIPC(const std::string&); + + std::mutex callbackMutex; + std::deque>> callbacks; +}; + +inline std::unique_ptr gIPC; +inline bool modulesReady = false; +}; + diff --git a/include/modules/hyprland/window.hpp b/include/modules/hyprland/window.hpp new file mode 100644 index 00000000..f80159b3 --- /dev/null +++ b/include/modules/hyprland/window.hpp @@ -0,0 +1,29 @@ +#include + +#include + +#include "ALabel.hpp" +#include "bar.hpp" +#include "modules/hyprland/backend.hpp" +#include "util/json.hpp" + +namespace waybar::modules::hyprland { + +class Window : public waybar::ALabel { +public: + Window(const std::string&, const waybar::Bar&, const Json::Value&); + ~Window() = default; + +private: + void onEvent(const std::string&); + + const Bar& bar_; + IPC ipc; + unsigned app_icon_size_{24}; + bool update_app_icon_{true}; + std::string app_icon_name_; + util::JsonParser parser_; + std::string lastView; +}; + +} \ No newline at end of file diff --git a/meson.build b/meson.build index 441ccfb2..3c320068 100644 --- a/meson.build +++ b/meson.build @@ -201,6 +201,12 @@ if true src_files += 'src/modules/river/window.cpp' endif +if true + add_project_arguments('-DHAVE_HYPRLAND', language: 'cpp') + src_files += 'src/modules/hyprland/backend.cpp' + src_files += 'src/modules/hyprland/window.cpp' +endif + if libnl.found() and libnlgen.found() add_project_arguments('-DHAVE_LIBNL', language: 'cpp') src_files += 'src/modules/network.cpp' diff --git a/src/factory.cpp b/src/factory.cpp index 841465f4..6df69d5c 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -56,6 +56,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "river/window") { return new waybar::modules::river::Window(id, bar_, config_[name]); } +#endif +#ifdef HAVE_HYPRLAND + if (ref == "hyprland/window") { + return new waybar::modules::hyprland::Window(id, bar_, config_[name]); + } #endif if (ref == "idle_inhibitor") { return new waybar::modules::IdleInhibitor(id, bar_, config_[name]); diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp new file mode 100644 index 00000000..16deb182 --- /dev/null +++ b/src/modules/hyprland/backend.cpp @@ -0,0 +1,105 @@ +#include "modules/hyprland/backend.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +void waybar::modules::hyprland::IPC::startIPC() { + // will start IPC and relay events to parseIPC + + std::thread([&]() { + // check for hyprland + const char* HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE"); + + if (!HIS) { + spdlog::warn("Hyprland is not running, Hyprland IPC will not be available."); + return; + } + + if (!modulesReady) return; + + spdlog::info("Hyprland IPC starting"); + + struct sockaddr_un addr; + int socketfd = socket(AF_UNIX, SOCK_STREAM, 0); + + if (socketfd == -1) { + spdlog::error("Hyprland IPC: socketfd failed"); + return; + } + + addr.sun_family = AF_UNIX; + + // socket path + std::string socketPath = "/tmp/hypr/" + std::string(HIS) + "/.socket2.sock"; + + strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1); + + addr.sun_path[sizeof(addr.sun_path) - 1] = 0; + + int l = sizeof(struct sockaddr_un); + + if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) { + spdlog::error("Hyprland IPC: Unable to connect?"); + return; + } + + auto file = fdopen(socketfd, "r"); + + while (1) { + // read + + char buffer[1024]; // Hyprland socket2 events are max 1024 bytes + auto recievedCharPtr = fgets(buffer, 1024, file); + + if (!recievedCharPtr) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + + callbackMutex.lock(); + + std::string messageRecieved(buffer); + + messageRecieved = messageRecieved.substr(0, messageRecieved.find_first_of('\n')); + + spdlog::debug("hyprland IPC received {}", messageRecieved); + + parseIPC(messageRecieved); + + callbackMutex.unlock(); + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + }).detach(); +} + +void waybar::modules::hyprland::IPC::parseIPC(const std::string& ev) { + // todo + std::string request = ev.substr(0, ev.find_first_of('>')); + + for (auto& [eventname, handler] : callbacks) { + if (eventname == request) { + handler(ev); + } + } +} + +void waybar::modules::hyprland::IPC::registerForIPC(const std::string& ev, + std::function fn) { + callbackMutex.lock(); + + callbacks.emplace_back(std::make_pair(ev, fn)); + + callbackMutex.unlock(); +} \ No newline at end of file diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp new file mode 100644 index 00000000..96d07bd3 --- /dev/null +++ b/src/modules/hyprland/window.cpp @@ -0,0 +1,54 @@ +#include "modules/hyprland/backend.hpp" +#include "modules/hyprland/window.hpp" + +#include + +namespace waybar::modules::hyprland { + +Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) + : ALabel(config, "window", id, "{}", 0, true), bar_(bar) { + modulesReady = true; + + if (!gIPC.get()) { + gIPC = std::make_unique(); + } + + label_.hide(); + ALabel::update(); + + // register for hyprland ipc + gIPC->registerForIPC("activewindow", [&](const std::string& ev) { this->onEvent(ev); }); +} + +void Window::onEvent(const std::string& ev) { + auto windowName = ev.substr(ev.find_first_of(',') + 1).substr(0, 256); + + // fix ampersands + auto replaceAll = [](std::string str, const std::string& from, + const std::string& to) -> std::string { + size_t start_pos = 0; + while ((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + return str; + }; + + windowName = replaceAll(windowName, "&", "&"); + + if (windowName == lastView) return; + + lastView = windowName; + + spdlog::debug("hyprland window onevent with {}", windowName); + + if (!format_.empty()) { + label_.show(); + label_.set_markup(fmt::format(format_, windowName)); + } else { + label_.hide(); + } + + ALabel::update(); +} +} \ No newline at end of file