#include #include #include #include #include #include #include "i3ipc++/ipc-util.hpp" #include "i3ipc++/ipc.hpp" #include "i3ipc++/log.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::CharReaderBuilder b; \ const std::unique_ptr reader(b.newCharReader()); \ JSONCPP_STRING error; \ if(!reader->parse(buf->payload, buf->payload + buf->header->size, &ROOT, &error)) { \ throw invalid_reply_payload_error(auss_t() << "Failed to parse reply on \"" i3IPC_TYPE_STR "\": " << error); \ } \ } #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) { rect_t r{}; r.x = value["x"].asUInt(); r.y = value["y"].asUInt(); r.width = value["width"].asUInt(); r.height = value["height"].asUInt(); return r; } static std::shared_ptr parse_container_from_json(const Json::Value& o) { #define i3IPC_TYPE_STR "PARSE CONTAINER FROM JSON" if (o.isNull()) return std::shared_ptr(); std::shared_ptr container (new container_t()); 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])); } } return container; #undef i3IPC_TYPE_STR } static std::shared_ptr parse_workspace_from_json(const Json::Value& value) { if (value.isNull()) return std::shared_ptr(); 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"]; std::shared_ptr p (new workspace_t()); 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 std::shared_ptr(); Json::Value name = value["name"]; Json::Value active = value["active"]; Json::Value current_workspace = value["current_workspace"]; Json::Value rect = value["rect"]; std::shared_ptr p (new output_t()); p->name = name.asString(); p->active = active.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 std::shared_ptr(); IPC_JSON_ASSERT_TYPE_OBJECT(value, "binding") std::shared_ptr b (new binding_t()); 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 std::shared_ptr(); Json::Value change = value["change"]; Json::Value pango_markup = value["pango_markup"]; std::shared_ptr p (new mode_t()); 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 std::shared_ptr(); IPC_JSON_ASSERT_TYPE_OBJECT(value, "(root)") std::shared_ptr bc (new bar_config_t()); 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 (mode == "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.empty() && 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" on_event = [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); } if (on_workspace_event) { on_workspace_event(ev); } break; } case ET_OUTPUT: I3IPC_DEBUG("OUTPUT") if (on_output_event) { on_output_event(); } break; case ET_MODE: { I3IPC_DEBUG("MODE") Json::Value root; IPC_JSON_READ(root); std::shared_ptr mode_data = parse_mode_from_json(root); if (on_mode_event) { on_mode_event(*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); } if (on_window_event) { on_window_event(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); if (on_barconfig_update_event) { on_barconfig_update_event(*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); if (on_binding_event) { on_binding_event(*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); } bool connection::handle_event() { if (m_event_socket <= 0) { this->connect_event_socket(); } auto buf = i3_recv(m_event_socket); if (buf && this->on_event) { this->on_event(static_cast(1 << (buf->header->type & 0x7f)), std::static_pointer_cast(buf)); } else if (buf) { return true; } return false; } 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") version_t v{}; v.human_readable = root["human_readable"].asString(); v.loaded_config_file_name = root["loaded_config_file_name"].asString(); v.major = root["major"].asUInt(); v.minor = root["minor"].asUInt(); v.patch = root["patch"].asUInt(); return v; #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 4 #define I3IPC_VERSION_PATCH 0 static version_t v{}; v.human_readable = auss_t() << I3IPC_VERSION_MAJOR << '.' << I3IPC_VERSION_MINOR << '.' << I3IPC_VERSION_PATCH; v.loaded_config_file_name = std::string(); v.major = I3IPC_VERSION_MAJOR; v.minor = I3IPC_VERSION_MINOR; v.patch = I3IPC_VERSION_PATCH; return v; } }