#include #include #include #include #include #include #include "log.hpp" #include "ipc-util.hpp" #include "ipc.hpp" namespace i3ipc { // For log.hpp std::vector g_logging_outs = { &std::cout, }; std::vector 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"].asUInt(), .y = value["y"].asUInt(), .width = value["width"].asUInt(), .height = value["height"].asUInt(), }; } window_properties_t parse_window_props_from_json(const Json::Value& value) { if (value.isNull()) { window_properties_t result; result.transient_for = 0ull; return result; } window_properties_t result { value["class"].asString(), value["instance"].asString(), value["window_role"].asString(), value["title"].asString(), 0ull }; const Json::Value transient_for = value["transient_for"]; if (!transient_for.isNull()) { result.transient_for = transient_for.asUInt64(); } return result; } static std::shared_ptr parse_container_from_json(const Json::Value& o) { #define i3IPC_TYPE_STR "PARSE CONTAINER FROM JSON" if (o.isNull()) return nullptr; auto container{std::make_shared()}; IPC_JSON_ASSERT_TYPE_OBJECT(o, "o") container->id = o["id"].asUInt64(); container->xwindow_id= o["window"].asUInt64(); container->name = o["name"].asString(); container->type = o["type"].asString(); container->current_border_width = o["current_border_width"].asInt(); container->percent = o["percent"].asFloat(); container->rect = parse_rect_from_json(o["rect"]); container->window_rect = parse_rect_from_json(o["window_rect"]); container->deco_rect = parse_rect_from_json(o["deco_rect"]); container->geometry = parse_rect_from_json(o["geometry"]); container->urgent = o["urgent"].asBool(); container->focused = o["focused"].asBool(); container->border = BorderStyle::UNKNOWN; std::string border = o["border"].asString(); if (border == "normal") { container->border = BorderStyle::NORMAL; } else if (border == "none") { container->border = BorderStyle::NONE; } else if (border == "pixel") { container->border = BorderStyle::PIXEL; } else if (border == "1pixel") { container->border = BorderStyle::ONE_PIXEL; } else { container->border_raw = border; I3IPC_WARN("Got a unknown \"border\" property: \"" << border << "\". Perhaps its neccessary to update i3ipc++. If you are using latest, note maintainer about this") } container->layout = ContainerLayout::UNKNOWN; std::string layout = o["layout"].asString(); if (layout == "splith") { container->layout = ContainerLayout::SPLIT_H; } else if (layout == "splitv") { container->layout = ContainerLayout::SPLIT_V; } else if (layout == "stacked") { container->layout = ContainerLayout::STACKED; } else if (layout == "tabbed") { container->layout = ContainerLayout::TABBED; } else if (layout == "dockarea") { container->layout = ContainerLayout::DOCKAREA; } else if (layout == "output") { container->layout = ContainerLayout::OUTPUT; } else { container->layout_raw = border; I3IPC_WARN("Got a unknown \"layout\" property: \"" << layout << "\". Perhaps its neccessary to update i3ipc++. If you are using latest, note maintainer about this") } Json::Value nodes = o["nodes"]; if (!nodes.isNull()) { IPC_JSON_ASSERT_TYPE_ARRAY(nodes, "nodes") for (Json::ArrayIndex i = 0; i < nodes.size(); i++) { container->nodes.push_back(parse_container_from_json(nodes[i])); } } Json::Value floating_nodes = o["floating_nodes"]; if (!floating_nodes.isNull()) { IPC_JSON_ASSERT_TYPE_ARRAY(floating_nodes, "floating_nodes") for (Json::ArrayIndex i = 0; i < floating_nodes.size(); i++) { container->floating_nodes.push_back(parse_container_from_json(floating_nodes[i])); } } container->window_properties = parse_window_props_from_json(o["window_properties"]); return container; #undef i3IPC_TYPE_STR } static std::shared_ptr parse_workspace_from_json(const Json::Value& value) { if (value.isNull()) return nullptr; Json::Value num = value["num"]; Json::Value name = value["name"]; Json::Value visible = value["visible"]; Json::Value focused = value["focused"]; Json::Value urgent = value["urgent"]; Json::Value rect = value["rect"]; Json::Value output = value["output"]; auto p{std::make_shared()}; p->num = num.asInt(); p->name = name.asString(); p->visible = visible.asBool(); p->focused = focused.asBool(); p->urgent = urgent.asBool(); p->rect = parse_rect_from_json(rect); p->output = output.asString(); return p; } static std::shared_ptr parse_output_from_json(const Json::Value& value) { if (value.isNull()) return nullptr; Json::Value name = value["name"]; Json::Value active = value["active"]; Json::Value primary = value["primary"]; Json::Value current_workspace = value["current_workspace"]; Json::Value rect = value["rect"]; auto p{std::make_shared()}; p->name = name.asString(); p->active = active.asBool(); p->primary = primary.asBool(); p->current_workspace = (current_workspace.isNull() ? std::string() : current_workspace.asString()); p->rect = parse_rect_from_json(rect); return p; } static std::shared_ptr parse_binding_from_json(const Json::Value& value) { #define i3IPC_TYPE_STR "PARSE BINDING FROM JSON" if (value.isNull()) return nullptr; IPC_JSON_ASSERT_TYPE_OBJECT(value, "binding") auto b{std::make_shared()}; b->command = value["command"].asString(); b->symbol = value["symbol"].asString(); b->input_code = value["input_code"].asInt(); Json::Value input_type = value["input_type"].asString(); if (input_type == "keyboard") { b->input_type = InputType::KEYBOARD; } else if (input_type == "mouse") { b->input_type = InputType::MOUSE; } else { b->input_type = InputType::UNKNOWN; } Json::Value esm_arr = value["event_state_mask"]; IPC_JSON_ASSERT_TYPE_ARRAY(esm_arr, "event_state_mask") b->event_state_mask.resize(esm_arr.size()); for (Json::ArrayIndex i = 0; i < esm_arr.size(); i++) { b->event_state_mask[i] = esm_arr[i].asString(); } return b; #undef i3IPC_TYPE_STR } static std::shared_ptr parse_mode_from_json(const Json::Value& value) { if (value.isNull()) return nullptr; Json::Value change = value["change"]; Json::Value pango_markup = value["pango_markup"]; auto p{std::make_shared()}; p->change = change.asString(); p->pango_markup = pango_markup.asBool(); return p; } static std::shared_ptr parse_bar_config_from_json(const Json::Value& value) { #define i3IPC_TYPE_STR "PARSE BAR CONFIG FROM JSON" if (value.isNull()) return nullptr; IPC_JSON_ASSERT_TYPE_OBJECT(value, "(root)") auto bc{std::make_shared()}; bc->id = value["id"].asString(); bc->status_command = value["status_command"].asString(); bc->font = value["font"].asString(); bc->workspace_buttons = value["workspace_buttons"].asBool(); bc->binding_mode_indicator = value["binding_mode_indicator"].asBool(); bc->verbose = value["verbose"].asBool(); std::string mode = value["mode"].asString(); if (mode == "dock") { bc->mode = BarMode::DOCK; } else if (mode == "hide") { bc->mode = BarMode::HIDE; } else { bc->mode = BarMode::UNKNOWN; I3IPC_WARN("Got a unknown \"mode\" property: \"" << mode << "\". Perhaps its neccessary to update i3ipc++. If you are using latest, note maintainer about this") } std::string position = value["position"].asString(); if (position == "top") { bc->position = Position::TOP; } else if (position == "bottom") { bc->position = Position::BOTTOM; } else { bc->position = Position::UNKNOWN; I3IPC_WARN("Got a unknown \"position\" property: \"" << position << "\". Perhaps its neccessary to update i3ipc++. If you are using latest, note maintainer about this") } Json::Value colors = value["colors"]; IPC_JSON_ASSERT_TYPE_OBJECT(value, "colors") auto colors_list = colors.getMemberNames(); for (auto& m : colors_list) { bc->colors[m] = std::stoul(colors[m].asString().substr(1), nullptr, 16); } return bc; #undef i3IPC_TYPE_STR } 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 errno_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; } connection::connection(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& buf) { switch (event_type) { case ET_WORKSPACE: { workspace_event_t ev; Json::Value root; IPC_JSON_READ(root); std::string change = root["change"].asString(); if (change == "focus") { ev.type = WorkspaceEventType::FOCUS; } else if (change == "init") { ev.type = WorkspaceEventType::INIT; } else if (change == "empty") { ev.type = WorkspaceEventType::EMPTY; } else if (change == "urgent") { ev.type = WorkspaceEventType::URGENT; } else if (change == "rename") { ev.type = WorkspaceEventType::RENAME; } else if (change == "reload") { ev.type = WorkspaceEventType::RELOAD; } else if (change == "restored") { ev.type = WorkspaceEventType::RESTORED; } else { I3IPC_WARN("Unknown workspace event type " << change) break; } I3IPC_DEBUG("WORKSPACE " << change) Json::Value current = root["current"]; Json::Value old = root["old"]; if (!current.isNull()) { ev.current = parse_workspace_from_json(current); } if (!old.isNull()) { ev.old = parse_workspace_from_json(old); } signal_workspace_event.emit(ev); break; } case ET_OUTPUT: I3IPC_DEBUG("OUTPUT") signal_output_event.emit(); break; case ET_MODE: { I3IPC_DEBUG("MODE") Json::Value root; IPC_JSON_READ(root); std::shared_ptr mode_data = parse_mode_from_json(root); signal_mode_event.emit(*mode_data); break; } case ET_WINDOW: { window_event_t ev; Json::Value root; IPC_JSON_READ(root); std::string change = root["change"].asString(); if (change == "new") { ev.type = WindowEventType::NEW; } else if (change == "close") { ev.type = WindowEventType::CLOSE; } else if (change == "focus") { ev.type = WindowEventType::FOCUS; } else if (change == "title") { ev.type = WindowEventType::TITLE; } else if (change == "fullscreen_mode") { ev.type = WindowEventType::FULLSCREEN_MODE; } else if (change == "move") { ev.type = WindowEventType::MOVE; } else if (change == "floating") { ev.type = WindowEventType::FLOATING; } else if (change == "urgent") { ev.type = WindowEventType::URGENT; } I3IPC_DEBUG("WINDOW " << change) Json::Value container = root["container"]; if (!container.isNull()) { ev.container = parse_container_from_json(container); } signal_window_event.emit(ev); break; } case ET_BARCONFIG_UPDATE: { I3IPC_DEBUG("BARCONFIG_UPDATE") Json::Value root; IPC_JSON_READ(root); std::shared_ptr barconf = parse_bar_config_from_json(root); signal_barconfig_update_event.emit(*barconf); break; } case ET_BINDING: { Json::Value root; IPC_JSON_READ(root); std::string change = root["change"].asString(); if (change != "run") { I3IPC_WARN("Got \"" << change << "\" in field \"change\" of binding_event. Expected \"run\"") } Json::Value binding_json = root["binding"]; std::shared_ptr bptr; if (!binding_json.isNull()) { bptr = parse_binding_from_json(binding_json); } if (!bptr) { I3IPC_ERR("Failed to parse field \"binding\" from binding_event") } else { I3IPC_DEBUG("BINDING " << bptr->symbol); signal_binding_event.emit(*bptr); } break; } }; }); #undef i3IPC_TYPE_STR } connection::~connection() { i3_disconnect(m_main_socket); if (m_event_socket > 0) this->disconnect_event_socket(); } void connection::connect_event_socket(const bool reconnect) { if (m_event_socket > 0) { if (reconnect) { this->disconnect_event_socket(); } else { I3IPC_ERR("Trying to initialize event socket secondary") return; } } m_event_socket = i3_connect(m_socket_path); this->subscribe(m_subscriptions); } void connection::disconnect_event_socket() { if (m_event_socket <= 0) { I3IPC_WARN("Trying to disconnect non-connected event socket") return; } i3_disconnect(m_event_socket); } void connection::handle_event() { if (m_event_socket <= 0) { this->connect_event_socket(); } auto buf = i3_recv(m_event_socket); this->signal_event.emit(static_cast(1 << (buf->header->type & 0x7f)), std::static_pointer_cast(buf)); } bool connection::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(ET_WORKSPACE)) { payload_auss << "\"workspace\","; } if (events & static_cast(ET_OUTPUT)) { payload_auss << "\"output\","; } if (events & static_cast(ET_MODE)) { payload_auss << "\"mode\","; } if (events & static_cast(ET_WINDOW)) { payload_auss << "\"window\","; } if (events & static_cast(ET_BARCONFIG_UPDATE)) { payload_auss << "\"barconfig_update\","; } if (events & static_cast(ET_BINDING)) { payload_auss << "\"binding\","; } 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 connection::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::shared_ptr connection::get_tree() const { #define i3IPC_TYPE_STR "GET_TREE" auto buf = i3_msg(m_main_socket, ClientMessageType::GET_TREE); Json::Value root; IPC_JSON_READ(root); return parse_container_from_json(root); #undef i3IPC_TYPE_STR } std::vector< std::shared_ptr > connection::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< std::shared_ptr > outputs; for (auto w : root) { outputs.push_back(parse_output_from_json(w)); } return outputs; #undef i3IPC_TYPE_STR } std::vector< std::shared_ptr > connection::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< std::shared_ptr > workspaces; for (auto w : root) { workspaces.push_back(parse_workspace_from_json(w)); } return workspaces; #undef i3IPC_TYPE_STR } std::vector connection::get_bar_configs_list() const { #define i3IPC_TYPE_STR "GET_BAR_CONFIG (get_bar_configs_list)" auto buf = i3_msg(m_main_socket, ClientMessageType::GET_BAR_CONFIG); Json::Value root; IPC_JSON_READ(root) IPC_JSON_ASSERT_TYPE_ARRAY(root, "root") std::vector l; for (auto w : root) { l.push_back(w.asString()); } return l; #undef i3IPC_TYPE_STR } std::shared_ptr connection::get_bar_config(const std::string& name) const { #define i3IPC_TYPE_STR "GET_BAR_CONFIG" auto buf = i3_msg(m_main_socket, ClientMessageType::GET_BAR_CONFIG, name); Json::Value root; IPC_JSON_READ(root) return parse_bar_config_from_json(root); #undef i3IPC_TYPE_STR } bool connection::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 } int32_t connection::get_main_socket_fd() { return m_main_socket; } int32_t connection::get_event_socket_fd() { return m_event_socket; } const version_t& get_version() { #define I3IPC_VERSION_MAJOR 0 #define I3IPC_VERSION_MINOR 5 #define I3IPC_VERSION_PATCH 0 static version_t version = { .human_readable = auss_t() << I3IPC_VERSION_MAJOR << '.' << I3IPC_VERSION_MINOR << '.' << I3IPC_VERSION_PATCH << " (built on " << I3IPC_BUILD_DATETIME << ")", .loaded_config_file_name = std::string(), .major = I3IPC_VERSION_MAJOR, .minor = I3IPC_VERSION_MINOR, .patch = I3IPC_VERSION_PATCH, }; return version; } }