1 #include <cstdio>
2 #include <cstring>
3 #include <stdexcept>
4 #include <iostream>
5
6 #include <json/json.h>
7 #include <auss.hpp>
8
9 #include "i3ipc++/ipc-util.hpp"
10 #include "i3ipc++/ipc.hpp"
11 #include "i3ipc++/log.hpp"
12
13 namespace i3ipc {
14
15 // For log.hpp
16 std::vector<std::ostream*> g_logging_outs = {
17 &std::cout,
18 };
19 std::vector<std::ostream*> g_logging_err_outs = {
20 &std::cerr,
21 };
22
23 #define IPC_JSON_READ(ROOT) \
24 { \
25 Json::CharReaderBuilder b; \
26 const std::unique_ptr<Json::CharReader> reader(b.newCharReader()); \
27 JSONCPP_STRING error; \
28 if(!reader->parse(buf->payload, buf->payload + buf->header->size, &ROOT, &error)) { \
29 throw invalid_reply_payload_error(auss_t() << "Failed to parse reply on \"" i3IPC_TYPE_STR "\": " << error); \
30 } \
31 }
32
33 #define IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, TYPE_CHECK, TYPE_NAME) \
34 {\
35 if (!(OBJ).TYPE_CHECK()) { \
36 throw invalid_reply_payload_error(auss_t() << "Failed to parse reply on \"" i3IPC_TYPE_STR "\": " OBJ_DESCR " expected to be " TYPE_NAME); \
37 } \
38 }
39 #define IPC_JSON_ASSERT_TYPE_OBJECT(OBJ, OBJ_DESCR) IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, isObject, "an object")
40 #define IPC_JSON_ASSERT_TYPE_ARRAY(OBJ, OBJ_DESCR) IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, isArray, "an array")
41 #define IPC_JSON_ASSERT_TYPE_BOOL(OBJ, OBJ_DESCR) IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, isBool, "a bool")
42 #define IPC_JSON_ASSERT_TYPE_INT(OBJ, OBJ_DESCR) IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, isInt, "an integer")
43
44
parse_rect_from_json(const Json::Value & value)45 inline rect_t parse_rect_from_json(const Json::Value& value) {
46 rect_t r{};
47 r.x = value["x"].asUInt();
48 r.y = value["y"].asUInt();
49 r.width = value["width"].asUInt();
50 r.height = value["height"].asUInt();
51 return r;
52 }
53
54
parse_container_from_json(const Json::Value & o)55 static std::shared_ptr<container_t> parse_container_from_json(const Json::Value& o) {
56 #define i3IPC_TYPE_STR "PARSE CONTAINER FROM JSON"
57 if (o.isNull())
58 return std::shared_ptr<container_t>();
59 std::shared_ptr<container_t> container (new container_t());
60 IPC_JSON_ASSERT_TYPE_OBJECT(o, "o")
61
62 container->id = o["id"].asUInt64();
63 container->xwindow_id= o["window"].asUInt64();
64 container->name = o["name"].asString();
65 container->type = o["type"].asString();
66 container->current_border_width = o["current_border_width"].asInt();
67 container->percent = o["percent"].asFloat();
68 container->rect = parse_rect_from_json(o["rect"]);
69 container->window_rect = parse_rect_from_json(o["window_rect"]);
70 container->deco_rect = parse_rect_from_json(o["deco_rect"]);
71 container->geometry = parse_rect_from_json(o["geometry"]);
72 container->urgent = o["urgent"].asBool();
73 container->focused = o["focused"].asBool();
74
75 container->border = BorderStyle::UNKNOWN;
76 std::string border = o["border"].asString();
77 if (border == "normal") {
78 container->border = BorderStyle::NORMAL;
79 } else if (border == "none") {
80 container->border = BorderStyle::NONE;
81 } else if (border == "pixel") {
82 container->border = BorderStyle::PIXEL;
83 } else if (border == "1pixel") {
84 container->border = BorderStyle::ONE_PIXEL;
85 } else {
86 container->border_raw = border;
87 I3IPC_WARN("Got a unknown \"border\" property: \"" << border << "\". Perhaps its neccessary to update i3ipc++. If you are using latest, note maintainer about this")
88 }
89
90 container->layout = ContainerLayout::UNKNOWN;
91 std::string layout = o["layout"].asString();
92
93 if (layout == "splith") {
94 container->layout = ContainerLayout::SPLIT_H;
95 } else if (layout == "splitv") {
96 container->layout = ContainerLayout::SPLIT_V;
97 } else if (layout == "stacked") {
98 container->layout = ContainerLayout::STACKED;
99 } else if (layout == "tabbed") {
100 container->layout = ContainerLayout::TABBED;
101 } else if (layout == "dockarea") {
102 container->layout = ContainerLayout::DOCKAREA;
103 } else if (layout == "output") {
104 container->layout = ContainerLayout::OUTPUT;
105 } else {
106 container->layout_raw = border;
107 I3IPC_WARN("Got a unknown \"layout\" property: \"" << layout << "\". Perhaps its neccessary to update i3ipc++. If you are using latest, note maintainer about this")
108 }
109
110 Json::Value nodes = o["nodes"];
111 if (!nodes.isNull()) {
112 IPC_JSON_ASSERT_TYPE_ARRAY(nodes, "nodes")
113 for (Json::ArrayIndex i = 0; i < nodes.size(); i++) {
114 container->nodes.push_back(parse_container_from_json(nodes[i]));
115 }
116 }
117
118 return container;
119 #undef i3IPC_TYPE_STR
120 }
121
parse_workspace_from_json(const Json::Value & value)122 static std::shared_ptr<workspace_t> parse_workspace_from_json(const Json::Value& value) {
123 if (value.isNull())
124 return std::shared_ptr<workspace_t>();
125 Json::Value num = value["num"];
126 Json::Value name = value["name"];
127 Json::Value visible = value["visible"];
128 Json::Value focused = value["focused"];
129 Json::Value urgent = value["urgent"];
130 Json::Value rect = value["rect"];
131 Json::Value output = value["output"];
132
133 std::shared_ptr<workspace_t> p (new workspace_t());
134 p->num = num.asInt();
135 p->name = name.asString();
136 p->visible = visible.asBool();
137 p->focused = focused.asBool();
138 p->urgent = urgent.asBool();
139 p->rect = parse_rect_from_json(rect);
140 p->output = output.asString();
141 return p;
142 }
143
parse_output_from_json(const Json::Value & value)144 static std::shared_ptr<output_t> parse_output_from_json(const Json::Value& value) {
145 if (value.isNull())
146 return std::shared_ptr<output_t>();
147 Json::Value name = value["name"];
148 Json::Value active = value["active"];
149 Json::Value current_workspace = value["current_workspace"];
150 Json::Value rect = value["rect"];
151
152 std::shared_ptr<output_t> p (new output_t());
153 p->name = name.asString();
154 p->active = active.asBool();
155 p->current_workspace = (current_workspace.isNull() ? std::string() : current_workspace.asString());
156 p->rect = parse_rect_from_json(rect);
157 return p;
158 }
159
parse_binding_from_json(const Json::Value & value)160 static std::shared_ptr<binding_t> parse_binding_from_json(const Json::Value& value) {
161 #define i3IPC_TYPE_STR "PARSE BINDING FROM JSON"
162 if (value.isNull())
163 return std::shared_ptr<binding_t>();
164 IPC_JSON_ASSERT_TYPE_OBJECT(value, "binding")
165 std::shared_ptr<binding_t> b (new binding_t());
166
167 b->command = value["command"].asString();
168 b->symbol = value["symbol"].asString();
169 b->input_code = value["input_code"].asInt();
170
171 Json::Value input_type = value["input_type"].asString();
172 if (input_type == "keyboard") {
173 b->input_type = InputType::KEYBOARD;
174 } else if (input_type == "mouse") {
175 b->input_type = InputType::MOUSE;
176 } else {
177 b->input_type = InputType::UNKNOWN;
178 }
179
180 Json::Value esm_arr = value["event_state_mask"];
181 IPC_JSON_ASSERT_TYPE_ARRAY(esm_arr, "event_state_mask")
182
183 b->event_state_mask.resize(esm_arr.size());
184
185 for (Json::ArrayIndex i = 0; i < esm_arr.size(); i++) {
186 b->event_state_mask[i] = esm_arr[i].asString();
187 }
188
189 return b;
190 #undef i3IPC_TYPE_STR
191 }
192
parse_mode_from_json(const Json::Value & value)193 static std::shared_ptr<mode_t> parse_mode_from_json(const Json::Value& value) {
194 if (value.isNull())
195 return std::shared_ptr<mode_t>();
196 Json::Value change = value["change"];
197 Json::Value pango_markup = value["pango_markup"];
198
199 std::shared_ptr<mode_t> p (new mode_t());
200 p->change = change.asString();
201 p->pango_markup = pango_markup.asBool();
202 return p;
203 }
204
205
parse_bar_config_from_json(const Json::Value & value)206 static std::shared_ptr<bar_config_t> parse_bar_config_from_json(const Json::Value& value) {
207 #define i3IPC_TYPE_STR "PARSE BAR CONFIG FROM JSON"
208 if (value.isNull())
209 return std::shared_ptr<bar_config_t>();
210 IPC_JSON_ASSERT_TYPE_OBJECT(value, "(root)")
211 std::shared_ptr<bar_config_t> bc (new bar_config_t());
212
213 bc->id = value["id"].asString();
214 bc->status_command = value["status_command"].asString();
215 bc->font = value["font"].asString();
216 bc->workspace_buttons = value["workspace_buttons"].asBool();
217 bc->binding_mode_indicator = value["binding_mode_indicator"].asBool();
218 bc->verbose = value["verbose"].asBool();
219
220 std::string mode = value["mode"].asString();
221 if (mode == "dock") {
222 bc->mode = BarMode::DOCK;
223 } else if (mode == "hide") {
224 bc->mode = BarMode::HIDE;
225 } else {
226 bc->mode = BarMode::UNKNOWN;
227 I3IPC_WARN("Got a unknown \"mode\" property: \"" << mode << "\". Perhaps its neccessary to update i3ipc++. If you are using latest, note maintainer about this")
228 }
229
230 std::string position = value["position"].asString();
231 if (position == "top") {
232 bc->position = Position::TOP;
233 } else if (mode == "bottom") {
234 bc->position = Position::BOTTOM;
235 } else {
236 bc->position = Position::UNKNOWN;
237 I3IPC_WARN("Got a unknown \"position\" property: \"" << position << "\". Perhaps its neccessary to update i3ipc++. If you are using latest, note maintainer about this")
238 }
239
240 Json::Value colors = value["colors"];
241 IPC_JSON_ASSERT_TYPE_OBJECT(value, "colors")
242 auto colors_list = colors.getMemberNames();
243 for (auto& m : colors_list) {
244 bc->colors[m] = std::stoul(colors[m].asString().substr(1), nullptr, 16);
245 }
246
247 return bc;
248 #undef i3IPC_TYPE_STR
249 }
250
251
get_socketpath()252 std::string get_socketpath() {
253 std::string str;
254 {
255 auss_t str_buf;
256 FILE* in;
257 char buf[512] = {0};
258 if (!(in = popen("i3 --get-socketpath", "r"))) {
259 throw errno_error("Failed to get socket path");
260 }
261
262 while (fgets(buf, sizeof(buf), in) != nullptr) {
263 str_buf << buf;
264 }
265 pclose(in);
266 str = str_buf;
267 }
268 if (!str.empty() && str.back() == '\n') {
269 str.pop_back();
270 }
271 return str;
272 }
273
274
connection(const std::string & socket_path)275 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) {
276 #define i3IPC_TYPE_STR "i3's event"
277 on_event = [this](EventType event_type, const std::shared_ptr<const buf_t>& buf) {
278 switch (event_type) {
279 case ET_WORKSPACE: {
280 workspace_event_t ev;
281 Json::Value root;
282 IPC_JSON_READ(root);
283 std::string change = root["change"].asString();
284 if (change == "focus") {
285 ev.type = WorkspaceEventType::FOCUS;
286 } else if (change == "init") {
287 ev.type = WorkspaceEventType::INIT;
288 } else if (change == "empty") {
289 ev.type = WorkspaceEventType::EMPTY;
290 } else if (change == "urgent") {
291 ev.type = WorkspaceEventType::URGENT;
292 } else if (change == "rename") {
293 ev.type = WorkspaceEventType::RENAME;
294 } else if (change == "reload") {
295 ev.type = WorkspaceEventType::RELOAD;
296 } else if (change == "restored") {
297 ev.type = WorkspaceEventType::RESTORED;
298 } else {
299 I3IPC_WARN("Unknown workspace event type " << change)
300 break;
301 }
302 I3IPC_DEBUG("WORKSPACE " << change)
303
304 Json::Value current = root["current"];
305 Json::Value old = root["old"];
306
307 if (!current.isNull()) {
308 ev.current = parse_workspace_from_json(current);
309 }
310 if (!old.isNull()) {
311 ev.old = parse_workspace_from_json(old);
312 }
313
314 if (on_workspace_event) {
315 on_workspace_event(ev);
316 }
317 break;
318 }
319 case ET_OUTPUT:
320 I3IPC_DEBUG("OUTPUT")
321 if (on_output_event) {
322 on_output_event();
323 }
324 break;
325 case ET_MODE: {
326 I3IPC_DEBUG("MODE")
327 Json::Value root;
328 IPC_JSON_READ(root);
329 std::shared_ptr<mode_t> mode_data = parse_mode_from_json(root);
330 if (on_mode_event) {
331 on_mode_event(*mode_data);
332 }
333 break;
334 }
335 case ET_WINDOW: {
336 window_event_t ev;
337 Json::Value root;
338 IPC_JSON_READ(root);
339 std::string change = root["change"].asString();
340 if (change == "new") {
341 ev.type = WindowEventType::NEW;
342 } else if (change == "close") {
343 ev.type = WindowEventType::CLOSE;
344 } else if (change == "focus") {
345 ev.type = WindowEventType::FOCUS;
346 } else if (change == "title") {
347 ev.type = WindowEventType::TITLE;
348 } else if (change == "fullscreen_mode") {
349 ev.type = WindowEventType::FULLSCREEN_MODE;
350 } else if (change == "move") {
351 ev.type = WindowEventType::MOVE;
352 } else if (change == "floating") {
353 ev.type = WindowEventType::FLOATING;
354 } else if (change == "urgent") {
355 ev.type = WindowEventType::URGENT;
356 }
357 I3IPC_DEBUG("WINDOW " << change)
358
359 Json::Value container = root["container"];
360 if (!container.isNull()) {
361 ev.container = parse_container_from_json(container);
362 }
363
364 if (on_window_event) {
365 on_window_event(ev);
366 }
367 break;
368 }
369 case ET_BARCONFIG_UPDATE: {
370 I3IPC_DEBUG("BARCONFIG_UPDATE")
371 Json::Value root;
372 IPC_JSON_READ(root);
373 std::shared_ptr<bar_config_t> barconf = parse_bar_config_from_json(root);
374 if (on_barconfig_update_event) {
375 on_barconfig_update_event(*barconf);
376 }
377 break;
378 }
379 case ET_BINDING: {
380 Json::Value root;
381 IPC_JSON_READ(root);
382 std::string change = root["change"].asString();
383 if (change != "run") {
384 I3IPC_WARN("Got \"" << change << "\" in field \"change\" of binding_event. Expected \"run\"")
385 }
386
387 Json::Value binding_json = root["binding"];
388 std::shared_ptr<binding_t> bptr;
389 if (!binding_json.isNull()) {
390 bptr = parse_binding_from_json(binding_json);
391 }
392
393 if (!bptr) {
394 I3IPC_ERR("Failed to parse field \"binding\" from binding_event")
395 } else {
396 I3IPC_DEBUG("BINDING " << bptr->symbol);
397 if (on_binding_event) {
398 on_binding_event(*bptr);
399 }
400 }
401 break;
402 }
403 };
404 };
405 #undef i3IPC_TYPE_STR
406 }
~connection()407 connection::~connection() {
408 i3_disconnect(m_main_socket);
409 if (m_event_socket > 0)
410 this->disconnect_event_socket();
411 }
412
413
connect_event_socket(const bool reconnect)414 void connection::connect_event_socket(const bool reconnect) {
415 if (m_event_socket > 0) {
416 if (reconnect) {
417 this->disconnect_event_socket();
418 } else {
419 I3IPC_ERR("Trying to initialize event socket secondary")
420 return;
421 }
422 }
423 m_event_socket = i3_connect(m_socket_path);
424 this->subscribe(m_subscriptions);
425 }
426
427
disconnect_event_socket()428 void connection::disconnect_event_socket() {
429 if (m_event_socket <= 0) {
430 I3IPC_WARN("Trying to disconnect non-connected event socket")
431 return;
432 }
433 i3_disconnect(m_event_socket);
434 }
435
436
handle_event()437 bool connection::handle_event() {
438 if (m_event_socket <= 0) {
439 this->connect_event_socket();
440 }
441
442 auto buf = i3_recv(m_event_socket);
443
444 if (buf && this->on_event) {
445 this->on_event(static_cast<EventType>(1 << (buf->header->type & 0x7f)), std::static_pointer_cast<const buf_t>(buf));
446 } else if (buf) {
447 return true;
448 }
449 return false;
450 }
451
452
subscribe(const int32_t events)453 bool connection::subscribe(const int32_t events) {
454 #define i3IPC_TYPE_STR "SUBSCRIBE"
455 if (m_event_socket <= 0) {
456 m_subscriptions |= events;
457 return true;
458 }
459 std::string payload;
460 {
461 auss_t payload_auss;
462 if (events & static_cast<int32_t>(ET_WORKSPACE)) {
463 payload_auss << "\"workspace\",";
464 }
465 if (events & static_cast<int32_t>(ET_OUTPUT)) {
466 payload_auss << "\"output\",";
467 }
468 if (events & static_cast<int32_t>(ET_MODE)) {
469 payload_auss << "\"mode\",";
470 }
471 if (events & static_cast<int32_t>(ET_WINDOW)) {
472 payload_auss << "\"window\",";
473 }
474 if (events & static_cast<int32_t>(ET_BARCONFIG_UPDATE)) {
475 payload_auss << "\"barconfig_update\",";
476 }
477 if (events & static_cast<int32_t>(ET_BINDING)) {
478 payload_auss << "\"binding\",";
479 }
480 payload = payload_auss;
481 if (payload.empty()) {
482 return true;
483 }
484 payload.pop_back();
485 }
486 I3IPC_DEBUG("i3 IPC subscriptions: " << payload)
487
488 auto buf = i3_msg(m_event_socket, ClientMessageType::SUBSCRIBE, auss_t() << '[' << payload << ']');
489 Json::Value root;
490 IPC_JSON_READ(root)
491
492 m_subscriptions |= events;
493
494 return root["success"].asBool();
495 #undef i3IPC_TYPE_STR
496 }
497
498
get_version() const499 version_t connection::get_version() const {
500 #define i3IPC_TYPE_STR "GET_VERSION"
501 auto buf = i3_msg(m_main_socket, ClientMessageType::GET_VERSION);
502 Json::Value root;
503 IPC_JSON_READ(root)
504 IPC_JSON_ASSERT_TYPE_OBJECT(root, "root")
505
506 version_t v{};
507 v.human_readable = root["human_readable"].asString();
508 v.loaded_config_file_name = root["loaded_config_file_name"].asString();
509 v.major = root["major"].asUInt();
510 v.minor = root["minor"].asUInt();
511 v.patch = root["patch"].asUInt();
512 return v;
513 #undef i3IPC_TYPE_STR
514 }
515
516
get_tree() const517 std::shared_ptr<container_t> connection::get_tree() const {
518 #define i3IPC_TYPE_STR "GET_TREE"
519 auto buf = i3_msg(m_main_socket, ClientMessageType::GET_TREE);
520 Json::Value root;
521 IPC_JSON_READ(root);
522 return parse_container_from_json(root);
523 #undef i3IPC_TYPE_STR
524 }
525
526
get_outputs() const527 std::vector< std::shared_ptr<output_t> > connection::get_outputs() const {
528 #define i3IPC_TYPE_STR "GET_OUTPUTS"
529 auto buf = i3_msg(m_main_socket, ClientMessageType::GET_OUTPUTS);
530 Json::Value root;
531 IPC_JSON_READ(root)
532 IPC_JSON_ASSERT_TYPE_ARRAY(root, "root")
533
534 std::vector< std::shared_ptr<output_t> > outputs;
535
536 for (auto w : root) {
537 outputs.push_back(parse_output_from_json(w));
538 }
539
540 return outputs;
541 #undef i3IPC_TYPE_STR
542 }
543
544
get_workspaces() const545 std::vector< std::shared_ptr<workspace_t> > connection::get_workspaces() const {
546 #define i3IPC_TYPE_STR "GET_WORKSPACES"
547 auto buf = i3_msg(m_main_socket, ClientMessageType::GET_WORKSPACES);
548 Json::Value root;
549 IPC_JSON_READ(root)
550 IPC_JSON_ASSERT_TYPE_ARRAY(root, "root")
551
552 std::vector< std::shared_ptr<workspace_t> > workspaces;
553
554 for (auto w : root) {
555 workspaces.push_back(parse_workspace_from_json(w));
556 }
557
558 return workspaces;
559 #undef i3IPC_TYPE_STR
560 }
561
562
get_bar_configs_list() const563 std::vector<std::string> connection::get_bar_configs_list() const {
564 #define i3IPC_TYPE_STR "GET_BAR_CONFIG (get_bar_configs_list)"
565 auto buf = i3_msg(m_main_socket, ClientMessageType::GET_BAR_CONFIG);
566 Json::Value root;
567 IPC_JSON_READ(root)
568 IPC_JSON_ASSERT_TYPE_ARRAY(root, "root")
569
570 std::vector<std::string> l;
571
572 for (auto w : root) {
573 l.push_back(w.asString());
574 }
575
576 return l;
577 #undef i3IPC_TYPE_STR
578 }
579
580
get_bar_config(const std::string & name) const581 std::shared_ptr<bar_config_t> connection::get_bar_config(const std::string& name) const {
582 #define i3IPC_TYPE_STR "GET_BAR_CONFIG"
583 auto buf = i3_msg(m_main_socket, ClientMessageType::GET_BAR_CONFIG, name);
584 Json::Value root;
585 IPC_JSON_READ(root)
586 return parse_bar_config_from_json(root);
587 #undef i3IPC_TYPE_STR
588 }
589
590
send_command(const std::string & command) const591 bool connection::send_command(const std::string& command) const {
592 #define i3IPC_TYPE_STR "COMMAND"
593 auto buf = i3_msg(m_main_socket, ClientMessageType::COMMAND, command);
594 Json::Value root;
595 IPC_JSON_READ(root)
596 IPC_JSON_ASSERT_TYPE_ARRAY(root, "root")
597 Json::Value payload = root[0];
598 IPC_JSON_ASSERT_TYPE_OBJECT(payload, " first item of root")
599
600 if (payload["success"].asBool()) {
601 return true;
602 } else {
603 Json::Value error = payload["error"];
604 if (!error.isNull()) {
605 I3IPC_ERR("Failed to execute command: " << error.asString())
606 }
607 return false;
608 }
609 #undef i3IPC_TYPE_STR
610 }
611
get_main_socket_fd()612 int32_t connection::get_main_socket_fd() { return m_main_socket; }
613
get_event_socket_fd()614 int32_t connection::get_event_socket_fd() { return m_event_socket; }
615
616
get_version()617 const version_t& get_version() {
618 #define I3IPC_VERSION_MAJOR 0
619 #define I3IPC_VERSION_MINOR 4
620 #define I3IPC_VERSION_PATCH 0
621 static version_t v{};
622 v.human_readable = auss_t() << I3IPC_VERSION_MAJOR << '.' << I3IPC_VERSION_MINOR << '.' << I3IPC_VERSION_PATCH;
623 v.loaded_config_file_name = std::string();
624 v.major = I3IPC_VERSION_MAJOR;
625 v.minor = I3IPC_VERSION_MINOR;
626 v.patch = I3IPC_VERSION_PATCH;
627 return v;
628 }
629
630 }
631