Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a Hyprland backend and a Window module #1656

Merged
merged 13 commits into from
Aug 18, 2022
4 changes: 4 additions & 0 deletions include/factory.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions include/modules/hyprland/backend.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once
#include <string>
#include <memory>
#include <mutex>
#include <deque>
#include <functional>
#include <thread>

namespace waybar::modules::hyprland {
class IPC {
public:
IPC() { startIPC(); }

void registerForIPC(const std::string&, std::function<void(const std::string&)>);

std::string getSocket1Reply(const std::string& rq);

private:

void startIPC();
void parseIPC(const std::string&);

std::mutex callbackMutex;
std::deque<std::pair<std::string, std::function<void(const std::string&)>>> callbacks;
};

inline std::unique_ptr<IPC> gIPC;
inline bool modulesReady = false;
};

28 changes: 28 additions & 0 deletions include/modules/hyprland/window.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include <fmt/format.h>

#include <tuple>

#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;

auto update() -> void;

private:
void onEvent(const std::string&);

std::mutex mutex_;
const Bar& bar_;
util::JsonParser parser_;
std::string lastView;
};

}
31 changes: 31 additions & 0 deletions man/waybar-hyprland-window.5.scd
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
waybar-hyprland-window(5)

# NAME

waybar - hyprland window module

# DESCRIPTION

The *window* module displays the title of the currently focused window in Hyprland.

# CONFIGURATION

Addressed by *hyprland/window*

*format*: ++
typeof: string ++
default: {} ++
The format, how information should be displayed. On {} the current window title is displayed.


# EXAMPLES

```
"hyprland/window": {
"format": "{}"
}
```

# STYLE

- *#window*
6 changes: 6 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
5 changes: 5 additions & 0 deletions src/factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down
171 changes: 171 additions & 0 deletions src/modules/hyprland/backend.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#include "modules/hyprland/backend.hpp"

#include <ctype.h>
#include <netdb.h>
#include <netinet/in.h>
#include <spdlog/spdlog.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 <fstream>
#include <iostream>
#include <string>

namespace waybar::modules::hyprland {

void 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 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 IPC::registerForIPC(const std::string& ev, std::function<void(const std::string&)> fn) {
callbackMutex.lock();

callbacks.emplace_back(std::make_pair(ev, fn));

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);
}

} // namespace waybar::modules::hyprland
63 changes: 63 additions & 0 deletions src/modules/hyprland/window.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#include "modules/hyprland/window.hpp"

#include <spdlog/spdlog.h>

#include "modules/hyprland/backend.hpp"

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<IPC>();
}

label_.hide();
ALabel::update();

// register for hyprland ipc
gIPC->registerForIPC("activewindow", [&](const std::string& ev) { this->onEvent(ev); });
}

auto Window::update() -> void {
// fix ampersands
std::lock_guard<std::mutex> lg(mutex_);

if (!format_.empty()) {
label_.show();
label_.set_markup(fmt::format(format_, lastView));
} else {
label_.hide();
}

ALabel::update();
}

void Window::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lg(mutex_);
auto windowName = ev.substr(ev.find_first_of(',') + 1).substr(0, 256);

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, "&", "&amp;");

if (windowName == lastView) return;

lastView = windowName;

spdlog::debug("hyprland window onevent with {}", windowName);

dp.emit();
}

} // namespace waybar::modules::hyprland