Skip to content

Commit 848ae1f

Browse files
authored
Merge pull request #1656 from vaxerski/hyprland
Added a Hyprland backend and a Window module
2 parents 77bea7c + 406eb0e commit 848ae1f

File tree

8 files changed

+338
-0
lines changed

8 files changed

+338
-0
lines changed

include/factory.hpp

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
#include "modules/river/tags.hpp"
2222
#include "modules/river/window.hpp"
2323
#endif
24+
#ifdef HAVE_HYPRLAND
25+
#include "modules/hyprland/backend.hpp"
26+
#include "modules/hyprland/window.hpp"
27+
#endif
2428
#if defined(__linux__) && !defined(NO_FILESYSTEM)
2529
#include "modules/battery.hpp"
2630
#endif

include/modules/hyprland/backend.hpp

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#pragma once
2+
#include <string>
3+
#include <memory>
4+
#include <mutex>
5+
#include <deque>
6+
#include <functional>
7+
#include <thread>
8+
9+
namespace waybar::modules::hyprland {
10+
class IPC {
11+
public:
12+
IPC() { startIPC(); }
13+
14+
void registerForIPC(const std::string&, std::function<void(const std::string&)>);
15+
16+
std::string getSocket1Reply(const std::string& rq);
17+
18+
private:
19+
20+
void startIPC();
21+
void parseIPC(const std::string&);
22+
23+
std::mutex callbackMutex;
24+
std::deque<std::pair<std::string, std::function<void(const std::string&)>>> callbacks;
25+
};
26+
27+
inline std::unique_ptr<IPC> gIPC;
28+
inline bool modulesReady = false;
29+
};
30+

include/modules/hyprland/window.hpp

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include <fmt/format.h>
2+
3+
#include <tuple>
4+
5+
#include "ALabel.hpp"
6+
#include "bar.hpp"
7+
#include "modules/hyprland/backend.hpp"
8+
#include "util/json.hpp"
9+
10+
namespace waybar::modules::hyprland {
11+
12+
class Window : public waybar::ALabel {
13+
public:
14+
Window(const std::string&, const waybar::Bar&, const Json::Value&);
15+
~Window() = default;
16+
17+
auto update() -> void;
18+
19+
private:
20+
void onEvent(const std::string&);
21+
22+
std::mutex mutex_;
23+
const Bar& bar_;
24+
util::JsonParser parser_;
25+
std::string lastView;
26+
};
27+
28+
}

man/waybar-hyprland-window.5.scd

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
waybar-hyprland-window(5)
2+
3+
# NAME
4+
5+
waybar - hyprland window module
6+
7+
# DESCRIPTION
8+
9+
The *window* module displays the title of the currently focused window in Hyprland.
10+
11+
# CONFIGURATION
12+
13+
Addressed by *hyprland/window*
14+
15+
*format*: ++
16+
typeof: string ++
17+
default: {} ++
18+
The format, how information should be displayed. On {} the current window title is displayed.
19+
20+
21+
# EXAMPLES
22+
23+
```
24+
"hyprland/window": {
25+
"format": "{}"
26+
}
27+
```
28+
29+
# STYLE
30+
31+
- *#window*

meson.build

+6
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@ if true
201201
src_files += 'src/modules/river/window.cpp'
202202
endif
203203

204+
if true
205+
add_project_arguments('-DHAVE_HYPRLAND', language: 'cpp')
206+
src_files += 'src/modules/hyprland/backend.cpp'
207+
src_files += 'src/modules/hyprland/window.cpp'
208+
endif
209+
204210
if libnl.found() and libnlgen.found()
205211
add_project_arguments('-DHAVE_LIBNL', language: 'cpp')
206212
src_files += 'src/modules/network.cpp'

src/factory.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
5656
if (ref == "river/window") {
5757
return new waybar::modules::river::Window(id, bar_, config_[name]);
5858
}
59+
#endif
60+
#ifdef HAVE_HYPRLAND
61+
if (ref == "hyprland/window") {
62+
return new waybar::modules::hyprland::Window(id, bar_, config_[name]);
63+
}
5964
#endif
6065
if (ref == "idle_inhibitor") {
6166
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);

src/modules/hyprland/backend.cpp

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#include "modules/hyprland/backend.hpp"
2+
3+
#include <ctype.h>
4+
#include <netdb.h>
5+
#include <netinet/in.h>
6+
#include <spdlog/spdlog.h>
7+
#include <stdio.h>
8+
#include <stdlib.h>
9+
#include <string.h>
10+
#include <sys/socket.h>
11+
#include <sys/stat.h>
12+
#include <sys/types.h>
13+
#include <sys/un.h>
14+
#include <unistd.h>
15+
16+
#include <fstream>
17+
#include <iostream>
18+
#include <string>
19+
20+
namespace waybar::modules::hyprland {
21+
22+
void IPC::startIPC() {
23+
// will start IPC and relay events to parseIPC
24+
25+
std::thread([&]() {
26+
// check for hyprland
27+
const char* HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
28+
29+
if (!HIS) {
30+
spdlog::warn("Hyprland is not running, Hyprland IPC will not be available.");
31+
return;
32+
}
33+
34+
if (!modulesReady) return;
35+
36+
spdlog::info("Hyprland IPC starting");
37+
38+
struct sockaddr_un addr;
39+
int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
40+
41+
if (socketfd == -1) {
42+
spdlog::error("Hyprland IPC: socketfd failed");
43+
return;
44+
}
45+
46+
addr.sun_family = AF_UNIX;
47+
48+
// socket path
49+
std::string socketPath = "/tmp/hypr/" + std::string(HIS) + "/.socket2.sock";
50+
51+
strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
52+
53+
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
54+
55+
int l = sizeof(struct sockaddr_un);
56+
57+
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {
58+
spdlog::error("Hyprland IPC: Unable to connect?");
59+
return;
60+
}
61+
62+
auto file = fdopen(socketfd, "r");
63+
64+
while (1) {
65+
// read
66+
67+
char buffer[1024]; // Hyprland socket2 events are max 1024 bytes
68+
auto recievedCharPtr = fgets(buffer, 1024, file);
69+
70+
if (!recievedCharPtr) {
71+
std::this_thread::sleep_for(std::chrono::milliseconds(1));
72+
continue;
73+
}
74+
75+
callbackMutex.lock();
76+
77+
std::string messageRecieved(buffer);
78+
79+
messageRecieved = messageRecieved.substr(0, messageRecieved.find_first_of('\n'));
80+
81+
spdlog::debug("hyprland IPC received {}", messageRecieved);
82+
83+
parseIPC(messageRecieved);
84+
85+
callbackMutex.unlock();
86+
87+
std::this_thread::sleep_for(std::chrono::milliseconds(1));
88+
}
89+
}).detach();
90+
}
91+
92+
void IPC::parseIPC(const std::string& ev) {
93+
// todo
94+
std::string request = ev.substr(0, ev.find_first_of('>'));
95+
96+
for (auto& [eventname, handler] : callbacks) {
97+
if (eventname == request) {
98+
handler(ev);
99+
}
100+
}
101+
}
102+
103+
void IPC::registerForIPC(const std::string& ev, std::function<void(const std::string&)> fn) {
104+
callbackMutex.lock();
105+
106+
callbacks.emplace_back(std::make_pair(ev, fn));
107+
108+
callbackMutex.unlock();
109+
}
110+
111+
std::string IPC::getSocket1Reply(const std::string& rq) {
112+
// basically hyprctl
113+
114+
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
115+
116+
if (SERVERSOCKET < 0) {
117+
spdlog::error("Hyprland IPC: Couldn't open a socket (1)");
118+
return "";
119+
}
120+
121+
const auto SERVER = gethostbyname("localhost");
122+
123+
if (!SERVER) {
124+
spdlog::error("Hyprland IPC: Couldn't get host (2)");
125+
return "";
126+
}
127+
128+
// get the instance signature
129+
auto instanceSig = getenv("HYPRLAND_INSTANCE_SIGNATURE");
130+
131+
if (!instanceSig) {
132+
spdlog::error("Hyprland IPC: HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?)");
133+
return "";
134+
}
135+
136+
std::string instanceSigStr = std::string(instanceSig);
137+
138+
sockaddr_un serverAddress = {0};
139+
serverAddress.sun_family = AF_UNIX;
140+
141+
std::string socketPath = "/tmp/hypr/" + instanceSigStr + "/.socket.sock";
142+
143+
strcpy(serverAddress.sun_path, socketPath.c_str());
144+
145+
if (connect(SERVERSOCKET, (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) {
146+
spdlog::error("Hyprland IPC: Couldn't connect to " + socketPath + ". (3)");
147+
return "";
148+
}
149+
150+
auto sizeWritten = write(SERVERSOCKET, rq.c_str(), rq.length());
151+
152+
if (sizeWritten < 0) {
153+
spdlog::error("Hyprland IPC: Couldn't write (4)");
154+
return "";
155+
}
156+
157+
char buffer[8192] = {0};
158+
159+
sizeWritten = read(SERVERSOCKET, buffer, 8192);
160+
161+
if (sizeWritten < 0) {
162+
spdlog::error("Hyprland IPC: Couldn't read (5)");
163+
return "";
164+
}
165+
166+
close(SERVERSOCKET);
167+
168+
return std::string(buffer);
169+
}
170+
171+
} // namespace waybar::modules::hyprland

src/modules/hyprland/window.cpp

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#include "modules/hyprland/window.hpp"
2+
3+
#include <spdlog/spdlog.h>
4+
5+
#include "modules/hyprland/backend.hpp"
6+
7+
namespace waybar::modules::hyprland {
8+
9+
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
10+
: ALabel(config, "window", id, "{}", 0, true), bar_(bar) {
11+
modulesReady = true;
12+
13+
if (!gIPC.get()) {
14+
gIPC = std::make_unique<IPC>();
15+
}
16+
17+
label_.hide();
18+
ALabel::update();
19+
20+
// register for hyprland ipc
21+
gIPC->registerForIPC("activewindow", [&](const std::string& ev) { this->onEvent(ev); });
22+
}
23+
24+
auto Window::update() -> void {
25+
// fix ampersands
26+
std::lock_guard<std::mutex> lg(mutex_);
27+
28+
if (!format_.empty()) {
29+
label_.show();
30+
label_.set_markup(fmt::format(format_, lastView));
31+
} else {
32+
label_.hide();
33+
}
34+
35+
ALabel::update();
36+
}
37+
38+
void Window::onEvent(const std::string& ev) {
39+
std::lock_guard<std::mutex> lg(mutex_);
40+
auto windowName = ev.substr(ev.find_first_of(',') + 1).substr(0, 256);
41+
42+
auto replaceAll = [](std::string str, const std::string& from,
43+
const std::string& to) -> std::string {
44+
size_t start_pos = 0;
45+
while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
46+
str.replace(start_pos, from.length(), to);
47+
start_pos += to.length();
48+
}
49+
return str;
50+
};
51+
52+
windowName = replaceAll(windowName, "&", "&amp;");
53+
54+
if (windowName == lastView) return;
55+
56+
lastView = windowName;
57+
58+
spdlog::debug("hyprland window onevent with {}", windowName);
59+
60+
dp.emit();
61+
}
62+
63+
} // namespace waybar::modules::hyprland

0 commit comments

Comments
 (0)