This commit is contained in:
Sergey Naumov
2015-12-29 12:44:39 +03:00
commit e59ccb1ffb
13 changed files with 3368 additions and 0 deletions

160
src/ipc-util.cpp Normal file
View File

@@ -0,0 +1,160 @@
extern "C" {
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
}
#include <cstring>
#include <ios>
#include <auss.hpp>
#include "ipc-util.hpp"
namespace i3ipc {
static const std::string g_i3_ipc_magic = "i3-ipc";
buf_t::buf_t(uint32_t payload_size) : size(sizeof(header_t) + payload_size) {
data = new uint8_t[size];
header = (header_t*)data;
payload = (char*)(data + sizeof(header_t));
strncpy(header->magic, g_i3_ipc_magic.c_str(), sizeof(header->magic));
header->size = payload_size;
header->type = 0x0;
}
buf_t::~buf_t() {
delete[] data;
}
void buf_t::realloc_payload_to_header() {
uint8_t* new_data = new uint8_t[sizeof(header_t) + header->size];
memcpy(new_data, header, sizeof(header_t));
delete[] data;
data = new_data;
header = (header_t*)data;
payload = (char*)(data + sizeof(header_t));
}
int32_t i3_connect(const std::string& socket_path) {
int32_t sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd == -1) {
throw std::runtime_error("Could not create a socket");
}
(void)fcntl(sockfd, F_SETFD, FD_CLOEXEC); // What for?
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
strncpy(addr.sun_path, socket_path.c_str(), sizeof(addr.sun_path) - 1);
if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) {
throw std::runtime_error("Failed to connect to i3");
}
return sockfd;
}
void i3_disconnect(const int32_t sockfd) {
close(sockfd);
}
std::shared_ptr<buf_t> i3_pack(const ClientMessageType type, const std::string& payload) {
buf_t* buff = new buf_t(payload.length());
buff->header->type = static_cast<uint32_t>(type);
strncpy(buff->payload, payload.c_str(), buff->header->size);
return std::shared_ptr<buf_t>(buff);
}
ssize_t writeall(int fd, const uint8_t* buf, size_t count) {
size_t written = 0;
ssize_t n = 0;
while (written < count) {
n = write(fd, buf + written, count - written);
if (n == -1) {
if (errno == EINTR || errno == EAGAIN)
continue;
return n;
}
written += (size_t)n;
}
return written;
}
ssize_t swrite(int fd, const uint8_t* buf, size_t count) {
ssize_t n;
n = writeall(fd, buf, count);
if (n == -1)
throw std::runtime_error(auss_t() << "Failed to write " << std::hex << fd);
else
return n;
}
void i3_send(const int32_t sockfd, const buf_t& buff) {
swrite(sockfd, buff.data, buff.size);
}
std::shared_ptr<buf_t> i3_recv(const int32_t sockfd) throw (invalid_header_error, eof_error) {
buf_t* buff = new buf_t(0);
const uint32_t header_size = sizeof(header_t);
{
uint8_t* header = (uint8_t*)buff->header;
uint32_t readed = 0;
while (readed < header_size) {
int n = read(sockfd, header + readed, header_size - readed);
if (n == -1) {
throw std::runtime_error(auss_t() << "Failed to read header from socket 0x" << std::hex << sockfd);
}
if (n == 0) {
throw eof_error("Unexpected EOF while reading header");
}
readed += n;
}
}
if (g_i3_ipc_magic != std::string(buff->header->magic, g_i3_ipc_magic.length())) {
throw invalid_header_error("Invalid magic in reply");
}
buff->realloc_payload_to_header();
{
uint32_t readed = 0;
int n;
while (readed < buff->header->size) {
if ((n = read(sockfd, buff->payload + readed, buff->header->size - readed)) == -1) {
if (errno == EINTR || errno == EAGAIN)
continue;
throw std::runtime_error(auss_t() << "Failed to read payload from socket 0x" << std::hex << sockfd);
}
readed += n;
}
}
return std::shared_ptr<buf_t>(buff);
}
std::shared_ptr<buf_t> i3_msg(const int32_t sockfd, const ClientMessageType type, const std::string& payload) throw (invalid_header_error, eof_error) {
auto send_buff = i3_pack(type, payload);
i3_send(sockfd, *send_buff);
auto recv_buff = i3_recv(sockfd);
if (send_buff->header->type != recv_buff->header->type) {
throw invalid_header_error(auss_t() << "Invalid reply type: Expected 0x" << std::hex << send_buff->header->type << ", got 0x" << recv_buff->header->type);
}
return recv_buff;
}
}

310
src/ipc.cpp Normal file
View File

@@ -0,0 +1,310 @@
#include <cstdio>
#include <cstring>
#include <stdexcept>
#include <iostream>
#include <auss.hpp>
#include <json/json.h>
#include "log.hpp"
#include "ipc-util.hpp"
#include "ipc.hpp"
namespace i3ipc {
// For log.hpp
std::vector<std::ostream*> g_logging_outs = {
&std::cout,
};
std::vector<std::ostream*> g_logging_err_outs = {
&std::cerr,
};
#define IPC_JSON_READ(ROOT) \
{ \
Json::Reader reader; \
if (!reader.parse(std::string(buf->payload, buf->header->size), ROOT, false)) { \
throw invalid_reply_payload_error(auss_t() << "Failed to parse reply on \"" i3IPC_TYPE_STR "\": " << reader.getFormattedErrorMessages()); \
} \
}
#define IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, TYPE_CHECK, TYPE_NAME) \
{\
if (!(OBJ).TYPE_CHECK()) { \
throw invalid_reply_payload_error(auss_t() << "Failed to parse reply on \"" i3IPC_TYPE_STR "\": " OBJ_DESCR " expected to be " TYPE_NAME); \
} \
}
#define IPC_JSON_ASSERT_TYPE_OBJECT(OBJ, OBJ_DESCR) IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, isObject, "an object")
#define IPC_JSON_ASSERT_TYPE_ARRAY(OBJ, OBJ_DESCR) IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, isArray, "an array")
#define IPC_JSON_ASSERT_TYPE_BOOL(OBJ, OBJ_DESCR) IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, isBool, "a bool")
#define IPC_JSON_ASSERT_TYPE_INT(OBJ, OBJ_DESCR) IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, isInt, "an integer")
inline rect_t parse_rect_from_json(const Json::Value& value) {
return {
.x = value["x"].asInt(),
.y = value["y"].asInt(),
.width = value["width"].asInt(),
.height = value["height"].asInt(),
};
}
std::string get_socketpath() {
std::string str;
{
auss_t str_buf;
FILE* in;
char buf[512] = {0};
if (!(in = popen("i3 --get-socketpath", "r"))) {
throw std::runtime_error("Failed to get socket path");
}
while (fgets(buf, sizeof(buf), in) != nullptr) {
str_buf << buf;
}
pclose(in);
str = str_buf;
}
if (str.back() == '\n') {
str.pop_back();
}
return str;
}
I3Connection::I3Connection(const std::string& socket_path) : m_main_socket(i3_connect(socket_path)), m_event_socket(-1), m_subscriptions(0), m_socket_path(socket_path) {
#define i3IPC_TYPE_STR "i3's event"
signal_event.connect([this](EventType event_type, const std::shared_ptr<const buf_t>& buf) {
switch (event_type) {
case ET_WORKSPACE: {
WorkspaceEventType type;
Json::Value root;
IPC_JSON_READ(root);
std::string change = root["change"].asString();
if (change == "focus") {
type = WorkspaceEventType::FOCUS;
} else if (change == "init") {
type = WorkspaceEventType::INIT;
} else if (change == "empty") {
type = WorkspaceEventType::EMPTY;
} else if (change == "urgent") {
type = WorkspaceEventType::URGENT;
} else {
I3IPC_WARN("Unknown workspace event type " << change)
break;
}
I3IPC_DEBUG("WORKSPACE " << change)
signal_workspace_event.emit(type);
break;
}
case ET_OUTPUT:
I3IPC_DEBUG("OUTPUT")
signal_output_event.emit();
break;
case ET_MODE:
I3IPC_DEBUG("MODE")
signal_mode_event.emit();
break;
case ET_WINDOW: {
WindowEventType type;
Json::Value root;
IPC_JSON_READ(root);
std::string change = root["change"].asString();
if (change == "new") {
type = WindowEventType::NEW;
} else if (change == "close") {
type = WindowEventType::CLOSE;
} else if (change == "focus") {
type = WindowEventType::FOCUS;
} else if (change == "title") {
type = WindowEventType::TITLE;
} else if (change == "fullscreen_mode") {
type = WindowEventType::FULLSCREEN_MODE;
} else if (change == "move") {
type = WindowEventType::MOVE;
} else if (change == "floating") {
type = WindowEventType::FLOATING;
} else if (change == "urgent") {
type = WindowEventType::URGENT;
}
I3IPC_DEBUG("WINDOW " << change)
signal_window_event.emit(type);
break;
}
case ET_BARCONFIG_UPDATE:
I3IPC_DEBUG("BARCONFIG_UPDATE")
signal_barconfig_update_event.emit();
break;
};
});
#undef i3IPC_TYPE_STR
}
I3Connection::~I3Connection() {
i3_disconnect(m_main_socket);
if (m_event_socket > 0)
i3_disconnect(m_event_socket);
}
void I3Connection::prepare_to_event_handling() {
m_event_socket = i3_connect(m_socket_path);
this->subscribe(m_subscriptions);
}
void I3Connection::handle_event() {
if (m_event_socket <= 0) {
throw std::runtime_error("event_socket_fd <= 0");
}
auto buf = i3_recv(m_event_socket);
this->signal_event.emit(static_cast<EventType>(1 << (buf->header->type & 0x7f)), std::static_pointer_cast<const buf_t>(buf));
}
bool I3Connection::subscribe(const int32_t events) {
#define i3IPC_TYPE_STR "SUBSCRIBE"
if (m_event_socket <= 0) {
m_subscriptions |= events;
return true;
}
std::string payload;
{
auss_t payload_auss;
if (events & static_cast<int32_t>(ET_WORKSPACE)) {
payload_auss << "\"workspace\",";
}
if (events & static_cast<int32_t>(ET_OUTPUT)) {
payload_auss << "\"output\",";
}
if (events & static_cast<int32_t>(ET_MODE)) {
payload_auss << "\"mode\",";
}
if (events & static_cast<int32_t>(ET_WINDOW)) {
payload_auss << "\"window\",";
}
if (events & static_cast<int32_t>(ET_BARCONFIG_UPDATE)) {
payload_auss << "\"barconfig_update\",";
}
payload = payload_auss;
if (payload.empty()) {
return true;
}
payload.pop_back();
}
I3IPC_DEBUG("i3 IPC subscriptions: " << payload)
auto buf = i3_msg(m_event_socket, ClientMessageType::SUBSCRIBE, auss_t() << '[' << payload << ']');
Json::Value root;
IPC_JSON_READ(root)
m_subscriptions |= events;
return root["success"].asBool();
#undef i3IPC_TYPE_STR
}
version_t I3Connection::get_version() const {
#define i3IPC_TYPE_STR "GET_VERSION"
auto buf = i3_msg(m_main_socket, ClientMessageType::GET_VERSION);
Json::Value root;
IPC_JSON_READ(root)
IPC_JSON_ASSERT_TYPE_OBJECT(root, "root")
return {
.human_readable = root["human_readable"].asString(),
.loaded_config_file_name = root["loaded_config_file_name"].asString(),
.major = root["major"].asUInt(),
.minor = root["minor"].asUInt(),
.patch = root["patch"].asUInt(),
};
#undef i3IPC_TYPE_STR
}
std::vector<output_t> I3Connection::get_outputs() const {
#define i3IPC_TYPE_STR "GET_OUTPUTS"
auto buf = i3_msg(m_main_socket, ClientMessageType::GET_OUTPUTS);
Json::Value root;
IPC_JSON_READ(root)
IPC_JSON_ASSERT_TYPE_ARRAY(root, "root")
std::vector<output_t> outputs;
for (auto w : root) {
Json::Value name = w["name"];
Json::Value active = w["active"];
Json::Value current_workspace = w["current_workspace"];
Json::Value rect = w["rect"];
outputs.push_back({
.name = name.asString(),
.active = active.asBool(),
.rect = parse_rect_from_json(rect),
.current_workspace = (current_workspace.isNull() ? std::string() : current_workspace.asString()),
});
}
return outputs;
#undef i3IPC_TYPE_STR
}
std::vector<workspace_t> I3Connection::get_workspaces() const {
#define i3IPC_TYPE_STR "GET_WORKSPACES"
auto buf = i3_msg(m_main_socket, ClientMessageType::GET_WORKSPACES);
Json::Value root;
IPC_JSON_READ(root)
IPC_JSON_ASSERT_TYPE_ARRAY(root, "root")
std::vector<workspace_t> workspaces;
for (auto w : root) {
Json::Value num = w["num"];
Json::Value name = w["name"];
Json::Value visible = w["visible"];
Json::Value focused = w["focused"];
Json::Value urgent = w["urgent"];
Json::Value rect = w["rect"];
Json::Value output = w["output"];
workspaces.push_back({
.num = num.asInt(),
.name = name.asString(),
.visible = visible.asBool(),
.focused = focused.asBool(),
.urgent = urgent.asBool(),
.rect = parse_rect_from_json(rect),
.output = output.asString(),
});
}
return workspaces;
#undef i3IPC_TYPE_STR
}
bool I3Connection::send_command(const std::string& command) const {
#define i3IPC_TYPE_STR "COMMAND"
auto buf = i3_msg(m_main_socket, ClientMessageType::COMMAND, command);
Json::Value root;
IPC_JSON_READ(root)
IPC_JSON_ASSERT_TYPE_ARRAY(root, "root")
Json::Value payload = root[0];
IPC_JSON_ASSERT_TYPE_OBJECT(payload, " first item of root")
if (payload["success"].asBool()) {
return true;
} else {
Json::Value error = payload["error"];
if (!error.isNull()) {
I3IPC_ERR("Failed to execute command: " << error.asString())
}
return false;
}
#undef i3IPC_TYPE_STR
}
}

86
src/log.hpp Normal file
View File

@@ -0,0 +1,86 @@
#pragma once
#include <ostream>
#include <vector>
#include <auss.hpp>
/**
* @addtogroup logging Logging
* @{
*/
namespace i3ipc {
/**
* @brief Common logging outputs
*/
extern std::vector<std::ostream*> g_logging_outs;
/**
* @brief Logging outputs for error messages
*/
extern std::vector<std::ostream*> g_logging_err_outs;
/**
* @brief Put to a logging outputs some dtat
* @param data data, that you want to put to the logging outputs
* @param err is your information is error report or something that must be putted to the error logging outputs
*/
template<typename T>
inline void log(const T& data, const bool err=false) {
for (auto out : (err ? g_logging_err_outs : g_logging_outs)) {
*out << data << std::endl;
}
}
template<>
inline void log(const auss_t& data, const bool err) {
log(data.to_string());
}
}
/**
* Internal macro used in I3IPC_*-logging macros
*/
#define I3IPC_LOG(T, ERR) \
::i3ipc::log((T), (ERR));
/**
* Put information message to log
* @param T message
*/
#define I3IPC_INFO(T) I3IPC_LOG(auss_t() << "i: " << T, false)
/**
* Put error message to log
* @param T message
*/
#define I3IPC_ERR(T) I3IPC_LOG(auss_t() << "E: " << T, true)
/**
* Put warning message to log
* @param T message
*/
#define I3IPC_WARN(T) I3IPC_LOG(auss_t() << "W: " << T, true)
#ifdef DEBUG
/**
* Put debug message to log
* @param T message
*/
#define I3IPC_DEBUG(T) I3IPC_LOG(auss_t() << "D: " << T, true)
#else
/**
* Put debug message to log
* @param T message
*/
#define I3IPC_DEBUG(T)
#endif
/**
* @}
*/