feat: add mango modules: workspace,language,keymode,window,layout
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
#include "modules/mango/backend.hpp"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "util/scoped_fd.hpp"
|
||||
|
||||
namespace waybar::modules::mango {
|
||||
|
||||
int IPC::connectToSocket() {
|
||||
const char* socket_path = getenv("MANGO_INSTANCE_SIGNATURE");
|
||||
if (!socket_path) {
|
||||
throw std::runtime_error("Mango IPC: MANGO_INSTANCE_SIGNATURE not set");
|
||||
}
|
||||
|
||||
struct sockaddr_un addr;
|
||||
util::ScopedFd fd(socket(AF_UNIX, SOCK_STREAM, 0));
|
||||
if (fd == -1) throw std::runtime_error("socket() 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;
|
||||
|
||||
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||
throw std::runtime_error("connect() failed");
|
||||
}
|
||||
return fd.release();
|
||||
}
|
||||
|
||||
Json::Value IPC::sendCommand(const std::string& cmd) {
|
||||
util::ScopedFd fd(IPC::connectToSocket());
|
||||
std::string full_cmd = cmd + "\n";
|
||||
|
||||
ssize_t total_written = 0;
|
||||
while (total_written < (ssize_t)full_cmd.size()) {
|
||||
ssize_t res = write(fd, full_cmd.c_str() + total_written, full_cmd.size() - total_written);
|
||||
if (res < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
throw std::runtime_error("Failed to write command");
|
||||
}
|
||||
total_written += res;
|
||||
}
|
||||
|
||||
char buf[4096];
|
||||
std::string response;
|
||||
while (true) {
|
||||
ssize_t n = read(fd, buf, sizeof(buf) - 1);
|
||||
if (n <= 0) {
|
||||
if (n == 0) break;
|
||||
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
|
||||
throw std::runtime_error("Read error");
|
||||
}
|
||||
buf[n] = '\0';
|
||||
response += buf;
|
||||
if (response.find('\n') != std::string::npos) break;
|
||||
}
|
||||
|
||||
Json::Value root;
|
||||
std::istringstream iss(response);
|
||||
Json::CharReaderBuilder builder;
|
||||
std::string errors;
|
||||
if (!Json::parseFromStream(builder, iss, &root, &errors)) {
|
||||
throw std::runtime_error("JSON parse error: " + errors);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
Json::Value IPC::send(const Json::Value& request) {
|
||||
if (!request.isMember("command")) {
|
||||
throw std::runtime_error("Mango IPC: request must have 'command' field");
|
||||
}
|
||||
return sendCommand(request["command"].asString());
|
||||
}
|
||||
|
||||
void IPC::sendAsync(const Json::Value& request) {
|
||||
if (!request.isMember("command")) {
|
||||
spdlog::error("Mango IPC: request must have 'command' field");
|
||||
return;
|
||||
}
|
||||
std::string cmd = request["command"].asString();
|
||||
|
||||
std::thread([cmd]() {
|
||||
try {
|
||||
IPC::sendCommand(cmd);
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("IPC async send failed: {}", e.what());
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
IPC::IPC() : sockfd_(-1), active_client_(Json::nullValue) { startIPC(); }
|
||||
|
||||
IPC::~IPC() {
|
||||
if (sockfd_ != -1) close(sockfd_);
|
||||
if (ipc_thread_.joinable()) ipc_thread_.join();
|
||||
}
|
||||
|
||||
void IPC::startIPC() {
|
||||
sockfd_ = IPC::connectToSocket();
|
||||
|
||||
ipc_thread_ = std::thread([this]() {
|
||||
spdlog::info("Mango IPC thread started");
|
||||
|
||||
struct pollfd pfd;
|
||||
pfd.fd = sockfd_;
|
||||
pfd.events = POLLIN;
|
||||
|
||||
const std::vector<std::string> subs = {"watch all-monitors"};
|
||||
for (const auto& cmd : subs) {
|
||||
if (write(sockfd_, cmd.c_str(), cmd.size()) != (ssize_t)cmd.size() ||
|
||||
write(sockfd_, "\n", 1) != 1) {
|
||||
spdlog::error("Failed to subscribe to {}", cmd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
char buf[4096];
|
||||
std::string buffer;
|
||||
while (true) {
|
||||
int ret = poll(&pfd, 1, 1000);
|
||||
if (ret == 0) continue;
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
spdlog::error("IPC poll error: {}", strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
||||
spdlog::info("Mango IPC socket closed or invalid");
|
||||
break;
|
||||
}
|
||||
|
||||
if (pfd.revents & POLLIN) {
|
||||
ssize_t n = read(sockfd_, buf, sizeof(buf));
|
||||
if (n == 0) {
|
||||
spdlog::info("Mango IPC connection closed");
|
||||
break;
|
||||
}
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
spdlog::error("IPC read error: {}", strerror(errno));
|
||||
break;
|
||||
}
|
||||
buffer.append(buf, n);
|
||||
|
||||
size_t pos;
|
||||
while ((pos = buffer.find('\n')) != std::string::npos) {
|
||||
std::string line = buffer.substr(0, pos);
|
||||
buffer.erase(0, pos + 1);
|
||||
if (line.empty()) continue;
|
||||
try {
|
||||
parseIPC(line);
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::warn("Failed to parse IPC line: {} - {}", line, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void IPC::parseIPC(const std::string& line) {
|
||||
Json::Value root;
|
||||
Json::CharReaderBuilder builder;
|
||||
std::string errors;
|
||||
std::istringstream iss(line);
|
||||
if (!Json::parseFromStream(builder, iss, &root, &errors)) {
|
||||
throw std::runtime_error("JSON parse error: " + errors);
|
||||
}
|
||||
|
||||
if (root.isMember("monitors") && root["monitors"].isArray()) {
|
||||
for (const auto& mon : root["monitors"]) {
|
||||
handleMonitorUpdate(mon);
|
||||
}
|
||||
|
||||
Json::Value active_monitor;
|
||||
for (const auto& mon : root["monitors"]) {
|
||||
if (mon["active"].asBool()) {
|
||||
active_monitor = mon;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!active_monitor.isNull()) {
|
||||
const auto& active_client = active_monitor["active_client"];
|
||||
updateFocusingClient(active_client);
|
||||
|
||||
if (active_monitor.isMember("keyboardlayout")) {
|
||||
updateKeyboardLayout(active_monitor["keyboardlayout"].asString());
|
||||
}
|
||||
|
||||
if (active_monitor.isMember("keymode")) {
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
keymode_ = active_monitor["keymode"].asString();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<EventHandler*> handlers_to_notify;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||
for (auto& [ev, handler] : callbacks_) {
|
||||
if (ev == "monitor") {
|
||||
handlers_to_notify.push_back(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* handler : handlers_to_notify) {
|
||||
handler->onEvent(root);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::debug("Unhandled IPC message: {}", line);
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, Json::Value> IPC::getMonitors() const {
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
return monitors_;
|
||||
}
|
||||
|
||||
IPC& IPC::getInstance() {
|
||||
static IPC instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
Json::Value IPC::getMonitor(const std::string& name) {
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
auto it = monitors_.find(name);
|
||||
if (it != monitors_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return Json::nullValue;
|
||||
}
|
||||
|
||||
std::string IPC::getKeyboardLayout() const {
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
return keyboard_layout_;
|
||||
}
|
||||
|
||||
std::string IPC::getKeymode() const {
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
return keymode_;
|
||||
}
|
||||
|
||||
Json::Value IPC::getActiveClientForMonitor(const std::string& name) const {
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
auto it = monitors_.find(name);
|
||||
if (it != monitors_.end() && it->second.isMember("active_client")) {
|
||||
return it->second["active_client"];
|
||||
}
|
||||
return Json::nullValue;
|
||||
}
|
||||
|
||||
std::string IPC::getLayoutSymbolForMonitor(const std::string& name) const {
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
auto it = monitors_.find(name);
|
||||
if (it != monitors_.end() && it->second.isMember("layout_symbol")) {
|
||||
return it->second["layout_symbol"].asString();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void IPC::handleMonitorUpdate(const Json::Value& mon) {
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
monitors_[mon["name"].asString()] = mon;
|
||||
}
|
||||
|
||||
void IPC::updateFocusingClient(const Json::Value& client) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
active_client_ = client;
|
||||
|
||||
if (client.isNull() || !client.isObject() || client["id"].isNull()) {
|
||||
focusing_client_id_ = 0;
|
||||
} else {
|
||||
focusing_client_id_ = client["id"].asUInt64();
|
||||
clients_[focusing_client_id_] = client;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IPC::updateKeyboardLayout(const std::string& layout) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
keyboard_layout_ = layout;
|
||||
}
|
||||
}
|
||||
|
||||
void IPC::registerForIPC(const std::string& ev, EventHandler* handler) {
|
||||
if (!handler) return;
|
||||
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||
callbacks_.emplace_back(ev, handler);
|
||||
}
|
||||
|
||||
void IPC::unregisterForIPC(EventHandler* handler) {
|
||||
if (!handler) return;
|
||||
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||
for (auto it = callbacks_.begin(); it != callbacks_.end();) {
|
||||
if (it->second == handler)
|
||||
it = callbacks_.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::mango
|
||||
Reference in New Issue
Block a user