Inital
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
build
|
||||
doc
|
||||
*.sublime-workspace
|
||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal 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
1
3rd/auss
Submodule
Submodule 3rd/auss added at efc484eebb
1
3rd/jsoncpp
Submodule
1
3rd/jsoncpp
Submodule
Submodule 3rd/jsoncpp added at 9234cbbc90
50
CMakeLists.txt
Normal file
50
CMakeLists.txt
Normal 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()
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal 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
160
include/ipc-util.hpp
Normal 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
189
include/ipc.hpp
Normal 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
160
src/ipc-util.cpp
Normal 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
310
src/ipc.cpp
Normal 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
86
src/log.hpp
Normal 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
28
test/test_ipc.hpp
Normal 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")
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user