262 lines
8.2 KiB
C++
262 lines
8.2 KiB
C++
#include "modules/niri/backend.hpp"
|
|
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
#include <spdlog/spdlog.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/un.h>
|
|
#include <unistd.h>
|
|
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
#include "giomm/datainputstream.h"
|
|
#include "giomm/dataoutputstream.h"
|
|
#include "giomm/unixinputstream.h"
|
|
#include "giomm/unixoutputstream.h"
|
|
|
|
namespace waybar::modules::niri {
|
|
|
|
int IPC::connectToSocket() {
|
|
const char *socket_path = getenv("NIRI_SOCKET");
|
|
|
|
if (socket_path == nullptr) {
|
|
spdlog::warn("Niri is not running, niri IPC will not be available.");
|
|
return -1;
|
|
}
|
|
|
|
struct sockaddr_un addr;
|
|
int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
|
|
if (socketfd == -1) {
|
|
throw std::runtime_error("socketfd failed");
|
|
}
|
|
|
|
addr.sun_family = AF_UNIX;
|
|
|
|
strncpy(addr.sun_path, socket_path, 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) {
|
|
close(socketfd);
|
|
throw std::runtime_error("unable to connect");
|
|
}
|
|
|
|
return socketfd;
|
|
}
|
|
|
|
void IPC::startIPC() {
|
|
// will start IPC and relay events to parseIPC
|
|
|
|
std::thread([&]() {
|
|
int socketfd;
|
|
try {
|
|
socketfd = connectToSocket();
|
|
} catch (std::exception &e) {
|
|
spdlog::error("Niri IPC: failed to start, reason: {}", e.what());
|
|
return;
|
|
}
|
|
if (socketfd == -1) return;
|
|
|
|
spdlog::info("Niri IPC starting");
|
|
|
|
auto unix_istream = Gio::UnixInputStream::create(socketfd, true);
|
|
auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false);
|
|
auto istream = Gio::DataInputStream::create(unix_istream);
|
|
auto ostream = Gio::DataOutputStream::create(unix_ostream);
|
|
|
|
if (!ostream->put_string("\"EventStream\"\n") || !ostream->flush()) {
|
|
spdlog::error("Niri IPC: failed to start event stream");
|
|
return;
|
|
}
|
|
|
|
std::string line;
|
|
if (!istream->read_line(line) || line != R"({"Ok":"Handled"})") {
|
|
spdlog::error("Niri IPC: failed to start event stream");
|
|
return;
|
|
}
|
|
|
|
while (istream->read_line(line)) {
|
|
spdlog::debug("Niri IPC: received {}", line);
|
|
|
|
try {
|
|
parseIPC(line);
|
|
} catch (std::exception &e) {
|
|
spdlog::warn("Failed to parse IPC message: {}, reason: {}", line, e.what());
|
|
} catch (...) {
|
|
throw;
|
|
}
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
}
|
|
}).detach();
|
|
}
|
|
|
|
void IPC::parseIPC(const std::string &line) {
|
|
const auto ev = parser_.parse(line);
|
|
const auto members = ev.getMemberNames();
|
|
if (members.size() != 1) throw std::runtime_error("Event must have a single member");
|
|
|
|
{
|
|
auto lock = lockData();
|
|
|
|
if (const auto &payload = ev["WorkspacesChanged"]) {
|
|
workspaces_.clear();
|
|
const auto &values = payload["workspaces"];
|
|
std::copy(values.begin(), values.end(), std::back_inserter(workspaces_));
|
|
|
|
std::sort(workspaces_.begin(), workspaces_.end(), [](const auto &a, const auto &b) {
|
|
const auto &aOutput = a["output"].asString();
|
|
const auto &bOutput = b["output"].asString();
|
|
const auto aIdx = a["idx"].asUInt();
|
|
const auto bIdx = b["idx"].asUInt();
|
|
if (aOutput == bOutput) return aIdx < bIdx;
|
|
return aOutput < bOutput;
|
|
});
|
|
} else if (const auto &payload = ev["WorkspaceActivated"]) {
|
|
const auto id = payload["id"].asUInt64();
|
|
const auto focused = payload["focused"].asBool();
|
|
auto it = std::find_if(workspaces_.begin(), workspaces_.end(),
|
|
[id](const auto &ws) { return ws["id"].asUInt64() == id; });
|
|
if (it != workspaces_.end()) {
|
|
const auto &ws = *it;
|
|
const auto &output = ws["output"].asString();
|
|
for (auto &ws : workspaces_) {
|
|
const auto got_activated = (ws["id"].asUInt64() == id);
|
|
if (ws["output"] == output) ws["is_active"] = got_activated;
|
|
|
|
if (focused) ws["is_focused"] = got_activated;
|
|
}
|
|
} else {
|
|
spdlog::error("Activated unknown workspace");
|
|
}
|
|
} else if (const auto &payload = ev["WorkspaceActiveWindowChanged"]) {
|
|
const auto workspaceId = payload["workspace_id"].asUInt64();
|
|
auto it = std::find_if(workspaces_.begin(), workspaces_.end(), [workspaceId](const auto &ws) {
|
|
return ws["id"].asUInt64() == workspaceId;
|
|
});
|
|
if (it != workspaces_.end()) {
|
|
auto &ws = *it;
|
|
ws["active_window_id"] = payload["active_window_id"];
|
|
} else {
|
|
spdlog::error("Active window changed on unknown workspace");
|
|
}
|
|
} else if (const auto &payload = ev["KeyboardLayoutsChanged"]) {
|
|
const auto &layouts = payload["keyboard_layouts"];
|
|
const auto &names = layouts["names"];
|
|
keyboardLayoutCurrent_ = layouts["current_idx"].asUInt();
|
|
|
|
keyboardLayoutNames_.clear();
|
|
for (const auto &fullName : names) keyboardLayoutNames_.push_back(fullName.asString());
|
|
} else if (const auto &payload = ev["KeyboardLayoutSwitched"]) {
|
|
keyboardLayoutCurrent_ = payload["idx"].asUInt();
|
|
} else if (const auto &payload = ev["WindowsChanged"]) {
|
|
windows_.clear();
|
|
const auto &values = payload["windows"];
|
|
std::copy(values.begin(), values.end(), std::back_inserter(windows_));
|
|
} else if (const auto &payload = ev["WindowOpenedOrChanged"]) {
|
|
const auto &window = payload["window"];
|
|
const auto id = window["id"].asUInt64();
|
|
auto it = std::find_if(windows_.begin(), windows_.end(),
|
|
[id](const auto &win) { return win["id"].asUInt64() == id; });
|
|
if (it == windows_.end()) {
|
|
windows_.push_back(window);
|
|
|
|
if (window["is_focused"].asBool()) {
|
|
for (auto &win : windows_) {
|
|
win["is_focused"] = win["id"].asUInt64() == id;
|
|
}
|
|
}
|
|
} else {
|
|
*it = window;
|
|
}
|
|
} else if (const auto &payload = ev["WindowClosed"]) {
|
|
const auto id = payload["id"].asUInt64();
|
|
auto it = std::find_if(windows_.begin(), windows_.end(),
|
|
[id](const auto &win) { return win["id"].asUInt64() == id; });
|
|
if (it != windows_.end()) {
|
|
windows_.erase(it);
|
|
} else {
|
|
spdlog::error("Unknown window closed");
|
|
}
|
|
} else if (const auto &payload = ev["WindowFocusChanged"]) {
|
|
const auto focused = !payload["id"].isNull();
|
|
const auto id = payload["id"].asUInt64();
|
|
for (auto &win : windows_) {
|
|
win["is_focused"] = focused && win["id"].asUInt64() == id;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::unique_lock lock(callbackMutex_);
|
|
|
|
for (auto &[eventname, handler] : callbacks_) {
|
|
if (eventname == members[0]) {
|
|
handler->onEvent(ev);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IPC::registerForIPC(const std::string &ev, EventHandler *ev_handler) {
|
|
if (ev_handler == nullptr) {
|
|
return;
|
|
}
|
|
|
|
std::unique_lock lock(callbackMutex_);
|
|
callbacks_.emplace_back(ev, ev_handler);
|
|
}
|
|
|
|
void IPC::unregisterForIPC(EventHandler *ev_handler) {
|
|
if (ev_handler == nullptr) {
|
|
return;
|
|
}
|
|
|
|
std::unique_lock lock(callbackMutex_);
|
|
|
|
for (auto it = callbacks_.begin(); it != callbacks_.end();) {
|
|
auto &[eventname, handler] = *it;
|
|
if (handler == ev_handler) {
|
|
it = callbacks_.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
Json::Value IPC::send(const Json::Value &request) {
|
|
int socketfd = connectToSocket();
|
|
if (socketfd == -1) throw std::runtime_error("Niri is not running");
|
|
|
|
auto unix_istream = Gio::UnixInputStream::create(socketfd, true);
|
|
auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false);
|
|
auto istream = Gio::DataInputStream::create(unix_istream);
|
|
auto ostream = Gio::DataOutputStream::create(unix_ostream);
|
|
|
|
// Niri needs the request on a single line.
|
|
Json::StreamWriterBuilder builder;
|
|
builder["indentation"] = "";
|
|
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
|
|
std::ostringstream oss;
|
|
writer->write(request, &oss);
|
|
oss << '\n';
|
|
|
|
if (!ostream->put_string(oss.str()) || !ostream->flush())
|
|
throw std::runtime_error("error writing to niri socket");
|
|
|
|
std::string line;
|
|
if (!istream->read_line(line)) throw std::runtime_error("error reading from niri socket");
|
|
|
|
std::istringstream iss(std::move(line));
|
|
Json::Value response;
|
|
iss >> response;
|
|
return response;
|
|
}
|
|
|
|
} // namespace waybar::modules::niri
|