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

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
build
doc
*.sublime-workspace

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[submodule "3rd/auss"]
path = 3rd/auss
url = https://github.com/drmgc/auss.git
[submodule "3rd/jsoncpp"]
path = 3rd/jsoncpp
url = https://github.com/open-source-parsers/jsoncpp

1
3rd/auss Submodule

Submodule 3rd/auss added at efc484eebb

1
3rd/jsoncpp Submodule

Submodule 3rd/jsoncpp added at 9234cbbc90

50
CMakeLists.txt Normal file
View File

@@ -0,0 +1,50 @@
cmake_minimum_required(VERSION 3.0)
project(i3ipc++)
SET(BUILD_STATIC_LIBS ON)
SET(BUILD_SHARED_LIBS OFF)
add_subdirectory(3rd/jsoncpp)
UNSET(BUILD_STATIC_LIBS)
UNSET(BUILD_SHARED_LIBS)
find_package(PkgConfig)
pkg_check_modules(SIGCPP REQUIRED sigc++-2.0)
include_directories(
${SIGCPP_INCLUDE_DIRS}
3rd/jsoncpp/include
3rd/auss/include
include/
)
link_directories(
${SIBCPP_LIBRARY_DIRS}
3rd/jsoncpp/src/lib_json/
)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Wno-unused-parameter")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g3 -DDEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
file(GLOB_RECURSE SRC src/*.cpp)
add_library(i3ipc++_static STATIC ${SRC})
SET(I3IPCpp_LIBRARY_DIRS ${CMAKE_CURRENT_BINARY_DIR})
SET(I3IPCpp_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR})
SET(I3IPCpp_LIBRARIES i3ipc++_static ${SIGCPP_LIBRARIES} jsoncpp_lib_static)
find_package(CxxTest)
if(CXXTEST_FOUND)
include_directories(${CXXTEST_INCLUDE_DIR})
include_directories(src/)
add_definitions(
-DTEST_SRC_ROOT="${CMAKE_CURRENT_SOURCE_DIR}/test"
)
enable_testing()
file(GLOB SRC_TEST test/*.hpp)
CXXTEST_ADD_TEST(i3ipcpp_check test.cpp ${SRC_TEST})
target_link_libraries(i3ipcpp_check ${I3IPCpp_LIBRARIES})
else()
message(WARNING "CxxTest not found. Unable to run unit-tests")
endif()

2353
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Sergey Naumov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

160
include/ipc-util.hpp Normal file
View File

@@ -0,0 +1,160 @@
#pragma once
#include <cstdint>
#include <string>
#include <memory>
#include <stdexcept>
// extern "C" {
// #include <i3/ipc.h>
// }
namespace i3ipc {
/** @defgroup i3ipc_util i3 IPC internal utilities
* Stuff for internal usage in I3Connection
* @{
*/
// extern "C" {
/**
* i3 IPC header
*/
struct header_t {
/* 6 = strlen(I3_IPC_MAGIC) */
char magic[6]; /**< Magic string @see I3_IPC_MAGIC */
uint32_t size; /**< Size of payload */
uint32_t type; /**< Message type */
} __attribute__ ((packed));
/**
* @brief Base class of i3 IPC errors
*/
class ipc_error : public std::runtime_error { using std::runtime_error::runtime_error; };
/**
* @brief Something wrong in message header (wrong magic number, message type etc.)
*/
class invalid_header_error : public ipc_error { using ipc_error::ipc_error; };
/**
* @brief Socket return EOF, but expected a data
*/
class eof_error : public ipc_error { using ipc_error::ipc_error; };
/**
* @brief If something wrong in a payload of i3's reply
*/
class invalid_reply_payload_error : public ipc_error { using ipc_error::ipc_error; };
/**
* @brief Messages (requests), that can be sended from the client
*/
enum class ClientMessageType : uint32_t {
COMMAND = 0,
GET_WORKSPACES = 1,
SUBSCRIBE = 2,
GET_OUTPUTS = 3,
GET_TREE = 4,
GET_MARKS = 5,
GET_BAR_CONFIG = 6,
GET_VERSION = 7,
};
/**
* @brief Replies, that can be sended from the i3 to the client
*/
enum class ReplyType : uint32_t {
COMMAND = 0,
WORKSPACES = 1,
SUBSCRIBE = 2,
OUTPUTS = 3,
TREE = 4,
MARKS = 5,
BAR_CONFIG = 6,
VERSION = 7,
};
/**
* @brief i3 IPC message buffer
*/
struct buf_t {
uint32_t size; /**< @brief Size of whole buffer */
uint8_t* data; /**< @brief Pointer to the message */
/**
* @brief i3 IPC message header
*
* Pointing on the begining
*/
header_t* header;
/**
* @brief Message payload
*
* Pointing on the byte after the header
*/
char* payload;
buf_t(uint32_t payload_size);
~buf_t();
/**
* @brief Resize payload to the payload_size in the header
*/
void realloc_payload_to_header();
};
/**
* Connect to the i3 socket
* @param socket_path a socket path
* @return socket id
*/
int32_t i3_connect(const std::string& socket_path);
/**
* @brief Close the connection
* @param sockfd socket
*/
void i3_disconnect(const int32_t sockfd);
/**
* @brief Send message to the socket
* @param sockfd a socket
* @param buff a message
*/
void i3_send(const int32_t sockfd, const buf_t& buff);
/**
* @brief Recive a message from i3
* @param sockfd a socket
* @return a buffer of the message
*/
std::shared_ptr<buf_t> i3_recv(const int32_t sockfd) throw (invalid_header_error, eof_error);
/**
* @brief Pack a buffer of message
*/
std::shared_ptr<buf_t> i3_pack(const ClientMessageType type, const std::string& payload);
/**
* @brief Pack, send a message and receiv a reply
*
* Almost same to:
* @code{.cpp}
* i3_send(sockfd, i3_pack(type, payload));
* auto reply = i3_recv(sockfd);
* @endcode
*/
std::shared_ptr<buf_t> i3_msg(const int32_t sockfd, const ClientMessageType type, const std::string& payload = std::string()) throw (invalid_header_error, eof_error);
/**
* @}
*/
}

189
include/ipc.hpp Normal file
View File

@@ -0,0 +1,189 @@
#pragma once
#include <cstdint>
#include <string>
#include <memory>
#include <vector>
#include <sigc++/sigc++.h>
extern "C" {
#include <i3/ipc.h>
}
/**
* @addtogroup i3ipc i3 IPC C++ binding
* @{
*/
namespace i3ipc {
/**
* Get path to the i3 IPC socket
* @return Path to a socket
*/
std::string get_socketpath();
/**
* Primitive of rectangle
*/
struct rect_t {
int x; /**< Position on X axis */
int y; /**< Position on Y axis */
int width; /**< Width of rectangle */
int height; /**< Height of rectangle */
};
/**
* i3's workspace
*/
struct workspace_t {
int num; /**< Index of the worksapce */
std::string name; /**< Name of the workspace */
bool visible; /**< Is the workspace visible */
bool focused; /**< Is the workspace is currently focused */
bool urgent; /**< Is the workspace is urgent */
rect_t rect; /**< A size of the workspace */
std::string output; /**< An output of the workspace */
};
/**
* i3's output
*/
struct output_t {
std::string name; /**< Name of the output */
bool active; /**< Is the output currently active */
std::string current_workspace; /**< Name of current workspace */
rect_t rect; /**< Size of the output */
};
/**
* Version of i3
*/
struct version_t {
std::string human_readable; /**< Human redable version string */
std::string loaded_config_file_name; /**< Path to current config of i3 */
uint32_t major; /**< Major version of i3 */
uint32_t minor; /**< Minor version of i3 */
uint32_t patch; /**< Patch number of i3 */
};
/**
* Types of the events of i3
*/
enum EventType {
ET_WORKSPACE = (1 << 0), /**< Workspace event */
ET_OUTPUT = (1 << 1), /**< Output event */
ET_MODE = (1 << 2), /**< Output mode event */
ET_WINDOW = (1 << 3), /**< Window event */
ET_BARCONFIG_UPDATE = (1 << 4), /**< Bar config update event @attention Yet is not implemented as signal in I3Connection */
};
/**
* Types of workspace events
*/
enum class WorkspaceEventType : char {
FOCUS = 'f', /**< Focused */
INIT = 'i', /**< Initialized */
EMPTY = 'e', /**< Became empty */
URGENT = 'u', /**< Became urgent */
};
/**
* Types of window events
*/
enum class WindowEventType : char {
NEW = 'n', /**< Window created */
CLOSE = 'c', /**< Window closed */
FOCUS = 'f', /**< Window got focus */
TITLE = 't', /**< Title of window has been changed */
FULLSCREEN_MODE = 'F', /**< Window toggled to fullscreen mode */
MOVE = 'M', /**< Window moved */
FLOATING = '_', /**< Window toggled floating mode */
URGENT = 'u', /**< Window became urgent */
};
struct buf_t;
/**
* Connection to the i3
*/
class I3Connection {
public:
/**
* Connect to the i3
* @param socket_path path to a i3 IPC socket
*/
I3Connection(const std::string& socket_path = get_socketpath());
~I3Connection();
/**
* Send a command to i3
* @param command command
* @return Is command successfully executed
*/
bool send_command(const std::string& command) const;
/**
* Request a list of workspaces
* @return List of workspaces
*/
std::vector<workspace_t> get_workspaces() const;
/**
* Request a list of outputs
* @return List of outputs
*/
std::vector<output_t> get_outputs() const;
/**
* Request a version of i3
* @return Version of i3
*/
version_t get_version() const;
/**
* Subscribe on an events of i3
*
* If connection isn't handling events at the moment, event numer will be added to subscription list.
* Else will also send subscripe request to i3
*
* Example:
* @code{.cpp}
* I3Connection conn;
* conn.subscribe(lebar::ipc::ET_WORKSPACE | lebar::ipc::ET_WINDOW);
* @endcode
*
* @param events event type (EventType enum)
* @return Is successfully subscribed. If connection isn't handling events at the moment, then always true.
*/
bool subscribe(const int32_t events);
/**
* Prepare connection to the handling of i3's events
* @note Used only in main()
*/
void prepare_to_event_handling();
/**
* Handle an event from i3
* @note Used only in main()
*/
void handle_event();
sigc::signal<void, WorkspaceEventType> signal_workspace_event; /**< Workspace event signal */
sigc::signal<void> signal_output_event; /**< Output event signal */
sigc::signal<void> signal_mode_event; /**< Output mode event signal */
sigc::signal<void, WindowEventType> signal_window_event; /**< Window event signal */
sigc::signal<void> signal_barconfig_update_event; /**< Barconfig update event signal */
sigc::signal<void, EventType, const std::shared_ptr<const buf_t>&> signal_event; /**< i3 event signal @note Default handler routes event to signal according to type */
private:
const int32_t m_main_socket;
int32_t m_event_socket;
int32_t m_subscriptions;
const std::string m_socket_path;
};
}
/**
* @}
*/

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
/**
* @}
*/

28
test/test_ipc.hpp Normal file
View File

@@ -0,0 +1,28 @@
#include <iostream>
#include <auss.hpp>
#include "ipc-util.hpp"
#include <cxxtest/TestSuite.h>
class testsuite_ipc_util : public CxxTest::TestSuite {
public:
void test_pack() {
{
using namespace i3ipc;
auto buff = i3_pack(ClientMessageType::COMMAND, "exit");
auss_t auss;
auss << std::hex;
for (uint32_t i = 0; i < buff->size; i++) {
if (buff->data[i] < 0x10) {
auss << '0';
}
auss << static_cast<uint32_t>(buff->data[i]) << ' ';
}
std::string str = auss;
str.pop_back();
TS_ASSERT_EQUALS(str, "69 33 2d 69 70 63 04 00 00 00 00 00 00 00 65 78 69 74")
}
}
};